* 川合のプログラミング言語自作のためのテキスト第三版#7 -(by [[K]], 2021.02.04) ** (15) TL-7 -まずC言語の演算子一覧を書きます。 |優先順位|演算子|形式|名前|結合方向|TL-7| |1|( )|func(x,y,z)|関数呼び出し演算子|左|×| |1|[ ]|a[i]|添え字演算子|左|×| |1|.|abc.x|ドット演算子|左|×| |1|->|p->x|アロー演算子|左|×| |1|++|i++|後置インクリメント演算子|左|〇| |1|--|j--|後置デクリメント演算子|左|×| |2|++|++i|前置インクリメント演算子|右|〇| |2|--|--j|後置デクリメント演算子|右|×| |2|sizeof|sizeof a|sizeof演算子|右|×| |2|&|&x|単項&演算子|右|×| |2|*|*p|単項*演算子|右|×| |2|+|+a|単項+演算子|右|×| |2|-|-b|単項-演算子|右|〇| |2|~|~i|補数演算子|右|×| |2|!|!j|論理否定演算子|右|×| |3|( )|(typ)obj|型キャスト演算子|右|×| |4|*|x * y|二項*演算子|左|〇| |4|/|x / y|除算演算子|左|×| |4|%|x % y|剰余演算子|左|×| |5|+|x + y|二項+演算子|左|〇| |5|-|x - y|二項-演算子|左|〇| |6|<< >>|i << j など|シフト演算子|左|×| |7|< <= > >=|x < y など|比較演算子|左|×| |8|== !=|x == y など|比較演算子|左|〇| |9|&|i & j|ビットAND演算子|左|×| |10|^|i ^ j|ビットXOR演算子|左|×| |11|||i | j|ビットOR演算子|左|×| |12|&&|i && j|論理AND演算子|左|×| |13||||i || j|論理OR演算子|左|×| |14|? :|x ? y : z|条件演算子|右|×| |15|=|x = y|単純代入演算子|右|〇| |15|+= -= など|x += y など|複合代入演算子|右|×| |16|,|x, y|コンマ演算子|左|×| -全部の演算子をサポートするとTL-7のプログラムが長くなってしまうので、この中の一部だけを実装することにします、残りは拡張したい人が拡張するということにします。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> typedef unsigned char *String; // こう書くと String は unsigned char * の代用になる. int loadText(String path, String t, int siz) → TL-4と同じなので省略 int isAlphabet(unsigned char c) → TL-2と同じなので省略 #define MAX_TC 255 // トークンコードの最大値. String ts[MAX_TC + 1]; // トークンの内容(文字列)を記憶. int tl[MAX_TC + 1]; // トークンの長さ. unsigned char tcBuf[(MAX_TC + 1) * 10]; // トークン1つ当たり平均10バイトを想定. int tcs = 0, tcb = 0; int getTc(String s, int len) → TL-4と同じなので省略 int lexer(String s, int tc[]) → TL-2と同じなので省略 enum { TcSemi = 0, TcBrOpn, TcBrCls, TcPlus, TcMinus, TcPlPlus, TcMulti, TcEqu, TcEEq, TcNEq, TcComma, TcWiCard, TcExpr, TcExpr0, TcTmp0, TcTmp1, TcTmp2, TcTmp3, TcTmp4, TcTmp5, TcTmp6, TcTmp7, TcTmp8, TcTmp9, TcZero }; int phrCmp_tc[100 * 20], phrCmp_exprPc0[9], phrCmp_exprPc1[9], phrCmp_pc1; int phrCmp(int pid, String phr, int tc[], int pc) // TL-5のとは違う(改造). { int i, i1, j, k; // j, kを追加. if (phrCmp_tc[pid * 20 + 19] == 0) { i1 = lexer(phr, &phrCmp_tc[pid * 20]); phrCmp_tc[pid * 20 + 19] = i1 + 1; } i1 = phrCmp_tc[pid * 20 + 19] - 1; for (i = 0; i < i1; i++) { if (phrCmp_tc[pid * 20 + i] == TcWiCard) { // 任意の1トークンに一致. pc++; continue; } if (phrCmp_tc[pid * 20 + i] == TcExpr || phrCmp_tc[pid * 20 + i] == TcExpr0) { // 任意の式に一致. i++; j = strtol(ts[phrCmp_tc[pid * 20 + i]], 0, 0); // 後続の番号を取得(式番号). phrCmp_exprPc0[j] = pc; k = 0; // かっこの対応関係を数える. for (;;) { if (tc[pc] == TcSemi) break; if (tc[pc] == TcComma && k == 0) break; if (tc[pc] == TcBrOpn) k++; if (tc[pc] == TcBrCls) k--; if (k < 0) break; pc++; } phrCmp_exprPc1[j] = pc; if (phrCmp_tc[pid * 20 + i] == TcExpr && phrCmp_exprPc0[j] == pc) return 0; // "!!**"では、長さ0は不一致とする. if (k > 0) return 0; continue; } if (phrCmp_tc[pid * 20 + i] != tc[pc]) return 0; // マッチせず. pc++; // マッチしたので次へ. } phrCmp_pc1 = pc; return 1; // すべてマッチした. } int var[MAX_TC + 1], tc[1000]; // 変数(var)とトークンコード(tc). typedef void *PtrTyp; // こう書くと PtrTyp は void * の代わりに使えるようになる. typedef int *IntP; // こう書くと IntP は int * の代わりに使えるようになる. enum { OpCpy = 0, OpAdd, OpSub, OpPrint, OpGoto, OpJeq, OpJne, OpTime, OpEnd, OpAdd1, OpLop, OpNeg, OpMul, OpCeq, OpCne }; PtrTyp ic[1000], *icq; // 内部コード. char tmp_flag[10]; // 一時変数の利用状況を管理. int tmpAlloc() // 未使用の一時変数を確保. { int i; for (i = 0; i < 10; i++) { if (tmp_flag[i] == 0) break; } if (i >= 10) return -1; tmp_flag[i] = 1; return i + TcTmp0; } void tmpFree(int i) // 一時変数を未使用に戻す. ただし、指定されたトークンコードが一時変数でないときは何もしない. { if (TcTmp0 <= i && i <= TcTmp9) { tmp_flag[i - TcTmp0] = 0; } } int epc, epc1; // exprのためのpcとpc1. void putIc3(int op, PtrTyp p0, PtrTyp p1, PtrTyp p2) → TL-6と同じなので省略 int exprSub(int priority); // exprSub1()が参照するので、プロトタイプ宣言. int exprSub1(int i, int priority, int op) // 二項演算子の処理の標準形. { int j, k; epc++; j = exprSub(priority); k = tmpAlloc(); putIc3(op, &var[k], &var[i], &var[j]); tmpFree(i); tmpFree(j); return k; } int exprSub(int priority) { int i = -1, j; if (tc[epc] == TcBrOpn) { // かっこ. epc++; i = exprSub(99); if (tc[epc] != TcBrCls) return -1; epc++; } else if (tc[epc] == TcPlPlus) { // 前置インクリメント. epc++; i = exprSub(2); putIc3(OpAdd1, &var[i], 0, 0); } else if (tc[epc] == TcMinus) { // 単項マイナス. epc++; j = exprSub(2); i = tmpAlloc(); putIc3(OpNeg, &var[i], &var[j], 0); tmpFree(j); } else if (tc[epc] >= TcExpr) { // 変数もしくは定数. i = tc[epc]; epc++; } for (;;) { if (i < 0) return -1; // ここまででエラーがあれば、処理を打ち切り. if (epc >= epc1) break; if (tc[epc] == TcPlPlus) { // 後置インクリメント. epc++; j = i; i = tmpAlloc(); putIc3(OpCpy, &var[i], &var[j], 0); putIc3(OpAdd1, &var[j], 0, 0); tmpFree(j); } else if (tc[epc] == TcMulti && priority >= 4) { i = exprSub1(i, 3, OpMul); // 左結合なので4より1小さくする. } else if (tc[epc] == TcPlus && priority >= 5) { i = exprSub1(i, 4, OpAdd); // 左結合なので5より1小さくする. } else if (tc[epc] == TcMinus && priority >= 5) { i = exprSub1(i, 4, OpSub); // 左結合なので5より1小さくする. } else if (tc[epc] == TcEEq && priority >= 8) { i = exprSub1(i, 7, OpCeq); // 左結合なので8より1小さくする. } else if (tc[epc] == TcNEq && priority >= 8) { i = exprSub1(i, 7, OpCne); // 左結合なので8より1小さくする. } else if (tc[epc] == TcEqu && priority >= 15) { epc++; j = exprSub(15); // 右結合なので15のまま. putIc3(OpCpy, &var[i], &var[j], 0); tmpFree(j); } else break; } return i; } int expr(int j) { int i; if (phrCmp_exprPc0[j] == phrCmp_exprPc1[j]) return TcSemi; // エラーではない. epc = phrCmp_exprPc0[j]; epc1 = phrCmp_exprPc1[j]; i = exprSub(99); if (epc < epc1) return -1; // 途中で止まってしまったらエラー. return i; } int compile(String s) { int pc, pc1, i, tcs0 = tcs; PtrTyp *icq1; pc1 = lexer(s, tc); tc[pc1++] = TcSemi; // 末尾に「;」を付け忘れることが多いので、付けてあげる. tc[pc1] = tc[pc1 + 1] = tc[pc1 + 2] = tc[pc1 + 3] = getTc(" ", 1); // エラー表示用のために末尾にスペースを登録しておく. for (i = tcs0; i < tcs; i++) { // 定数の初期値を代入. var[i] = strtol(ts[i], 0, 0); // 定数だった場合に初期値を設定(定数ではないときは0になる). } icq = ic; for (pc = 0; pc < pc1; pc++) { // コンパイル開始. if (phrCmp( 13, "!!* = !!* + 1 ; if ( !!* != !!* ) goto !!* ;", tc, pc) && tc[pc] == tc[pc + 2] && tc[pc] == tc[pc + 8]) { putIc3(OpLop, &var[tc[pc + 13]], &var[tc[pc]], &var[tc[pc + 10]]); pc += 13; continue; } if (tc[pc + 1] == TcEqu) { if (tc[pc + 3] == TcSemi) { // 単純代入. putIc3(OpCpy, &var[tc[pc]], &var[tc[pc + 2]], 0); } else if (phrCmp( 12, "!!* = !!* + 1 ;", tc, pc) && tc[pc] == tc[pc + 2]) { // 高速化のために+1専用の命令を用意(せこくてすみません). putIc3(OpAdd1, &var[tc[pc]], 0, 0); } else if (phrCmp( 3, "+ !!* ;", tc, pc + 3)) { // 加算. putIc3(OpAdd, &var[tc[pc]], &var[tc[pc + 2]], &var[tc[pc + 4]]); } else if (phrCmp( 4, "- !!* ;", tc, pc + 3)) { // 減算. putIc3(OpSub, &var[tc[pc]], &var[tc[pc + 2]], &var[tc[pc + 4]]); } else if (phrCmp( 14, "!!**0 ;", tc, pc)) { i = expr(0); if (i < 0) goto err; tmpFree(i); } else goto err; } else if (phrCmp( 5, "print !!**0 ;", tc, pc)) { // print. i = expr(0); if (i < 0) goto err; putIc3(OpPrint, &var[i], 0, 0); tmpFree(i); } else if (phrCmp( 6, ":", tc, pc + 1)) { // ラベル定義命令. var[tc[pc]] = icq - ic; // ラベルに対応するicqを記録しておく. pc++; // 1単語だけ読み飛ばす(for文がもう1単語を読み飛ばしてくれる). continue; } else if (phrCmp( 7, "goto !!* ;", tc, pc)) { // goto. putIc3(OpGoto, &var[tc[pc + 1]], 0, 0); } else if (phrCmp( 8, "if ( !!* !!* !!* ) goto !!* ;", tc, pc)) { // if (...) goto. i = -1; if (tc[pc + 3] == TcNEq) { i = OpJne; } if (tc[pc + 3] == TcEEq) { i = OpJeq; } if (i < 0) goto err; putIc3(i, &var[tc[pc + 7]], &var[tc[pc + 2]], &var[tc[pc + 4]]); } else if (phrCmp( 11, "time ;", tc, pc)) { putIc3(OpTime, 0, 0, 0); } else if (phrCmp( 15, "if ( !!**0 ) goto !!* ;", tc, pc)) { // if (...) goto. i = expr(0); if (i < 0) goto err; putIc3(OpJne, &var[tc[phrCmp_exprPc1[0] + 2]], &var[i], &var[TcZero]); tmpFree(i); } else if (tc[pc] == TcSemi) { // 何もしない. } else if (phrCmp( 14, "!!**0 ;", tc, pc)) { // これはなんでもマッチするので最後にする. i = expr(0); if (i < 0) goto err; tmpFree(i); } else goto err; while (tc[pc] != TcSemi) pc++; } putIc3(OpEnd, 0, 0, 0); icq1 = icq; for (icq = ic; icq < icq1; icq += 5) { // goto先の設定. i = (int) icq[0]; if (i == OpGoto || i == OpJne || i == OpJeq || i == OpLop) { icq[1] = &ic[*(IntP)icq[1]]; } } return icq1 - ic; err: printf("syntax error : %s %s %s %s\n", ts[tc[pc]], ts[tc[pc + 1]], ts[tc[pc + 2]], ts[tc[pc + 3]]); return -1; } void exec() { clock_t t0 = clock(); int i; PtrTyp *icp = ic; for (;;) { switch ((int) icp[0]) { case OpCpy: (以下、OpLopまではTL-6aと同じなので省略) + case OpNeg: + *(IntP)icp[1] = - *(IntP)icp[2]; + icp += 5; + continue; + case OpMul: + *(IntP)icp[1] = *(IntP)icp[2] * *(IntP)icp[3]; + icp += 5; + continue; + case OpCeq: + *(IntP)icp[1] = *(IntP)icp[2] == *(IntP)icp[3]; + icp += 5; + continue; + case OpCne: + *(IntP)icp[1] = *(IntP)icp[2] != *(IntP)icp[3]; + icp += 5; + continue; } } } int run(String s) → TL-6と同じなので省略 int main(int argc, const char **argv) { unsigned char txt[10000]; int i; + lexer("; ( ) + - ++ * = == != , !!* !!** !!*** _t0 _t1 _t2 _t3 _t4 _t5 _t6 _t7 _t8 _t9 0", tc); // tc[]に入った結果は使わずに捨てる. if (argc >= 2) { (以下、TL-4と同じなので省略) } -トータルの行数は455行になっています。かなり増えました。しかしかっこよさもかなり増しています。 -まず、print命令で、単に変数や定数を指定するだけではなく、式が書けるようになりました。 >print 1+2*3 7 >print (1+2)*3 9 -こんなふうに、ちゃんと演算子の優先順位も反映されます。 -次に、連続代入ができるようになりました。「x = y = z = 0;」とかそういうやつです。 -インクリメントもできます。 >a = 0; print ++a; print a 1 1 >a = 0; print a++; print a 0 1 -この違いがわかるでしょうか。C言語では、前置インクリメントと後置インクリメントは意味が違うのです。それもきちんと真似できています。 -TL-6aまでは、なんかこう「おもちゃ言語」の感じがしていた気がするのですが、一気にまともになった気がします! ** (16) TL-7の簡単な説明 -(準備中) ** 次回に続く -次回: ''a21_txt01_8'' *こめんと欄 #comment