目前我正在努力实现一个语法非常类似红宝石。为了保持它的简单,词法分析器会忽略空格字符。
然而,在某些情况下,空间信令很大的区别:
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
;
所以,我喜欢问是否有关于如何解决这个语法任何的最佳做法。
先感谢您!
无需您试图解析语法的完整规范,它是不容易给出一个确切的答案。在下文中,我假设这些是仅有的两个地方的空白的两个标记之间的存在(或不存在)影响解析。
f(...)
和f (...)
区分发生在语言数量惊人。一个共同的策略是词法分析器识别哪个后面紧跟一个开括号为“FUNCTION_CALL”令牌的标识符。
你会发现,在大多数awk
实现,例如:在awk中,函数调用和连接之间的模糊性是通过要求在函数调用左括号紧跟在标识解决。类似地,C-预处理器宏定义指令#define foo(A) A
和#define foo (A)
(带有参数的宏的定义)(一个普通的宏其膨胀与(
令牌开始之间进行区分。
如果你正在使用(F)法这样做,你可以使用/
尾随上下文操作:
[[: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
产生不同的结果,这一事实很容易导致微妙的错误,这可能是防不胜防。在使用语言与这类含糊不清的许多项目,该项目的风格指南将禁止使用语义不清的法律结构。您可能要考虑到这一点在你的语言设计,如果你还没有这样做。