VTL(Very Tiny Language)の作成


はじめに

VTL(Very Tiny Language)はパソコンの黎明期(「パソコン」ではなく「マイコン」という言葉が一般的だった1970年代頃)に作られたインタープリタ型のプログラミング言語で,「マイクロBASIC」とも呼ばれます.
その特徴は,インタープリタ本体のサイズがわずか768バイト(1Kバイトの3/4)に収まるというコンパクトさです.
この言語はマイクロBASICとも呼ばれるように,通常のBASIC(Visual Basic等のモダンなBASICではない古典的なBASIC)と同様に行番号によりプログラムの流れをコントロールしますが,BASICよりもかなり機械語に近い低級言語といえます.
特に代入操作を基本としてプログラムを書くようになっており,プログラムカウンタやメモリマップドI/Oを直接操作するような感覚でプログラミングを行います.

このVTLという言語を知ったのは,自分が高校生の頃に学校の図書館に置いてあった古いASCIIという雑誌からでした.
それは1980年前後のマイコンブームの頃のもので,当時のCPUやメモリなどは驚くほど貧弱でしたが,誌面からはマイコンという新しいものに対する熱気が感じられて,自作のプログラムを紹介する記事も多く,非常に面白い内容でした.
そのような時代にVTLという言語は,CPUのクロックが500kHz(500,000Hz,念のため),RAMの容量が1Kバイト(1024バイト,同じく)のAltair680というマシン用に作られました.
この言語を知ったとき,そのシンプルさに惹かれて大変興味を持ちました.
なかなか自分で実装しようとまでは思いませんでしたが,AVR等の組み込み用CPUを使うようになり,この小さな言語をそれらの非力なCPUでも動かしてみたいと思ってVTLの処理系を作成してみました.

このVTLが過去にどれだけ普及したのかは分かりませんが,VTLを拡張したGAME(General Algorithmic Micro Expressions)という言語がASCII誌上で発表され,その後このGAMEで書かれた多くのプログラムがASCIIに掲載されました.
今(2008年12月)検索エンジンで調べてみると,Return of the Very Tiny LanguageというVTLを元にした処理系が見つかりました[6].また,いくつかのVTLに関する資料[5][7]も見つかりましたが,あまり多くの情報は見つけられませんでした.

VTL入門

ここでは,VTLの使い方を簡単に説明します.

ダイレクトモードとプログラムモード

VTLは通常の(古典的な)BASICと同様に対話的な編集・実行環境を持っています.
例えば,

?=23+45

と入力すると(行末でリターンキーを押します),"23+45"の計算結果である"68"が画面に出力されます."?"はユーザーターミナルを表す「システム変数」と呼ばれる特別な変数で,この"?"という変数に対して代入した値は画面へ出力されます.
ダイレクトモードでは,このように入力したステートメントがすぐに実行されます.
一方でプログラムモードでは,あらかじめメモリ上にプログラムを格納しておいて後で実行します.
プログラムをメモリ上に格納するには,ステートメントの前に行番号(1~65535の数字)と1つのスペースをつけて入力します.
例えば,

100 A=123
200 ?=A*2

と入力すると,「変数Aに123という値を代入して(100行目)」「変数Aの値に2を掛けた値を画面に出力する(200行目)」というプログラムをメモリに格納します. メモリ上に格納されたプログラムを実行するには,

#=1

と入力します(プログラムを先頭から実行するという意味で,BASICでいう"RUN"命令に相当します).すると,画面上には"246"と表示されるはずです.
プログラムリストを表示するには"0"と入力します(BASICでいう"LIST"命令に相当します).
またプログラム中の行を削除する場合は,その行番号だけを入力します(これは通常のBASICと同じです).

キーボードから一度に入力できる文字数は73バイトで,バックスペースキーで直前の1文字を削除できます.
74文字以上入力された場合やCTRL-U(68版VTLではShift-P,80版VTLでは"@")が入力されると,入力中の文がキャンセルされて改行されます.

数値と演算子

VTLで扱うことのできる数値は,0~65535の範囲の数(符号無し2バイトの整数のみ)です.

VTLでは,次の4つの算術演算子が使えます:

+ 加算
- 減算
* 乗算
/ 除算

除算の結果の余りについては,後述する"%"というシステム変数で参照することができます.

また,次の3つの関係演算子が使えます:

= 等しい
< 小なり
> 大なりまたは等しい

">"は"≥"の意味で扱われます.
これらの関係演算子は, 条件が成り立てば"1"を,成り立たなければ"0"を返します.
例えば,"A=B=C"というステートメントは,変数BとCの値が等しければ変数Aに1を,そうでなければAに0を代入することになります.

VTLでは,演算子の間に優先順位は存在せず,左から順番に演算が行われます.演算の順序を変えたい場合は括弧"()"をつけます.例えば,"2+3*4"の結果は"20"となり,"2+(3*4)"の結果は"14"となります.

