【问题标题】:Can Bison verify scope as well as syntax?Bison 可以验证范围和语法吗?
【发布时间】:2012-03-06 00:52:13
【问题描述】:

我正在使用 Lex/Bison 创建一个简单的脚本语言,它可以翻译成 C。一个反编译器。

我知道 Bison 可以验证语法,但是范围界定呢?它可以验证之前声明的正在使用的标识符吗?是否必须手动完成?如果是,在哪一步?

例如这是语法正确的。 但不应该编译,因为消息在错误的范围内。此外,message2 从未被声明过。

String s1
if (s1=='knock on door')
   String message1='Hello';

print message1;
print message2;

【问题讨论】:

    标签: compiler-construction bison yacc lex parser-generator


    【解决方案1】:

    解析器(无论如何生成)通常无法检查范围规则。

    一般来说,要进行范围检查,通常您必须构建整个 AST,然后验证标识符是否具有良好的范围。对于在变量使用之前引入了范围的静态范围的语言,您可能只能在规则中执行此操作。对于具有命名空间的语言,命名空间声明可能出现在任何地方,如果不收集该命名空间以及整个程序,您就无法做到这一点。 (命名空间声明甚至可能与 use 不在同一个文件中;现在您解析了一个完全不同的文件)。

    因此,通常的作用域检查方式是在解析之后,遍历程序树。通常,如果您要执行解析之外的任何操作,则无论如何都需要树和完整的符号表,所以这并不是什么大问题。

    一些早期的编译器没有将整个树保存在内存中的奢侈。它们要么在使用前具有带有范围声明的语言,要么在多次传递中实现。

    【讨论】:

    • 在某些语言中,您不能在拥有 AST 之前推迟范围界定,因为如果不查找该信息,解析器将无法工作。这是什么:X(Y); /* C 或 C++ */ 这是一个函数调用吗? Y被传递给X?还是声明 Y 属于 X 类型?您必须检查是否声明了 X 以简单地确定它是哪种语法符号:类型名称或主表达式(函数或变量)。
    • @Kaz:这是一个民间定理,由无法设计出能够很好地处理歧义的解析器的人们推动。 syntax 的要点是您不必在解析时做出决定;如果它可能是几个不同的东西,只需将它解析为可能是几个不同的东西。您可以稍后构建符号表,然后选择可接受的子解析。我们的 C 和 C++ 前端 (semanticdesigns.com/Products/FrontEnds/CppFrontEnd.html) 就是这样工作的,而且工作得很好;它们已被用于处理数百万行 C 和 C++ 代码。
    • 嗯,在您可以轻松分辨的语言中(基于解析树的先前部分),我在构建 AST 时在解析器中获取它似乎比拥有发明一个神奇的 type_or_variable 符号,然后必须在构建整个 AST 时确定它是哪个符号。这取决于您如何定义“解析”以及更容易实现的内容;但从我的角度来看,您将解析器的部分工作转移到其他地方只是为了避免将扫描仪绑定到部分构建的 AST。
    • @Edmund:软件工程是关于有效地构建软件。一个关键方法是确保任务的责任被打包在一个地方,并且它的结构对其他机制(Parnas)隐藏。通过纠缠解析和符号表构造,你得到了一个非常丑陋的机器,你必须手动实现和维护。通过隔离解析和符号表构造,这样做变得容易多了。在 C++ 的情况下,这非常重要,因为 C++ 中的符号表查找确实非常困难(参见 Keonig 查找)......
    • @Ira,我同意。但解析工作的一部分是确定每个符号的角色(广义上)。通过将该决定推迟到稍后阶段,您将解析器的部分职责转移到其他地方。我意识到 C++ 是最难解析的常用语言之一,因此我可以相信您在这种情况下做了正确的事情;我只是说这两种方法都不是 100% 干净的,因此您需要考虑每种方法的权衡。
    【解决方案2】:

    找到答案。实际上还有第三个阶段,称为语义分析。步骤:

    1. Lex(获取令牌)
    2. 解析(确保在正确的上下文中使用令牌)
    3. 语义分析(类型检查/范围界定)

    语义分析分析步骤 2 中创建的解析树。

    【讨论】:

      【解决方案3】:

      Bison 不处理标识符范围和查找。该功能由您在规则操作中提供。

      【讨论】:

        【解决方案4】:

        在获得 AST(抽象语法树)后,在 Bison 中处理它的一种方法是,

        • 创建一个名为“符号表”的数据结构,它支持嵌套范围,基本上它是一棵哈希表树,每次您将pop_scope()push_scope() 切换到新范围时,都会切换到范围的新兄弟节点死范围信息仍然保存。
        • 在您创建新范围的 Bison 规则中(因此在您的示例中,if 语句),使用 Bison midrule action feature 在“入口令牌”处调用 push_scope()if 语句,{块开始等)。并在主操作体中调用pop_scope()

        具体来说,用您的语言,您的原始规则可能如下所示:

        selection-statement: _IF '(' expression ')' statement {
          /* main action */
        }
        

        通过midrule action,你可以为_IF插入一个action,然后直接在Bison的对应位置pop、push scopes,就变成了:

        selection-statement: _IF { push_scope(); } '(' expression ')' statement {
          /* main action */
          pop_scope();
        }
        

        因此,在您的示例中,在定义符号 message1 时,当 Bison 减少此规则时,在解析器遇到 _IF 标记的位置已经调用了 push_scope()。然后,当您尝试在其范围之外读取 message1 时,您的符号表接口应该返回一些错误,并且您应该能够使用 yyerror() 向 Bison 抛出错误消息。

        附:当您插入中规动作时,您应该移动表达式的参考编号(例如,$0$1)。对于具有中间规则动作的复杂规则,这很快就会变得非常混乱且难以阅读。一个有用的技巧是使用 Bison named references 来避免错误地使用错误的参考名称。

        【讨论】:

          猜你喜欢
          • 2012-10-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-09-24
          • 2015-04-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多