在Flex/Bison处理新线路



我正在尝试使用Flex/Bison制作一种类似C的语言。我的问题是我找不到合适的方法来处理新的行。我必须忽略所有新行,这样我就不会把它们作为记号返回给Bison,因为这会让语法规则变得很难制定,但在某些规则中,我被要求强制更改行。例如:

程序";标识符"->强制更改线路

函数";标识符";("参数")->强制更改线路

如果我返回\n作为flex的标记,那么我必须在所有语法规则中添加新行,这肯定不实用。我试着让一个变量像开关一样工作,但它不太管用。有什么帮助或建议吗?

如果所需换行符只是美观的,也就是说,如果为了避免歧义而不需要换行符,那么执行它的最简单方法通常只是跟踪令牌位置(这是bison和flex可以帮助您的),这样您就可以在减少操作中检查两个连续的令牌不在同一行上:

func_defn: "function" IDENT '(' opt_arg_list ')' body "end" {
if (@5.last_line == @6.first_line) {
yyerror("Body of function must start on a new line");
/* YYABORT; */ /* If you want to kill the parse at this point. */
}
// ...
}

Bison不需要任何声明或选项来使用位置;如果它注意到您在任何操作中使用@N(这就是您引用令牌位置的方式),它将插入位置支持。但是,有时插入%locations声明以强制位置支持是有用的。通常情况下,你的语法不需要做其他改变。

为了向解析器报告位置值,您必须在lexer中插入一点代码。位置通过一个名为yylloc的全局变量进行通信,该变量的值为YYLTYPE类型。默认情况下,YYLTYPE是一个包含四个int成员的结构体:first_linefirst_columnlast_linelast_column。(有关更多详细信息,请参阅Bison手册。)需要在lexer中为每个令牌设置这些字段。幸运的是,flex允许您定义宏YY_USER_ACTION,它包含在每个操作(甚至是空操作)之前执行的代码,您可以使用它来填充yylloc。这里有一个将适用于许多简单的词汇分析器;您可以将它放在flex文件顶部的代码块中。

/* Simple YY_USER_ACTION. Will not work if any action includes
* yyless(), yymore(), input() or REJECT.
*/
#define YY_USER_ACTION                                             
yylloc.first_line = yylloc.last_line;                            
yylloc.first_column = yylloc.last_column;                        
if (yylloc.last_line == yylineno)                                
yylloc.last_column += yyleng;                                  
else {                                                           
yylloc.last_line = yylineno;                                   
yylloc.last_column = yytext + yyleng - strrchr(yytext, 'n');  
}

如果上面描述的简单位置检查不足以满足您的用例,那么您可以通过所谓的";词汇反馈":一种解析器不仅从词法扫描器收集信息,而且在需要某种词法更改时还与lexer进行通信的机制。

词汇反馈通常是不鼓励的,因为它可能是脆弱的。记住解析器和扫描程序不一定是同步的,这一点非常重要。解析器通常(但并不总是)需要知道当前生产之后的下一个令牌,因此执行生产的操作时的词法状态可能是下一个标记之后的词法状态,而不是生产中最后一个令牌之后的状态。但它可能不会;包括Bison在内的许多解析器生成器,如果能够发现无论下一个令牌是什么,都将执行相同的操作,则会尝试立即执行该操作。不幸的是,这并不总是可以预测的。例如,在Bison的情况下,将解析算法从默认的LALR(1)更改为规范LR(1。

因此,如果你要尝试与扫描仪进行通信,无论扫描仪是否已经被要求提供前瞻性令牌,你都应该尝试以一种有效的方式进行通信。一种方法是将与扫描仪通信的代码放在中间规则操作中,比要影响的令牌早一个令牌。[注1]

为了使换行符";大部分是可选的";,我们需要告诉lexer何时应该返回换行符,而不是忽略它。一种方法是导出lexer可以调用的函数。我们将该函数的定义放入生成的解析器中,并将其声明放入生成的头文件中:

/* Anything in code requires and code provides sections is also
* copied into the generated header. So we can use it to declare
* exported functions.
*/
%code requires {
#include <stdbool.h>
bool need_nl(void);
}
%%
// ...
/* See [Note 2], below. */
/* Program directive. */
prog_decl: "program" { need_nl_flag = true; } IDENT 'n'
/* Function definition */
func_defn: "function" IDENT
'(' opt_arg_list { need_nl_flag = true; } ')' 'n'
body
"end"
// ...
%%
static bool need_nl_flag = false;
/* The scanner should call this function when it sees a newline.
* If the function returns true, the newline should be returned as a token.
* The function resets the value of the flag, so it must not be called for any
* other purpose. (This interface allows us to set the flag in a parser action
* without having to worry about clearing it later.)
*/
bool need_nl(void) {
bool temp = need_nl_flag;
need_nl_flag = false;
return temp;
}
// ...

然后我们只需要对扫描仪进行一个小的调整就可以调用该函数。这使用Flex的集差运算符{-}来生成一个字符类,该类包含除换行符之外的所有空白。因为我们把这个规则放在第一位,所以第二个规则将只用于包含至少一个换行符的空白。请注意,对于任何一系列的空行,我们只返回一个换行符。

([[:space:]]{-}[n])+  { /* ignore whitespace */ }
[[:space:]]+           { if (need_nl()) return 'n'; }

注释

  1. 这也不是你可以不假思索的事情:过早更改扫描仪配置也可能是一个错误。在操作中,您可以通过查看yychar的值来检查前瞻性令牌是否已经被读取。如果yycharYYEMPTY,则没有读取任何前瞻性令牌。如果是YYEOF,则尝试读取先行令牌,但遇到了输入结束。否则,前瞻性令牌已被读取。

    使用两个操作似乎很诱人,一个在要影响的令牌之前的令牌之前,另一个在该令牌之前。第一个操作只能在yychar不是YYEMPTY的情况下执行,这表明前瞻性令牌已经被读取,并且扫描仪即将读取您想要更改的令牌,而第二个操作只会在此时的yycharYYEMPTY时执行。但对于一个特定的解析,这两个条件都有可能为真,或者两者都不为真。

    Bison确实有一个配置,您可以使用它使前瞻性决策完全可预测。如果您设置了%define lr.default-reduction accepting,那么Bison将始终尝试读取先行符号,并且您可以确保提前一个令牌放置操作将起作用。除非以交互方式使用解析器,否则启用此选项不会产生实际成本。但它不能与旧的Bison版本或其他解析器生成器(如byacc)一起使用。

  2. 对于该语法,我们可以将中间规则操作放在'n'标记之前,而不是提前一个标记(只要解析器从未转换为GLR或规范LR解析器)。这是因为在这两个规则中,MRA将介于两个代币之间,并且(大概)没有其他规则可能适用于这些代币中的第一个。在这种情况下,Bison当然可以知道MRA可以被减少,而无需检查前瞻性令牌来查看它是否是n:要么下一个令牌是换行符并且需要减少,要么下一令牌不是换行符,这将是语法错误。由于Bison不能保证在减少操作运行之前检测到语法错误,因此它可以在知道解析是否成功之前减少MRA操作。

有一种称为尾随上下文的模式,您无法尝试:https://people.cs.aau.dk/~marius/sw/flex/flex-Regular-Expressions.html

"标识符"/[\n]

"函数标识符"/[\n]

最新更新