自作の言語処理系開発日記の第7回です。前回までで変数の実装が終わったので、ここからはいよいよ制御構文を実装…と思ったのですが、制御のためには比較演算子を実装する必要がありました。
ということで、今回は比較演算子を実装していきます。基本的には四則演算と変わりないのであまり難しくはありません。
比較演算子の仕様
比較演算子を実装する前に、その仕様について少し考えておきます。
比較演算子の結果は真偽の2値ですが、データ上は真を1、偽を0で表すこととします。こうした理由は特にないのですが、一番直感的に分かりやすい形にしました。処理系の中で統一されていれば問題ないので、0/1でなくてもOKです。
実装してみる
トークナイザ
トークナイズの処理はこれまでと変わらずですが、==
(等価)や<=
(以下)は2文字で1つの演算子になるので、=
(代入)や<
(未満)と間違えて解釈されないように注意します。
この場合、最長一致で見つけていくのが良いので、以下のように処理の順番を1文字の演算子よりも先に持ってくるようにします。
〜省略〜
else if(strncmp(input, "<=", 2) == 0){
Token token = { TK_RESERVED, input, 2 };
tokens.push_back(token);
input += 2;
}
else if(strncmp(input, "<", 1) == 0){
Token token = { TK_RESERVED, input, 1 };
tokens.push_back(token);
input++;
}
〜省略〜
構文解析器
新しい演算子を追加するので、生成規則にもそれらを追加します。修正後の生成規則はこんな感じです。
expr = ident "=" compare | compare
compare = add | add ("==" | "!=" | "<" | "<=" | ">" | ">=") add
add = mul ("+" mul | "-" mul)*
mul = unary ("*" unary | "/" unary)*
unary = ("+" | "-")? primary
primary = ident | num | "(" compare ")"
新しい生成規則としてcompare
を追加し、これまでの規則中のadd
をそれに置き換えています。というのも、比較演算子の優先順位は四則演算よりも低いので、構文解析時には優先的に展開される必要があるためです。これにより、1+2<1+3
のような式も正しく処理することができます。
続いて実装ですが、ノード種別の追加は==
、!=
、<
、<=
だけで十分です。>
と>=
については、<
と<=
の左辺と右辺を入れ替えて評価するだけでよいためです。
// ノード関連の定義
typedef enum {
ND_NUM, // 数値
〜省略〜
ND_EQ, // ==
ND_NEQ, // !=
ND_LESS, // <
ND_EQLESS, // <=
} NodeKind;
構文木を作る処理もこの通り、左辺と右辺の入れ替えで対応します。
〜省略〜
else if(consume("<")){
node = new Node{ ND_LESS, addNode, add()};
}
else if(consume("<=")){
node = new Node{ ND_EQLESS, addNode, add()};
}
else if(consume(">")){
node = new Node{ ND_LESS, add(), addNode}; // 右左辺を入れ替え
}
else if(consume(">=")){
node = new Node{ ND_EQLESS, add(), addNode}; // 右左辺を入れ替え
}
〜省略〜
コード生成器
コード生成についても構文解析と同じく、==
、!=
、<
、<=
に対応した各命令を新たに定義します。
// 命令関連の定義
typedef enum {
〜省略〜
OP_EQ, // ==
OP_NEQ, // !=
OP_LESS, // <
OP_EQLESS, // <=
〜省略〜
} OP_CODE;
実行系(仮想マシン)
仮想マシンについては、新規追加した各命令に対する実処理を実装するだけです。特に四則演算と変わりません。比較結果がスタックにPushされます。
〜省略〜
case OP_EQ:
{
push(reg[op->operand] == reg[op->operand2]);
break;
}
case OP_NEQ:
{
push(reg[op->operand] != reg[op->operand2]);
break;
}
case OP_LESS:
{
push(reg[op->operand] < reg[op->operand2]);
break;
}
case OP_EQLESS:
{
push(reg[op->operand] <= reg[op->operand2]);
break;
}
〜省略〜
これで比較演算子を扱えるようになりました。今はまだ使いどころがないですが、次回以降で制御構文を実装すると、これらの演算子が活きてくることになります。
今回の実装は以下のコミットです。全コードはこちらをご覧くださいm(_ _)m
ではでは