コマンド処理
1行の処理(^ParseLine)から呼び出されて1つのコマンド(命令文)をコンパイルします。
コマンドの解析
コマンドの種類を解析して、対応するルーチンに分岐します。分岐先のルーチンでは一気にコード生成まで行います。rvtlの文法では、基本的にコマンドの先頭の1文字だけでコマンドの種類が決まるため、先頭文字からIFで対応する処理に分岐させます。
18670 :---------------------------------------------------------- 18680 : 1命令の処理 18690 :---------------------------------------------------------- 18700 ^Command 18710 c=C(I) I=I+1 18720 d=c 18730 ;=D>1 / "<com [" $=d "]>" 18740 c=C(I) : 1文字先読み 18750 ;=d='!' #=^Com_GOSUB : 21 ! GOSUB 18760 ;=d='"' #=^Com_String : 22 " 文字列出力 18770 ;=d='#' #=^Com_GO : 23 # GOTO 実行中の行番号を保持 18780 ;=d='$' #=^Com_OutChar : 24 $ 文字コード出力 18790 ;=d='%' #=^Com_Error : 25 % 直前の除算の剰余または usec を保持 18800 ;=d='&' #=^Com_Error : 26 & NEW, VTLコードの最終使用アドレスを保持 18810 ;=d=$27 #=^Com_Error : 27 ' 文字定数 18820 ;=d='(' #=^Com_FileWrite :28 ( File 書き出し 18830 ;=d=')' #=^Com_FileRead : 29 ) File 読み込み, 読み込みサイズ保持 18840 ;=d='*' #=^Com_BRK : 2A * メモリ最終(brk)を設定, 保持 18850 ;=d='+' #=^Com_VarPush : 2B + ローカル変数PUSH, 加算演算子, 絶対値 18860 ;=d=',' #=^Com_Exec : 2C , fork & exec 18870 ;=d='-' #=^Com_VarPop : 2D - ローカル変数POP,減算演算子,負の十進数 18880 ;=d='.' #=^Com_Space : 2E . 空白出力 18890 ;=d='/' #=^Com_NewLine : 2F / 改行出力, 除算演算子 18900 ;=d=':' #=^Com_Comment : 3A : 行末まで注釈 18910 ;=d=';' #=^Com_IF : 3B ; IF 18920 ;=d='<' #=^Com_Error : 3C < rvtlコードのファイル出力 18930 ;=d='=' #=^Com_Error : 3D = コード先頭アドレス 18940 ;=d='>' #=^Com_Error : 3E > rvtlコードのファイル入力 18950 ;=d='?' #=^Com_OutNum : 3F ? 数値出力 数値入力 18960 ;=d='@' #=^Com_DO : 40 @ DO UNTIL NEXT 18970 ;=d='\' #=^Com_Ext : 5C \ 拡張用 除算演算子(unsigned) 18980 ;=d=']' #=^Com_Return : 5D ] RETURN 18990 ;=d='^' #=^Com_Label : 5E ^ ラベル宣言, 排他OR演算子, ラベル参照 19000 ;=d='_' #=^Com_USleep : 5F _ usleep, gettimeofday 19010 ;=d='`' #=^Com_RANDOM : 60 ` 擬似乱数を保持 (乱数シード設定) 19020 ;=d='{' #=^Com_FileTop : 7B { ファイル先頭(ヒープ領域) 19030 ;=d='|' #=^Com_Function : 7C | 組み込みコマンド, エラーコード保持 19040 ;=d='}' #=^Com_FileEnd : 7D } ファイル末(ヒープ領域) 19050 ;=d='~' #=^Com_Exit : 7E ~ VTL終了 19060 ;=(d>='A')*(d<='z') #=^Com_Var 19070 #=^Com_Error 19080 ] 19090 ^NextCom 19100 : 19110 ]
変数代入
右辺の式の値を左辺の変数に代入します。変数には単純変数、配列変数があります。変数名は先頭の1文字のみ有効で続く英文字は読み飛ばされます。変数名の次の文字で単純変数、配列変数、文字列のコピーを判断します。
19120 :---------------------------------------------------------- 19130 : 代入文 19140 :---------------------------------------------------------- 19150 ^Com_Var 19160 ;=D>1 "<Com_Var>" 19170 !=^SkipAlfa 19180 v=d : variable 19190 ;=C(I)='=' !=^SimpleVar #=^NextCom 19200 ;=C(I)='*' !=^CopyString #=^NextCom 19210 ;=C(I)='(' !=^SetArray1 #=^NextCom 19220 ;=C(I)='{' !=^SetArray2 #=^NextCom 19230 ;=C(I)='[' !=^SetArray4 #=^NextCom 19240 !=^Com_Error 19250 #=^NextCom 19260 :
単純変数
単純変数は A から Z と a から z までの 52 個の変数を使うことができます。変数名は 1 文字だけですが、冗長型として Integer, Command, ok などのように長い名前を使うことができます。 この場合には I と Integer は同じものとみなされることに注意してください。単純変数を格納する領域はあらかじめ用意されています。単純変数への代入文の右辺にカンマをはさんで式が続いた場合は FOR文となります。
19270 ^SimpleVar 19280 I=I+1 : skip = 19290 !=^Expression 19300 +=$9E89 !=^PutObj2 : MOV [ESI+v*4],EBX 19310 +=v*4 !=^PutObj4 19320 ;=C(I)=',' !=^ForCmd : FOR statement 19330 ] 19340 :
文字列を配列に代入
代入文の特殊な形式として文字列からなる配列(256KBまで)を別の領域の配列にコピーする命令があります。配列のためのメモリ領域を確保しておく必要があります。文字列定数を代入する形式もあります。実行後、% に文字数を返します。文字列を直接指定した場合は 256 バイト以内に限定されます。
例: 200 A*=e : 256 キロバイトまで 210 B*="ABCDEFG" : 256 バイト以内
コピー先とコピー元のアドレスが同じ場合はコピーは実行されず、% に文字が 0 となるまでの文字数を返します。文字列長を求めることができます。
例: 200 A*=A ?=%
19350 ^CopyString 19360 I=I+2 : skip *= 19370 ;=C(I)='"' #=^CopyString2 19380 !=^Expression 19390 +=$868B !=^PutObj2 : MOV EAX, [ESI+v*4] 19400 +=v*4 !=^PutObj4 19410 !=^XchgAXBX : EBX <--> EAX 19420 +=$B9 !=^PutObj1 : MOV ECX, 256K 19430 +=256*1024 !=^PutObj4 19440 +=41 !=^CallLib : call CopyString 19450 +=$DB31 !=^PutObj2 : XOR EBX,EBX 19460 +=$B3 !=^PutObj1 : MOV BL, '%' 19470 +='%' !=^PutObj1 19480 +=$89 !=^PutObj1 : MOV [ESI+EBX*4], ECX 19490 +=$9E0C !=^PutObj2 19500 ] 19510 ^CopyString2 19520 I=I+1 : skip = 19530 +=$9E8B !=^PutObj2 : MOV EBX, [ESI+v*4] 19540 +=v*4 !=^PutObj4 19550 !=^GetString : eax : string top address 19560 +=$B9 !=^PutObj1 : MOV ECX, 126 19570 +=126 !=^PutObj4 19580 +=41 !=^CallLib : call CopyString 19590 +=$DB31 !=^PutObj2 : XOR EBX,EBX 19600 +=$B3 !=^PutObj1 : MOV BL, '%' 19610 +='%' !=^PutObj1 19620 +=$89 !=^PutObj1 : MOV [ESI+EBX*4], ECX 19630 +=$9E0C !=^PutObj2 19640 ] 19650 :
配列への代入
配列は C 言語の配列のように配列を格納する領域の先頭アドレスを配列名に設定しておく必要があります。以下の例では変数Aに空きメモリ先頭アドレスを代入し、変数Bには変数Aより100バイト大きい値を設定しています。これで変数Aは100バイトの領域を配列として使用できます。1バイト配列では A(99)まで、2バイト配列では A{49}まで、4バイト配列ではA[24]までが B(0) と重ならないでアクセスできます。配列 B は残りの空きメモリをすべて使用可能です。システム変数「*」は rvtl のプログラムの大きさ + 空きメモリ = 256Kバイトが初期値になっていますが、システム変数「*」を再設定すればメモリの許す限りの領域をアクセスできます。
例: 100 A=& 110 B=A+100 120 A(99)=123 130 B[5]=456 140 ?=B[5]
配列への代入は配列のインデックスの式を最初に評価した結果に設定される ebx をスタックに退避します。次に代入する式を評価して ebx を eax に転送、配列のインデックスの値を ebx に戻し、配列のサイズを乗じた後、配列変数のアドレスに加えます。結果として得られたアドレスに eax を転送します。
19660 ^SetArray1 19670 I=I+1 19680 !=^Expression : index 19690 +=$53 !=^PutObj1 : PUSH EBX 19700 I=I+2 : skip ')=' 19710 !=^Expression 19720 !=^XchgAXBX 19730 +=$5B !=^PutObj1 : POP EBX 19740 +=$8E8B !=^PutObj2 19750 +=v*4 !=^PutObj4 : MOV ECX,[ESI+v*4] 19760 +=$88 !=^PutObj1 19770 +=$1904 !=^PutObj2 : MOV [ECX+EBX*4],AL 19780 ] 19790 : 19800 ^SetArray2 19810 I=I+1 19820 !=^Expression : index 19830 +=$53 !=^PutObj1 : PUSH EBX 19840 I=I+2 : skip '}=' 19850 !=^Expression 19860 !=^XchgAXBX 19870 +=$5B !=^PutObj1 : POP EBX 19880 +=$8E8B !=^PutObj2 19890 +=v*4 !=^PutObj4 : MOV ECX,[ESI+v*4] 19900 +=$8966 !=^PutObj2 19910 +=$5904 !=^PutObj2 : MOV [ECX+EBX*4],AX 19920 ] 19930 : 19940 ^SetArray4 19950 I=I+1 : skip [ 19960 !=^Expression : index 19970 +=$53 !=^PutObj1 : PUSH EBX 19980 I=I+2 : skip ']=' 19990 !=^Expression 20000 !=^XchgAXBX 20010 +=$5B !=^PutObj1 : POP EBX 20020 +=$8E8B !=^PutObj2 20030 +=v*4 !=^PutObj4 : MOV ECX,[ESI+v*4] 20040 +=$89 !=^PutObj1 20050 +=$9904 !=^PutObj2 : MOV [ECX+EBX*4],AX 20060 ] 20070 :
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
rvtlcでは終了条件が制御変数の値と一致する必要があります。
1010 I=1,10 1020 ?=I 1030 @=I+1
上のコードをコンパイルして、逆アセンブルすると次のようになります。
1010 I=1,10 : FOR 80494da: b8 01 00 00 00 mov $0x1,%eax 80494df: 93 xchg %eax,%ebx 80494e0: 89 9e 24 01 00 00 mov %ebx,0x124(%esi) 80494e6: b8 0a 00 00 00 mov $0xa,%eax 80494eb: 93 xchg %eax,%ebx 80494ec: 53 push %ebx 1020 ?=I 80494ed: 8b 86 24 01 00 00 mov 0x124(%esi),%eax 80494f3: e8 ef ed ff ff call 0x80482e7 1030 @=I+1 : NEXT 80494f8: 8b 86 24 01 00 00 mov 0x124(%esi),%eax 80494fe: 93 xchg %eax,%ebx 80494ff: b8 01 00 00 00 mov $0x1,%eax 8049504: 01 c3 add %eax,%ebx 8049506: 93 xchg %eax,%ebx 8049507: bb 49 00 00 00 mov $0x49,%ebx 804950c: 8b 0c 9e mov (%esi,%ebx,4),%ecx 804950f: 89 04 9e mov %eax,(%esi,%ebx,4) 8049512: 5b pop %ebx 8049513: 53 push %ebx 8049514: 39 d9 cmp %ebx,%ecx 8049516: 0f 85 d1 ff ff ff jne 0x80494ed 804951c: 5b pop %ebx
FOR
変数に初期値を代入して、終了条件をスタックに積み、戻りアドレスを登録します。
20080 :---------------------------------------------------------- 20090 : For 20100 :---------------------------------------------------------- 20110 ^ForCmd 20120 ;=D>1 "<for>" 20130 : T[U*3]:type, T[U*3+1]:var_addr, T[U*3+2]:jmp_addr 20140 I=I+1 20150 U=U+1 20160 T[U*3]=1 : for 20170 T[U*3+1]=v : variable 20180 !=^Expression : 終了条件 20190 !=^PushBX : PUSH EBX 20200 T[U*3+2]=P+H['B'] 20210 ] 20220 :
NEXT
変数の値を読んでスタック上の終了条件と一致しなければ、変数の値を更新して繰り返します。更新後の変数値を終了条件と比較するとループが1回少なくなってしまうため、更新前に変数の値を終了条件と比較します。
変数の増分が正負のどちらも可能なため、「終了条件と一致」しないと終了しません。「終了条件と一致するか超えたら」という実行時の判定は行っていません。
20230 :---------------------------------------------------------- 20240 : Next 20250 :---------------------------------------------------------- 20260 ^Next 20270 ;=D>1 "<next>" 20280 :I=I+1 20290 !=^Expression : 変数値更新 20300 !=^XchgAXBX 20310 +=$BB !=^PutObj1 : MOV EBX, variable 20320 +=Table[U*3+1] !=^PutObj4 20330 +=$8B !=^PutObj1 : MOV ECX,[ESI+EBX*4] 20340 +=$9E0C !=^PutObj2 20350 +=$89 !=^PutObj1 : MOV [ESI+EBX*4],EAX 20360 +=$9E04 !=^PutObj2 20370 +=$5B !=^PutObj1 : POP EBX 20380 +=$53 !=^PutObj1 : PUSH EBX 20390 +=$D939 !=^PutObj2 : CMP ECX,EBX 20400 +=$850F !=^PutObj2 : JNZ xxxxx 20410 +=T[U*3+2] !=^PutAddr : 20420 +=$5B !=^PutObj1 : POP EBX 20430 U=U-1 20440 ] 20450 :
DO - UNTIL
UNTIL文の式 e の値が偽(0)の場合には @ (DO) に戻ります。真(0以外)なら次の文を実行します。NEXT文と異なり式を括弧で囲む必要があります。繰り返し回数が実行するまでわからない場合に使います。
1040 I=0 1050 @ 1060 ?=I 1070 I=I+1 1080 @=(I>10)
上のコードをコンパイルして、逆アセンブルすると次のようになります。
1040 I=0 804951d: b8 00 00 00 00 mov $0x0,%eax 8049522: 93 xchg %eax,%ebx 8049523: 89 9e 24 01 00 00 mov %ebx,0x124(%esi) 1050 @ : DO 1060 ?=I 8049529: 8b 86 24 01 00 00 mov 0x124(%esi),%eax 804952f: e8 b3 ed ff ff call 0x80482e7 1070 I=I+1 8049534: 8b 86 24 01 00 00 mov 0x124(%esi),%eax 804953a: 93 xchg %eax,%ebx 804953b: b8 01 00 00 00 mov $0x1,%eax 8049540: 01 c3 add %eax,%ebx 8049542: 89 9e 24 01 00 00 mov %ebx,0x124(%esi) 1080 @=(I>10) : UNTIL 8049548: 8b 86 24 01 00 00 mov 0x124(%esi),%eax 804954e: 93 xchg %eax,%ebx 804954f: b8 0a 00 00 00 mov $0xa,%eax 8049554: 39 c3 cmp %eax,%ebx 8049556: bb 01 00 00 00 mov $0x1,%ebx 804955b: 7f 02 jg 0x804955f 804955d: 31 db xor %ebx,%ebx 804955f: 85 db test %ebx,%ebx 8049561: 0f 84 c2 ff ff ff je 0x8049529
DO
現在のアドレスを戻り先アドレスとしてテーブルに登録するだけでコードは生成しません。
24440 :---------------------------------------------------------- 24450 : DO NEXT UNTIL 24460 :---------------------------------------------------------- 24470 ^Com_DO 24480 c=C(I) 24490 ;=c<>'=' !=^DoCmd #=^NextCom 24500 I=I+1 24510 c=C(I) 24520 ;=c='(' !=^Until #=^NextCom 24530 ;=((c>='A')&(c<='Z'))|((c>='a')&(c<='z')) !=^Next 24540 #=^NextCom 24550 :---------------------------------------------------------- 24560 : Do 24570 :---------------------------------------------------------- 24580 ^DoCmd 24590 ;=D>1 "<do>" 24600 : T[U*3]:type, T[U*3+1]:var_addr, T[U*3+2]:jmp_addr 24610 U=U+1 24620 Table[U*3+2]=P+H['B'] 24630 Table[U*3]=2 : 2 : do 24640 ]
UNTIL
式の処理を行って結果が0の場合、DOで登録されたテーブルの戻りアドレスにジャンプするコードを生成します。
24650 :---------------------------------------------------------- 24660 : Until 24670 :---------------------------------------------------------- 24680 ^Until 24690 ;=D>1 "<until>" 24700 I=I+1 24710 !=^Expression 24720 +=$DB85 !=^PutObj2 : TEST EBX,EBX 24730 +=$840F !=^PutObj2 : JZ 戻る 24740 +=Table[U*3+2] !=^PutAddr 24750 U=U-1 24760 ]
GOSUB
指定した行番号にジャンプします。飛び先で ](リターン文)が実行されると次の文から実行が再開されます。!=^Label のように飛び先をラベルとした場合はラベル宣言(^Label)した行の次の行にジャンプします。
例: !=100 例: !=^LINE
飛び先がラベルの場合はラベル項の結果のアドレス、行番号の場合は行番号:アドレステーブルから得られるアドレスに対する call 命令を生成します。飛び先アドレスは相対アドレスを設定します。
20460 :---------------------------------------------------------- 20470 : GOSUB 20480 :---------------------------------------------------------- 20490 ^Com_GOSUB 20500 I=I+1 : skip = 20510 ;=C(I)='^' +=$D3 !=^JumpOrCall #=^NextCom 20520 !=^GetNo n=r 20530 ;=Q>1 !=^SearchLine 20540 +=$E8 !=^PutObj1 : CALL r 20550 +=r !=^PutAddr 20560 #=^NextCom
GOTO
指定した行番号にジャンプします。#=^Label のように飛び先をラベルとした場合はラベル宣言(^Label)した行の次の行にジャンプします。
例: #=100 例: #=^STOP
飛び先がラベルの場合はラベル項の結果のアドレス、行番号の場合は行番号:アドレステーブルから得られるアドレスに対する jmp 命令を生成します。飛び先アドレスは相対アドレスを設定します。
20570 :---------------------------------------------------------- 20580 : GOTO 20590 :---------------------------------------------------------- 20600 ^Com_GO 20610 I=I+1 : skip = 20620 ;=C(I)='^' +=$E3 !=^JumpOrCall #=^NextCom 20630 !=^GetNo n=r 20640 ;=n<0 #=^Com_Exit : 終了 20650 ;=Q>1 !=^SearchLine 20660 +=$E9 !=^PutObj1 : JMP r 20670 +=r !=^PutAddr 20680 #=^NextCom 20690 : 20700 ^JumpOrCall 20710 !=^Expression : ラベル項はアドレスを返す 20720 +=$FF !=^PutObj1 : 20730 !=^PutObj1 : call/jmp [ebx] 20740 ] 20750 :
数値取得
ソース内の10進数または16進数を表す文字列を数値に変換します。コンパイラは生成する実行ファイルに行番号の情報を持たないため、GOTO(#=)、GOSUB(!=) ともに計算型GOTO/GOSUBは使用できません。計算を含む式を評価してそこへジャンプした場合、ほぼ確実に暴走します。rvtlc では 行番号を示す定数でなければコンパイラはエラーを表示して終了します。
12320 :---------------------------------------------------------- 12330 : ソースから数値を取得 10進 結果:r 12340 :---------------------------------------------------------- 12350 ^GetNo 12360 +f 12370 r=0 f=0 12380 c=C(I) 12390 ;=(c='$') #=^GetHex 12400 ;=(c='-') f=1 I=I+1 c=C(I) 12410 @ 12420 ;=(c>='0')&(c<='9') r=r*10+(c-'0') I=I+1 c=C(I) 12430 @=((c<'0')|(c>'9')) 12440 ;=D>2 / "getNo:" ?=r / 12450 ;=f>0 r=-r 12460 -f 12470 ] 12480 : 12490 :---------------------------------------------------------- 12500 : ソースから数値を取得 16進 結果:r 12510 :---------------------------------------------------------- 12520 ^GetHex 12530 I=I+1 12540 c=C(I) 12550 r=0 12560 @ 12570 f=1 12580 ;=(c>='0')&(c<='9') r=r*16+(c-'0') I=I+1 c=C(I) f=0 12590 ;=(c>='A')&(c<='F') r=r*16+(c-'A'+10) I=I+1 c=C(I) f=0 12600 ;=(c>='a')&(c<='f') r=r*16+(c-'a'+10) I=I+1 c=C(I) f=0 12610 @=(f=1) 12620 ;=D>2 / "getHex:" ??=r / 12630 ] 12640 :
文字列出力
文字列は文字列直後へのジャンプ命令を生成し、コード内に文字列を埋め込み、文字列表示ルーチンに文字列先頭アドレスを渡します。
4byte 12byte +----+----+----+----+ | jump 命令 | ---+ +----+----+----+----+ | addr | S | t | r | i | | +----+----+----+----+ | | n | g | s | 0 | | +----+----+----+----+ | | call 文字列表示 | <--+ +----+----+----+----+
20760 :---------------------------------------------------------- 20770 : 文字列出力 20780 :---------------------------------------------------------- 20790 ^Com_String 20800 ;=D>1 "<String>" 20810 ;=C(I)='"' I=I+1 #=^NextCom 20820 !=^GetString 20830 +=5 !=^CallLib : OutAsciiZ 20840 I=I+1 20850 #=^NextCom 20860 : 20870 :---------------------------------------------------------- 20880 : 文字列をコードに埋め込み、先頭アドレスをEAXに設定 20890 :---------------------------------------------------------- 20900 ^GetString 20910 +qsj 20920 q=Obj+P+2 s=P+2 20930 +=$EB !=^PutObj1 : JMP SHORT xxxx 20940 j=0; 20950 ;=C(I)='"' #=^gs 20960 @ 20970 q(j)=C(I) 20980 I=I+1 20990 j=j+1 21000 ;=j>127 !=^Com_Error 21010 @=((C(I)='"')|(C(I)=0)) 21020 ^gs 21030 q(j)=0 21040 +=j+1 !=^PutObj1 : jump here 21050 P=P+j+1 21060 +=$B8 !=^PutObj1 : MOV EAX, 21070 +=s+H['B'] !=^PutObj4 : 文字列先頭 21080 -jsq 21090 ] 21100 :
1文字表示
1文字を表示するコードを生成します。
形式 | 動作 |
---|---|
$=e | 式の値を文字コードとして出力。 |
$$=e | 式の値を2文字の文字コードとして出力。 |
$#=e | 式の値を4文字の文字コードとして出力。 |
$*=e | 式の値を先頭アドレスとするASCIIZ文字列を出力。 |
21110 :---------------------------------------------------------- 21120 : 1文字表示 21130 :---------------------------------------------------------- 21140 ^Com_OutChar 21150 ;=D>1 "<OutChar>" 21160 c=C(I) 21170 ;=c='=' I=I+1 #=^OutChar1 21180 ;=c='$' I=I+1 #=^OutChar2 21190 ;=c='#' I=I+1 #=^OutChar4 21200 ;=c='*' I=I+1 #=^OutString 21210 #=^Com_Error 21220 ^OutChar1 21230 !=^Expression 21240 !=^XchgAXBX 21250 +=7 !=^CallLib : OutChar 21260 #=^NextCom 21270 ^OutChar2 21280 I=I+1 : skip = 21290 !=^Expression 21300 +=$F888 !=^PutObj2 : MOV AL,BH 21310 +=7 !=^CallLib : OutChar 21320 +=$d888 !=^PutObj2 : MOV AL,BL 21330 +=7 !=^CallLib : OutChar 21340 #=^NextCom 21350 ^OutChar4 21360 I=I+1 : skip = 21370 !=^Expression 21380 !=^PushBX 21390 +=$c1 !=^PutObj1 : SHR EBX,16 21400 +=$10eb !=^PutObj2 21410 +=$F888 !=^PutObj2 : MOV AL,BH 21420 +=7 !=^CallLib : OutChar 21430 +=$d888 !=^PutObj2 : MOV AL,BL 21440 +=7 !=^CallLib : OutChar 21450 !=^PopBX 21460 +=$F888 !=^PutObj2 : MOV AL,BH 21470 +=7 !=^CallLib : OutChar 21480 +=$d888 !=^PutObj2 : MOV AL,BL 21490 +=7 !=^CallLib : OutChar 21500 #=^NextCom 21510 :
文字列表示
式を評価して文字列先頭アドレスを取得し、文字列表示ルーチンをcallします。
21520 :---------------------------------------------------------- 21530 : $*= 21540 :---------------------------------------------------------- 21550 ^OutString 21560 I=I+1 : skip *= 21570 ;=C(I)='"' #=^OutString2 21580 !=^Expression 21590 !=^XchgAXBX : EBX <--> EAX 21600 +=05 !=^CallLib : call OutAsciiZ 21610 #=^NextCom 21620 : 21630 ^OutString2 21640 ;=C(I)='"' I=I+1 #=^NextCom : empty string 21650 I=I+1 : skip " 21660 !=^GetString : eax : string top address 21670 +=05 !=^CallLib : call OutAsciiZ 21680 #=^NextCom 21690 :
エラー
コンパイラが文法のエラーを検出した場合に行番号と位置を行の内容を表示します。
21700 :---------------------------------------------------------- 21710 : エラー 21720 :---------------------------------------------------------- 21730 ^Com_Error 21740 / "Error (line:" ?=N[1] " at " ?=I ")" / 21750 ;=D>1 i=0 p=N+8 @ " " ?$=p(i) i=i+1 @=(p(i)=0) / 21760 i=0 p=N+8 @ $=p(i) i=i+1 @=(p(i)=0) / 21770 .=I-1 "^" 21780 !=^PrintLabel 21790 #=-1
ファイル出力
{ (ファイル先頭位置) と } (ファイル末位置) で指定した範囲を""に囲まれたファイル名のファイルとして書き込みます。0で終了する文字列の先頭アドレスを渡す形式も使用できます。
例: (="ファイル名" (*=A
ファイル先頭位置「{」 と ファイル末位置「}」を取得して、ランタイムライブラリを呼び出すコードを生成します。
21800 :---------------------------------------------------------- 21810 : ファイル出力 21820 :---------------------------------------------------------- 21830 ^Com_FileWrite 21840 ;=D>1 "<FileWrite>" 21850 ;=C(I)<>'*' #=^Com_FWrite2 21860 I=I+2 : skip *= 21870 !=^Expression : ebx : &filename 21880 #=^Com_FWrite3 21890 : 21900 ^Com_FWrite2 21910 I=I+2 : skip =" 21920 : 文字列をコードに埋め込み、先頭アドレスをEAXに設定 21930 !=^GetString 21940 !=^XchgAXBX : EBX <--> EAX 21950 ^Com_FWrite3 21960 +=$C931 !=^PutObj2 : XOR ECX,ECX 21970 +=$B1 !=^PutObj1 : MOV CL, '}' 21980 +='}' !=^PutObj1 21990 +=$8B !=^PutObj1 : MOV EAX,[ESI+ECX*4] 22000 +=$8E04 !=^PutObj2 : 22010 +=$B1 !=^PutObj1 : MOV CL, '{' 22020 +='{' !=^PutObj1 22030 +=$8B !=^PutObj1 : MOV ECX,[ESI+ECX*4] 22040 +=$8E0C !=^PutObj2 : buffer 22050 +=$C829 !=^PutObj2 : SUB EAX,ECX 22060 : eax:size, ebx:filename, ecx:buffer 22070 +=46 !=^CallLib : call FileWrite 22080 +=$C931 !=^PutObj2 : XOR ECX,ECX 22090 +=$B1 !=^PutObj1 : MOV CL, '|' 22100 +='|' !=^PutObj1 22110 +=$89 !=^PutObj1 : MOV [ESI+ECX*4],EAX 22120 +=$8E04 !=^PutObj2 22130 #=^NextCom 22140 :
ファイル入力
""に囲まれたファイル名のファイルを { (ファイル先頭位置) で指定されたメモリアドレスに読み込みます。読み込んだファイルサイズは }-{(ファイル末位置 - ファイル先頭位置) で求めることができます。0で終了する文字列の先頭アドレスを渡す形式も使用できます。
例: )="ファイル名" )*=A
ファイル先頭位置「{」を取得して、ランタイムライブラリを呼び出すコードを生成します。ファイルバッファー最終位置を「}」、読み込みサイズを「)」に設定するコードを生成します。
22150 :---------------------------------------------------------- 22160 : ファイル入力 22170 :---------------------------------------------------------- 22180 ^Com_FileRead 22190 ;=D>1 "<FileRead>" 22200 ;=C(I)<>'*' #=^Com_FRead2 22210 I=I+2 : skip *= 22220 !=^Expression : ebx : &filename 22230 #=^Com_FRead3 22240 ^Com_FRead2 22250 I=I+2 : skip =" 22260 : 文字列をコードに埋め込み、先頭アドレスをEAXに設定 22270 !=^GetString 22280 !=^XchgAXBX : EBX <--> EAX 22290 ^Com_FRead3 22300 +=$C931 !=^PutObj2 : XOR ECX,ECX 22310 +=$B1 !=^PutObj1 : MOV CL, '{' 22320 +='{' !=^PutObj1 22330 +=$8B !=^PutObj1 : MOV ECX,[ESI+ECX*4] 22340 +=$8E0C !=^PutObj2 : buffer 22350 !=^PushBX 22360 +=47 !=^CallLib : call FileSize 22370 !=^PopBX 22380 +=$C289 !=^PutObj2 : MOV EDX,EAX 22390 : ebx:filename, ecx:buffer, edx:size 22400 +=48 !=^CallLib : call FileRead 22410 +=$C289 !=^PutObj2 : MOV EDX,EAX 22420 +=$C801 !=^PutObj2 : ADD EAX,ECX 22430 +=$C931 !=^PutObj2 : XOR ECX,ECX 22440 +=$B1 !=^PutObj1 : MOV CL, '}' 22450 +='}' !=^PutObj1 22460 +=$89 !=^PutObj1 : MOV [ESI+ECX*4],EAX 22470 +=$8E04 !=^PutObj2 : 22480 +=$B1 !=^PutObj1 : MOV CL, ')' 22490 +=')' !=^PutObj1 22500 +=$89 !=^PutObj1 : MOV [ESI+ECX*4],EDX 22510 +=$8E14 !=^PutObj2 : 22520 #=^NextCom 22530 :
メモリの拡張
rvtlcは指定しない場合、約256KBのデータ領域を確保します。brk システムコールを実行して使用可能なメモリ終端位置を変更します。
22540 :---------------------------------------------------------- 22550 : メモリの拡張 22560 :---------------------------------------------------------- 22570 ^Com_BRK 22580 I=I+1 22590 !=^Expression 22600 +=$B8 !=^PutObj1 : MOV EAX, SYS_brk(45) 22610 +=45 !=^PutObj4 22620 +=$80CD !=^PutObj2 : INT 0x80 22630 +=$DB31 !=^PutObj2 : XOR EBX,EBX 22640 +=$B3 !=^PutObj1 : MOV BL, '*' RAM末設定 22650 +='*' !=^PutObj1 22660 +=$89 !=^PutObj1 : MOV [ESI+EBX*4], EAX 22670 +=$9E04 !=^PutObj2 22680 #=^NextCom 22690 :
PUSH 変数スタック
変数は変数専用スタックに格納(プッシュ)することができます。例えば「 +ABC 」の場合は、変数が A、B、C の順にスタックに保存されます。1文字以上の長さの変数名の場合でも先頭の1文字だけを指定します。スタックから戻す(ポップ)場合は「 -CBA 」とプッシュした順の逆に戻します。サブルーチンの先頭で使う変数をプッシュ、リターンの前でポップすることで、変数を局所変数として使うことができ、再帰的にサブルーチンを呼び出すことができます。「+=式」で式の値を変数を介さず変数スタックに格納することができます。また、式中で「;」を参照すると変数スタックトップを返します(ポップ)。
実行時にメモリ中に用意した変数スタックに変数の内容を格納するコードを生成します。変数スタック用のスタックポインタは edi を使用します。
22700 :---------------------------------------------------------- 22710 : 変数をスタックへPUSH 22720 :---------------------------------------------------------- 22730 ^Com_VarPush 22740 ;=C(I)='=' !=^PushExp #=^NextCom 22750 c=C(I) 22760 +=$C931 !=^PutObj2 : XOR ECX,ECX 22770 @ 22780 +=$B1 !=^PutObj1 : MOV CL, VAR 22790 +c !=^PutObj1 22800 +=$8B !=^PutObj1 : MOV EAX, [ESI+ECX*4] 22810 +=$8E04 !=^PutObj2 22820 +=$0789 !=^PutObj2 : MOV [EDI],EAX 22830 +=$EF81 !=^PutObj2 : SUB EDI,4 22840 +=$04 !=^PutObj4 22850 I=I+1 22860 c=C(I) 22870 @=(((c<'A')|(c>'Z'))&((c<'a')|(c>'z'))) 22880 #=^NextCom 22890 ^PushExp 22900 I=I+1 22910 !=^Expression 22920 +=$1F89 !=^PutObj2 : MOV [EDI],EBX 22930 +=$EF81 !=^PutObj2 : SUB EDI,4 22940 +=$04 !=^PutObj4 22950 ] 22960 :
POP 変数スタック
変数スタックに退避していた値を変数へ戻ひます。プッシュした順の逆に戻します。
23220 :---------------------------------------------------------- 23230 : 変数スタックから POP 23240 :---------------------------------------------------------- 23250 ^Com_VarPop 23260 c=C(I) 23270 +=$C931 !=^PutObj2 : XOR ECX,ECX 23280 @ 23290 +=$C781 !=^PutObj2 : ADD EDI,4 23300 +=04 !=^PutObj4 23310 +=$078B !=^PutObj2 : MOV EAX,[EDI] 23320 +=$B1 !=^PutObj1 : MOV CL, VAR 23330 +c !=^PutObj1 23340 +=$89 !=^PutObj1 : MOV [ESI+ECX*4],EAX 23350 +=$8E04 !=^PutObj2 23360 I=I+1 23370 c=C(I) 23380 @=(((c<'A')|(c>'Z'))&((c<'a')|(c>'z'))) 23390 #=^NextCom
子プロセスの起動
子プロセスを起動します。実行ファイル名はフルパスで指定して下さい。 | を使ったパイプと > による出力のリダイレクトが可能です。
例: ,="/bin/ls -lt | /usr/bin/grep e >test.txt" "DONE" /
上記のようにコマンドの後ろに文が続かない場合は、次のような省略記法が使えます。
例: , /bin/ls -lt | /usr/bin/grep e >test.txt
0で終了する文字列の先頭アドレスを渡す形式も使用できます。文字列の先頭アドレスを変数に設定した後に実行します。
例: ,*=A
文字列の先頭アドレスを設定して、ランタイムライブラリを呼び出すコードを生成します。
22970 :---------------------------------------------------------- 22980 : 外部コマンドの実行 22990 :---------------------------------------------------------- 23000 ^Com_Exec 23010 ;=D>1 "<Exec>" 23020 ;=C(I)<>'*' #=^Com_Exec2 23030 I=I+2 : skip *= 23040 !=^Expression 23050 !=^XchgAXBX : EBX <--> EAX 23060 : eax のアドレスからFileNameにコピー 23070 +=40 !=^CallLib : call GetString2 23080 : FileName に保存された外部プログラムを実行 23090 +=39 !=^CallLib : call ForkExec 23100 #=^NextCom 23110 : 23120 ^Com_Exec2 23130 I=I+2 : skip =" 23140 : 文字列をコードに埋め込み、先頭アドレスをEAXに設定 23150 !=^GetString 23160 : eax のアドレスからFileNameにコピー 23170 +=40 !=^CallLib : call GetString2 23180 : FileName に保存された外部プログラムを実行 23190 +=39 !=^CallLib : call ForkExec 23200 #=^NextCom 23210 :
空白出力
「.」は式の値の数の空白を標準出力に書き出します。1文字表示ルーチンにスペースを渡して、式で指定された回数をループするコードを生成します。
23400 :---------------------------------------------------------- 23410 : 空白出力 23420 :---------------------------------------------------------- 23430 ^Com_Space 23440 ;=D>1 "<Space>" 23450 I=I+1 23460 !=^Expression 23470 +=$D989 !=^PutObj2 : MOV ECX, EBX 23480 +=$20B0 !=^PutObj2 : MOV AL, $20 23490 +=7 !=^CallLib : OutChar 23500 +=$F9E2 !=^PutObj2 : LOOP -7 23510 #=^NextCom
改行出力
「/」は改行を標準出力に書き出すライブラリルーチンを call します。
23520 :---------------------------------------------------------- 23530 : 改行 23540 :---------------------------------------------------------- 23550 ^Com_NewLine 23560 ;=D>1 "<NewLine>" 23570 +=9 !=^CallLib : NewLine 23580 #=^NextCom
ラベル宣言
「^」で始まる行はラベル宣言となります。 行番号:ラベルテーブルに登録しますが、コードを生成する必要はありません。ラベルは11文字までが有効でそれ以降は無視されます。ラベルを参照するとラベル宣言の次の行先頭アドレスを返します。
23590 :---------------------------------------------------------- 23600 : ラベル処理 ラベル:行番号テーブルの登録 23610 : adr:int32 label:char[12] 23620 :---------------------------------------------------------- 23630 ^Com_Label 23640 ;=D>1 "<Label>" / 23650 ;=Q>1 !=^LineEnd ] : PASS1のみ 23660 L[K*4]=N[1] : 行番号 23670 p=L+(K*16+4) : ラベル登録 23680 i=I 23690 j=0 23700 @ : ラベルを11文字登録 23710 c=C(i) 23720 p(0)=c : 1文字登録 23730 ;=j=11 p(0)=0 : 12文字目は0 23740 j=j+1 23750 p=p+1 23760 i=i+1 23770 @=((c=0)|(j=12)) 23780 K=K+1 I=i-1 23790 ;=c<>0 !=^LineEnd 23800 #=^NextCom 23810 ^LineEnd 23820 @ 23830 c=C(I) 23840 ;=c<>0 I=I+1 23850 @=(c=0) : 行末に設定 23860 ] 23870 :
コメント
「:」 の後は、コメントとして行末まで無視されるため、単にソースを行末まで読み飛ばします。
23880 :---------------------------------------------------------- 23890 : コメント 23900 :---------------------------------------------------------- 23910 ^Com_Comment 23920 @ I=I+1 @=(C(I)=0) c=0 I=I-1 : 行末に設定 23930 #=^NextCom
IF
式の値が偽(0) なら次の行から実行します。真の場合は次の文を実行します。
例: ;=e "A=" A=?
式を評価して0ならば次行先頭にジャンプするコードを生成します。
23940 :---------------------------------------------------------- 23950 : IF 23960 :---------------------------------------------------------- 23970 ^Com_IF 23980 ;=D>1 "<IF>" 23990 I=I+1 24000 !=^Expression 24010 n=N[1]+1 24020 ;=Q>1 !=^SearchLine 24030 +=$DB85 !=^PutObj2 : TEST EBX,EBX 24040 +=$840F !=^PutObj2 : JZ 次行先頭 24050 +=r !=^PutAddr 24060 #=^NextCom
数値出力
標準出力に数値を出力する命令です。対応するランタイムライブラリを呼び出します。
形式 | |
?=e | 式の値を10進数で出力。 |
?(n)=e | 式の値を10進数を n 桁右寄せで出力。 |
?[n]=+e | 式の値を10進数を上位桁を0で埋めた n 桁で出力。 |
?$=e | 式の値を16進数2桁で出力。 |
?#=e | 式の値を16進数4桁で出力。 |
??=e | 式の値を16進数8桁で出力。 |
?*=e | 式の値を符号無し10進数で出力。 |
?{n}=e | 式の値を8進数n桁で出力。 |
?!n!=e | 式の値の下位を2進数n桁で出力。 |
24070 :---------------------------------------------------------- 24080 : 数値出力 24090 :---------------------------------------------------------- 24100 ^Com_OutNum 24110 +n 24120 c=C(I) 24130 ;=c='=' n=18 #=^OutNum 24140 ;=c='?' I=I+1 n=15 #=^OutNum 24150 ;=c='#' I=I+1 n=14 #=^OutNum 24160 ;=c='$' I=I+1 n=13 #=^OutNum 24170 ;=c='*' I=I+1 n=17 #=^OutNum 24180 ;=c='(' I=I+1 n=21 #=^RightNum 24190 ;=c='[' I=I+1 n=19 #=^RightNum 24200 ;=c='{' I=I+1 n=12 #=^RightNum 24210 ;=c='!' I=I+1 n=11 #=^RightNum 24220 -n !=^Com_Error 24230 ^OutNum 24240 ;=D>1 "<OutNum>" 24250 I=I+1 : skip = 24260 !=^Expression 24270 !=^XchgAXBX 24280 +n !=^CallLib : PrintLeft 24290 -n 24300 #=^NextCom 24310 ^RightNum 24320 ;=D>1 "<RightNum>" 24330 !=^Expression : 桁数 ebx 24340 +=$D989 !=^PutObj2 : MOV ECX,EBX 24350 I=I+2 : skip x= 24360 +=$51 !=^PutObj1 : PUSH ECX 24370 !=^Expression : 数値 ebx 24380 +=$59 !=^PutObj1 : POP ECX 24390 !=^XchgAXBX 24400 +n !=^CallLib : PrintLeft 24410 -n 24420 #=^NextCom 24430 :
リターン
GOSUB文(!=)の次の命令に戻ります。
例: 100 ^SUB 110 "do something" 120 ]
リターンは単にret 命令(0xC3)を書き込むだけです。
24820 :---------------------------------------------------------- 24830 : リターン 24840 :---------------------------------------------------------- 24850 ^Com_Return 24860 ;=D>1 "<ret>" 24870 +=$C3 !=^PutObj1 : RET 24880 #=^NextCom
マイクロ秒スリープ
停止時間をマイクロ秒単位で与えてプログラムを一時停止します。マシンの処理速度が異なっても同じタイミングで動作させたい場合などに使用します。
例: _=e
ランタイムライブラリを呼び出すコードを生成します。
25260 :---------------------------------------------------------- 25270 : マイクロ秒スリープ 25280 :---------------------------------------------------------- 25290 ^Com_USleep 25300 I=I+1 25310 !=^Expression 25320 !=^XchgAXBX 25330 +=38 !=^CallLib 25340 #=^NextCom
乱数シード設定
乱数は rvtl の起動直後には毎回同じ系列で生成されるため、rvtl の起動後には常に同じ乱数が帰ります。いつも異なる乱数を生成するためには、擬似乱数のシードに別の値を設定します。例えば、擬似乱数のシードを現在時間のマイクロ秒に設定「A=_ `=%」すると起動のたびに予想できない異なる系列の乱数列を生成することができます。
例: `=e
ランタイムライブラリを呼び出すコードを生成します。
24980 :---------------------------------------------------------- 24990 : 乱数シード設定 25000 :---------------------------------------------------------- 25010 ^Com_RANDOM 25020 ;=D>1 "<rand>" 25030 I=I+1 25040 !=^Expression : 数値 ebx 25050 !=^XchgAXBX 25060 +=35 !=^CallLib : sgenrand 25070 #=^NextCom
ファイル先頭位置設定
ファイル書き出しコマンドの書き込み先頭位置を設定します。
例: {=e
変数「{」へ代入するコードを生成します。
25080 :---------------------------------------------------------- 25090 : ファイル先頭位置設定 25100 :---------------------------------------------------------- 25110 ^Com_FileTop 25120 I=I+1 : skip = 25130 !=^Expression 25140 +=$9E89 !=^PutObj2 : MOV [ESI+r*4],EBX 25150 +='{'*4 !=^PutObj4 25160 #=^NextCom
ファイル終了位置設定
ファイル書き出しコマンドの書き込み最終位置を設定します。
例: }=e
変数「}」へ代入するコードを生成します。
25170 :---------------------------------------------------------- 25180 : ファイル終了位置設定 25190 :---------------------------------------------------------- 25200 ^Com_FileEnd 25210 I=I+1 : skip = 25220 !=^Expression 25230 +=$9E89 !=^PutObj2 : MOV [ESI+r*4],EBX 25240 +='}'*4 !=^PutObj4 25250 #=^NextCom
プログラム終了
負の行番号へのGOTO(#=-1)は、TERMIOを起動時の設定に戻し、exitシステムコールを実行するコードを生成します。翻訳の最後(ソースプログラムの最後)でも同じコードを出力します。
25260 :---------------------------------------------------------- 25270 : プログラム終了 25280 :---------------------------------------------------------- 25290 ^Com_Exit 25300 !=^ExitCode 25310 #=^NextCom 25320 :
拡張コマンド
「\」を使う将来の拡張用です。何も行っていません。
24770 :---------------------------------------------------------- 24780 : 拡張コマンド 24790 :---------------------------------------------------------- 24800 ^Com_Ext 24810 #=^NextCom
[前] [目次] [次]