|
千丈之堤, 以螻蟻之穴潰 |
「
yacc/bison 系列 (2) - 輸出 AST, if statement」展示了 bison 和 c++ 的用法, 雖然可以用, 但不算是正式的用法, bison 有「支援真正的 c++」用法, 輸出的 parser 是 c++ 版本, 還跟上 c++20 的標準, 對於我這個 c++ 愛好者來說, 這樣很棒。
但是我不會用 ...
好不容易花了很大的力氣才有點會用 bison, 突然要改用 c++ 版本, 又要突破一些障礙才行, 感覺又要重學。我真的應該為了使用 c++ 而去學習嗎?
而且網路上的文章很少這樣用, 用 bison, c++ keyword 找到的文章, 大部份找到和 c++ 的搭配都是我之前的那種用法; 另外的就是 bison 文件裡頭的 c++ 說明 -
10.1.1 A Simple C++ Example
還有範例: https://github.com/akimd/bison/tree/master/examples/c%2B%2B
bison 文件除了 c++ 還有 d, java 的說明。
其中的 calc++ 範例從 bison 弄出可以編譯的版本有點麻煩, 我直接把 calc++ 這個範例放在 bitbucket。
另外找到這篇:
Flex and Bison in C++
flex 也有個輸出 c++ lexer 的版本, 組合下來的情況有點亂, 都不知道怎麼相互搭配了。另外還有一個
bisoncpp, 讓情況更複雜了。
以 calc++ 來說明 flex/bison 怎麼搭配使用。bison 輸出的是 c++ code parser, flex 輸出的是 c++ code, 但不是 class 版本的 yylex(), 然後使用的 yylex() prototype 是 yy::parser::symbol_type yylex(driver& drv), 看傳回值的部份, 不是原本的 int, 所以這邊用了
這樣會就使用 yy::parser::symbol_type yylex() 而不是 int yylex(), 那一定要用 yy::parser::symbol_type yylex(), 不能用 int yylex() 嗎? 看起來是不行, 如果可以 return token::NUMBER 也許還可以, 不過 list 1 定義的 enum 是被放在 class private, 所以無法直接存取, 還是得透過 make_XXXX 來使用這些 token enum, 就算可以好了, 也沒有 yylval 來把 yylex 的 token 傳給 bison。補充的 hoc_cpp_1.yy 勉強可以這樣用。
目前我遇到的困境是, 使用 bison 輸出 c++ parser 的版本, 不知道怎麼和 flex 輸出的 lexer 搭配。原本的 c parser 是搭配 int yylex(), 但是 c++ parser 是搭配 parser::symbol_type yylex(), 我目前還不知道怎麼用 flex 輸出 parser::symbol_type yylex()。
不過沒關係, 先來搞定 bison 輸出 c++ parser 的用法。為什麼要這麼麻煩呢? 因為我想要用 std::string, 但是原本的 c parser union 在使用 std::string 時, 會有問題, bison 會輸出類似 u.cpp 的 union, 用 c++ 編譯會有問題, 需要自己補上相關的 ctor 才行, 而要讓 bison 輸出 c parser 編譯可以過, 還要 copy ctor。
前言說完了, 該進入正題, 來把最一開始的四則運算改寫為 c++ 版本的 bison 語法。
hoc_cpp.yy L1 ~ 52 從 https://github.com/akimd/bison/blob/master/examples/c%2B%2B/variant.yy 這邊照抄, 其他部份也是從這個檔案改寫而來。
最主要是 parser::symbol_type yylex (); 的改寫, 本來 return NUMBER 這樣的 macro 改為 return parser::make_NUMBER (input_val, loc), 另外也要定義 hoc_cpp.yy L55 ~ L65 的 token, 這樣才能用 parser::make_END_OF_FILE(), parser::make_NEWLINE(), parser::make_PLUS(), parser::make_MINUS() 這些 member function。
來看看 make_NUMBER (int v, location_type l) ref: list 2, 怎麼那麼巧, 第一個參數是 int, 那就是 hoc_cpp.yy L54 定義的
54 %token <int> NUMBER;, 如果是寫成 %token <std::string> NUMBER;, 那 make_NUMBER(std::string v, location_type l) 就會長這樣。
比較麻煩的是本來可以 return getch 的 c, 我不知道要怎麼產生一個類似 make_CHAR 的 member function, 所以用 parser::make_NUMBER 代替, 另外要處理 parser::make_PLUS(), parser::make_MINUS() 也比原本 return c 麻煩不少。
L58, L59 MINUS, PLUS 似乎要用 "+", 用 '+' 就會有奇怪的錯誤, 這個經過測試有點複雜, 某些組合是可以用 '+', 但是要改規則寫法, 就不多提了。
再來 main call parse() 也不一樣, 變成 member function 了。
以下是編譯指令:
g++ -g -std=c++17 -Wall -c hoc_cpp.cpp
g++ -g -std=c++17 -Wall hoc_cpp.o -o hoc_cpp
這樣就完成一個 c++ 版本的 bison parser。
另外補充一個寫法 hoc_cpp_1.yy, 沒有使用 %define api.token.constructor, 影響到什麼呢? yylex 的 function prototype, hoc_cpp_1.yy L24 那樣, 而 yylex return 也不同, 改成 hoc_cpp_1.yy L133, L134, 使用了 emplace(), 相當奇怪的用法。「
10.1.7 C++ Scanner Interface」提到了這個, 有興趣的朋友自己看, 就不說明了。
編譯指令:
bison -d hoc_cpp_1.yy
g++ hoc_cpp_1.tab.cc -o hoc_cpp_1
另外 flex 不是 gnu 套件的一部份, 有點驚訝, 不知道為什麼?
ref:
沒有留言:
張貼留言
使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。
我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。