変数

変数には,"A"から"Z"のアルファベット1文字を使うことができます.

配列変数は,1次元のものを1種類だけ使うことができます.n (n≥0)番目の配列変数は":n)"と記述します.例えば":X+3)=Y"のように使います.配列は後述するシステム変数"&"以降のメモリ領域を使いますが,メモリの上限を超えたかどうかの配列の添え字のチェックはしていないので注意が必要です.

いくつかの記号は特別な意味を持つ「システム変数」となっています.以下,VTLにおけるシステム変数について説明します.

システム変数

* (メモリ最終番地)
"*"はメモリの最終番地を表わします.システムは,この変数の値のアドレスまでRAMが存在すると認識するので,VTLの実行直後に"*=1024"などのようにRAMのサイズをこの変数に指定しておく必要があります.ただしC言語版VTLでは,初期化ルーチン(system.c中のinitl())で初期化しているので,ユーザーが指定する必要はありません.
& (プログラム最終ポインタ)
"&"はメモリ上でのプログラムの最終アドレスを指し示すポインタです.68版VTLや80版VTLやこのC言語版VTLでは0~263番地はインタープリタがワークエリアとして使用するため,"&=264"としてその直後の264番地を指定するようにします.ただしシステム変数"*"同様にこのC言語版VTLではinitl()で初期化しているので,ユーザーが指定する必要はありません.
メモリ上にプログラムが存在する状態で"&=264"と入力すると,プログラムをクリアしたことになります(BASICでいう"NEW"命令).
また"?=*-&"と入力すると,メモリのサイズからプログラムの最終番地を引いた値,つまりプログラムを蓄えるためのプログラムエリアの残りサイズを表示します.このサイズは,プログラム中で配列変数として使えるメモリサイズにもなります.
% (割り算の余り)
"%"には,演算子"/"で除算を行った結果の余りが格納されています.
) (コメント)
")"に対する代入の形でプログラムのコメントを記述することができます.また,その際代入を表す"="の代わりに空白を使うことができます.例えば,"10 ) Copyright XXX All Rights Reserved."のように使います.
? (ユーザーターミナル)
"?"への書き込みはユーザーターミナルへの出力命令として,読み込みは入力命令として扱われます.
例えば,"?=2+3"とすれば"5"を画面へ出力します.
右辺にダブルクォーテーションで囲まれた文字列を指定すれば,文字列の出力を行うことができます.例えば,'?="Hello World"'とすれば"Hello World"と画面へ出力します.
数値の出力を行った後は自動的に改行されないので,改行を行いたい場合は'?=""'などとする必要があります.文字列の出力を行った後は自動的に改行されますが,改行させないようにするには文字列の後にセミコロン";"を付けて'?="Hello";'のようにします.
ユーザーから数値を入力する場合は"?"を式の右辺に使います.例えば"A=?"とすると,ユーザーが入力した数値を変数"A"に代入します.
# (プログラムカウンタ)
"#"は現在インタープリタが実行中のプログラムの行番号を表します."#=100"という文は,100行目から実行するようにインタープリタに指示することになります.つまり,BASICのGOTO文と同じ意味を持ちます.
プログラムを先頭から実行する場合(BASICでいう"RUN"命令)には"#=1"とします.VTLでは,存在しない文番号が"#"に指定された場合,その数字よりも大きな最も近い行番号を探して実行するので,1以外の数字でもプログラムの先頭の行番号と同じかそれ未満の数字を指定すれば同じ意味となります.
プログラムを途中で終了する場合(BASICでいう"END"命令)は,"#=9999"などのようにして,プログラム中のどの行番号よりも大きな値を指定します.
"#"は,条件分岐にも使用することができます."#=N=10*200"とした場合,変数Nの値が10の場合は"N=10"の値が1となるため,200行目へジャンプすることになります.変数Nの値が10以外の場合は,"N=10"の値が0となるため"#"に0を代入することになりますが,"#"に0が代入された場合はジャンプは行われず無視されることになっているので何も起こりません.つまり上記の文は,通常のBASICでは"IF N=10 THEN GOTO 200"と書くのと同じ意味になります.
! (サブルーチン・リターン)
VTLではサブルーチン呼び出しを実現するため,システム変数"#"への代入によりプログラムの分岐を行った際に,そのとき実行していた行番号+1の値が"!"に格納されます.そのため,"#"により実行されたサブルーチンの最後で"#=!"とすると,サブルーチンを呼び出した文の直後の文に戻ることができます.
"!"の値は"#"への代入により失われるため,再帰呼び出し等でサブルーチンの中でさらに分岐を行う際には,あらかじめ"!"の内容を保管しておく必要があります.
$ (1文字入出力)
"$"は,文字の入出力を行います.入出力の文字コードはASCIIコードが用いられます.
"$=65"とすると(ASCIIコードで65番は"A"なので)画面に"A"と表示され,"?=$"とするとキーボードから入力した文字のASCIIコードが画面に表示されます.
' (乱数)
シングルクォーテーション"'"は0~65535の乱数を返します.
例えば"A='/10"とすると,システム変数"%"に0から9の範囲の乱数が得られます.
> (マシン語サブルーチンコール)
">"への代入は,マシン語で書かれたサブルーチンの呼び出しを実行します.68版VTLと80版VTLでは動作が異なっており.C言語版VTLではこのシステム変数に対する動作は実装されていません.

