如何获取(LA)LR解析器中的标识符

时间:2014-08-05 13:15:14

标签: c# parsing scope lalr

(LA)LR 解析器的缺点是reduce仅在规则结束时处理。这是使用范围变量(如javascript

的编程语言中的问题

示例:

var a = 2;
function (a) {
   a = 4;
}

参见上面的代码示例。解析器可能如下所示:

program : instruction program {}
        | {}
        ;

instruction : command {}
            | function {}
            ;

command : "var" identifier "=" literal ";" {}
        ;

function : "function" "(" arguments ")" "{" program "}" {/*1*/}
         ;

arguments : identifier {}
          | identifier "," arguments {}
          | {}
          ;

现在很明显,解析器每次都会使用标识符。可以在寄存器中注册标识符。但问题是函数(行/*1*/)仅在函数末尾被考虑。因此,在函数中使用标识符(如a = 4;)的指令不能在解析器时绑定到本地/全局标识符,因为它在那时是未知的。

解决这个问题的好方法是什么,C#(标准库)提供哪些功能来处理这种情况?

1 个答案:

答案 0 :(得分:1)

声明

您问题中的语法是yacc格式,因此我假设您计划使用yacc兼容的解析器生成器。因此,我不知道您对C#标准库的期望。这个答案一般适用于yaccbison和其他使用相同输入格式的解析器生成器。对于jison,请参阅以下有关中段规则操作的说明。

标准Yacc解决方案

yacc及其衍生产品允许您使用中规则操作编写规则;这些操作在规则其余部分的任何减少之前执行。这使得插入设置和拆卸动作变得非常容易。例如:

function: "function"    { start_scope(); }
          '(' arguments ')'
          '{' body '}'  { end_scope(); /* ... */ }
        ;

中规则操作不会向上下文无关语法添加任何工具,因为它们可以用其他方式编写(见下文),但它们有时更易于读写。中间规则操作具有与生产中的任何其他组件类似的值,并且可以由稍后的操作引用。例如,它可能有助于做这样的事情:

function: "function"    { $$ = new_scope(); }
          '(' arguments ')'
          '{' body '}'  { close_scope($2); /* ... */ }
        ;

尽管如果您希望在初始解析期间将名称查找应用于当前作用域,仍然需要使用持久状态来记录当前作用域。

此外,向(LA)LR(1)语法添加中规则动作可能会删除(LA)LR(1)属性。在bison manual中有一个例子,它并不完全巧合地与范围局部变量的范围有关。

范围规则

虽然这种策略很有诱惑力,但是有很多语言对你的预期没有多大帮助。例如,在javascript中,在定义本地变量之前引用它是完全合法的。请考虑以下内容,例如:

a=42;
function geta() {
  var rv = a;
  var a = 43;
  return rv;
}
geta();

geta的结果是未定义,因为rva初始化时,该特定a属于geta var b = 43;尚未分配值。如果下一行已更改为geta,则42将返回a,因为现在a是对封闭范围中变量的引用。 Python范围大致相同(尽管语言在本地函数的范围方面有所不同),但它远非普遍;可能有更多的语言,其范围规则就像Lua,它可以从左到右作为范围,getaa的第一次使用是指外a,而范围内部(foo)*(bar)的内容从其声明开始并延伸到块的末尾。 (当然,Lua的语法略有不同,所以你不能只提供那个例子。)

C和大多数C衍生物都是基于&#34;声明使用前&#34;规则,但C ++不要求在函数的定义之前声明内联类成员函数体中使用的名称,除非名称是模板或typedef。因此,在C ++中,名称解析的语法部分可以从左到右完成,您可以决定function是演员表还是产品,但您仍需要重新访问表达式才能进行名称解析。< / p>

因此,在AST的后续步骤中将名称附加到范围通常会更好,在这种情况下,在jison生产减少之前,范围不会附加到函数不再重要

没有中间规则操作的解析器生成器的替代方法

并非所有LALR(1)解析器生成器都允许中规则操作。例如,lemon - 否则使用非常相似的语法格式 - 也不jison实现该功能。 (有一个相当长的杰出X: a b c { /* mid-rule action */ } d e f { /* final action */ }; feature request。)但它并不重要,因为MRA只是语法糖。有时,它们可以使用具有空右侧的唯一命名的生产来实现。换句话说,规则:

@32: /* empty */ { /* mid-rule action */ };
X: a b c @32 d e f { /* final action */ };

可以转化为:

@32

,当d减少时,即在efX: @32 d e f { /* final action (with semantic values renumbered) */ }; @32: a b c { /* mid-rule action */ } 减少之前,将执行中间规则操作。

大多数情况下,MRA需要能够在右侧参考其左侧的符号的语义值,因此将MRA视为语法糖是更准确的rhs的前缀:

@32

实际的野牛实施更接近上面的第一个建议,因为野牛知道当a减少时,前三个堆栈元素将始终为bc和{{ 1}}。这是有效的,因为创建的非终端只出现在语法的一个地方。 (Bison让您可以访问此堆栈探测功能,但除非您真的知道自己在做什么,否则请不要使用它。)因此,如果您需要,您更有可能使用前缀解决方案相当于jison或lemon中的MRA。

您可能仍会遇到问题,因为最终操作可能会引用一个语义值,该语义值现在已被前缀规则包含。为了处理这种情况,您可能需要通过使用其语义值通过前缀规则传递一个或多个语义规则。幸运的是,这些案件很少见。