Parsing 解析maxscript-换行符问题
我正在尝试使用其官方语言为MAXScript语言创建解析器。我使用flex和bison来创建lexer和解析器 然而,我遇到了以下问题。在传统语言(例如C)中,语句由一个特殊的标记(Parsing 解析maxscript-换行符问题,parsing,bison,maxscript,Parsing,Bison,Maxscript,我正在尝试使用其官方语言为MAXScript语言创建解析器。我使用flex和bison来创建lexer和解析器 然而,我遇到了以下问题。在传统语言(例如C)中,语句由一个特殊的标记(;在C中)分隔。但是在MAXScript中,复合表达式中的表达式可以通过分隔或换行符。还有其他一些语言在解析器中使用空白字符,比如Python。但是Python对新行的位置要求严格得多,下面的Python程序是无效的: #编译错误 def 傅(x): 打印(x) #编译错误 def棒 (x) : 傅(x) 但是,在
;
在C中)分隔。但是在MAXScript中,复合表达式中的表达式可以通过分隔代码>或换行符
。还有其他一些语言在解析器中使用空白字符,比如Python。但是Python对新行的位置要求严格得多,下面的Python程序是无效的:
#编译错误
def
傅(x):
打印(x)
#编译错误
def棒
(x) :
傅(x)
但是,在MAXScript中,以下程序有效:
fn
福克斯=
(//括号开始复合表达式
a=3+2;//分号是可选的
打印x
)
fn棒
x=
福克斯
你甚至可以这样写:
for
x
in
#(1,2,3,4)
do
format "%," x
for
x
in
#(1,2,3,4)
do
format "%,"
x
它将计算fine并将1,2,3,4,
打印到输出。因此,newline
s可以插入到许多没有特殊意义的地方
但是,如果在程序中再插入一个换行符
,如下所示:
for
x
in
#(1,2,3,4)
do
format "%," x
for
x
in
#(1,2,3,4)
do
format "%,"
x
当format
函数希望传递多个参数时,将出现运行时错误
这是我拥有的bison输入文件的一部分:
expr:
simple_expr
| if_expr
| while_loop
| do_loop
| for_loop
| expr_seq
expr_seq:
"(" expr_semicolon_list ")"
expr_semicolon_list:
expr
| expr TK_SEMICOLON expr_semicolon_list
| expr TK_EOL expr_semicolon_list
if_expr:
"if" expr "then" expr "else" expr
| "if" expr "then" expr
| "if" expr "do" expr
// etc.
这将只解析仅使用换行符
作为表达式分隔符的程序,并且不希望换行符
分散在程序中的其他位置
我的问题是:有没有办法告诉bison将代币视为可选代币?对野牛来说,这意味着:
- 如果您找到
换行符
标记,并且可以使用它进行移位或减少,那么就这样做
- 否则,只需丢弃
换行符
标记并继续解析
因为如果没有办法做到这一点,我能想到的唯一其他解决方案就是修改bison语法文件,以便它在任何地方都能看到那些newline
s。和规则的优先级,其中换行符
用作表达式分隔符。像这样:
%precedence EXPR_SEPARATOR // high precedence
%%
// w = sequence of whitespace tokens
w: %empty // either nothing
| TK_EOL w // or newline followed by other whitespace tokens
expr:
w simple_expr w
| w if_expr w
| w while_loop w
| w do_loop w
| w for_loop w
| w expr_seq w
expr_seq:
w "(" w expr_semicolon_list w ")" w
expr_semicolon_list:
expr
| expr w TK_SEMICOLON w expr_semicolon_list
| expr TK_EOL w expr_semicolon_list %prec EXPR_SEPARATOR
if_expr:
w "if" w expr w "then" w expr w "else" w expr w
| w "if" w expr w "then" w expr w
| w "if" w expr w "do" w expr w
// etc.
然而,这看起来非常丑陋且容易出错,如果可能的话,我希望避免这种解决方案
我的问题是:有没有办法告诉bison将代币视为可选代币
不,没有。(有关图表的详细说明,请参见下文。)
尽管如此,解决方案并不像你想象的那么丑陋,尽管它也不是没有问题的
<> P>为了简化事情,我假设LeXER可以被确信只产生一个单独的<代码> \n′/COS>令牌,而不管程序文本中出现了多少个连续的换行符,包括在空白行中散布注释的情况。这可以通过复杂的正则表达式实现,但更简单的方法是使用开始条件来抑制\n
标记,直到遇到正则标记为止。lexer的初始启动条件应该是禁止换行标记的条件,这样程序文本开头的空行就不会混淆任何内容
现在,关键的洞察是我们不必在整个语法中插入“maybeanewline”标记,因为每个新行都必须在某个真正的标记之后出现。这意味着我们可以为每个终端添加一个非终端:
tok_id: ID | ID '\n'
tok_if: "if" | "if" '\n'
tok_then: "then" | "then" '\n'
tok_else: "else" | "else" '\n'
tok_do: "do" | "do" '\n'
tok_semi: ';' | ';' '\n'
tok_dot: '.' | '.' '\n'
tok_plus: '+' | '+' '\n'
tok_dash: '-' | '-' '\n'
tok_star: '*' | '*' '\n'
tok_slash: '/' | '/' '\n'
tok_caret: '^' | '^' '\n'
tok_open: '(' | '(' '\n'
tok_close: ')' | ')' '\n'
tok_openb: '[' | '[' '\n'
tok_closeb: ']' | ']' '\n'
/* Etc. */
现在,问题是用上面定义的相应非终端替换终端的使用。(无w
non-terminal是必需的。)一旦我们这样做了,bison将在刚才添加的非terminal定义中报告一些shift-reduce冲突;任何可能出现在表达式末尾的终端都会引发冲突,因为换行符可能会被终端的非终端包装器或expr\u分号列表
产品所吸收。我们希望换行符成为expr\u分号列表的一部分,因此我们需要添加以换行符开头的优先级声明,以便它的优先级低于任何其他标记
这很可能对你的语法有用,但不是100%确定。基于优先级的解决方案的问题在于,它们可以隐藏真实的转换以减少冲突问题。因此,我建议在语法上运行bison,并在添加优先级声明之前验证所有shift-reduce冲突是否出现在预期的位置(在包装器产品中)
为什么令牌回退不像看上去那么简单
从理论上讲,可以实现与您建议的功能相似的功能。[注1]
但这并不简单,因为LALR解析器构造算法结合状态的方式。结果是,解析器可能“不知道”在完成一个或多个缩减之前,前瞻标记不能被移位。因此,当它发现lookahead令牌无效时,它已经执行了必须撤消的缩减,以便在没有lookahead令牌的情况下继续解析
大多数解析器生成器通过删除与前瞻标记对应的错误操作(如果该标记的状态中的默认操作是减少)来加剧问题。其效果是再次延迟错误检测,直到一个或多个无效的减少之后,但它的好处是显著减少了转换表的大小(因为默认条目不需要显式存储)。由于延迟错误将在消耗任何更多输入之前被检测到,因此通常认为延迟是可接受的。(但是,野牛可以选择阻止这种优化。)
作为一个实际的例子,这里有一个非常简单的表达式语法,只有两个运算符:
prog: expr '\n' | prog expr '\n'
expr: prod | expr '+' prod
prod: term | prod '*' term
term: ID | '(' expr ')'
这导致了该状态图[注2]:
假设我们想忽略换行符
a + b
bison -o ex.tab.c --report=all -g ex.y
dot -Tpng -oex.png ex.dot
bison -o ex_canon.c --report=all -g -Dlr.type=canonical-lr ex.y
dot -Tpng -oex_canon.png ex_canon.dot