我试图重现cout如何使用flex和bison工作。例如:
cout << "hello world";
将打印:
hello world
当只有一个参数时,它工作得很好,但是一旦我放了多个变量,我就会遇到麻烦。
cout << "I like " << "bananas" << endl;
将打印
\nbananasI like
而不是
I like bananas\n
我猜它是因为它在打印之前解析整行,所以它开始于&#34; endl&#34;那么&#34;香蕉&#34;然后&#34;我喜欢&#34;但我不知道怎么扭转它。我尝试在多个设置中重新排列我的令牌以反转优先级,如乘法和加法,但到目前为止没有成功。
这些是myfile.y
的有用部分%union {
char* string;
}
%token <string> STRING
%token ENDL
%token COUT
%token INSERT
%%
displayBegin :
COUT displayContent
;
displayContent :
';'
| INSERT displayEnd
| INSERT STRING displayContent { printf("%s",$2); }
;
displayEnd :
ENDL ';' { printf("\n"); }
;
%%
myfile.l
相同"cout" { return COUT; }
"endl" { return ENDL; }
"<<" { return INSERT; }
[\"][^\"]+[\"] { char* tmp = strdup(yytext); tmp++; tmp[strlen(tmp)-1] = '\0'; yylval.string = tmp; return STRING; }
我编辑了myfile.y,如下所示,但是由于它是双递归的,所以它并不那么好(在编译时显示警告):
displayBegin :
COUT INSERT displayContent
;
displayContent :
';'
| displayEnd
| STRING { printf("%s",$1); }
| displayContent INSERT displayContent
;
答案 0 :(得分:1)
在C ++中,<<
运算符是左关联,这意味着a << b << c
与((a << b) << c)
相同。如果它是右关联的(如赋值运算符),则分组将嵌套在另一个方向:(a << (b << c))
。
虽然看起来它纯粹是语义的,但它也反映了字符串的解析方式。特别是,必须首先减少最里面的表达式,因为减少的结果是下一个最内层表达式中下一个最内层非终端的参数之一。如果评估是通过减少行动进行的,则评估顺序将由减少顺序确定。
简而言之,如果您想立即进行评估,并且希望评估<<
是从左到右,则需要确保将<<
解析为左关联。
一般而言,左关联运算符与左递归生成相关联,而右关联运算符与右递归生成相关联。您的原始定义是正确的递归,因此生产
displayContent: INSERT STRING displayContent
要求在displayContent
可以包含在外STRING
之前减少(并因此打印)右侧的displayContent
。换句话说,它评估从右到左,这是不合需要的。 (它也无法真正表示表达式的语义,因为<<
运算符与其左侧操作数没有关联。)
您的第二个定义含糊不清,尽管可以使用优先声明来修复。但是,如果只是为了显示优先级声明的作用,那么以非模糊形式编写语法是很有用的。所以这里是左关联(和左递归)语法:
displayStatement: display ';' ;
display: COUT
| display "<<" STRING { printf("%s",$3); }
| display "<<" ENDL { putchar('\n'); }
;
与原始语法不同,此语法允许endl
的多个实例,并且不要求行以一个结尾。所以它更接近C ++。但它仍然没有完全反映C ++语义,因为它无法显示如何使用<<
的左手运算符。特别是,左手操作符和结果是流;用C表示,这可能是FILE*
。所以,让我们这样做:
%union {
char* string;
FILE* file;
}
%token <string> STRING
%token ENDL "endl"
%token COUT "cout"
%token INSERT "<<"
%type <file> display
%%
displayStatement: display ';' ;
display: "cout" { $$ = stdin; }
| display "<<" STRING { fprintf($1, "%s", $3); $$ = $1; }
| display "<<" "endl" { putc('\n', $1); $$ = $1; }
;
这样可以更容易地将输出能力添加到stderr;所有必要的是将cerr
添加到词法分析器,并将作品添加到display
:&#34; cerr&#34; {$$ = stderr; }`。
最后一点,您正确地在扫描仪中调用strdup
,以便将yytext
的副本传递给解析器。但是你永远不会free()
重复的字符串,所以你最终会泄漏内存。作为第一步,您可能希望在打印后释放字符串,从而进行第二次display
制作动作:
{ fprintf($1, "%s", $3); free($3); $$ = $1; }
另请查看bison的%destructor
声明,了解在语法错误的情况下如何避免内存泄漏。
答案 1 :(得分:0)
递归解析输入。解析完所有内容后,将成功解析规则。因此,在您的情况下,成功解析的第一件事将是displayEnd
,打印\n
,然后汇总到最高级别,然后打印,等等。