看板 CompilerDev 關於我們 聯絡資訊
「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, 所 以這邊用了 driver.hh 26 // Give Flex the prototype of yylex we want ... 27 # define YY_DECL \ 28 yy::parser::symbol_type yylex (driver& drv) 29 // ... and declare it for the parser's sake. 30 YY_DECL; 這樣會就使用 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 勉強可 以這樣用。 list 1. hoc_cpp.cpp 695 /// Token kinds. 696 struct token 697 { 698 enum token_kind_type 699 { 700 YYEMPTY = -2, 701 END_OF_FILE = 0, // END_OF_FILE 702 YYerror = 256, // error 703 YYUNDEF = 257, // "invalid token" 704 NUMBER = 258, // NUMBER 705 ASSIGN = 259, // ":=" 706 MINUS = 260, // "-" 707 PLUS = 261, // "+" 708 STAR = 262, // "*" 709 SLASH = 263, // "/" 710 LPAREN = 264, // "(" 711 RPAREN = 265, // ")" 712 NEWLINE = 266 // "\n" 713 }; [S: 目前我遇到的困境是, 使用 bison 輸出 c++ parser 的版本, 不知道怎麼和 flex 輸 出的 lexer 搭配。原本的 c parser 是搭配 int yylex(), 但是 c++ parser 是搭配 parser::symbol_type yylex(), 我目前還不知道怎麼用 flex 輸出 parser::symbol_type yylex()。 :S] 不過沒關係, 先來搞定 bison 輸出 c++ parser 的用法。為什麼要這麼麻煩呢? 因為我 想 要用 std::string, 但是原本的 c parser union 在使用 std::string 時, 會有問題, bison 會輸出類似 u.cpp 的 union, 用 c++ 編譯會有問題, 需要自己補上相關的 ctor 才行, 而要讓 bison 輸出 c parser 編譯可以過, 還要 copy ctor。 u.cpp 2 #include <cstdio> 3 #include <string> 4 using namespace std; 5 6 union YYSTYPE 7 { 8 string id; 9 int num; 10 #if 0 11 YYSTYPE(){}; 12 ~YYSTYPE(){}; 13 YYSTYPE operator=(const YYSTYPE&){} 14 #endif 15 }; 16 17 YYSTYPE yylval; 18 19 int main(int argc, char *argv[]) 20 { 21 22 return 0; 23 } 24 25 g++ -g -std=c++17 -Wall a1.cpp -o a1 26 a1.cpp:16:9: error: use of deleted function ‘YYSTYPE::YYSTYPE()’ 27 16 | YYSTYPE yylval; 前言說完了, 該進入正題, 來把最一開始的四則運算改寫為 c++ 版本的 bison 語法。 hoc_cpp.yy 1 %require "3.2" 2 %debug 3 %language "c++" 4 %define api.token.constructor 5 %define api.value.type variant 6 %define api.location.file none 7 %define parse.assert 8 %locations 9 10 %code requires // *.hh 11 { 12 #include <string> 13 #include <vector> 14 typedef std::vector<std::string> strings_type; 15 } 16 17 %code // *.cc 18 { 19 #include <iostream> 20 #include <sstream> 21 22 namespace yy 23 { 24 // Prototype of the yylex function providing subsequent tokens. 25 static parser::symbol_type yylex (); 26 27 // Print a vector of strings. 28 std::ostream& 29 operator<< (std::ostream& o, const strings_type& ss) 30 { 31 o << '{'; 32 const char *sep = ""; 33 for (strings_type::const_iterator i = ss.begin (), end = ss.end (); 34 i != end; ++i) 35 { 36 o << sep << *i; 37 sep = ", "; 38 } 39 return o << '}'; 40 } 41 } 42 43 // Convert to string. 44 template <typename T> 45 std::string 46 to_string (const T& t) 47 { 48 std::ostringstream o; 49 o << t; 50 return o.str (); 51 } 52 } 53 54 %token <int> NUMBER; 55 %token END_OF_FILE 0; 56 %token 57 ASSIGN ":=" 58 MINUS "-" 59 PLUS "+" 60 STAR "*" 61 SLASH "/" 62 LPAREN "(" 63 RPAREN ")" 64 NEWLINE "\n" 65 ; 66 67 %type <int> list; 68 %type <int> expr; 69 70 %left "+" "-" 71 %left "*" "/" 72 73 %% 74 75 list: {printf("\taaempty\n");} 76 | list "\n" {printf("list \\n\n");} 77 | list expr "\n" { printf("%d\n", $2); } 78 79 expr: NUMBER {$$ = $1; printf("xx num %d\n", $1);} 80 | expr "+" expr {$$ = $1 + $3;} 81 | expr "-" expr {$$ = $1 - $3;} 82 | expr "*" expr {$$ = $1 * $3;} 83 | expr "/" expr {$$ = $1 / $3;} 84 | '(' expr ')' 85 86 87 %% 88 89 char *progname; 90 int lineno = 1; 91 92 namespace yy 93 { 94 // Use nullptr with pre-C++11. 95 #if !defined __cplusplus || __cplusplus < 201103L 96 # define NULLPTR 0 97 #else 98 # define NULLPTR nullptr 99 #endif 100 101 // The yylex function providing subsequent tokens: 102 // TEXT "I have three numbers for you." 103 // NUMBER 1 104 // NUMBER 2 105 // NUMBER 3 106 // TEXT "And that's all!" 107 // END_OF_FILE 108 109 static 110 parser::symbol_type 111 yylex () 112 { 113 int c; 114 int input_val; 115 static int count = 0; 116 const int stage = count; 117 ++count; 118 parser::location_type loc (NULLPTR, stage + 1, stage + 1); 119 120 while ((c=getchar()) == ' ' || c == '\t') 121 ; 122 123 if (c == EOF) 124 return parser::make_END_OF_FILE (loc); 125 if (c == '.' || isdigit(c) ) 126 { 127 ungetc(c, stdin); 128 //scanf("%lf", &input_val); 129 scanf("%d", &input_val); 130 //val = 5; 131 return parser::make_NUMBER (input_val, loc); 132 } 133 134 switch (c) 135 { 136 case '+': 137 { 138 return parser::make_PLUS(loc); 139 break; 140 } 141 case '-': 142 { 143 return parser::make_MINUS(loc); 144 break; 145 } 146 } 147 148 if (c == '\n') 149 { 150 ++lineno; 151 return parser::make_NEWLINE(loc); 152 } 153 //return c; 154 return parser::make_NUMBER (c, loc); 155 156 #if 0 157 static int count = 0; 158 const int stage = count; 159 ++count; 160 parser::location_type loc (NULLPTR, stage + 1, stage + 1); 161 switch (stage) 162 { 163 case 0: 164 return parser::make_TEXT ("I have three numbers for you.", loc); 165 case 1: 166 case 2: 167 case 3: 168 return parser::make_NUMBER (stage, loc); 169 case 4: 170 return parser::make_TEXT ("And that's all!", loc); 171 default: 172 return parser::make_END_OF_FILE (loc); 173 } 174 #endif 175 } 176 177 // Mandatory error function 178 void parser::error (const parser::location_type& loc, const std::string& msg) 179 { 180 std::cerr << loc << ": " << msg << '\n'; 181 } 182 } 183 184 int main () 185 { 186 yy::parser p; 187 p.set_debug_level (!!getenv ("YYDEBUG")); 188 return p.parse (); 189 } 190 191 // Local Variables: 192 // mode: C++ 193 // End: 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) 就會長這 樣。 list 2. hoc_cpp.cpp 1071 #if 201103L <= YY_CPLUSPLUS 1072 static 1073 symbol_type 1074 make_NUMBER (int v, location_type l) 1075 { 1076 return symbol_type (token::NUMBER, std::move (v), std::move (l)); 1077 } 1078 #else 1079 static 1080 symbol_type 1081 make_NUMBER (const int& v, const location_type& l) 1082 { 1083 return symbol_type (token::NUMBER, v, l); 1084 } 1085 #endif 比較麻煩的是本來可以 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」提到了這個, 有興趣的朋友自己看, 就不說明了。 hoc_cpp_1.yy 1 %language "c++" 2 %require "3.2" 3 %debug 4 %define api.value.type variant 5 %define parse.assert 6 %locations 7 8 %code requires // *.hh 9 { 10 #include <string> 11 #include <vector> 12 typedef std::vector<std::string> strings_type; 13 14 #include "hoc_cpp_1.tab.hh" 15 } 16 17 %code // *.cc 18 { 19 #include <iostream> 20 #include <sstream> 21 22 namespace yy 23 { 24 int yylex (yy::parser::value_type *yylval, yy::parser::location_type *yylloc); 25 26 // Print a vector of strings. 27 std::ostream& 28 operator<< (std::ostream& o, const strings_type& ss) 29 { 30 o << '{'; 31 const char *sep = ""; 32 for (strings_type::const_iterator i = ss.begin (), end = ss.end (); 33 i != end; ++i) 34 { 35 o << sep << *i; 36 sep = ", "; 37 } 38 return o << '}'; 39 } 40 } 41 42 // Convert to string. 43 template <typename T> 44 std::string 45 to_string (const T& t) 46 { 47 std::ostringstream o; 48 o << t; 49 return o.str (); 50 } 51 } 52 53 %token <int> NUMBER; 54 %token END_OF_FILE 0; 55 %token 56 ASSIGN ":=" 57 MINUS "-" 58 PLUS "+" 59 STAR "*" 60 SLASH "/" 61 LPAREN "(" 62 RPAREN ")" 63 NEWLINE "\n" 64 ; 65 66 %type <int> list; 67 %type <int> expr; 68 69 %left "+" "-" 70 %left "*" "/" 71 72 %% 73 74 list: {printf("\taaempty\n");} 75 | list "\n" {printf("list \\n\n");} 76 | list expr "\n" { printf("%d\n", $2); } 77 78 expr: NUMBER {$$ = $1; printf("xx num %d\n", $1);} 79 | expr "+" expr {$$ = $1 + $3;} 80 | expr "-" expr {$$ = $1 - $3;} 81 | expr "*" expr {$$ = $1 * $3;} 82 | expr "/" expr {$$ = $1 / $3;} 83 | '(' expr ')' 84 85 86 %% 87 88 char *progname; 89 int lineno = 1; 90 91 namespace yy 92 { 93 // Use nullptr with pre-C++11. 94 #if !defined __cplusplus || __cplusplus < 201103L 95 # define NULLPTR 0 96 #else 97 # define NULLPTR nullptr 98 #endif 99 100 // The yylex function providing subsequent tokens: 101 // TEXT "I have three numbers for you." 102 // NUMBER 1 103 // NUMBER 2 104 // NUMBER 3 105 // TEXT "And that's all!" 106 // END_OF_FILE 107 108 int yylex (yy::parser::value_type *yylval, yy::parser::location_type *yylloc) 109 { 110 int c; 111 int input_val; 112 static int count = 0; 113 const int stage = count; 114 ++count; 115 //parser::location_type loc (NULLPTR, stage + 1, stage + 1); 116 117 while ((c=getchar()) == ' ' || c == '\t') 118 ; 119 120 if (c == EOF) 121 { 122 ;//return parser::make_END_OF_FILE (loc); 123 return yy::parser::token::END_OF_FILE; 124 } 125 if (c == '.' || isdigit(c) ) 126 { 127 ungetc(c, stdin); 128 //scanf("%lf", &input_val); 129 scanf("%d", &input_val); 130 //scanf("%d", yyla->value); 131 //val = 5; 132 ;//return parser::make_NUMBER (input_val, loc); 133 yylval->emplace<int>() = input_val; 134 return yy::parser::token::NUMBER; 135 } 136 137 switch (c) 138 { 139 case '+': 140 { 141 ;//return parser::make_PLUS(loc); 142 return yy::parser::token::PLUS; 143 break; 144 } 145 case '-': 146 { 147 ;//return parser::make_MINUS(loc); 148 return yy::parser::token::MINUS; 149 break; 150 } 151 } 152 153 if (c == '\n') 154 { 155 ++lineno; 156 ;//return parser::make_NEWLINE(loc); 157 return yy::parser::token::NEWLINE; 158 } 159 yylval->emplace<int>() = c; 160 //return yy::parser::token::NUMBER; 161 return c; 162 //return parser::make_NUMBER (c, loc); 163 164 #if 0 165 static int count = 0; 166 const int stage = count; 167 ++count; 168 parser::location_type loc (NULLPTR, stage + 1, stage + 1); 169 switch (stage) 170 { 171 case 0: 172 return parser::make_TEXT ("I have three numbers for you.", loc); 173 case 1: 174 case 2: 175 case 3: 176 return parser::make_NUMBER (stage, loc); 177 case 4: 178 return parser::make_TEXT ("And that's all!", loc); 179 default: 180 return parser::make_END_OF_FILE (loc); 181 } 182 #endif 183 } 184 185 // Mandatory error function 186 void parser::error (const parser::location_type& loc, const std::string& msg) 187 { 188 std::cerr << loc << ": " << msg << '\n'; 189 } 190 } 191 192 int main () 193 { 194 yy::parser p; 195 p.set_debug_level (!!getenv ("YYDEBUG")); 196 return p.parse (); 197 } 198 199 // Local Variables: 200 // mode: C++ 201 // End: 編譯指令: bison -d hoc_cpp_1.yy g++ hoc_cpp_1.tab.cc -o hoc_cpp_1 ref: ‧ 文本解析工具使用总结 (覺得不太正確) ‧ Flex and Bison in C++ blog 版本 https://descent-incoming.blogspot.com/2022/02/bison-c.html -- 紙上得來終覺淺,絕知此事要躬行。 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 1.200.148.76 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/CompilerDev/M.1645523600.A.B51.html
mshockwave: 居然會生出c++20的parser XDD 02/23 13:10
ketrobo: 想玩純C++可以用Boost Spirit 02/24 21:53