仕様と実装に関する備考

VTLの文法について上記のように説明してきましたが,VTLの中身(実装)はもっと単純化されています(68版VTL,80版VTL,C言語版VTLの場合).
例えばステートメントは"X=Y"のように"="を使って書くことになっていますが,この文字はVTL内部では単に読み飛ばされているので,"="以外の文字を使うことができます.
また変数として使えるのは大文字のアルファベット26文字としていますが,実際の処理系では"["など他の文字も使えます.
")"を使えばコメントを記述できるとしていますが,これは")"を特別扱いしているのではなく,単にこの変数には何が入っていてもよいという取り決めをしているだけです.
"?"への代入による文字列の出力は特別扱いされており,実際は式の右辺がダブルクォーテーションで囲まれていれば,左辺(と"="の記号)は何でも構わないので,例えば'?="ABC"'と'AA"ABC"'は同じ意味になります.
VTLではエラーメッセージというものは存在せず,上記のように論理回路のDon't careのような扱いにより入力を一意に解釈して実行するようになっています.そのため仕様のとおりには動作しますが,仕様から外れた入力をした場合でも多くの場合何らかの動作をします.ただし将来の拡張や互換性(?)を考えると,仕様からはずれた使い方はお勧めできません.

インタープリタ本体の説明

今回は,C言語でVTLインタープリタを書きました.オリジナルの68版[1]やそれを移植した80版[2]はアセンブラで書かれていますが,移植性を重視してCで書きました.そのためコードサイズは少し大きくなりますが,様々なCPUで動かすことができます.
プログラムの作成は,文献[2]が大変参考になりました.この文献では,80版VTLのソースコードが詳しく解説されています.
今回作成したC言語版VTLはプログラムの基本的なロジックは同じなので,80版VTLと完全に互換性があると思われます. また80版は68版と互換性があるようなので,このC言語版はオリジナルの68版とも互換性があると思われます.昔の雑誌に載っていたVTL用のプログラムをこのC言語版で動かしましたが問題なく動作しました.
メモリマップは,この表のようになっています.基本的に68版や80版と同じです.

いくつかのCPUやOS上でこのC言語版VTLを動かしてみました.「AVR版」「H8版」「UNIX版」「MinGW版」があり,それぞれソースファイルに含まれる"avr/","h8/","unix/","mingw/"というディレクトリに移動してmakeでコンパイルできます. 以下,各機種ごとの説明です.

AVR版
AVR用のソースです.32KBのRAMを外付けしたAT90S8515で動作確認をしています.コードサイズは約2.6kバイトなので,フラッシュROMがこれより大きいAVRで動作可能だと思います.アセンブラで書けば,おそらくオリジナルのように1Kバイト未満に収められると思います.
入出力は,RS-232C(UART)で接続した端末を通じて行います(TeraTermが便利です).
H8版
H8用のソースです. トランジスタ技術2004年4月号に付属のH8/3694Fで動作確認をしました.
入出力は,やはりRS-232C(SCI)で接続した端末を通じて行います.
UNIX版
UNIX系用のソースです.Linux,FreeBSD,およびWindows上のCygwinで動作確認をしています.
このUNIX版では,メモリイメージの自動セーブ・ロード機能があります.VTLの動作中にCTRL+Cを押すとVTLを終了し,その時のメモリの内容を"vtl.img"というファイルにダンプします.また,起動時に"vtl.img"というファイルがカレントディレクトリにあれば,それをメモリの初期状態として読み込みます.これにより,作成したプログラムを自動的に保存したり,次回起動時に再び利用することができます.
MinGW版
Windows上のMinGW用のソースです.コンパイル済みの実行形式も用意しておきました.
UNIX版と同様に,メモリイメージの自動セーブ・ロード機能があります.

上記のCPUやOS以外でも,system.c中のinitl(),getchr(),putchr()の3つの関数を用意すれば,このC言語版VTLを動作させることができます.initl()は(必要であれば)機種に依存した初期化処理,getchr()は(バッファリングを行わない,エコー出力を行う)1文字入力,putchr()は(バッファリングを行わない)1文字出力です.詳しい内容は,AVR版,H8版,UNIX版,MinGW版のsystem.cをご参考にしてください.

