2017年3月17日 星期五

compiler [2.9] - 建立 function call AST

有匪君子, 如切如磋, 如琢如磨。
有了函式定義之後, 再來就是要呼叫這個函式。function call 的 AST 應該長什麼樣子呢? 像 list 1 這樣。

list 1. function call AST
21 int f1(char c, int i)
22 {
23 }
24 
25 int main()
26 {
27   f1('c', 6);  
28   return 0;
29 }
30
31
 5                                   root
 6                                    |
 7                                   prog
 8            ________________________|________________________
 9            |                       |                       |
10  f1 |int |func |global  main |int |func |global           main
11        ____|_____                  |
12        |        |              func_body
13       para  func_body           ___|___
14    ____|____                    |     |
15    |       |                    f1  return
16 c |char  i |int                _|__   |
17                                |  |   0
18                                c  6

list 2, list 3 是我早期的版本, 那時候還可以這樣寫, 現在已經不行了。得用 list 1 的寫法才行, 也更像 c 語言了。

list 2. test_pattern/fc1.tree
 1 f1(1)
 2 token: f1
 3 token: (
 4 token: 1
 5 token: )
 6 
 7    root
 8     |
 9    prog
10     |
11 func call
12     |
13     1

list 3. test_pattern/fc2.tree
 1 f1(1, 2, x+y, "abc", 1+2*3)
 2 token: f1
 3 token: (
 4 token: 1
 5 token: ,
 6  token: 2
 7 token: ,
 8  token: x
 9 token: +
10 token: y
11 token: ,
12  token: abc
13 token: ,
14  token: 1
15 token: +
16 token: 2
17 token: *
18 token: 3
19 token: )
20 
21        root
22         |
23        prog
24         |
25     func call
26 ________|________
27 |   |   |   |   |
28 1   2   +  abc  +
29        _|__    _|__
30        |  |    |  |
31        x  y    1  *
32                  _|__
33                  |  |
34                  2  3

test_pattern/fc3.tree
 1 f1()
 2 token: f1
 3 token: (
 4 token: )
 5 
 6    root
 7     |
 8    prog
 9     |
10 func call

c_parser.cpp L334 是 function call 的 EBNF, 比較麻煩的是 function 的參數可不見得只是變數或是整數, 還可能是一個 function 或是運算式。當然, 如果想簡化, 就不要處理這些, 程式會簡單些, 不過我就是不爽不能用 function 或是運算式, 堅持把這個加了進去。

一樣對照著 ebnf 看, ebnf 怎麼寫, 程式就怎麼寫, 難不倒你的。

一開始先判斷目前的 token 是不是 NAME (變數名稱), 再來的 token 是不是 (, 再來有點麻煩, 因為參數可以沒有, 若接下來的 token 是 ), 表示沒有參數, 這個就簡單了, parse 完成。

如果不是 ), 表示有參數, 看看是不是 function call 還是運算式, 再接著判斷是不是還有下一個參數 (L371), 再來看下一個 token 是不是 ',', 再來就和前面一樣, 看看是不是 function call 還是運算式, 我寫得很輕鬆, 你應該混亂了, 請慢慢欣賞, 不要心急, 平心靜氣一定看得懂, 在看不懂就出動 gdb 吧!

c_parser.cpp
 159 /*!
 160  * primary   : "(" expr ")" | NUMBER | IDENTIFIER | STRING | func_call
 161  */
 162 ASTNode* primary()
 163 {
 164   Token token = peek_token(); 
 165   if (is_func_call())
 166   {
 167     ASTNode *e = func_call();
 168     return e;
 169   }
 170   ...

 321 bool is_func_call()
 322 {
 323   Token t = peek_token();
 324   if (t.ast_type() == NAME)
 325   {
 326     t = peek_token(1);
 327     if (t.str() == "(") // func_call
 328       return true;
 329   }
 330 
 331   return false;
 332 }
 334 /// func_call: NAME '(' [ (expr | func_call)  { ',' (expr | func_call) } ] ')' // 這是左遞迴嗎?
 335 ASTNode* func_call()
 336 {
 337   ASTNode *fc = 0;
 338   Token t = peek_token();
 339 
 340   if (t.ast_type() == NAME)
 341   {
 342     fc = new ASTNode(func_call_token);
 343     fc->set_str(t.str());
 344 
 345     ASTNode *e=0;
 346     pop_token();
 347     t = peek_token();
 348     if (t.str() == ("("))
 349     {
 350       pop_token();
 351 
 352       //if((e = expr()) != 0)
 353       t = peek_token();
 354       if(t.str() != ")")
 355       {
 356 
 357         if (is_func_call())
 358         {
 359           e = func_call();
 360         }
 361         else
 362         {
 363           e = expr();
 364         }
 365 
 366         if (e)
 367           fc->add_child(e);
 368 
 369         Token t = peek_token();
 370 
 371         while (t.str() != ")")
 372         {
 373 
 374 
 375             if (t.str() == ",")
 376             {
 377               pop_token();
 378 
 379               if (is_func_call())
 380               {
 381                 e = func_call();
 382               }
 383               else
 384               {
 385                 e = expr();
 386               }
 387 
 388               if (e);
 389                 fc->add_child(e);
 390             }
 391             else
 392             {
 393               err("func_call: should ','", t.str());
 394             }
 395             t = peek_token();
 396         } 
 397 
 398       }
 399       else // function call passes no argument
 400       {
 401 
 402       }
 403 
 404       t = peek_token();
 405       if (t.str() == (")"))
 406       {
 407         pop_token();
 408       }
 409       else
 410       {
 411         err("func_call: should )", t.str());
 412       }
 413     }
 414     else
 415     {
 416       err("func_call: should (", t.str());
 417     }
 418   }
 419   else
 420   {
 421     err("func_call: should NAME", t.str());
 422   }
 423 
 424   return fc;
 425 }

https://github.com/descent/simple_compiler/blob/master/c_parser.cpp
source code:
git commit: 8fcfec025156ca9196c03433867fb4132acac0bd

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。