川合のプログラミング言語自作のためのテキスト第三版#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後置デクリメント演算子×
    2sizeofsizeof asizeof演算子×
    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演算子×
    11i | 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

こめんと欄


コメントお名前NameLink

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2021-02-18 (木) 19:36:12 (16d)