Parsing 解析maxscript-换行符问题

Parsing 解析maxscript-换行符问题,parsing,bison,maxscript,Parsing,Bison,Maxscript,我正在尝试使用其官方语言为MAXScript语言创建解析器。我使用flex和bison来创建lexer和解析器 然而,我遇到了以下问题。在传统语言(例如C)中,语句由一个特殊的标记(;在C中)分隔。但是在MAXScript中,复合表达式中的表达式可以通过分隔或换行符。还有其他一些语言在解析器中使用空白字符,比如Python。但是Python对新行的位置要求严格得多,下面的Python程序是无效的: #编译错误 def 傅(x): 打印(x) #编译错误 def棒 (x) : 傅(x) 但是,在

我正在尝试使用其官方语言为MAXScript语言创建解析器。我使用flex和bison来创建lexer和解析器

然而,我遇到了以下问题。在传统语言(例如C)中,语句由一个特殊的标记(
在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