コンパイラ作成(15) 簡単な式

今回の目標

今まで避けてた式の構文解析に挑戦してみるよ。

main()
{
    print(32 + 47);
    print(32 * 47);
}

当面演算子は+と*だけにするつもり。この二つがちゃんとできれば他の演算子は比較的簡単に追加できるんじゃないかな。

Clang先輩

まずはClangのアセンブリコードでお勉強。

	.text
	.intel_syntax noprefix
	.file	"i4.myc"
	.globl	main                    # -- Begin function main
	.p2align	4, 0x90
	.type	main,@function
main:                                   # @main
# BB#0:
	push	rbp
	mov	rbp, rsp
	sub	rsp, 16
	mov	edi, 79
	mov	al, 0
	call	print
	mov	edi, 1504
	mov	dword ptr [rbp - 4], eax # 4-byte Spill
	mov	al, 0
	call	print
	xor	edi, edi
	mov	dword ptr [rbp - 8], eax # 4-byte Spill
	mov	eax, edi
	add	rsp, 16
	pop	rbp
	ret
.Lfunc_end0:
	.size	main, .Lfunc_end0-main
                                        # -- End function

	.ident	"clang version 5.0.1-4 (tags/RELEASE_501/final)"
	.section	".note.GNU-stack","",@progbits

ありゃ、最適化されっちゃってる。これじゃ参考にならないなあ。しょうがないんで自力で頑張るよ。

Lexerクラス

まずLexerクラスのTK::SYMBOLのところに”*”を追加。前回の”:”の追加とおんなじ感じ。

組込関数print

以前作ったprintの処理を修正して式を書けるようにするよ。

    elsif kind == TK::ID && str == "print" then
      # 組み込み関数printの処理
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      expr kind, str
      codegen "  mov edi, eax"
      codegen "  call print"
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after builtin function"
      return true;

exprってのが式の処理をしてくれるメソッド。eaxに計算結果を入れるようコード生成してる。

expr

exprの中身はこんな感じ。

  # 式の構文解析
  def expr(fkind,fstr)
    kind, str = @lex.gettoken
    if kind == TK::SYMBOL && str == ";" then
      expr2 fkind,fstr,kind,str
      return true
    elsif kind == TK::SYMBOL && str == "+" then
      expr2 fkind,fstr,kind,str
      return true
    elsif kind == TK::SYMBOL && str == "*" then
      expr2 fkind,fstr,kind,str
      return true
    end
  end

  # 式の構文解析
  def expr2(fkind,fstr,skind,sstr)
    if fkind == TK::NUMBER then
      codegen "  mov eax, " + fstr
      if skind == TK::SYMBOL && sstr == ";" then
        return
      elsif skind == TK::SYMBOL && sstr == "+" then
        kind, str = @lex.gettoken
        if kind == TK::NUMBER then
          codegen "  add eax, " + str
        end
        kind, str = @lex.gettoken
        if kind == TK::SYMBOL && str == ";" then return end
        if kind == TK::SYMBOL && str == ")" then return end
        perror "expected ';' after expr"
      elsif skind == TK::SYMBOL && sstr == "*" then
        kind, str = @lex.gettoken
        if kind == TK::NUMBER then
          codegen "  mov ebx, " + str
          codegen "  mul ebx"
        end
        kind, str = @lex.gettoken
        if kind == TK::SYMBOL && str == ";" then return end
        if kind == TK::SYMBOL && str == ")" then return end
        perror "expected ';' after expr"
      end
    end
  end
動作チェック

コーディングできたんで早速テストしてみる。

~/myc$ ./myc.rb i5.myc 
~/myc$ clang-5.0 i5.s print.o -o i5
~/myc$ ./i5
79
1504
~/myc$

お、動いた。

問題点

これで一番単純な式の処理はできるようになったよ。でも今回のコーディングはイマイチかな。

  • 複雑な式への拡張ができるか
  • エラー処理が不十分
  • レジスタの使い方に問題はないか

この記事書きながらプログラム見てたらやっぱり色々拙いような気がしてきた。式の処理だけはしっかりやらないと駄目だよなあ。もう一回考え直そう。