1

我有一个手写扫描仪和一个可以解析这个句子的野牛解析器(使它成为问题上下文的缩写):

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(包括 parser.tab.hppscanner.cpp并使用从令牌生成的枚举)。此外,在此之前,它会将正确的值放入lvalp诸如strcpy(lvalp->s, nextTokenString.c_str())lvalp->n = toInt(nextTokenString)......输出为:

defined variable x with type int

但我想使用 STL 容器和智能指针。在这个关于纯调用的页面中,如果您的令牌类型不同,则没有说明如何在没有lvalp*联合的情况下使用。此外,根据这个页面,我应该使用接受语义类型而不是. 好吧,这会导致以下错误:%language "c++"%define api.value.type variantunion

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

所以我想在将正确的标记返回给解析器而不使用联合的同时分配值,以便我可以使用所有 C++ 功能。

注意:我看到了这个例子,但我还是不明白函数是make_Number已经存在还是已经生成了?如何从我的 next_token() 为属于已定义 %token 的 $ 变量添加值?

提前致谢。

4

1 回答 1

3

#define api.pure仅适用于使用 C API 生成的解析器。如果您要求 bison 生成 C++ 解析器,则不需要该声明,因为它是不必要的

解析器通过调用 yylex 来调用扫描器。与 C 解析器相反,C++ 解析器总是纯粹的:使用%define api.pure指令没有意义。

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

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

如果您打算使用智能指针,您可能会发现自己需要调用 tostd::move以避免复制不可复制的对象。(Bison 堆栈上的对象在解析过程中经常被重复复制。)您还需要使用它std::move来避免过度复制字符串。您可以要求 Bisonstd::move每次访问语义值时自动插入调用,但如果启用此选项,您需要注意每个语义值只使用一次。(手册中有一个例子。)

一旦决定使用 Bison 的 C++ API,就需要在词法扫描器的两个调用约定之间进行选择。一种选择是手册所称的“拆分符号”,这只是传统的 C 方法(使用 C 纯 API 进行修改):词法分析器返回一个整数(令牌类型)并将语义值放在指向的 STYPE 中论点。如果 STYPE 是 Bison 变体,您将希望使用该emplace方法就地构造一个值(从而避免复制)。

Bison 手册的链接页面中有示例。有两个使用示例有点令人困惑emplace;我的理解是第二个示例(其中emplace采用构造函数参数)可用于 C++11 或更新版本,这些天应该非常普遍(恕我直言)。

或者,您可以使用“完整符号”,这些符号描述得更详细,并带有更多示例。如果您告诉 Bison 使用“完整符号”API(带有%define api.token.constructor声明),那么 Bison 将自动生成各种make_XXX函数。要使用这些函数,您必须更改扫描仪的get_next_token成员函数以返回一个symbol_type对象而不是一个int(然后它不需要 yylvalp 参数)。这可能是一个比你想做的更大的改变。


笔记:

  1. 您可以使用std::variant具有显式定义的 Boost 等价物,api.value.type但不会产生make_*调用,而且 Bison 也不知道如何从变体中提取单个类型,因此整个%type机制将无法正常工作,使其不是很吸引人的。
于 2020-09-26T16:55:32.127 回答