★ Return of Very Tiny Language (64bit) ver.4.01 - 2015/10/05 Jun Mizutani rvtl64 は Tiny Basic系のプログラミング環境です。アセンブラで作成している ため、10-30キロバイトほどの非常に小さいプログラムですが、対話的に操作 できる (REPL:read-eval-print loop) 豊富な機能を持っています。 rvtl は特殊な文法 (基本的にすべて代入文) を持つ CASUAL や VTL の子孫と なる言語です。 特殊といっても、日本では、GAME III が有名です。古くから 趣味でプログラミングしていた人には懐かしい言語ではないかと思います。 ラベルを使えるように文法を拡張しています。文法が単純なためインタープリタ でも高速に動作します。CPUに依存しますが、1秒間に100万から500万命令程度は 実行できると思います。 ■ rvtlの文法の紹介 次のコードを見てください。大昔からプログラミングをしていた人以外は、 まったく意味が分からないと思います。 このような記号と数字ばかりの 変な言語にもそれなりの理由があります。その理由を知ればそれほど異常な 文法ではないことが分かってもらえると思います。 10 A=? 20 #=(A>100)*50 30 ?=10 40 #=60 50 ?=100 60 #=-1 このコードの意味は後ほど説明するとして、まず元になるBASIC言語から紹介します。 BASIC は1960年代に初心者向けに設計されたプログラミング言語で、1980年代 から1990年代にかけてよく使われてました。 BASIC言語は処理系ごとに拡張 された部分が多く、時代とともに変化していますが、基本的に各行の先頭に 行番号と呼ばれる数字を置き、その後ろに命令文を書きます。 行番号は、10、20、30、... というようにして、行と行の間に挿入できるように 番号付けするのが普通です。11、12、13、... でも500、600、900でも文法的 には問題ありません。 行番号の大きくなる順にソートされます。行番号は、 1行ずつしか入力を受け付けず、ほとんど修正もできないような貧弱な環境でも プログラムが作成できるように、行に番号を付けて大きくなる順にソートする ことで、長いプログラムに対して途中に行を挿入したり、削除するための仕組みです。 10 ' BASIC 20 PRINT "hello, world" 30 GOTO 20 40 END この例では、最初の行番号10の行はコメントです。行番号20の行で、 「hello,worldと表示」します。 次の行番号30の行で、「行番号20の行に行く」 という内容を実行します。永遠に hello,world を表示し続けるプログラムと なります。行の先頭に行番号が付いている以外は特に違和感はないと思います。 同じことを rvtl で書きます。各行の動作は上のBASICの例と同じです。 10 : rvtl 20 "hello, world" 30 #=20 40 #=-1 rvtl の文法は、rvtl の遠い先祖である VTL という言語がもとになっています。 VTL は処理系のプログラムサイズが 768バイトという非常に小さいものであった ため文法が工夫されています。 「#」はナンバー記号(number sign)と呼ばれる 記号ですが、この記号を実行中の命令のある行番号を表す変数名とします。 「#=10」 のように行番号変数に代入すると、行番号10にある命令を実行する、 つまり GOTO 10 と同じ働きになります。 存在しない行番号のときは、それより 大きくて最も近い行番号に移動します。 「#=-1」 のように行番号が負の場合や、 実行する行がなければ終了します。「#=1」は行番号 1 からの実行なので、常に プログラムの先頭から実行して、BASIC のRUN コマンドと同じ「プログラムを 起動」という動作になります。「#=0」 のように行番号に 0 を指定した場合は、 先頭に飛ぶのではなく、「#=0」 の次の行に処理が移る仕様になっています。 また、数値を「?」に代入すると標準出力に数値が出力されます。「A=?」の ように変数に代入すると標準入力から入力された数値が変数に格納されます。 つまり「?」が数値の入出力に使われます。文字列の表示はダブルクォートで 囲んだ文字列が標準出力に出力されます。上の例では「"hello, world"」だけで hello,world を表示します。 「#=0」のときには次の行に移る機能を使うと if のように条件分岐が可能と なります。2行目の (A>100) という式は、変数Aの持つ値が100より大きい 場合は、真を表す1という数値となり、A が 100 以下の場合は、(A>100)は偽を 表す 0 となります。 結果として、変数Aの持つ値によって #=50 または #=0 となるため、条件分岐やループが可能です。 10 A=? 20 #=(A>100)*50 30 ?=10 40 #=60 50 ?=100 60 #=-1 このプログラムは rvtl でも実行して動作を確認することができます。 $ rvtl64 RVTL64 v.4.01 2015/10/05, Copyright 2002-2015 Jun Mizutani RVTL64 may be copied under the terms of the GNU General Public License. <12E2> 10 A=? <12E2> 20 #=(A>100)*50 <12E2> 30 ?=10 <12E2> 40 #=60 <12E2> 50 ?=100 <12E2> 60 #=-1 <12E2> 0 【リスト表示コマンド】 10 A=? 20 #=(A>100)*50 30 ?=10 40 #=60 50 ?=100 60 #=-1 <12E2> #=1 【実行コマンド】 99 10 <12E2> #=1 【実行コマンド】 200 100 <12E2> ~ 【終了コマンド】 「#=B*20+100」とすれば、Bの値で、120、140、160といった行番号まで処理を 移す switch - case 文も実現できます。 とはいっても読み難いプログラムに なってしまいます。 GAME III ではIF文やGOSUB、RETURNに相当する機能が拡張 され、rvtl ではラベルが使えるようになって、行番号にジャンプする必要は なくなっています。次の例は IF文の機能を持つ「;=」とラベルを使って書き 直したものです。 10 A=? 20 ;=A>100 ?=100 #=^skip 30 ?=10 40 ^skip 50 #=-1 このように変数名が記号となっているシステム変数というアイデアによって、 rvtl では色々な言語機能を実現しています。 ■ rvtlの機能 この rvtl64 は、32bit版 の rvtl と異なり64bitの命令で作成されているため、 64bit版の Linux 専用です。変数は 64bit の整数を格納することができ、 約±900京、±9,223,372,036,854,775,807 といった10進数で約20桁という非常に 大きな値を扱うことができます。また64bitの配列も使用できます。基本的に 32bit の rvtl に対して上位互換です。対話モードのエディタは行編集機能だけですが、 ファイル名補完、ヒストリー機能、utf-8の日本語の編集も可能です。 BASIC言語系のインタプリタ、エディタ、コマンドラインのヒストリ機能と ファイル名補完、組み込みコマンドとしてLinuxの基本コマンド(ls, cat, cd, mkdir, rm, mount, unmount, chmod, mv, rmdir, sync, pwd) や上級者向けコマンド (chroot, pivot_root, swapon, swapoff, exec)、そしてグラフィックで比較的 簡単にゲームを作成できるようにフレームバッファに対するパターン転送 (スプライト描画)、ライン描画などのコマンドも用意されています。 擬似乱数の生成には高速で上質な Mersenne Twister を使用しています。 Linux カーネル さえあれば、ディストリビューションの種類に関係なく 実行できます。(Address Space Layout Randomizationにも対応) rvtl64 は、歴史的な価値だけでなく、緊急時の限られた資源で実用となるような 機能を持っています。 rvtl64 を init プロセスとして使って Linux カーネルに 20キロバイト程度の initrd.gz を加えた 1FDLinux を作成することもできます。 若干の制限はありますが、rvtl64 は子プロセスの起動、パイプ、リダイレクト といったシェルとしての機能も持っています。 先頭行を #!/usr/bin/rvtl64 として実行パーミッションを付加するとシェルスクリプトとして使うことも できます。 ■ インストールとアンインストール rvtl64 を展開したディレクトリで ./rvtl64 と入力するだけで、共有ライブラリや カーネルのバージョンに関係なく使用できます。より便利に使うには、パスの 通ったディレクトリに rvtl64 をコピーすることをお勧めします。 デフォルトではシェルスクリプトとして使う場合を考えて /usr/bin に置くよう にしています。 # cp rvtl64 /usr/bin アセンブルする場合は、binutils (ARM64) または nasm (x86_64) が使用できる 環境で make して下さい。 スーパーユーザで make install すると /usr/bin に rvtl64 がコピーされます。 rvtl64 を rvtlw という名で起動すると、rvtlのプログラムが終了(#=-1)したり エラーでrvtl自身が終了します。 シンボリックリンクを次のように作成すると rvtlw で起動できます。 # cd /usr/bin # ln -s rvtl64 rvtlw rvtl64 のアンインストールは単に rvtl64 を削除するだけです。 ■ rvtl v.3.05、rvtl64 v.4.01の変更点 ● 64bit版(x86_64, Arm64) と 32bit版(x86, ARM) の違い 64bit版(x86_64, Arm64) では、変数はすべて64bitとまでの数値が代入可能です。 基本的に 32bit版(x86, ARM) の rvtl に対して rvtl64 は上位互換です。 ラベル名は rvtl では11文字(バイト)までまで区別されますが、rvtl64では 23文字(バイト)まで区別されます。 それ以上の長さのラベル名を使うことは できますが、11文字(32bit)、23文字(64bit)を超える部分は区別されません。 ?%=e 式の値を16進数16桁で出力 (64bit版専用) $%=e 式の値を8文字の文字コードとして出力(64bit版専用) A;n] 変数 A の値を先頭アドレスとする8バイト配列 (64bit版専用) フレームバッファを操作する命令 |fb? はアドレスとして64bitを渡す必要がある ため、引数の指定方法が変更(配列に64ビットの値を渡す部分)されています。 ● rvtl v.3.05、rvtl64 v.4.01 共通 以下の機能が追加されています。 <A Aの下位32bitを64bitにゼロ拡張する単項演算子 (32bit版では何もしない) ! 直近のジャンプ (#=, !=) を実行した行番号を保持 |zc rvtl 起動後のコマンド実行回数を % に返す |ve rvtl のバージョンを % に返す(64bit版は上位32bitに1) |vc CPU の種類を % に返す |rt コンソールをローカルエコー+Cookedモードに再設定 組み込みラインエディタは utf-8 の日本語の編集が可能となりました。 また、64bitのLinuxカーネルでは、システムコール番号が32bitシステムコールから 変更されています。したがって、|zz で直接システムコールを呼び出す場合は、 システムコール番号(変数 a)を変更する必要があります。 32bitの配列、例えばA[10] を変数、または64bitの配列に代入(例 A;10;=B[5]) する場合には自動的に符号拡張(マイナスの整数はそのままマイナスの整数となる) されます。 符号拡張しない場合は単項演算子「<」を使ってください(例 A=<B[5])。 vtl ディレクトリ中のサンプルのマイナーなバグを修正しました。 ネットワークを利用するサンプルを追加しました。 rvtl64 http_(cpu).vtl - IP_address / rvtl64 dns_(cpu).vtl - 8.8.8.8 www.mztn.org rvtl64 wget_(cpu).vtl - 8.8.8.8 www.mztn.org /index.html ■ 簡単なプログラム rvtl64 は BASICのコマンドを記号で置き換えたような言語です。 最近の言語にはない特徴として、各行の先頭には行番号を必要としています。 慣習では、10単位の行番号を使います。後から行を挿入する時に備えるため です。後ほど行番号を整形するプログラムを紹介します。 rvtlを起動すると次のように表示されます。 $ rvtl64 RVTL64 Arm64 v.4.01 2015/10/05,(C)2015 Jun Mizutani RVTL may be copied under the terms of the GNU General Public License. <040D> 「<040D>」はプロンプトで、このあとに rvtl64 のプログラムを入力、編集したり、 実行するコマンドを入力します。<>内の数値はプロセスIDの16進数表示となって います。子プロセスで rvtl64 を起動している場合や /sbin/init として動作して いる場合の識別に使いますが、通常は気にする必要はありません。 プロンプト以降にコマンドやプログラムを入力します。ここでは簡単な編集機能 があり、左右矢印キー、ctrl-F、ctrl-Bでカーソル移動、ctrl-D でカーソル位置 の文字を削除します。ctrl-H、BackSpaceはカーソルの直前の文字を削除します。 上下矢印キー、ctrl-N、ctrl-Pでヒストリーに保持されている最新の異なる16行 を呼び出すことができます。TABキーでファイル名補完が働きます。 入力行の最初 (空白を除く) が数字ならば、プログラムとしてメモリに格納 されます。プロンプトのあとに次のように入力してみてください。 入力されたプログラムは行番号の順に自動的に整列されます。行を挿入する には、すでに入力されて行番号の間の行番号をつけます。例えば、行40と 行50の間に挿入するには行番号として 45 を使います。 10 "A=" A=? / 20 "B=" B=? / 30 "A+B=" ?=A+B / 40 "A-B=" ?=A-B / 50 "A*B=" ?=A*B / 60 "A/B=" ?=A/B / 70 "Hit Any Key (q:quit) :" C=$ / 80 ;=(C='q')|(C='Q') #=-1 90 #=10 ダブルクォート「"」で囲まれた文字列は、そのまま表示されます。A=? は BASIC のINPUT文に相当して、キーボードから入力された数値を変数 A に 代入します。 「/」は改行を出力します。 ?=A+B などは「?=」に続く式の 値を10進数で表示します。行番号 70 の C=$ はキーボ-ドから1文字入力 して、変数 C に格納するコマンドです。コマンドとコマンドの区切りは1つ 以上の空白(半角スペース)で、コマンド内 (例えば式中) に空白を使うこと はできません。行番号 80 の「;=」は IF 文に相当して、続く式を計算して 結果が 0 ならば次の行の実行に移ります。計算結果が 0 以外なら同じ行の 命令を実行します。この場合は入力文字が「q」または「Q」のときEND文で ある #=-1 を実行してプログラムは終了します。行番号 90 の #=10 は GOTO文に相当して、「#=」に続く式を計算して結果の行番号にジャンプします。 入力したプログラムを確認するには、プロンプト「<xxxx> 」のあとに プログラムリストを表示するコマンドである 0 (ゼロ)を入力します。 30-60 のように行番号30から行番号60までを部分的にリストを表示する こともできます。50+4 のように指定した場合は、行番号50から4行を表示 します。「10」のように番号だけを入力すると、その番号と同じ行番号の 行が削除されます。変更したい行がある場合は、「40!」のように行番号の 直後に「!」をつけると、「40 "A-B=" ?=A-B /」のように表示されて編集 (修正) ができます。(プログラムの編集を参照) 実行するには 「#=1」と入力します。実行中のプログラムを強制的に終了 させる場合は ctrl-C (コントロールキーとCを同時に押す) を入力すると プロンプトを表示します。 rvtl64 自身を終了するには「~ (チルダ) 」を入力して Enter を押します。 ~」の入力は日本語キーボードでは、シフトキーを押しながら「^」または シフトキーを押しながら「半角/全角」(英語モードの場合)で入力できます。 vtl ディレクトリの fb_demo.vtl はフレームバッファを操作する機能の デモです。bmp ファイルをロードするため、vtl ディレクトリに移動した後 に実行してください。フレームバッファを使ったグラフィックのプログラム も高速に実行できます。 ■ プログラムの編集 入力行の先頭(空白を除く)が数字の場合は、数字を行番号として認識して 編集モードになります。行番号の直後に入力される記号によって、プログラムの 表示、編集、挿入、削除ができます。 文字コードが utf-8 の場合は日本語の編集も可能です。 o 行番号 0 ならプログラム全体を表示します。 o 行番号の直後が - なら指定範囲内の行を表示します。(例 100-200) o 行番号の直後が + なら行番号以降の20行を表示します。 + の後ろに表示行数を指定できます。(例 100+30) o 行番号の直後が ! なら指定した行番号の行を編集することができます。 このとき行番号を変更すると、変更した行が別に挿入され、元の行は そのまま残ります。 左右矢印キー、ctrl-F、ctrl-Bでカーソル移動、ctrl-D でカーソル位置の 文字を削除します。ctrl-H、BackSpaceはカーソルの直前の文字を削除 します。上下矢印キー、ctrl-N、ctrl-Pでヒストリーに保持されている 最新の異なる16行を呼び出すことができます。 o 同じ行番号の行が存在すれば入力された行と置換します。 o 入力された行と同じ行番号がなければ入力された行を挿入します。 o 行が行番号のみの場合は対応する行を削除します。 ■コマンドの解説 rvtl64 のコマンドを BASIC のコマンドを見出しとして説明します。 行番号つけたプログラムを #=1 で実行する間接モードと、プロンプトのあと 行番号なしで直接実行する直接モードがあります。FOR-NEXT、DO-UNTIL、 GOSUBは間接モード専用ですが、その他のコマンドはどちらのモードでも 実行できます。以下の説明の内、Aは任意の変数、e と n は任意の式を 表しています。 ● コメント : の後は、コメントとして行末まで無視されます。 #! もコメントとみなされます。プログラムの先頭行に次のように記述して おいて、実行権限をつけておくとシェルスクリプトやPerlのスクリプトの ように直接実行できます。 #!/usr/bin/rvtl 100 A=? 110 B=? 120 ?=A+B ● 代入文 右辺の式の値を左辺の変数に代入します。 A=e 代入文の特殊な形式として文字列からなる配列(256KBまで)を別の 領域の配列にコピーする命令があります。配列のためのメモリ領域を 確保しておく必要があります。文字列定数を代入する形式もあります。 実行後、% に文字数を返します。 文字列を直接指定した場合は 256 バイト以内に限定されます。 200 A*=e : 256 キロバイトまで 210 B*="ABCDEFG" : 256 バイト以内 コマンドライン引数をコピーする場合は範囲チェックをOFF「[=0」に した後に実行してください。 200 [=0 210 A*=\0 220 [=1 コピー先とコピー元のアドレスが同じ場合はコピーは実行されず、 % に文字が 0 となるまでの文字数を返します。文字列長を求めることが できます。 200 A*=A ?=% ● PRINT文 標準出力に数値や文字を書き出す命令です。 ?=e 式の値を10進数で出力。 ?(n)=e 式の値を10進数を n 桁右寄せで出力。 ?[n]=+e 式の値を10進数を上位桁を0で埋めた n 桁で出力。 ?$=e 式の値を16進数2桁で出力。 ?#=e 式の値を16進数4桁で出力。 ??=e 式の値を16進数8桁で出力。 ?%=e 式の値を16進数16桁で出力。(64bit版専用) ?*=e 式の値を符号無し10進数で出力。 ?{n}=e 式の値を8進数n桁で出力。 ?!n!=e 式の値の下位を2進数n桁で出力。 $=e 式の値を文字コードとして出力。 $$=e 式の値を2文字の文字コードとして出力。 $#=e 式の値を4文字の文字コードとして出力。 $%=e 式の値を8文字の文字コードとして出力。(64bit版専用) $*=e 式の値を先頭アドレスとするASCIIZ文字列を出力。 "STR" 文字列を出力。 / 改行を出力。 .=e 空白を式 e の値の数だけ出力。 ● INPUT文 入力した値を変数に代入する命令です。 A=? 10進数値入力(非数値の場合0が入力)。 A=$ 1文字入力。 A=@ リアルタイム文字入力。入力がない場合は0が返ります。 A*=$$ Aの示すアドレスに1行入力(編集機能付き)。 入力文字数が % に返ります。 ● GOTO文 指定した行番号にジャンプします。 #=^Label のように飛び先をラベルとした場合はラベル宣言(^Label) した行の次の行にジャンプします。 #=100 #=^STOP 行番号の部分に式を記述できます。(例 #=N*10+2000) ● IF文 式の値が偽(0) なら次の行から実行します。真の場合は次の文を 実行します。 ;=e "A=" A=? ● GOSUB文 指定した行番号にジャンプします。飛び先で ](リターン文)が実行 されると次の文から実行が再開されます。 !=^Label のように飛び先をラベルとした場合はラベル宣言(^Label) した行の次の行にジャンプします。 !=100 !=^LINE 行番号の部分に式を記述できます。(例 #=N*10+2000) ● RETURN文 GOSUB文(!=)の次の命令に戻ります。GOSUB - RETURN は最大32回 ネストすることができます。 100 ^SUB 110 "do something" 120 ] ● FOR - NEXT文 FOR文は代入文にカンマと終了値を加えた形式で記述します。 NEXT文は @=A+1 のようにFOR文で使った変数を@=の直後に置きます。 増分は +1 以外の値も可能です。また乗算を使用することもできます。 通常、繰り返し回数が決まっている場合に使います。 100 A=1,10 110 "do something" 120 @=A+1 1行中にFOR-NEXTループを置くこともできます。この例では 1 から 10 までの数値を順に詰めて表示して最後に改行します。 100 A=1,10 ?=A @=A+1 / 次のように降順にすることもできます。 100 A=10,1 110 "do something " ?=A / 120 @=A-1 ● DO - UNTIL文 UNTIL文の式 e の値が偽(0)の場合には @ (DO) に戻ります。 真(0以外)なら次の文を実行します。NEXT文と異なり式を括弧で囲む 必要があります。繰り返し回数が実行するまでわからない場合に 便利です。 100 @ 110 "do something" 120 @=(e) ● END文 実行中のrvtlのプログラムを停止します。rvtl自身は終了しません。 #=-1 ● NEW コマンド 行番号付きでメモリに格納された rvtl64 のプログラムを消去します。 &=0 ● SAVE コマンド rvtlプログラムをファイル名を指定してファイルに出力します。 <="ファイル名" ● LOAD コマンド ファイル名で指定された rvtlプログラムをロードします。すでに メモリに格納されているプログラムは消去されずにロードしたプログラム が挿入されます。必要ならば NEW (&=0) を先に実行してください。 >="ファイル名" ● LIST コマンド プログラムのリスト表示と編集のためのコマンドです。 0 rvtlプログラム全体のリスト 100- 行番号100以降すべてのリスト 100-500 行番号100以降500までのリスト 100+ 行番号100以降の20行のリスト 100+30 行番号100以降の30行のリスト 100! 行番号100の1行を表示して編集 ● RUN コマンド 直接モードで GOTO文を使ってrvtlのプログラムを実行します。 実行開始行番号を指定することができます。通常は #=1 とします。 実行中のプログラムを強制的に停止させるにはコントロールキー を押しながらC (ctrl-C) を押します。 #=1 ● ファイル書き出し { (ファイル先頭位置) と } (ファイル末位置) で指定した範囲を ""に囲まれたファイル名のファイルとして書き込みます。 0で終了する文字列の先頭アドレスを渡す形式も使用できます。 (="ファイル名" (*=A ● ファイル読み込み ""に囲まれたファイル名のファイルを { (ファイル先頭位置) で指定 されたメモリアドレスに読み込みます。読み込んだファイルサイズは }-{ (ファイル末位置 - ファイル先頭位置) で求めることができます。 0で終了する文字列の先頭アドレスを渡す形式も使用できます。 )="ファイル名" )*=A ● ファイル先頭を設定 ファイル書き出しコマンドの書き込み先頭位置を設定します。 {=e ● ファイル末を設定 ファイル書き出しコマンドの書き込み最終位置を設定します。 }=e ● メモリ最終(brk)を設定 メモリ最終(brk)を変更することができます。例えば *=*+10000 では メモリを10000バイト広げられます。 *=e ● 子プロセスの起動 子プロセスを起動します。実行ファイル名はフルパスで指定して下さい。 | を使ったパイプと > による出力のリダイレクトが可能です。 ,="/bin/ls -lt | /usr/bin/grep e >test.txt" "DONE" / 上記のようにコマンドの後ろに文が続かない場合は、次のような 省略記法が使えます。 , /bin/ls -lt | /usr/bin/grep e >test.txt 0で終了する文字列の先頭アドレスを渡す形式も使用できます。 文字列の先頭アドレスを変数に設定した後に実行します。 ,*=A ● 配列範囲チェック 配列を使って任意のメモリアドレスにアクセスすることができます。 デフォルト( [=1 )では , から * の範囲のメモリ以外にアクセスすると 範囲チェックによりエラーとなりますが、[=0 を実行するとエラーに なりません。必要な部分のみを [=0 と [=1 で囲むようにします。 フレームバッファを使う場合や文字列のコピーでコマンドライン引数を 参照する場合(A*=\0等)には必須ですが、不正な領域にアクセスすると セグメンテーションフォルトにより rvtl64 が終了してしまうことに注意 して下さい。 100 [=0 110 f[200]=$FFFFFFFF 120 [=1 ● 擬似乱数シード設定 乱数は rvtl64 の起動直後には毎回同じ系列で生成されるため、rvtl64 の 起動後には常に同じ乱数が帰ります。いつも異なる乱数を生成する ためには、擬似乱数のシード (ネタ?) に別の値を設定します。 例えば、擬似乱数のシードを現在時間のマイクロ秒に設定「A=_ `=%」 すると起動のたびに予想できない異なる系列の乱数列を生成することが できます。 `=e ● コード先頭アドレス設定 rvtl64 のプログラムを格納する領域の先頭は、rvtl64 の起動時に決定した 変更できない値「,」がコード先頭アドレス「=」に設定されています。 メモリ内に複数のプログラムを格納してコード先頭アドレス「=」を 変更することで複数のプログラムを切り替えて使えます。 ==e ● マイクロ秒単位の sleep 停止時間をマイクロ秒単位で与えてプログラムを一時停止します。 マシンの処理速度が異なっても同じタイミングで動作させたい場合など に使用します。 _=e ● rvtlの終了 チルダ「~」で rvtl64 自身の実行を終了します。 「~」の入力は、シフトキーを押しながら「^」、またはシフトキーを 押しながら「半角/全角」で入力できます。 ■ 変数と配列 変数は64ビットの数値を格納する単純変数と 1,2,4,8 バイトの値を扱う配列変数 があります。 単純変数は A から Z と a から z までの 52 個の変数を使うことができます。 変数名は 1 文字だけですが、冗長型として Integer, Command, ok などのよう に長い名前を使うことができます。 この場合には I と Integer は同じものと みなされることに注意してください。単純変数を格納する領域はあらかじめ 用意されています。 配列は C 言語の配列のように配列を格納する領域の先頭アドレスを配列名に設定 しておく必要があります。以下の例では変数Aに空きメモリ先頭アドレスを代入し、 変数Bには変数Aより100バイト大きい値を設定しています。これで変数Aは100 バイトの領域を配列として使用できます。1バイト配列では A(99)まで、2バイト 配列では A{49}まで、4バイト配列ではA[24]まで、8バイト配列ではA;12]までが B(0) と重ならないでアクセスできます。配列 B は残りの空きメモリをすべて 使用可能です。使用可能メモリの愁嘆を表すシステム変数「*」は rvtl64 の プログラムの大きさ + 空きメモリ = 256Kバイトが初期値になっていますが、 システム変数「*」を再設定すればメモリの許す限りの領域をアクセスできます。 100 A=& 110 B=A+100 120 A(99)=123 130 B[5]=456 140 ?=B[5] この例では、配列 A で使うメモリ量を、次の配列 B の宣言時に100バイトである ことを2行目で設定しています。配列が多くなると管理しきれなくなります。 次の例では、変数 z を確保済みの最終アドレスと決めておくことで配列の宣言と メモリの確保が一定の形式でできるようになります。 お勧めの方法です。 1000 *=*+(1024*1024) : メモリを 1MB 余分に確保 1010 z=& : 空き領域先頭アドレスを z に設定 1020 CacheSize=32*1024 1030 s=z z=z+256 : 配列 s(256) 1040 u=z z=z+256 : 配列 u(256) 1050 h=z z=z+256 : 配列 h(256) 1060 b=z z=z+CacheSize : 配列 b(CacheSize) 1070 H=z z=z+(256*1024): 配列 H(256*1024) 1080 d=z d=z+256 : 配列 d(256) 1090 q=z z=z+512 : 配列 q(512) 1100 B=z z=z+512 : 配列 B(512) 1110 W=z z=z+512 : 配列 W(512) 1120 U=z z=z+256 : 配列 U(256) ● 配列のサイズとメモリ中の配置 変数Aに未使用領域の先頭アドレス(&)を代入している場合の配列の様子を示します。 同じ領域を要素のサイズが1バイトから8バイトの配列としてアクセスできます。 A[1]とA{2}、A{3}は同じメモリ領域を使います。32bit版のrvtlでは8バイト配列は 使用できません。 1 byte 2 byte 4 byte 8 byte +--------+--------+--------+--------+ A=& | A(0) | | | | +--------+ A{0} + + + | A(1) | | | | +--------+--------+ A[0] + + | A(2) | | | | +--------+ A{1} + + + | A(3) | | | | +--------+--------+--------+ A;0] + | A(4) | | | | +--------+ A{2} + + + | A(5) | | | | +--------+--------+ A[1] + + | A(6) | | | | +--------+ A{3} + + + | A(7) | | | | +--------+--------+--------+--------+ | A(8) | | | | +--------+ A{4} + + + | A(9) | | | | +--------+--------+ A[2] + + | A(10) | | | | +--------+ A{5} + + + | A(11) | | | | +--------+--------+--------+ A;1] + | A(12) | | | | +--------+ A{6} + + + | A(13) | | | | +--------+--------+ A[3] + + | A(14) | | | | +--------+ A{7} + + + | A(15) | | | | +--------+--------+--------+--------+ ● rvtl のメモリイメージ 下の図は、rvtl を実行中のプロセスのメモリの使い方です。空き領域は変数 & から 変数 * の範囲で配列用の領域として使用します。 +--------------------+ : : : : +--------------------+ | rvtlコマンド | | | +--------------------+ | 作業領域 | | | | 変数用領域 A-Za-z | | スタック領域 | | 変数スタック領域 | +--------------------+ : : (PowerPC版では連続) = +--------------------+ | (rvtlのプログラム) | | 100 A=& | | 110 B=A+100 | | 120 A(99)=123 | | 130 B[5]=456 | | 140 ?=B[5] | & +--------------------+ A=& プログラム末 | 配列Aの領域 | | | +--------------------+ B=A+100 | 配列Bの領域 | | | |- - - - - - - - - - | | | | 空き領域 | | | * +--------------------+ メモリ領域最終(可変) : : : : : : |- - - - - - - - - - | | プロセスのスタック | | 引数文字列 | +--------------------+ 変数は変数専用スタックに格納(プッシュ)することができます。例えば +ABC の 場合は、変数が A、B、C の順にスタックに保存されます。1文字以上の長さの 変数名の場合でも先頭の1文字だけを指定します。スタックから戻す(ポップ)場合は -CBA とプッシュした順の逆に戻します。 サブルーチンの先頭で使う変数をプッシュ、リターンの前でポップすることで、 変数を局所変数として再帰的にサブルーチンを呼び出すことができるようになります。 「+=式」で式の値を変数を介さず変数スタックに格納することができます。 また、式中で「;」を参照すると変数スタックトップを返します(ポップ)。 ■ 関数 式中で関数の値を使用するには、rvtl64 ではシステム変数を参照する形式になり ます。 値を参照できるシステム変数には次のものがあります。 # 実行中の行番号を保持 ! 直近のジャンプ (#=, !=) を実行した行番号を保持 % 直前の除算の剰余、直前の _ 参照の usec、文字列コピーの文字数を保持 & コードの最終使用アドレス+1 ) 読み込みサイズ保持 * メモリ最終位置を保持 = プログラム先頭アドレス [ 配列範囲チェック ^ ラベルの次行先頭アドレス _ 秒単位のUNIX時間を返します。同時にマイクロ秒が % に設定される。 { ファイル先頭位置 | エラーコード保持 } ファイル末位置 , アクセス可能先頭アドレス ` Mersenne Twister による乱数を返す。 . コンソールの大きさ(ウィンドウサイズ)を上位16ビットに幅、 下位16ビットに高さを文字単位で返す。 ; 変数スタックトップを返します(ポップ)。 _ 現在の秒を返し、% にマイクロ秒を返す。 ? 標準入力から10進数値を得る。 $ 標準入力から文字を得る。 @ 標準入力から文字を得る。入力がなければ 0 を返す。 \式 式の示す番号の引数文字列の先頭アドレスを返す。番号は0から。 \\式 式の示す番号の環境変数文字列の先頭アドレスを返す。 ■ 式 式は、数値、単純変数または配列変数と以下の演算子で構成され、32ビットの 値を返します。演算子の優先順位は ( ) 内が優先され、単項演算子と関数が 次に優先されます。 算術演算子と比較演算子は同じ優先順位として扱われ、左から右に順次実行さ れます。乗算と除算も加減算と同じ優先順位であることに注意してください。 通常の計算では、乗算と除算は加減算に優先されないことに注意すれば十分で すが、A=B*--+(10-20) のように複雑な式の例を考えて見ます。 最初に B と乗算する数値または変数があることが認識されます。その値に対し ては符号反転する必要があることが記憶されます。さらに符号反転、さらに 絶対値を求めることを記憶します。その後 ()内に優先的に処理する必要のある 式があるため、(10-20)を評価して -10 という結果を得ます。これまでに記憶 してきた処理を順次実行することで ( の前の + により絶対値の 10 が得られ、 2度の符号反転の結果、結局 10 という値をBと乗算することがわかります。 結果として、A=B*10 という計算になります。 ● 算術演算子 A+B 加算 A-B 減算 A*B 乗算 A/B 除算 A&B ビット論理積 A\B 無符号除算 A^B 排他的論理和 A|B ビット論理和 A>>B 右シフト A<<B 左シフト ● 単項演算子 -A Aの符号反転 +A Aの絶対値 <A Aの下位32bitを64ビットにゼロ拡張 (32bit版では何もしない) ● 比較演算子 A=B A と B が等しければ 1 を返し、それ以外は 0 を返す。 A<B A が B より小さければ 1 を返し、それ以外は 0 を返す。 A>B A が B より大きければ 1 を返し、それ以外は 0 を返す。 A>=B A が B が以上ならば 1 を返し、それ以外は 0 を返す。 A<=B A が B が以下ならば 1 を返し、それ以外は 0 を返す。 A<>B A と B が等しくなければ 1 を返し、それ以外は 0 を返す。 ■ 組み込みコマンド rvtl64 はコマンドを ,="/usr/bin/bash" のように実行することができますが、 最低限必要なコマンドを組み込みコマンドとして持っています。 Linux のコマンドのうち rm、ls、cd、chmod、chroot、pwd、exec、mkdir、 mv、rmdir、sync、mount、umount、cat、swapon、swapoff、pivot_root に 相当する組み込みコマンドがあります。 まずは、比較的使用頻度の高いコマンドです。rvtl64 をシェルスクリプトとして 使う場合や、rvtl64 中から ,="/usr/bin/ls -l" を実行するのが面倒な場合に使 います。 |ls*=A のように組み込みコマンドに続けて「*=式」とすると式の値を先頭 アドレスとするASCIIZ文字列 (0で終わる文字列) を組み込みコマンドの 引数として渡すことができます。|sy や |fb? のように引数を必要としない コマンドでは使えません。 【注意】 引数を必要とする組み込みコマンドは、行末までが引数と判断されます。 組み込みコマンドの後ろに文(マルチステートメント)を置かないように して下さい。 ● ファイルの内容表示 cat コマンドに相当します。 指定したファイルの内容を表示します。 |ca file ● カレントディレクトリ内容の表示 ls コマンドに相当します。 カレントディレクトリのファイルを 表示します。8進数で表した パーミッション、ファイルサイズ、 ファイル名の順で表示します。 |ls /usr のようにディレクトリを 指定することもできます。 ● カレントディレクトリの移動 cd コマンドに相当します。 カレントディレクトリを指定した ディレクトリに移動します。 |cd /etc ● パーミッションの変更 chmod コマンドに相当します。 ファイルまたはディレクトリの パーミッションを変更します。 パーミッションは8進数で指定します。 |cm 755 tmp.txt ● ディレクトリの作成 mkdir コマンドに相当します。 指定したディレクトリを作成します。 ● ファイル名の変更、ファイルの移動 mv コマンドに相当します。 指定したファイル名の変更、または ファイルを移動します。 |mv file1 file2 |mv file ./dir ● ディレクトリの削除 rmdir コマンドに相当します。 指定したディレクトリを削除します。 |rd dir ● カレントディレクトリの表示 pwd コマンドに相当します。 カレントディレクトリを表示します。 |cw ● ファイルの削除 rm コマンドに相当します。 指定したファイルを削除します。 |rm file ● ファイルシステムのマウント mount コマンドに相当します。 通常、スーパユーザ(root) で実行 する必要があります。 mountコマンドと同様にファイルシステムの マウントしますが、 引数の与え方が異なります。|mo /dev/hda3 /mnt ext2 や マウント済みのファイルシステムは |ca /proc/mounts での確認できます。 |mo /dev/hda3 /mnt vfat → Windows |mo /dev/hda1 /mnt ext2 → Linux 標準 |mo /dev/hdc5 /mnt reiserfs → reiserfs |mo none /proc proc → /proc |mo /dev/hdb /mnt iso9660 r → CD-ROM (読出しのみ) ● ファイルシステムのアンマウント umount コマンドに相当します。 通常、スーパユーザ(root) で 実行する必要があります。 マウントしているファイルシステムを アンマウントします。 |um の引数にデバイス名 (/dev/hda2等) は 指定できません。マウントポイント (ディレクトリ名) を指定してください。 |um /mnt |um /mnt/cdrom ● ディスクへの強制書き込み sync コマンドに相当します。 変更されたファイルをディスクキャッシュ からハードディスクなどの デバイスに保存します。 |sy ● ルートディレクトリの変更 chroot コマンドに相当します。 ルートディレクトリを変更します。 |cr /mnt ● 外部コマンドの実行 exec コマンドに相当します。 exec を実行します。rvtlのプロセスが 指定されたファイルのプロセスに 置き換わります。 |ex /sbin/init ● ルートファイルシステムの変更 pivot_root コマンドに相当します。 カレントプロセスのルート ファイルシステムを第2引数に指定した ディレクトリに変更して、 第1引数に指定したディレクトリを新しい ルートファイルシステムにします。 たとえば、フロッピーディスクから rvtl64 自身を/sbin/initとして起動 した後、 ルートファイルシステムをマウントしたハードディスクに変更して、 そのマウントポイントを新しいルートファイルシステムに変更して、 そこにある「本当の」/sbin/init を exec するような場合に使用します。 【実行例】 <0126> |mo /dev/hdc5 /mnt reiserfs Mount <0126> |cd /mnt Change Directory to /mnt <0126> |pv /mnt /mnt Pivot Root <0126> |ex /sbin/init ● スワップ領域のオープン swapon コマンドに相当します。 スワップ領域をオープンします。 【実行例】 <0126> |so /dev/hda3 Swap On ● スワップ領域クローズ swapoff コマンドに相当します。 スワップ領域クローズ 【実行例】 <0126> |sf /dev/hda3 Swap Off ● コマンドの実行回数 rvtlを起動してからの命令の実行回数の累計を% に返します。 【実行例】 <0126> |zc コマンド実行回数を (>ver.4.00) <0126> ?=% 8 ● システムコールの実行 変数 a, b, c, d, e, f, g を引数としてLinuxのシステムコールを実行します。 変数 a にはシステムコール番号、b が第1引数、c が第2引数、 あとは同様に 第6引数まで指定できます。 結果は、変数「|」 に返ります。 変数 a にはシステムコール番号は、32bit版では x86、ARM、PowerPCで共通ですが、 64ビット版では32ビット版と異なる上、x86_64とARM64でも異なります。 |zz ● URLデコード URLエンコードされた文字列をデコードします。 u;0] URLエンコード文字列の先頭設定 u[2] 変更範囲の文字数を設定 u;2] デコード後の文字列先頭を設定 u[6] デコード後の文字数を返す |ud ● rvtlのバージョン |ve を実行すると、% にrvtlのバージョンを返します。 (64bit版は % の上位32bitに1が返ります) 【実行例】 <08C1> |ve ?=<% 40100 ● CPUの種類 |vc を実行すると、CPUの種類を % に返します。 x86 は 1、arm は 2、x86_64 は 4、 arm64 は 5 が返ります。 【実行例】 <086B> |vc ?=% 5 ■ フレームバッファ用組み込みコマンド フレームバッファを使うための組み込みコマンドがあります。|fbo, |fbc, |fbs はカーネルがフレームバッファをサポートしていない場合はエラーと なります。それ以外の描画命令はフレームバッファのメモリに対して描画する だけでなく、任意のメモリ領域に対しても実行できます。メモリ中に確保した オフスクリーン領域に対して描画して、フレームバッファに転送することも できます。メモリ領域に対して一括して値を書き込む場合や、メモリのコピー として使用できます。|fb 命令に与える引数は、各々決まった配列変数を として渡します。 命令を使用する前に配列用の領域を確保しておく必要が あります。「m=& l=m+64 t=l+64 r=t+64 」のように各配列に一律に64バイト 確保しておけば十分です(変数の解説参照)。サンプルの fbinfo64.vtl、 fb_demo64.vtl を参考にしてください。 引数の配列のインデックスの指定方法が rvtlの32bit版と64bit版では異なる 部分があります。 メモリアドレスを指定する場合に rvtl の64bit版では アドレスに64bitを指定する必要があるため、引数の指定に8バイト配列表現 を使う部分があります。 例えば Ubuntu 15.04 でフレームバッファを使うには ctrl と Alt と F1 を同時に押します。GUIの画面に戻るには ctrl と Alt と F7 を押します。 また、一般ユーザでフレームバッファに書込みするには video グループ に所属する必要があります。 以下のコマンドで設定します。 sudo gpasswd -a ユーザ video video グループを有効にするために再ログインします。 ● フレームバッファのオープン 実行後フレームバッファが使える状態ならば、変数 f にフレームバッファ の先頭アドレス、変数 g の示すアドレスからフレームバッファの情報 (Virtual Screen Information と Physical Screen Information)が設定 されます。 f, g ともに rvtl が用意しているアドレスが返されるため、 配列用のメモリ を確保する必要はありません。 |fbo フレームバッファにアクセスする前に [=0 を実行して、アクセス範囲 チェックをOFFにする必要があります。 フレームバッファが有効でない場合には [ENODEV] No such device と表示 されて終了します。 フレームバッファに関する情報は配列 g[] から取得することができます。 Frame Buffer Virtual Screen Information [ 0] x resolution : g[0] [ 1] y resolution : g[1] [ 2] x res virtual : g[2] [ 3] y res virtual : g[3] [ 4] x offset : g[4] [ 5] y offset : g[5] [ 6] bits/pixel : g[6] [ 7] grayscale : g[7] [ 8] RED offset : g[8] [ 9] length : g[9] [10] msb_right : g[10] [11] GREEN offset : g[11] [12] length : g[12] [13] msb_right : g[13] [14] BLUE offset : g[14] [15] length : g[15] [16] msb_right : g[16] [17] TRANSP offset : g[17] [18] length : g[18] [19] msb_right : g[19] [20] nonstd : g[20] [21] activate : g[21] [22] height : g[22] [23] width : g[23] [24] accel_flags : g[24] [25] pixclock : g[25] [26] left margin : g[26] [27] right margin : g[27] [28] upper margin : g[28] [29] lower margin : g[29] [30] hsync length : g[30] [31] vsync length : g[31] [32] sync : g[32] [33] vmode : g[33] Frame Buffer Physical Screen Information [40] ID string : g(160) .. g(175) [44] frame buffer mem start : g;22] [45] frame buffer mem length : g[46] [46] frame buffer type : g[47] [47] interleave : g[48] [48] frame buffer visual : g[49] [49] x pan step : g{100} [49] y pan step : g{101} [50] y wrap step : g{102} [51] line length (bytes) : g[52] [52] Memory Mapped I/O start : g;27] [53] Memory Mapped I/O length: g[56] [54] acceleration type : g[57] ● フレームバッファのクローズ |fbo でオープンしたフレームバッファをクローズする場合に 使用します。 |fbc ● フレームバッファの設定変更 |fbo で返される g 配列にフレームバッファの設定を書いてから 呼び出します。 |fbs ● 点の描画 フレームバッファに点を描画します。配列 d に必要な値を設定して呼び出します。 d;0]=f とするとフレームバッファに対する操作となります。 d;0] に メモリアドレスを設定するとメモリに対しての描画 になり、バックバッファ に対する書き込みが可能です。 d;0] = addr 描画領域先頭アドレス d[2] = x 転送先のX座標 d[3] = y 転送先のY座標 d[4] = Color 色 d[5] = ScrX 転送先X方向のバイト数 d[6] = Depth 1ピクセルのビット数 |fbd ● 塗りつぶし フレームバッファを指定した色で塗りつぶします。 配列 m に必要な値を 設定して呼び出します。 フレームバッファをクリアする場合などに使用します。 m;0]=f とするとフレームバッファに対する操作となります。 m;0] にメモリアドレスを設定するとメモリに対しての描画 になり、 バックバッファに対する書き込みが可能です。 m;0] = addr メモリフィル先頭アドレス m[2] = offset オフセット(バイト) m[3] = length 長さ(ピクセル) m[4] = color 色 m[5] = Depth 1ピクセルのビット数 |fbf ● ライン描画 フレームバッファに指定した色の線を描画します。 配列 l (英小文字のエル) に必要な値を設定して呼び出します。 l;0]=f とするとフレームバッファに 対する操作となります。 l[7]、l[8] は内部で使用する作業領域です。 指定する必要はありません。 l;0] にメモリアドレスを設定するとメモリに 対しての描画 になり、バックバッファに対する書き込みが可能です。 l;0] = addr 描画領域先頭アドレス l[2] = x 始点のX座標 l[3] = y 始点のY座標 l[4] = x 終点のY座標 l[5] = y 終点のY座標 l[6] = col ラインの色 l[7] = incr1 作業領域 l[8] = incr2 作業領域 |fbl ● パターン転送 メモリアドレス mem を先頭に格納されているパターン(画像)を addr に 転送します。 配列 p に必要な値を設定して呼び出します。 p;0]=f とするとフレームバッファに対する操作となります。 p;0] にメモリアドレスを設定するとメモリに対しての描画 になり、 バックバッファに対する書き込みが可能です。 p;0] = addr 転送先アドレス p[2] = x 転送先のX座標 p[3] = y 転送先のY座標 p[4] = PatW パターンの幅 p[5] = PatH パターンの高さ p;3] = mem パターンの格納アドレス p[8] = ScrX X方向のバイト数 p[9] = Depth 1ピクセルのビット数 |fbp ● パターン転送2 メモリアドレス mem を先頭に格納されているパターン(画像)を addr に 転送します。転送元(mem) の画像の一部を転送します。 配列 t に必要な 値を設定して呼び出します。 t;0]=f とするとフレームバッファに対する 操作となります。さらに t;3]=f とした場合はフレームバッファ内で 矩形領域をコピーします。転送元と 転送先が重なった場合の動作は 保証されません。 t;0] にメモリアドレスを設定するとメモリに対しての描画 になり、 バックバッファに対する書き込みが可能です。 t;0] = addr 転送先アドレス t[2] = x 転送先のX座標 t[3] = y 転送先のY座標 t[4] = PatW パターンの幅 t[5] = PatH パターンの高さ t;3] = mem パターンの格納アドレス先頭 t[8] = ScrX 転送先のX方向のバイト数 t[9] = Depth 1ピクセルのビット数 t[10] = x2 転送元のX座標 t[11] = y2 転送元のY座標 t[12] = ScrX2 転送元のX方向のバイト数 |fbt ● マスク付きパターン転送 スプライトをフレームバッファに描画します。 配列 q に必要な値を設定して 呼び出します。 q[10] で転送しない色を指定できる他は |fbp と同じです。 背景画像に対してパターンを重ねる場合に使用します。 q;0] にメモリアドレスを設定するとメモリに対しての描画 になり、 バックバッファに対する書き込みが可能です。 q;0] = addr 転送先アドレス q[2] = x 転送先のX座標 q[3] = y 転送先のY座標 q[4] = PatW パターンの幅 q[5] = PatH パターンの高さ q;3] = Color パターンの格納アドレス q[8] = ScrX X方向のバイト数 q[9] = Depth 1ピクセルのビット数 q[10]= Mask マスク色 |fbq ● 矩形領域の塗りつぶし |fbf と似ていますが、任意の矩形領域を一定の色で塗りつぶします。 配列 r に必要な値を設定して呼び出します。 r;0] にメモリアドレスを設定するとメモリに対しての描画 になり、 バックバッファに対する書き込みが可能です。 r;0] = addr 転送先アドレス r[2] = x 転送先のX座標 r[3] = y 転送先のY座標 r[4] = PatW 領域の幅 r[5] = PatH 領域の高さ r[6] = Color 塗りつぶす色 r[7] = ScrX X方向のバイト数 r[8] = Depth 1ピクセルのビット数 |fbr ● メモリコピー メモリを c;0] で指定したアドレスから c;1] で指定したアドレスへ c[4] バイトコピーします。大量の転送を rvtl64 で記述すると遅い場合に 使用します。フレームバッファ以外の一般のメモリ転送にも使用できます。 転送する領域が重なっていても使用できます。 c;0] = source 転送元先頭アドレス c;1] = dest 転送先先頭アドレス c[4] = length 転送バイト数 |fbm ■ 上級者向けの機能 rvtl64 のコマンドラインで指定した rvtl64 のプログラムは、そのままメモリに 格納されるのではなく、コンソール(キーボード)から入力するように処理され ます。つまり、行番号は整列されている必要がなく、行番号が「ばらばら」でも メモリには整列されて格納されます。また、#=1 (RUN) や &=0 (NEW) のように 行番号なしで直接実行するコマンドを記述することが可能です。 例えば: 20 #=-1 : プログラム終了 10 "Hello world!" / : 文字列を出力して改行 0 : リスト出力 <="hello2.vtl" : リストをhello2.vtl というファイル名で保存 / : 改行出力 #=1 : プログラムを実行 ~ : rvtl64 の終了 のように書かれたファイル(hello1.vtl)を実行すると、次の結果となります。 $ rvtl64 hello1.vtl 10 "Hello world!" / : 文字列を出力して改行 20 #=-1 : プログラム終了 hello2.vtl Hello world! rvtl64 にはプログラムの先頭アドレスを変更する機能もあります。メモリ中に 複数のプログラムを格納して、切り替えて実行することもできます。 先頭アドレスは 「=」システム変数に格納されているため、これを書き換える ことで実現します。デフォルトのプログラムの先頭アドレスは「,」に格納され ていて、書き換えることはできません。「=」システム変数の初期値は「,」です。 「==,」が実行されていると考えてください。ここで「==,+2000」と実行したと します。これは、「プログラム先頭アドレスをデフォルトのアドレスに2000を 加えた位置に変更する」という意味になります。実際に「==,+2000」を実行すると、 「&=0 required.」と表示されます。適当にプログラムの先頭アドレスを変更した ためにプログラムの終了アドレス(の後ろ)が決められないため、「NEWを実行しろ!」 といってきています。「&=0」を実行して、プログラムを何か入力してください。 例えば「100 "Start"」とします。「#=1」で実行して、「==,」でデフォルトの プログラム先頭アドレスに戻り、再度「==,+2000」を実行すると、今度は 「&=0 required.」と表示されません。「0」でリスト表示をすると、 100 "Start" と表示されます。前に入力したプログラムがそのまま認識されています。 ==<アドレス> を実行して、プログラム先頭アドレスを変更した場合に、すでに 有効なプログラムが存在する場合には、そのプログラムの終了アドレスも認識 されて実行できるようになります。 rvtlのプログラムをシェルスクリプトとして直接実行するためには、 先頭行に #!/usr/bin/rvtl64 を付加します。# の前に空白などを挿入しないで 下さい。rvtl64 の格納場所が異なる場合は #!/usr/bin/rvtl64 などと します。以下のプログラムを script.vtl というファイル名で保存して、 chmod 755 script.vtl のようにパーミッションを実行可能に変更します。 #!/usr/bin/rvtl 10 "test" / 20 ?=12345 / #=1 ~ 実行すると行番号の付いた 2行のrvtlのプログラムがメモリ中に保存され、 #=1 で実行、~ で終了のように動作します。 $ ./script.vtl test 12345 ■ コマンドライン引数 rvtl64 の起動時に rvtl64 のプログラムを引数として与えることができます。 rvtl64 は起動とともにプログラムを読み込みます。このとき複数の rvtl プログラムを指定すると順に読み込みプログラムで使用している行番号に したがってメモリ中に混ざった形で格納されます。引数として与える プログラムとして、行番号を付けない形式の「#=1」実行、「~」終了、 「0」リストなどを含んだファイルを指定することもできます。 サンプルプログラムの run.vtl の内容は「#=1」だけ、runq.vtl は 「#=1」と「~」のファイルになっています。 $ rvtl64 abc.vtl runq.vtl とコマンドラインで指定すると、abc.vtl を実行して、rvtl自身も終了 することができます。 rvtl64 のプログラムにコマンドラインから文字列を与えることもできます。 コマンドライン引数の先頭が「-」である引数の後ろは、プログラム中で \0、\1 のように引数の文字列の先頭アドレスを参照することができます。 引数文字列は rvtl64 のプロセスのスタック領域を示しているため、 配列として参照したり、文字列コピー (例えば A*=\0) を実行する前には 範囲チェックをOFF ([=0) にしない場合に「Out of Range」エラーになる ことに注意してください。 次のプログラムを arg.vtl というファイル名で保存しているとします。 100 I=0 : 最初の引数 110 [=0 : 範囲チェックを OFF 120 @ : 繰り返し (DO) 130 S=\I : 引数の先頭アドレス 140 ;=S(0)<>0 $*=S / : 引数を表示 150 I=I+1 : 次の引数 160 @=(S(0)=0) : 空文字列なら繰り返しを終了 170 [=1 : 範囲チェックを ON に戻す 160 #=-1 : 実行終了 S=\I の部分で「-」の後ろの引数を参照しています。 行番号 110 の「[=0」は、1バイト配列で引数文字列をアクセスした場合の 範囲外エラーを回避するためのものです。 \I が空文字列 (行末の 0 のみ) を示す場合には引数を表示することなく (行番号 140)、ループを終了(行番号 160) します。 「-」という引数の後に適当に引数を与えて実行します。 $ rvtl64 arg.vtl runq.vtl - abc xyz arg.vtl abc xyz arg.vtl 引数に日本語の文字列を与えた場合も確認してみます。 $ rvtl64 arg.vtl - 日本語も できるかな? <0F2F> #=1 日本語も できるかな? 実際に実行可能なスクリプトを作成してみましょう。 #!/usr/bin/rvtl 100 $*=\0 / : 最初の引数を表示 110 [=0 120 |ls*=\0 : ファイルリストを表示 130 [=1 #=1 : 実行して ~ : 終了 ls.vtlが上の内容のファイルの場合、次のように ls.vtl に実行権限を与えて 実行すると「/etc」を表示した後、ファイルリストが表示されます。 $ ./ls.vtl - /etc /etc List Directory 640755 1392 . 040755 928 .. 440755 744 X11 300644 1615 rpc 300644 19 motd 300644 281 mtab 640755 704 rc.d 300644 126 ttys 320777 13 utmp 320777 13 wtmp : 略 : 次のように rvtl64 から実行しても、まったく同じ動作をします。 $ rvtl64 ls.vtl - /etc rvtl64 を /sbin/init (プロセスIDが1) として使う場合に、/sbin/init.vtl が存在すると起動時に自動的に読み込まれます。 1FDLinux や CD-ROMブートで rvtl64 をブートローダ代わりに使う例は http://www.mztn.org/ を参照してください。 ■ プログラム例とリナンバ rvtl64 自身にはリナンバ(行番号の整理)機能はありません。rvtlが自分自身を 書きなおしてリナンバすることができます。GOTO (#=) や GOSUB (!=) の飛び先 にラベルのみを使って行番号を指定していない場合には、次のプログラムを リナンバしたいプログラム(の最後部)にマージして #=90000 のように実行すれば リナンバできます。 rvtl64 のプログラムは、メモリに次のような形式で格納されています。 NextPtr(4), LineNo(4), Source Text(行番号を除く), 0 o NextPtr は次の行へのバイトオフセット. o NextPtr が $FFFFFFFF の時は最終行 90010 Z=# : 行番号を保存 90020 N== : コード領域先頭 90030 S=1000 : 行番号開始番号 90040 ;=N[1]>=Z #=-1 : 自分自身は処理しない 90050 S=S/10 : 新行番号は10番おきに設定 90060 @ 90070 N[1]=S+I*10 : 新行番号 90080 N=N+N[0] I=I+1 : 次行先頭 90090 @=((N[0]<0)+(N[1]>=Z)) : 最後の行まで処理したか? 一方、GOTO (#=) や GOSUB (!=) の飛び先に行番号を使っている場合には、 もう少し複雑な処理が必要です。#=10 を #=1010 のように置換する場合には 行の長さが変化します。行番号の長さの変化に追従するのは面倒なので、 次のプログラムではリナンバした結果を標準出力に書き出しています。 やはり、リナンバしたいプログラム(の最後部)にマージして #=90000 のように 実行すればリナンバした結果が表示されます。これでは表示だけなので、 標準出力をファイルにリダイレクトします。 1) リナンバしたいプログラムをファイルに書き出し <="xxx.vtl" 2) リナンバを実行 ,="./rvtl64 xxx.vtl renum.vtl > xxx2.vtl" 90000 Z=# : 行番号を保存 90010 :--------------- Renumber ----------------------------------- 90020 : renum.vtl 2003/10/03, 2015/09/17 Jun Mizutani 90030 : ,="./rvtl xxx.vtl renum.vtl > xxx2.vtl" 90040 :------------------------------------------------------------ 90050 : 90060 T== : コード領域先頭 90070 N=T : Nは行先頭ポインタを最初の行に設定 90080 B=& : 2500行分の旧行番号 90090 C=B+10000 : 2500行分の新行番号 90100 : 90110 S=1000 : 行番号開始番号 90120 ;=N[1]>=Z #=-1 : renum.vtl 自身は処理しない 90130 I=0 : 行番号テーブルのインデックス 90140 S=S/10 : 新行番号は10番おきに設定 90150 @ 90160 B[I]=N[1] : 旧行番号 90170 C[I]=S+I*10 : 新行番号 90180 N=N+N[0] : 次行先頭 90190 I=I+1 : 行番号テーブルインデックス更新 90200 @=((N[0]<0)+(N[1]>=Z)) : 最後の行まで処理したか? 90210 Q=I-1 90220 : !=^TableList 90230 N=T : 行先頭ポインタを最初の行に設定 90240 I=0 : 行内の位置 90250 @ 90260 ?=C[I] " " : 行番号出力 90270 K=8 : コード行頭 90280 F=0 90290 @ 90300 H=N(K) ;=H<>0 $=H : 1文字出力 90310 ;=((H='"')+(H=':'))*(F=0) F=1 #=^skip0 90320 ;=(H='"')*(F=1) F=0 : コメント、文字列中は無視 90330 ^skip0 90340 ;=((H='#')+(H='!'))*((N(K-1)=' ')+(K=8))*(F=0) !=^Replace 90350 K=K+1 : 次の文字 90360 @=(H=0) : 行末まで繰返す 90370 N=N+N[0] / : 次行先頭 90380 I=I+1 90390 @=((N[0]<0)+(N[1]>=Z)) : 最後の行まで処理したか? 90400 #=-1 90410 : 90420 ^Replace 90430 ;=N(K+1)<>'=' ] : GOTOまたはGOSUBでなければ戻る 90440 K=K+1 : 次の文字 90450 $=N(K) K=K+1 : = 出力 90460 ;=N(K)='-' "-" ] : 負数(#=-1)の場合は何もしない 90470 ;=N(K)='^' "^" ] : ラベルなら何もしない 90480 L=0 : 10進数字文字列を数値に変換 90490 @ 90500 H=N(K) ;=(H>='0')*(H<='9') L=L*10+(H-'0') 90510 K=K+1 90520 @=((H<'0')+(H>'9')) 90530 : / "L=" ?=L / 90540 J=0 : 行番号テーブルをスキャン 90550 @ 90560 ;=B[J]=L L=C[J] J=Q #=^skip1 : 同じ番号なら置換 90570 ;=B[J]>L L=C[J] J=Q : 同一番号が無ければ次 90580 ^skip1 90590 J=J+1 90600 @=(J>Q) : 見つけたら終り 90610 ?=L $=H K=K-1 90620 ] 90630 ^TableList 90640 J=0,Q : 行番号テーブルの表示 90650 ?(6)=B[J] " " ?(6)=C[J] / 90660 @=J+1 90670 ] #=90000 ~ vtl/renum.sh を使って、次の例のように check.vtl をリナンバして __check.vtl に書き出すこともできます。これが一番便利です。 $ ./renum.sh check.vtl ■ 制限 変数スタックに積むことのできる変数の最大個数 1024 サブルーチンのネスト+ (ループのネストx 2) 最大32 プログラム + データ用のメモリの初期値 256 キロバイト 使用可能なラベルの最大数 1024 ■ ファイルの構成 README.txt この文書 LICENSE-GPL2.txt ライセンス rvtl_ref.txt rvtlの機能一覧 rvtl64 rvtl64 の実行プログラム source/ rvtl64 のアセンブリソース vtl/ rvtl64 のサンプルプログラム ■ 最新情報 この文書以外の情報やサンプルプログラム、rvtlの最新情報は http://www.mztn.org/ を参照して下さい。 rvtl64 を使ってプログラムを作成された方は mizutani.jun@nifty.ne.jp まで お知らせください。http://www.mztn.org/ で紹介させて頂きます。 ■ ライセンス このソフトはフリーソフトウェアです. GNU General Pulic Licenceにしたがって 自由に使用,配布,改変して頂いてかまいません。 GNU General Pulic Licence の詳細はCOPYINGを参照して下さい。 著作権は私(水谷 純, mizutani.jun@nifty.ne.jp)が保有しています。 本ソフトウェアによって生じた損害について著作者は責任を負いません。 また、著作者はバージョンアップの義務を負いません。