rsp

C コンパイラをつくってみる (4)

低レイヤを知りたい人のためのCコンパイラ作成入門 ステップ5-6を参考にしています。

前回 (3)

次回 (5)

単項 +, - 演算子

結合順として乗算 * と項 (...) の間に、単項演算子を入れる。生成規則

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

差分はこちら

参考文献

*1:条件が成り立つとき整数の1、成り立たなければ整数の0になる演算

*2:片方に1を加えて slt するとか