我正在尝试学习如何构建基于C++的Lexer / Parser,我在网上关注了多个教程和指南,我的代码主要是基于这两个指南构建的:12,尽管我根据自己的需要进行了添加和调整。

首先,我有一个词法分析器的头文件:

#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头文件中定义的函数相同。但这种情况并非如此。如果我尝试通过从.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));

我猜想这可能是正确的方法,除了变量名yylvallocation吗?还是我缺少其他选择?

我正在将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;
                        }
%%

在野牛文件中,我刚刚删除了%define api.token.constructor行和两条%lex-param行。它工作正常。

因此,基本上我所做的就是更改yylex的返回类型,而不是其参数!为什么这在起作用,但是第一个实现却没有呢?为什么return int有效,但return symbol_type不起作用?

最佳答案

您误读了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_typeSHELL::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++接口(interface)对您不利。在基类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)

(将其声明为虚拟的没有多大意义。基类没有类似的原型(prototype),因此将其声明为虚拟的唯一原因是如果要继承ShellVariableParser。)

关于c++ - 野牛没有使用正确的yylex参数,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44471372/

10-11 17:06