野牛使用%define api.pure完全不使用%union,而是使用C ++变体 注意:

问题描述

我有一个手写的扫描仪和一个野牛解析器,可以解析此句子(简称为问题上下文):

var x : integer

野牛:

%require "3.2"
%define api.pure full


%code{
#include <stdio.h>
#include <string.h>
#include "Scanner.h"
#include<iostream>
}



%code{
    int yylex(YYSTYPE *lvalp);
    #include<iostream>
    #include<string>
    Scanner scanner;
    void yyerror(const char *error);
}


%union {
int n;
double d;
char s[1000];
}

%token VAR COL ITYPE
%token IDENTIFIER
%token INTEGER
%token EOL

%type <s> type PrimitiveType IDENTIFIER
%type <s> INTEGER    

%%
program:
| program EOL
| program SimpleDeclaration {  }
;

SimpleDeclaration: VariableDeclaration
;

VariableDeclaration: VAR IDENTIFIER COL type {std::cout<<"defined variable " << $2 << " with type " << $4 << std::endl; }

type: IDENTIFIER
| PrimitiveType
;

PrimitiveType: ITYPE { strcpy($$,"int"); }
;

%%
int main()
{
    scanner.set_file("inp.txt");
    return yyparse();
}

void yyerror(const char *error)
{
    std::cout << "Syntax error" << std::endl;
}

int yylex(YYSTYPE *lvalp)
{
    return scanner.get_next_token(lvalp);
}
例如,

scanner.get_next_token(lvalp)返回一个令牌INTEGER(包含在scanner.cpp中的parser.tab.hpp中,并利用这些令牌生成的枚举)。另外,在此之前,它将正确的值放在lvalp中,例如strcpy(lvalp->s,nextTokenString.c_str())lvalp->n = toInt(nextTokenString)等。 输出为:

defined variable x with type int

但是我想使用STL容器和智能指针。在this page about pure calling中,如果您的令牌不是同一类型,则不会告诉您如何使用lvalP*而不使用联合。另外,根据this page,除了%language "c++"之外,我还应该放置%define api.value.type variant来使用接受语义类型而不是union的C ++变体。好吧,这导致以下错误

parser.ypp:3.1-21: error: %define variable 'api.pure' is not used

因此,我想在将正确的令牌返回到解析器且不使用联合的情况下分配值,以便可以使用所有C ++功能

注意:我看到了this example,但我仍然不明白函数make_Number是否已经存在或已生成?如何在我的next_token()中为属于已定义的%token的$变量添加值?

谢谢。

解决方法

#define api.pure仅适用于使用C API生成的解析器。如果您要求野牛产生C ++解析器,则不需要该声明,因为it's unnecessary

解析器通过调用yylex来调用扫描器。与C解析器相反,C ++解析器始终是纯净的:使用%define api.pure指令毫无意义。

但是C ++ API与C API有很大不同。如果要使用它们,您确实需要阅读整个手册章节(阅读时请参考示例)。

请注意,Bison创建的变量类型与std::variant完全不同,因此它可能不是您想要的。与std::variant不同,Bison的变量不存储变量值的当前类型,因为解析器始终知道堆栈值的类型。解析时很好,但是这会使变体作为导出值的用处不大。 (以及在其他应用程序中也是如此。)但是,如果您想使用诸如std::string之类的非琐碎类型,它们会有所帮助,因为Bison可以确保正确调用析构函数。 [注1]

如果您要使用智能指针,可能会发现自己向std::move撒了很多电话,以避免复制不可复制的对象。 (在分析过程中,经常重复复制Bison堆栈上的对象。)您还需要使用std::move来避免过多地复制字符串。您可以要求Bison在每次访问时自动插入对std::move的调用,以获取语义值,但是如果启用此选项,则需要注意仅使用每个语义值一次。 (手册中有一个示例。)

一旦决定使用Bison的C ++ API,就需要在词汇扫描器的两种调用约定之间进行选择。一种选择是手册称为"split symbols",它只是传统的C方法(已使用C pure API进行了修改):词法分析器返回一个整数(令牌类型)并将语义值放入指向的STYPE中成为论点。如果STYPE是Bison变体,则需要使用emplace方法就地构造一个值(因此避免复制)。

Bison手册的链接页面中有示例。有两个使用emplace的例子有点让人困惑。我的理解是第二个示例(其中emplace使用构造函数参数)可用于C ++ 11或更高版本,这些天应该是相当通用的(IMHO)。

或者,您可以使用“完整符号”,在更详细的示例中有更多描述。如果您告诉Bison使用“完整符号” API(带有%define api.token.constructor声明),则Bison将自动生成各种make_XXX函数。要使用这些功能,您必须更改扫描仪的get_next_token成员函数才能返回symbol_type对象而不是int(然后它不需要yylvalp论据)。这可能是您要进行的较大更改。


注意:

  1. 您可以使用std::variant或带有api.value.type的明确定义的Boost等效项,但不会产生make_*调用,而且Bison也不知道如何提取单个类型,因此整个%type机制将无法正常工作,因此吸引力不大。