C コンパイラをつくってみる (4)
低レイヤを知りたい人のためのCコンパイラ作成入門 ステップ5-6を参考にしています。
単項 +, - 演算子
結合順として乗算 *
と項 (...)
の間に、単項演算子を入れる。生成規則
unary := term | "+" term | "-" term
として、構文解析のロジックを書き下す。
これまで書いてきた mul := term | mul "*" term ...
のように自己再帰的な、「0回以上の繰り返し」を表す生成規則に対応するコードとはすこし景色が違った(無条件forによる繰り返しがない・次トークンを見る前にはじめに決め打ちで解析するルールがない)。
ASTNode* unary(std::vector<Token>::const_iterator& token_itr){ if(consume(TokenType::PLUS, token_itr)){ // 単項 + はそのままオペランドを使う return term(token_itr); }else if(consume(TokenType::MINUS, token_itr)){ // 単項 - は、「0 - オペランド」の減算を生成する。 auto const zeroNode = newNodeNumber(0); auto const subtractionNode = newNodeBinary(ASTNodeType::BINARY_SUB, zeroNode, term(token_itr)); return subtractionNode; }else{ return term(token_itr); } }
比較演算子
加減算より結合の弱い演算子として大小比較の演算子群 <=
<
>=
>
を入れ、さらにそれより結合の弱い演算子として等値比較の演算子群 ==
!=
を入れた*1。
トークナイザを変更する際には、 <=
と >=
は、 <
>
より先にマッチさせる ことに留意した。
これまで同様に、結合順位を考慮して生成規則を変更した。
比較演算子の RV32I におけるアセンブリコードは以下のように書いた。(スタックマシン風の枠組みによって a0
に左オペランド、 a1
に右オペランドがロードされている。)
# == sub a0, a0, a1 # 減算して seqz a0, a0 # 0と等しければ1 # != sub a0, a0, a1 # 減算して snez a0, a0 # 0と異なれば1 # < slt a0, a0, a1 # <= sub a2, a0, a1 # 減算して seqz a2, a2 # 0と等しいか slt a0, a0, a1 # < が成り立つ or a0, a0, a2
<=
に関しては他にやり方*2がありそうだけどわかりやすさ重視でこうなった。
以下のような演算ができるようになった。
1 < 1 => 0 1 <= 1 => 1 5 == 2+3 => 1