【发布时间】:2021-11-24 09:11:20
【问题描述】:
我正在尝试使用 bison 编写可重入 C++ 解析器,我遵循 Complete C++ Example 并且正在使用 bison 3.5.1 和 flex 2.6.4(通过 apt-get 在 ubuntu buster 中提供的版本)。
完成后,我在词法分析器中遇到了问题
‘make_YYEOF’ is not a member of ‘yy::parser’
我的解决方案是声明%token YYEOF
然后它会编译,但解析器会给出syntax error, unexpected YYEOF, expecting $end,以获得明显有效的输入。
然后我制定了简化规则unit: IDENTIFIER YYEOF,但解析器又报告了同样的错误。
/*
The grammar file parser.yy starts by asking for the C++ deterministic
parser skeleton, the creation of the parser header file.
Because the C++ skeleton changed several times, it is safer to require
the version you designed the grammar for.
*/
// %skeleton "lalr1.cc" // -*- C++ -*-
%require "3.0"
%language "c++"
// %header
/* Because our scanner returns only genuine tokens and never simple characters
(e.g., it returns ‘PLUS’, not ‘'+'’), we can avoid conversions. */
%define api.token.raw
/* This example uses genuine C++ objects as semantic values, therefore,
we require the variant-based storage of semantic values. To make sure
we properly use it, we enable assertions. To fully benefit from type-safety
and more natural definition of “symbol”, we enable api.token.constructor. */
%define api.token.constructor
%define api.value.type variant
%define parse.assert
/* Then come the declarations/inclusions needed by the semantic values.
Because the parser uses the parsing driver and reciprocally, both would
like to include the header of the other, which is, of course, insane.
This mutual dependency will be broken using forward declarations.
Because the driver’s header needs detailed knowledge about the parser class
(in particular its inner types), it is the parser’s header which will use
a forward declaration of the driver.
See https://www.gnu.org/software/bison/manual/html_node/_0025code-Summary.html.
Code qualifies for C/C++: requires, provides, top.
Unqualified code will be equivalent to %{ %} for most purposes */
%code requires {
# include <string>
class driver;
}
/* The driver is passed by reference to the parser and to the scanner.
This provides a simple but effective pure interface, not relying on global variables.
*/
%param { driver& drv }
%locations
/* Use the following two directives to enable parser tracing and detailed error messages.
However, detailed error messages can contain incorrect information if lookahead correction
is not enabled (see LAC https://www.gnu.org/software/bison/manual/html_node/LAC.html). */
%define parse.trace
%define parse.error verbose
%define parse.lac full // none or full
/** The code between ‘%code {’ and ‘}’ is output in the *.cc file;
it needs detailed knowledge about the driver. */
%code {
# include "driver.hh"
}
/* User friendly names are provided for each symbol.
To avoid name clashes in the generated files (see Calc++ Scanner),
prefix tokens with TOK_ */
%define api.token.prefix {TOK_}
%token YYEOF;
/* Since we use variant-based semantic values, %union is not used,
and %token, %nterm and %type expect genuine types, not type tags. */
%token <std::string> IDENTIFIER "identifier"
/* No %destructor is needed to enable memory deallocation during error recovery;
the memory, for strings for instance, will be reclaimed by the regular destructors.
All the values are printed using their operator<< (see Printing Semantic Values
https://www.gnu.org/software/bison/manual/html_node/Printer-Decl.html).
*/
%printer { yyo << $$; } <*>;
/* The grammar itself is straightforward (see Location Tracking Calculator: ltcalc
https://www.gnu.org/software/bison/manual/html_node/Location-Tracking-Calc.html). */
%%
%start unit;
unit: IDENTIFIER YYEOF // assignments exp { drv.result = $2; };
%%
/* Finally the error member function reports the errors. */
void
yy::parser::error (const location_type& l, const std::string& m)
{
std::cerr << l << ": " << m << '\n';
}
driver.cc
#include "driver.hh"
#include "parser.hh"
driver::driver ()
: trace_parsing (false), trace_scanning (false){}
int driver::parse (const std::string &f)
{
file = f;
location.initialize (&file);
scan_begin ();
yy::parser parse (*this);
parse.set_debug_level (trace_parsing);
int res = parse ();
scan_end ();
return res;
}
司机.hh
/*
To support a pure interface with the parser (and the scanner) the technique of the
"parsing context" is convenient: a structure containing all the data to exchange.
Since, in addition to simply launch the parsing, there are several auxiliary tasks
to execute (open the file for scanning, instantiate the parser etc.), we recommend
transforming the simple parsing context structure into a fully blown parsing driver class.
The declaration of this driver class, in driver.hh, is as follows. The first part includes
the CPP guard and imports the required standard library components,
and the declaration of the parser class.
*/
#ifndef DRIVER_HH
# define DRIVER_HH
# include <string>
# include <map>
# include "parser.hh"
/*
Then comes the declaration of the scanning function. Flex expects the signature
of yylex to be defined in the macro YY_DECL, and the C++ parser expects it to be
declared. We can factor both as follows.
*/
// Give Flex the prototype of yylex we want ...
# define YY_DECL \
yy::parser::symbol_type yylex (driver& drv)
// ... and declare it for the parser's sake.
YY_DECL;
/* The driver class is then declared with its most obvious members. */
// Conducting the whole scanning and parsing of Calc++.
class driver
{
public:
driver ();
std::map<std::string, int> variables;
int result;
// Run the parser on file F. Return 0 on success.
int parse (const std::string& f);
// The name of the file being parsed.
std::string file;
// Whether to generate parser debug traces.
bool trace_parsing;
// Handling the scanner.
void scan_begin ();
void scan_end ();
// Whether to generate scanner debug traces.
bool trace_scanning;
// The token's location used by the scanner.
yy::location location;
};
#endif // ! DRIVER_HH
id.cc
#include <iostream>
#include "driver.hh"
int main (int argc, char *argv[])
{
int res = 0;
driver drv;
for (int i = 1; i < argc; ++i)
if (argv[i] == std::string ("-p"))
drv.trace_parsing = true;
else if (argv[i] == std::string ("-s"))
drv.trace_scanning = true;
else if (!drv.parse (argv[i]))
std::cout << drv.result << '\n';
else
res = 1;
return res;
}
bison -d parser.yy -o parser.cc
flex -o lex.yy.cc lexer.l
g++ -std=c++11 *.cc -o parser
echo b | ./parser -p -s -
这给了我
Starting parse
Entering state 0
Reading a token: --(end of buffer or a NUL)
--accepting rule at line 66 ("b")
Next token is token "identifier" (-:1.1: b)
Shifting token "identifier" (-:1.1: b)
Entering state 1
Reading a token: --(end of buffer or a NUL)
--accepting rule at line 65 ("
")
--(end of buffer or a NUL)
--EOF (start condition 0)
Next token is token YYEOF (-:2.1: )
Shifting token YYEOF (-:2.1: )
Entering state 3
Reducing stack by rule 1 (line 86):
$1 = token "identifier" (-:1.1: b)
$2 = token YYEOF (-:2.1: )
-> $$ = nterm unit (-:1.1-2.0: )
Stack now 0
Entering state 2
Reading a token: --(end of buffer or a NUL)
--EOF (start condition 0)
Next token is token YYEOF (-:2.1: )
LAC: initial context established for YYEOF
LAC: checking lookahead YYEOF: Err
LAC: checking lookahead $end: S4
LAC: checking lookahead YYEOF: Err
LAC: checking lookahead "identifier": Err
-:2.1: syntax error, unexpected YYEOF, expecting $end
Error: popping nterm unit (-:1.1-2.0: )
Stack now 0
Cleanup: discarding lookahead token YYEOF (-:2.1: )
为了优雅地处理输入的结尾,我必须进行哪些更改?
谢谢
【问题讨论】:
-
@rici 我没有提及我的更改,因为这会分散注意力。你可以简单地专注于目标,而不是我的方法。但当然,如果您仍然喜欢,我可以创建一个可重现的最小示例。
-
这可能是问题的一部分,第一个错误 YYSTYPE 没有定义类型。 YYLTYPE 也是一样,然后我们就会看到 g++ 的错误消息泛滥
-
如果你没有显示产生错误的代码以及错误本身,那么问题就太宽泛了,因为它属于“请给我一个关于如何做 X 的教程”
-
点了@bolov,我准备点东西回来。