コンパイラ作成(71) ポインタのポインタ型

今回の目標

ポインタのポインタ型に対応するよ。

// 間接参照演算子

int main()
{
    int a = 55;
    int *p = &a;
    int **pp = &p;
    printf("a = %d **pp = %d\n",a,**pp);
    **pp = 123;
    printf("a = %d **pp = %d\n",a,**pp);
}

変数宣言と連続した単項演算子の扱いが問題。それと2件ばかしおまけがあるよ。

おまけその1
// void型変数

int main()
{
    void a;
}

void型の変数は拙いのでエラーになるよう修正するよ。

おまけその2
// int*型 + int*型

int main()
{
    int *p;
    p + p;
}

前回、修正漏れがあってこれのチェックができてなかったよ。

codegen_els

まずはおまけその2の修正行くよ。

    # 型チェック
    if type_l != type_r then
      if is_pointer_type?(type_l) && type_r == "int" then
        if op.str != "+" && op.str != "-" then
          perror "mismatched types to binary operation"
        end
      elsif type_l == "int" && is_pointer_type?(type_r) then
        if op.str != "+" && op.str != "-" then
          perror "mismatched types to binary operation"
        end
      else
        perror "mismatched types to binary operation"
      end
    elsif is_pointer_type? type_l then
      perror "mismatched types to binary operation"
    end

型チェックのところが一か所is_pointer_type?になってなかった。

var_decl

続いて本編とおまけその1。

  # 変数宣言の処理
  def var_decl(kind, str)
    basetype = str
    loop do
      type = basetype
      kind, str = @lex.gettoken
      loop do
        if kind != TK::SYMBOL || str != "*" then break end
        type += str
        kind, str = @lex.gettoken
      end
      if kind != TK::ID then perror end
      print "var "+str+"\n" if $opt_d
      @lvarsize += sizeof(type)
      if check_var str then
        perror "redefinition variable \"" + str +"\""
      end
      if type == "void" then
        perror "invalid type 'void'"
      end
      set_var str, [type,@lvarsize]
      skind, sstr = @lex.gettoken
      if skind == TK::SYMBOL && sstr == "=" then
        kind, str = expr2 kind, str, skind, sstr;
      else
        kind, str = skind, sstr;
      end
      if kind != TK::SYMBOL || str != "," then break end
    end
    return kind, str
  end

loop回して*が連続してても大丈夫なように修正したよ。それとおまけ1のvoid型を弾く処理も追加した。

function

引数の処理部。

    # 引数の処理
    kind, str = @lex.gettoken
    loop do
      if kind == TK::SYMBOL && str == ")" then break end
      if kind == TK::TYPE then
        if str == "extern" then perror "invalid 'extern'" end
        type = str
        kind, str = @lex.gettoken
        loop do
          if kind != TK::SYMBOL || str != "*" then break end
          type += str
          kind, str = @lex.gettoken
        end
        paratype << type
        if kind != TK::ID then perror "wrong parameter name" end
        print "para "+str+"\n" if $opt_d
        size = sizeof type
        @lvarsize += size
        parametersize << size
        if check_var str then perror "redefinition parameter \"" + str +"\"" end
        if type == "void" then
          perror "invalid type 'void'"
        end
        set_var str, [type,@lvarsize]
      else
        perror
      end
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == "," then
        kind, str = @lex.gettoken
      end
    end

var_declと同じように修正。これ処理内容ほとんど共通だよなあ。今後のこと考えたら一個に纏めた方が良いかな。完全に共通じゃないからちょっと面倒かな。

modify_el_unaryop

今回一番修正が面倒だったとこ。

  # elの変形(単行演算子の処理)
  #   [-, 2, +, 3]
  #   =>[[-, 2], + 3]
  #   [-, 2, +, -, 3]
  #   =>[[-, 2], + [-, 3]]
  def modify_el_unaryop(el, opl)
    mel = []
    prev = nil
    loop do
      if el.size == 0 then break end
      x = el.shift
      if ((prev == nil) || (!prev.kind_of?(Array) && (prev.kind == TK::SYMBOL))) \
         && !x.kind_of?(Array) && x.kind == TK::SYMBOL
      then
        if opl.include? x.str then
          tel = []
          tel << x
          x = el.shift
          if x.kind_of?(Array) then
            x = modify_el_unaryop x, opl
          elsif opl.include? x.str then
            sel = []
            loop do
              sel << x
              if !opl.include? x.str then break end
              if el.size == 0 then break end
              x = el.shift
            end
            x = modify_el_unaryop sel, opl
            if x.size == 1 && x[0].kind_of?(Array) then
              x = x[0]
            end
          end
          tel << x
          mel << tel
          prev = tel
        else
          perror
        end
      else
        if x.kind_of?(Array) then x = modify_el_unaryop x, opl end
        mel << x
        prev = x
      end
    end
    return mel
  end

単項演算子が連続した場合の論理が入ってなかったんで追加した。数行追加したけどコーディングより、どういう処理だったか思い出す方が大変だったよ。

動作テスト

それではテスト。まずはおまけ1から。

~/myc$ myc err34.myc
err34.myc:5:9 error: invalid type 'void'
~/myc$ 

よしよし。つづいておまけその2。

~/myc$ myc err36.myc
err36.myc:6:10 error: mismatched types to binary operation
~/myc$

ちゃんとエラーになったよ。それでは本編。

~/myc$ myc o25.myc
~/myc$ ./o25
a = 55 **pp = 55
a = 123 **pp = 123
~/myc$

おお、動いたよ。ちょっと変えてみる。

// 間接参照演算子

void sub(int **pp)
{
    **pp = 123;
}

int main()
{
    int a = 55;
    int *p = &a;
    int **pp = &p;
    printf("a = %d **pp = %d\n",a,**pp);
    sub(pp);
    printf("a = %d **pp = %d\n",a,**pp);
}

ポインタのポインタを関数に渡してみたよ。

~/myc$ myc o26.myc
~/myc$ ./o26
a = 55 **pp = 55
a = 123 **pp = 123
~/myc$

これも大丈夫だね。もういっちょ行くよ。

// コマンドライン

int main(int argc, char **argv)
{
    for(int i = 0; i < argc; i = i + 1)
        printf("argv[%d]=%s\n", i, *(argv+i));
}

これも行けるかな。char**型の例だけど。

~/myc$ myc o27.myc
~/myc$ ./o27 abc 123 12.5
argv[0]=./o27
argv[1]=abc
argv[2]=123
argv[3]=12.5
~/myc$

おおお、ちゃんとコマンドライン引数表示されてる。C言語だと最初は実行ファイル名だったか。最近Rubyばっかり触ってたからすっかり忘れてたよ。さて次回は何やるかな。配列のサポートへ進みたいけど、その前にchar型のサポートをしたいってのもあるなあ。char型はコード生成の修正箇所多そうだよなあ。そんなに難しくはないけど面倒くさそうだな。
ちなみにmyc.rbは今1500行だよ。こんなもんでも結構色々できるもんだね。最終的にどんくらいの行数になるんだろうか。ていうかどうなったらmyc完成って言えるんだろうか。はて、さて。うーむ。