我正在尝试学习如何构建基于C ++的Lexer / Parser,我在线学习了多个教程和指南,我的代码主要基于这两个指南:1, 2虽然我按照我认为合适的方式添加和调整了。
首先,我有一个词法分析器的头文件:
#ifndef SHELL_VARIABLELEXER_HPP
#define SHELL_VARIABLELEXER_HPP
#if ! defined(yyFlexLexerOnce)
#include <FlexLexer.h>
#endif
#include "ShellVariableParser.hpp"
#include "location.hh"
namespace SHELL
{
/// This overloads the yyFlexLexer class
class ShellVariableLexer : public yyFlexLexer
{
private:
/// a yylval pointer
ShellVariableParser::semantic_type *yylval = nullptr;
/// a ocation pointer
ShellVariableParser::location_type *loc = nullptr;
public:
/// This constructor only creates the location type.
ShellVariableLexer(std::istream *in) : yyFlexLexer(in)
{
loc = new ShellVariableParser::location_type();
}
using FlexLexer::yylex;
/// A declaration for the yylex function, errors if not here!
virtual ShellVariableParser::symbol_type yylex(ShellVariableParser::semantic_type* const lval, ShellVariableParser::location_type* location);
};
}
#endif // ShellVariableLEXER_HPP
然后我定义了我的.l文件,它通过flex和g ++构建得很好:
%{
#include <string>
#include "Shell/ShellVariableLexer.hpp"
#undef YY_DECL
#define YY_DECL SHELL::ShellVariableParser::symbol_type SHELL::ShellVariableLexer::yylex(SHELL::ShellVariableParser::semantic_type * const lval, SHELL::ShellVariableParser::location_type *location)
#define yyterminate() return SHELL::ShellVariableParser::make_END(*loc)
#define YY_USER_ACTION loc->step(); loc->columns(yyleng);
%}
%option yylineno
%option yyclass="SHELL::ShellVariableLexer"
%option outfile="src/Shell/ShellVariableLexer.cpp"
%option noyywrap
%option c++
%%
%{
yylval = lval;
%}
[0-9]+\.[0-9]+ {
std::string Tmp(yytext, yyleng);
return SHELL::ShellVariableParser::make_FLOAT(std::stod(Tmp, NULL), *loc);
}
[0-9]+ {
std::string Tmp(yytext, yyleng);
return SHELL::ShellVariableParser::make_INTEGER(std::stoi(Tmp, NULL), *loc);
}
"$" return SHELL::ShellVariableParser::make_DOLLARSIGN(*loc);
"{" return SHELL::ShellVariableParser::make_LBRACE(*loc);
"}" return SHELL::ShellVariableParser::make_RBRACE(*loc);
"+" return SHELL::ShellVariableParser::make_PLUS(*loc);
"-" return SHELL::ShellVariableParser::make_MINUS(*loc);
"*" return SHELL::ShellVariableParser::make_MULTIPLY(*loc);
"/" return SHELL::ShellVariableParser::make_DIVIDE(*loc);
"(" return SHELL::ShellVariableParser::make_LPARAN(*loc);
")" return SHELL::ShellVariableParser::make_RPARAN(*loc);
"\"" return SHELL::ShellVariableParser::make_DQUOTE(*loc);
(?i:ON) return SHELL::ShellVariableParser::make_ON(*loc);
(?i:OFF) return SHELL::ShellVariableParser::make_OFF(*loc);
(?i:TRUE) return SHELL::ShellVariableParser::make_TRUE(*loc);
(?i:FALSE) return SHELL::ShellVariableParser::make_FALSE(*loc);
[ \t]
[a-zA-Z_][0-9a-zA-Z_]+ {
std::string Tmp(yytext, yyleng);
return SHELL::ShellVariableParser::make_VARIABLE(Tmp, *loc);
}
[0-9a-zA-Z_ :/\t]+ {
std::string Tmp(yytext, yyleng);
return SHELL::ShellVariableParser::make_STRING(Tmp, *loc);
}
%%
然后我创建了我的.y文件,该文件通过bison OK,但是在使用g ++编译时失败,我不会在这里显示整个文件,只是第一部分,规则与我的错误无关:
%defines "include/Shell/ShellVariableParser.hpp"
%skeleton "lalr1.cc"
%define api.namespace {SHELL}
%define parser_class_name {ShellVariableParser}
%output "src/Shell/ShellVariableParser.cpp"
%define api.value.type variant
%define api.token.constructor
%define parse.assert
// %lex-param {semantic_type* const yylval}
// %lex-param {location_type* location}
%code requires
{
#include <utility>
#include <string>
#include <vector>
#include "ShellVariables.hpp"
#include "Util/IO.hpp"
namespace SHELL
{
class ShellVariableLexer;
}
// The following definitions is missing when %locations isn't used
# ifndef YY_NULLPTR
# if defined __cplusplus && 201103L <= __cplusplus
# define YY_NULLPTR nullptr
# else
# define YY_NULLPTR 0
# endif
# endif
}
%parse-param {ShellVariableLexer &lexer}
%define parse.trace
%define parse.error verbose
%code
{
#include "ShellVariableLexer.hpp"
#undef yylex
#define yylex lexer.yylex
}
%token<double> FLOAT
%token<int64_t> INTEGER
%token<std::string> STRING
%token<std::string> VARIABLE
%token DOLLARSIGN
%token LBRACE
%token RBRACE
%token PLUS
%token MINUS
%token MULTIPLY
%token DIVIDE
%token LPARAN
%token RPARAN
%token DQUOTE
%token ON
%token OFF
%token TRUE
%token FALSE
%token END
%locations
%type <std::pair<uint8_t,VariableType>> All;
%type <std::pair<uint8_t,VariableType>> Bool;
%type <std::pair<uint8_t,VariableType>> Integer;
%type <std::pair<uint8_t,VariableType>> Float;
%type <std::pair<uint8_t,VariableType>> String;
%type <std::string> __string__;
%type <VariableType> variable;
%%
当我编译它时,我收到以下错误:
src/Shell/ShellVariableParser.cpp: In member function ‘virtual int SHELL::ShellVariableParser::parse()’:
src/Shell/ShellVariableParser.cpp:494:46: error: no matching function for call to ‘SHELL::ShellVariableParser::basic_symbol<SHELL::ShellVariableParser::by_type>::basic_symbol(int)’
symbol_type yylookahead (yylex ());
根据这个link我预计bison生成的yylex函数与lexer头文件中定义的yylex函数相同。但这种情况并非如此。如果我尝试通过取消注释.y文件中的两个%lex-param
行来手动添加这些参数,我会收到以下错误:
src/Shell/ShellVariableParser.cpp: In member function ‘virtual int SHELL::ShellVariableParser::parse()’:
src/Shell/ShellVariableParser.cpp:494:45: error: ‘yylval’ was not declared in this scope
symbol_type yylookahead (yylex (yylval, location));
我猜这可能是正确的方法,除了变量名yylval
和location
?或者我错过了其他一些选择?
我使用gcc版本6.2.0和Ubuntu GLIBC 2.24-3ubuntu2,flex版本2.6.1和bison版本3.0.4。
我仍然不知道造成这种情况的原因是什么,我尝试在.l文件中转换yylex
以返回整数,而不是make_
我返回了实际的代币:
%{
#include <string>
#include "Shell/ShellVariableLexer.hpp"
#undef YY_DECL
#define YY_DECL int SHELL::ShellVariableLexer::yylex(SHELL::ShellVariableParser::semantic_type * const lval, SHELL::ShellVariableParser::location_type *location)
#define yyterminate() return SHELL::ShellVariableParser::token::END
#define YY_USER_ACTION loc->step(); loc->columns(yyleng);
%}
%option yylineno
%option yyclass="SHELL::ShellVariableLexer"
%option outfile="src/Shell/ShellVariableLexer.cpp"
%option noyywrap
%option c++
%%
%{
yylval = lval;
%}
[0-9]+\.[0-9]+ {
std::string Tmp(yytext, yyleng);
yylval->build<double>(std::stod(Tmp));
return SHELL::ShellVariableParser::token::FLOAT;
}
[0-9]+ {
std::string Tmp(yytext, yyleng);
yylval->build<int64_t>(std::stoi(Tmp));
return SHELL::ShellVariableParser::token::INTEGER;
}
"$" return SHELL::ShellVariableParser::token::DOLLARSIGN;
"{" return SHELL::ShellVariableParser::token::LBRACE;
"}" return SHELL::ShellVariableParser::token::RBRACE;
"+" return SHELL::ShellVariableParser::token::PLUS;
"-" return SHELL::ShellVariableParser::token::MINUS;
"*" return SHELL::ShellVariableParser::token::MULTIPLY;
"/" return SHELL::ShellVariableParser::token::DIVIDE;
"(" return SHELL::ShellVariableParser::token::LPARAN;
")" return SHELL::ShellVariableParser::token::RPARAN;
"\"" return SHELL::ShellVariableParser::token::DQUOTE;
(?i:ON) return SHELL::ShellVariableParser::token::ON;
(?i:OFF) return SHELL::ShellVariableParser::token::OFF;
(?i:TRUE) return SHELL::ShellVariableParser::token::TRUE;
(?i:FALSE) return SHELL::ShellVariableParser::token::FALSE;
[ \t]
[a-zA-Z_][0-9a-zA-Z_]+ {
yylval->build<std::string>(yytext);
return SHELL::ShellVariableParser::token::VARIABLE;
}
[0-9a-zA-Z_ :/\t]+ {
yylval->build<std::string>(yytext);
return SHELL::ShellVariableParser::token::STRING;
}
%%
在bison文件中我刚删除了%define api.token.constructor
行和两个%lex-param
行。它工作正常。
所以基本上我所做的就是改变yylex的返回类型,而不是它的参数!为什么这个有效但第一个实现不起作用?为什么return int
有效但return symbol_type
没有?!
答案 0 :(得分:2)
您误读了C ++错误消息。编译器没有抱怨yylex
的参数。错误消息(为了便于阅读而强调SHELL::ShellVariableParser
)说:
error: no matching function for call to
‘SH...er::basic_symbol<SH...er::by_type>::basic_symbol(int)’
symbol_type yylookahead (yylex ());
事实上,symbol_type
是SHELL::ShellVariableParser::basic_symbol<SHELL::ShellVariableParser::by_type>
的类型别名,因此更具可读性的渲染
error: no matching function for call to ’symbol_type::symbol_type(int)’
换句话说,yylex()
会返回int
,但由于symbol_type
没有构造函数需要int
,因此yylookahead
的声明无效。
问题是yylex
的返回类型,您可能认为它是symbol_type
。
不幸的是,(在我看来笨重的)flex C ++界面正在对你不利。在基类FlexLexer
中,yylex()
被声明为返回int
的虚函数。由于您的ShellVariableLexer派生自FlexLexer
,因此您无法将yylex()
定义为返回其他类型。
当然,您可以添加一个不必要的参数,这或多或少是您对ShellVariableParser::yylex
的声明所做的:
virtual ShellVariableParser::symbol_type yylex(ShellVariableParser::semantic_type* const lval, ShellVariableParser::location_type* location);
但由于参数只是为了避免将方法与基类中声明的方法混淆,因此可以使用更简单的方法,例如:
ShellVariableParser::symbol_type yylex(int);
然后安排它成为使用
调用的版本#define yylex() lexer.yylex(0)
(并没有多少声明它是虚拟的。基类没有类似的原型,所以将它声明为虚拟的唯一理由就是如果你要继承ShellVariableParser
。)