自作の言語処理系開発日記の第4回です。前回までで乗除算を実装できたので、この調子でもう少し複雑な計算に対応したいと思います。今回はそれぞれの実装が少ないので、一気に2つのステップを進めます。
括弧を含む計算
これまでの実装では、乗除算は必ず加減算に先立って実行されます。しかし、それでは不十分なので、括弧を含む計算(例:(1+2)*3
)を実行できるようにします。
生成規則をいじる
今回も構文解析器の生成規則を修正することで、括弧付きの計算に対応させます。前回の乗除算を実装した際には、乗除算が加減算より深くで展開されるように生成規則を変えることで、計算の優先順位を表現しました。今回も考え方は同じです。
まず、前回までの生成規則はこちら。
add = mul ("+" mul | "-" mul)*
mul = num ("*" num | "/" num)*
そして、括弧付きの計算に対応した生成規則が以下になります。
add = mul ("+" mul | "-" mul)*
mul = primary ("*" primary | "/" primary)*
primary = num | "(" add ")"
primary
という新しい生成規則を定義し、mul
の中にあったnum
をそれに置き換えています。primary
はnum
と並列で括弧付きのadd
を定義しているため、「括弧付きの計算が一番深い=一番優先度が高い」という規則になっています。
実装はこれまでと変わらずなので割愛しますが、気になる方は以下のコミットをご覧ください。
単項演算子(+と-)
括弧付きの計算は実行できるようになりましたが、これだけでは-5+3
や1++3
のような式が計算できません。これらを扱うためには単項演算子としての+
と-
を実装する必要があります。
生成規則をいじる
こちらも生成規則をいじることで解決したいのですが、ややこしいのが加減算の+
と-
との棲み分けです。文字は同じでも、式中の位置や文脈によって解釈が変わるので、それを上手に生成規則に落とし込む必要があります。
最初はこんな規則を考えましたが、結論から言うとダメでした。
add = mul ("+" mul | "-" mul)*
mul = primary ("*" primary | "/" primary)*
primary = num | "+" num | "-" num | "(" add ")"
括弧付きの計算で作ったprimary
の規則中、+
付きのnum
と-
付きのnum
を入れることでパッと見うまくいっていたのですが、-(1+3)
のような式がパースできないことに気づき、ボツになりました(´・ω・)
括弧付きの計算にも対応しようと思うと、primaryの定義を
とすれば良いのですが、ちょっと冗長です。そこで、unary
という定義を追加して以下のようにするとすっきりします。
add = mul ("+" mul | "-" mul)*
mul = unary ("*" unary | "/" unary)*
unary = ("+" | "-")? primary
primary = num | "(" add ")"
生成規則中の?
は、直前の項目が0回または1回登場するという意味です。unary
だけを抜き出すと、実装はこんな感じになりました。
Node* Parser::unary(void)
{
if(consume("+")){
return primary();
}
else if(consume("-")){
Node* zero = new Node();
zero->kind = ND_NUM;
zero->val = 0;
Node* node = new Node{ ND_SUB, zero, primary() };
return node;
}
else{
return primary();
}
};
+
については特に何もしなくてOKですが、-
については0からの引き算として実装しています。全実装は以下をご覧ください。
これで、整数レベルの四則演算はすべてカバーできるようになりました。簡単な電卓レベルまで進化したことになるので、ちょっと嬉しいですね。
次回からはもう少し言語処理系っぽい機能を入れ込んでいこうと思います。
ではでは