使用bison将数据解析为结构
Parsing data into structures using bison
整个夏天我在学校学习了flex和bison,现在我想再深入一点。我很难理解Bison 3.0.2的文档。也许你们中的一些人可以帮我。我想解析一个表示方程的字符串,同时填写一个数据结构,其中包含有关解析内容的信息。例如,假设我有(ax+b)^2。我希望解析器生成一个包含字符串和整数常量的结构,如下所示。
( BEGINGROUP
a VARIABLE
x VARIABLE
+ ADDITION
b VARIABLE
) ENDGROUP
我使用flex创建了一个语言规范,并使用bison创建了一种语法。所需要的只是让两者将信息放入结构中。我有一些代码可以按照我想要的方式工作,但我忍不住认为我错过了一些东西。在Bison文档示例中,我看到他们使用$$或$1,他们说这是为了检查语义值?当我打印语义值时,我总是得到零。不管怎样,我的代码发布在下面。
数学.l
%{
#include "equation.h"
Equation* equ;
void setEquation(Equation* equation) {
equ = equation;
}
%}
/* Definitions */
space [ t]
digit [0-9]
letter [a-zA-Z]
number ({digit}+|{digit}+"."{digit}+|"."{digit}+|{digit}+".")
variable {letter}
/* actions */
%%
{space} ;
{number} equ->addElement(yytext, Equation::number); return(1);
{variable} equ->addElement(yytext, Equation::variable); return(2);
"+" equ->addElement(yytext, Equation::addition); return(10); /* Basic operators */
"-" return(11);
"*" return(12);
"/" return(13);
"^" return(14);
"log" return(15);
"sin" return(20); /* Trigonometric Functions */
"cos" return(21);
"tan" return(22);
"csc" return(23);
"sec" return(24);
"cot" return(25);
"arcsin" return(26);
"arccos" return(27);
"arctan" return(28);
"(" equ->addElement(yytext, Equation::begGroup); return(30); /* Grouping Operators */
")" equ->addElement(yytext, Equation::endGroup); return(31);
"[" return(32);
"]" return(33);
"," return(34);
. fprintf(stderr, "Error on character %sn", yytext);
math.y
/*
* Implement grammer for equations
*/
%{
#include "lex.yy.c"
#include "equation.h"
#include <iostream>
int yylex(void);
int yyerror(const char *msg);
void output(const char* where) {
std::cout << where << ": " << yytext << std::endl;
}
%}
%token e_num 1
%token e_var 2
%token e_plus 10
%token e_minus 11
%token e_mult 12
%token e_div 13
%token e_pow 14
%token e_log 15
%token e_sin 20
%token e_cos 21
%token e_tan 22
%token e_csc 23
%token e_sec 24
%token e_cot 25
%token e_asin 26
%token e_acos 27
%token e_atan 28
%token lparen 30
%token rparen 31
%token slparen 32
%token srparen 33
%token comma 34
%start Expression
%%
Expression : Term MoreTerms
| e_minus Term MoreTerms
;
MoreTerms : /* add a term */
e_plus Term MoreTerms
| /* subtract a term */
e_minus Term MoreTerms
| /* add a negetive term */
e_plus e_minus Term MoreTerms /* Add a negetive term */
| /* minus a negetive term */
e_minus e_minus Term MoreTerms /* Subtract a negetive term */
| /* no extra terms */
;
Term : Factor MoreFactors {equ->addElement("*", Equation::multiplication)};
;
MoreFactors: e_mult Factor MoreFactors
| e_div Factor MoreFactors
| Factor MoreFactors
|
;
Factor : e_num { std::cout << $1 << std::endl; } //returns zero no matter where I put this
| e_var
| Group
| Function
;
BeginGroup : lparen | slparen;
EndGroup : rparen | srparen;
Group : BeginGroup Expression EndGroup
;
Function : TrigFuncs
| PowerFunc
;
TrigFuncs : e_sin lparen Expression rparen
| e_cos lparen Expression rparen
| e_tan lparen Expression rparen
| e_csc lparen Expression rparen
| e_sec lparen Expression rparen
| e_cot lparen Expression rparen
| e_asin lparen Expression rparen
| e_acos lparen Expression rparen
| e_atan lparen Expression rparen
;
PowerFunc : e_num e_pow Factor
| e_var e_pow Factor
| Group e_pow Factor
| TrigFuncs e_pow Factor
;
我觉得我在做什么很清楚。正如您所看到的,扫描程序将yytext及其代码存储到等式类中,但有时解析器必须向等式类添加信息,而这正是事情变得繁忙的地方。首先,试图在语句之前或中间添加代码可能会导致大量的移位/减少冲突。其次,将代码放在语句末尾的效果是记录无序的事情。看一下术语规则。如果我键入"ax",这隐含地意味着"a"乘以"x"或"a*x"。我希望解析器在中将乘法添加到我的结构中,但解析器这样做是无序的。那么,有没有更好的方法来实现我的目标呢?
您可能想知道为什么四个月内没有人回答您的问题;当它看起来像是一个简单的计算器问题。这是因为这不是一个简单的问题。这是一个问题的集合,有很多隐蔽的角落和缝隙供不小心的人使用。现在,有相当多的野牛专家在Stack Overflow上提供答案,他们真的很了解自己的东西,他们都像瘟疫一样避免了这种情况。如果你简化了这个问题,一次只问一件事,你可能会得到一些答案,但你只是粘贴了一大堆代码,然后加上了一些"哦,顺便说一句,在做的时候解决其他所有问题!"。然而,如果有人想调试你的代码,他们就不能,因为你错过了一大堆关键的东西,比如Equation
对象。StackOverflow不是一堆免费的程序员,你知道!阅读一些指南。
让我们从一些简单的事情开始;这个$$
和$1
的东西,你开始的。你称之为"语义价值"。不是。它们是一种通过解析树将值传递回的机制,也是词法分析器将令牌值关联给解析器的机制。您总是得到一个零的原因是,您从未在lexer中为其分配任何值。该值是通过引用内置变量yylval
来分配的。这在文档中。让我们取一个返回e_num
令牌的数字lexeme。我可以这样写(在math.l
中):
{number} {yylval = atoi(yytext);return(e_num);}
这允许我们现在打印数字的值:
Factor : e_num { std::cout << $1 << std::endl; }
如果使用枚举常量的名称而不是那些绝对值,看起来会更好。很糟糕的编码把这些数字硬连接起来。
我还看到您已经创建了堆栈,并在lexer中对其进行推送。也许这不是一个好计划,但我们现在就这么做吧。如果需要,可以将令牌与这样的对象值相关联。%union
构建体(野牛)用于:
%union {
Equation* TokenValue;
}
现在你必须开始键入语法的终端和非终端:
%token <TokenValue> e_enum;
但现在,如果你愿意,我们可以使用这些结构来获得价值:
{number} {equ->addElement(yytext, Equation::number);
yylval.TokenValue = equ;
return(e_num);
}
野牛:
Factor : e_num { std::cout << $1->ElementText << std::endl; }
在野牛语法中,有些地方您可能希望将几个值从规则的右手边传递回左手边。这里将使用$$ = $2
形式的构造,但我将留给您阅读。
接下来要提到的是你描述算术表达式语法的方式。有两个问题,主要是由于否定词的处理和语法形式
我们这里需要一些计算机科学知识。您(主要)将表达式表示为运算符和操作数列表,这被视为正则语言或乔姆斯基类型3形式。问题是算术表达式主要嵌套在结构中。嵌套需要上下文无关语法或乔姆斯基类型2。这就是为什么所有计算器语法的例子都使用以下形式:
exp:exp OP exp
而不是您使用的列表形式。
现在,您已经使用规则层次结构来获取语法中运算符的一些优先级,但不幸的是,一元减号(或否定号)运算符的优先级将高于二元减法运算符,因此应该出现在Factor
规则中。这些就是为什么你会得到如此多的转变/减少冲突的原因。这不是正确的方法。所有的课本都以不同于你的例子的方式来做计算器和表达式,这是有原因的。你需要回到课本上去。
这就是为什么人们在大学里学习这些东西。我希望这能帮助一些在未来关注类似问题的读者。
- 如何在C++中序列化结构数据
- 正在转换结构数据的字节序
- C++ - 使用结构数据类型将单词中的单个小写字符更改为大写,反之亦然
- 在结构数据类型中更改每个字符的 ASCII 值
- C++ - 检查结构数据类型中的单词是否为回文
- 如何将结构数据成员传递到函数中
- 在std::线程中使用已分配的结构数据
- 将结构数据存储在循环缓冲区中
- 通过指针算法访问结构数据成员
- 将结构数据类型传递给 C++ 中的命名管道
- CLI/C# 将 std::vector<> 结构数据传递给 C#
- 使用来自 C 的内存地址的 Julia 读取/写入结构数据
- CPP 在读取结构数据时无限循环错误?
- 为什么C++编译器不优化对结构数据成员的读取和写入,而不是不同的局部变量?
- 使用Hazelcast替换共享内存结构数据
- 带有结构数据的 Gmock
- 结构数据D = {0}和结构数据D = {}之间是否存在任何区别
- 有没有办法将数组行为作为静态结构数据成员?
- 使用函数操作结构数据类型的值
- 有关结构数据'getting'的性能