当前,我正在尝试实现与 ruby 非常相似的语法。为简单起见,词法分析器当前忽略空格字符。
但是,在某些情况下,空格字母会产生很大的不同:
def some_callback(arg=0)
arg * 100
end
some_callback (1 + 1) + 1 # 300
some_callback(1 + 1) + 1 # 201
some_callback +1 # 100
some_callback+1 # 1
some_callback + 1 # 1
因此,当前所有空白都被词法分析器忽略:
{WHITESPACE} { ; }
例如,该语言说:
UnaryExpression:
PostfixExpression
| T_PLUS UnaryExpression
| T_MINUS UnaryExpression
;
我想解决这个问题的一种方法是在整个语法中显式添加空格,但是这样做会使整个语法的复杂性大大增加:
// OLD:
AdditiveExpression:
MultiplicativeExpression
| AdditiveExpression T_ADD MultiplicativeExpression
| AdditiveExpression T_SUB MultiplicativeExpression
;
// NEW:
_:
/* empty */
| WHITESPACE _;
AdditiveExpression:
MultiplicativeExpression
| AdditiveExpression _ T_ADD _ MultiplicativeExpression
| AdditiveExpression _ T_SUB _ MultiplicativeExpression
;
//...
UnaryExpression:
PostfixExpression
| T_PLUS UnaryExpression
| T_MINUS UnaryExpression
;
所以我想问一下关于如何解决此语法的最佳实践。
提前谢谢!
答案 0 :(得分:2)
如果没有要尝试解析的语法的完整规范,要给出准确的答案并不容易。在下文中,我假设这是两个标记之间空白的存在(或不存在)影响解析的仅有的两个地方。
f(...)
和f (...)
之间的区别以多种语言出现。一种常见的策略是让词法分析器识别一个标识符,该标识符后紧跟一个开放的括号作为“ FUNCTION_CALL”令牌。
例如,在大多数awk
实现中,您会发现它;在awk中,通过要求函数调用中的右括号紧随标识符之后,可以解决函数调用与串联之间的歧义。同样,C预处理程序宏定义指令将#define foo(A) A
(带参数的宏定义)和#define foo (A)
(以(
令牌开始扩展的普通宏)区分开。>
如果使用(f)lex进行此操作,则可以使用/
尾随上下文运算符:
[[:alpha:]_][[:alnum:]_]*/'(' { yylval = strdup(yytext); return FUNC_CALL; }
[[:alpha:]_][[:alnum:]_]* { yylval = strdup(yytext); return IDENT; }
语法现在非常简单明了:
call: FUNC_CALL '(' expression_list ')' /* foo(1, 2) */
| IDENT expression_list /* foo (1, 2) */
| IDENT /* foo * 3 */
这种区别在所有语法环境中都不会有用,因此添加非终结符通常会很有用,该非终结符将匹配任一标识符形式:
name: IDENT | FUNC_CALL
但是您将需要谨慎使用此非终端程序。特别是,将其用作表达语法的一部分可能会导致解析器冲突。但是在其他情况下,也可以:
func_defn: "def" name '(' parameters ')' block "end"
(我知道这不是Ruby函数定义的确切语法。仅出于说明目的。)
另一个令人困惑的问题是,在某些情况下,一元运算符+
和-
似乎应被视为整数文字的一部分。 Ruby解析器的行为表明,如果词法分析器可能是函数的第一个参数,则该词法分析器会将符号字符与紧随其后的数字组合在一起。 (也就是说,在<identifier><whitespace><sign><digits>
不是已经声明的局部变量的上下文<identifier>
中。)
这种上下文规则当然可以使用开始条件添加到词法扫描器中,尽管它有点丑陋。一个没有完全实现的实现,基于先前的实现:
%x SIGNED_NUMBERS
%%
[[:alpha:]_][[:alnum:]_]*/'(' { yylval.id = strdup(yytext);
return FUNC_CALL; }
[[:alpha:]_][[:alnum:]_]*/[[:blank:]] { yylval.id = strdup(yytext);
if (!is_local(yylval.id))
BEGIN(SIGNED_NUMBERS);
return IDENT; }
[[:alpha:]_][[:alnum:]_]*/ { yylval.id = strdup(yytext);
return IDENT; }
<SIGNED_NUMBERS>[[:blank:]]+ ;
/* Numeric patterns, one version for each context */
<SIGNED_NUMBERS>[+-]?[[:digit:]]+ { yylval.integer = strtol(yytext, NULL, 0);
BEGIN(INITIAL);
return INTEGER; }
[[:digit:]]+ { yylval.integer = strtol(yytext, NULL, 0);
return INTEGER; }
/* ... */
/* If the next character is not a digit or a sign, rescan in INITIAL state */
<SIGNED_NUMBERS>.|\n { yyless(0); BEGIN(INITIAL); }
另一个可能的解决方案是让词法分析器区分在空格后并直接跟有数字的符号字符,然后让解析器尝试确定符号是否应与以下数字组合。但是,这仍然取决于能否区分局部变量和其他标识符,这仍然需要通过符号表进行词汇反馈。
值得注意的是,所有这些复杂性的最终结果是一种语言,在某些特殊情况下其语义不是很明显。 f+3
和f +3
产生不同结果的事实很容易导致细微的错误,可能很难检测到。在许多使用具有此类歧义语言的项目中,项目样式指南将禁止语义不清晰的法律构造。如果尚未设计语言,则可能需要考虑到这一点。