[英]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 %s\n", yytext);
數學.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
東西。 你稱它們為“語義值”。 並不真地。 它們是一種通過解析樹將值傳回的機制,也是詞法分析器將標記值關聯起來以提供給解析器的機制。 你總是得到零的原因是你從來沒有在詞法分析器中為它分配任何值。 該值是通過引用內置變量yylval
來分配的。 那是在文檔中。 讓我們拿你的數字e_num
返回一個e_num
標記。 我可以這樣寫(在math.l
):
{number} {yylval = atoi(yytext);return(e_num);}
這允許我們現在打印數字的值:
Factor : e_num { std::cout << $1 << std::endl; }
如果您使用枚舉常量的名稱而不是那些絕對值,它看起來會好得多。 將這些數字硬連接起來的編碼非常糟糕。
我還看到你已經創建了你的堆棧並在詞法分析器中推送了一些東西。 可能不是一個好的計划,但現在讓我們繼續吧。 如果需要,您可以將令牌與這樣的對象值相關聯。 %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
形式的構造的地方,但我會讓你閱讀它。
接下來要提到的是您描述算術表達式語法的方式。 有兩個問題,主要是否定句的處理和語法形式。
這里我們需要一些計算機科學。 您已(主要)將表達式表示為運算符和操作數列表,這被視為正則語言或Chomsky Type 3形式。 問題是算術表達式主要是嵌套在結構中。 嵌套需要Context Free Grammar或Chomsky Type 2 。 這就是為什么所有計算器語法示例都使用以下形式的原因:
exp : exp OP exp
而不是您使用的列表表格。
現在您已經使用規則層次結構來獲得語法中運算符的一些優先級,但不幸的是,一元減(或否定)運算符的優先級高於二元減法運算符,因此應該出現在Factor
規則中。 這就是為什么您會遇到如此多的 shift/reduce 沖突的原因。 這不是正確的方法。 所有教科書都以與您的示例不同的方式進行計算器和表達式,這是有原因的。 你需要回到課本上。
這就是人們在大學學習這些東西的原因。 我希望對未來正在研究類似問題的一些讀者有所幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.