作者descent (「雄辩是银,沉默是金」)
看板CompilerDev
标题[分享] bison and c++
时间Tue Feb 22 17:53:08 2022
「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://webptt.com/cn.aspx?n=bbs/CompilerDev/M.1645523600.A.B51.html
1F:推 mshockwave: 居然会生出c++20的parser XDD 02/23 13:10
2F:→ ketrobo: 想玩纯C++可以用Boost Spirit 02/24 21:53