インタープリタ本体のプログラム

VTLのプログラム例

VTLで書かれたプログラムの例を紹介します. プログラムリストのファイルはUNIX版やMinGW版で用いられているメモリイメージのフォーマットにもなっているので,ファイルをダウンロードして"vtl.img"という名前で保存し,VTLを実行後に"#=1"と入力すると,プログラムを動かすことができます.

1. 階乗の計算 [プログラムリスト]

階乗を計算するプログラムです. 再帰呼び出しの例となっています.
プログラムを実行して数字を入力すると,入力された数の階乗の値を表示します.

2. Hit & Blow [プログラムリスト]

有名な数当てゲームです.
コンピュータが生成した4桁の数字(1234,9753など)を当てるのが目的です.4つの数字の中に同じ数字は含まれません.
プログラムを実行後,4桁の数字を予想して入力すると"1H2B"のようなヒントが返されます. これはHitが1つ,Blowが2つという意味です.入力した数字が,コンピュータが生成した数字の中に出現はするけれども桁の位置が違う場合はBlow,桁の位置も正しい場合はHitとなります. このヒントを頼りに,コンピュータが生成した数値を当てます. 正解の数字を入力すると"4H0B"と出力して,プログラムが終了します.

3. 15パズル [プログラムリスト]

15パズルです. 下の図のように左上から0で始まり,右下が空白になるように数字を移動します.

0123
4567
89AB
CDE 

10~15の数字はA~Eのアルファベットで表示されます.
"2","8","6","4",の各数字を入力すると,それぞれ空白の上,下,左,右にある数字を空白の位置へ移動します(テンキーで操作して下さい).
上の図のように数字を揃えるとゲームが終了します.

4. マインスイーパ [プログラムリスト]

マインスイーパです.
数字の"8","2","4","6"でカーソル("*"で表示されている)を上,下,左,右に動かし,スペースキーでカーソルの位置の地雷をチェックします.
地雷をチェックすると表示される数字は,カーソルの位置を中心として,その上下左右と斜め方向に隣接する計8個のマス目の中に,いくつの地雷が存在するかを表しています.もし地雷のあるマス目をチェックしてしまった場合はゲームオーバーです.
地雷が存在するマス目以外の全てのマス目をチェックすれば,ゲーム終了です.
マス目の数や地雷の数は,プログラムの先頭にある変数NとMの値を変えれば変更できます.

5. その他VTLのプログラムが掲載された資料等

ASCII誌上では,次のようなVTL用のプログラムが発表されています.
とてもシンプルな言語ですが,結構複雑なプログラムが記述できることがわかります.

参考資料

  1. 松井俊浩: Altair680とマイクロBASIC, エンサイクロペディア・アスキー Volume 1(1977年7月~1978年2月の雑誌アスキーの記事を掲載したもの), pp.59-60, アスキー.
    マイクロBASICの解説記事で,マイクロBASICの文法が説明されている.また,Altair 680b版マイクロBASICのダンプリストが掲載されている.
  2. 白石孝次: マイクロBASICインタプリタの製作, エンサイクロペディア・アスキー Volume 1(1977年7月~1978年2月の雑誌アスキーの記事を掲載したもの)(この記事自体は1978年2月号のアスキーに掲載されたもの), pp.102-115, アスキー.
    Altair 680b用マイクロBASICを解読して8080へ移植した記事.コメントが付与された8080版マイクロBASICのアセンブラリストだけではなく,各サブルーチンの詳細な解説がなされており,マイクロBASICを実装する上で非常に参考になる.
  3. 有近伸志郎: VTL-Z, エンサイクロペディア・アスキー Volume 2(1978年3月~1978年8月の記事を掲載したもの)(この記事自体は1978年6月号のアスキーに掲載されたもの), pp.177-?, アスキー.
    Z-80用のVTL.
  4. 塚越一雄: オリジナル言語入門, 電波新聞社.
    PC-9801用の,拡張されたVTLの製作.
  5. 冨永智之, 新里英之, 細谷将彦: 小型インタープリタ「VTL-MS」, 琉球大学理学部紀要, No. 63, pp.1-62.
    上記のPC-9801用VTLのMS-DOSへの移植.
  6. Return of the Very Tiny Language
    Linux用のVTL.
  7. VTL-2 Very Tiny Language
    VTLの改良版であるVTL-2のマニュアル

※エンサイクロペディア・アスキーは月刊雑誌のASCIIを数冊分まとめたもので,内容は雑誌の記事と同一です
※上記の参考資料の多くは絶版で入手が難しいと思われますが,大きな公立図書館や大学図書館では置いているところがあると思います(検索→NACSIS Webcat).


[戻る]
2009-01-03 ページ更新
2008-12-29 ページ作成
(2004-05 製作)
T. Nakagawa