【问题标题】:How do I implement forward references in a compiler?如何在编译器中实现前向引用?
【发布时间】:2010-10-30 06:36:21
【问题描述】:

我正在使用 Lex 和 YACC(实际上是 Flex 和 Bison)创建一个编译器。该语言允许无限前向引用任何符号(如 C#)。问题是在不知道标识符是什么的情况下解析语言是不可能的。

我知道的唯一解决方案是对整个源代码进行 lex,然后进行“广度优先”解析,因此类声明和函数声明等更高级别的内容在使用它们的函数之前得到解析。但是,对于大文件,这会占用大量内存,并且很难用 YACC 处理(我必须为每种类型的声明/正文创建单独的语法)。我还必须手写词法分析器(这不是什么大问题)。

我不太关心效率(尽管它仍然很重要),因为一旦我完成它,我将自己重写编译器,但我希望那个版本更快(所以如果有任何无法在 Lex/YACC 中完成但可以手动完成的快速通用技术,也请提出建议)。所以现在,易于开发是最重要的因素。

这个问题有什么好的解决办法吗?在 C# 或 Java 等语言的编译器中,这通常是如何完成的?

【问题讨论】:

    标签: parsing yacc bison compiler-development forward-reference


    【解决方案1】:

    完全可以解析。尽管标识符和关键字之间存在歧义,但 lex 很乐意通过赋予关键字优先级来解决这个问题。

    我看不出还有什么其他问题。您无需在解析阶段确定标识符是否有效。在解析时,您正在构建解析树或抽象语法树(差异很细微,但与本讨论的目的无关)。之后,您通过在解析期间生成的 AST 上执行传递来构建嵌套符号表结构。然后,您再次通过 AST 来检查使用的标识符是否有效。然后在 AST 上进行一个或多个附加解析以生成输出代码或其他一些中间数据结构,然后就完成了!

    编辑:如果您想了解它是如何完成的,请查看 Mono C# 编译器的源代码。这实际上是用 C# 而不是 C 或 C++ 编写的,但它确实使用了与 yacc 非常相似的 Jay 的 .NET 端口。

    【讨论】:

    • 与关键字无关。它更像这样:是 ABC(包 AB).(类 C)、(包 A)。(类 B)。(字段 C),或(字段 A)。(字段 B)。(字段 C)等。
    • 那么我的回答的第二段适用。你不需要知道这一点来解析。对待 '。'作为语法中的运算符。然后,在您的 AST 通行证中,您可以对照符号表检查它们。
    • 好吧,我想我将不得不制作一个解析树而不是一个 AST。正如你所说,它们是不同的。如果没有其他人提出更好的答案,我会接受这个,但我真的不想这样做......
    【解决方案2】:

    一种选择是通过扫描和缓存令牌来处理前向引用,直到您遇到您知道如何使用的东西(有点像“恐慌模式”错误恢复)。运行完整文件后,返回并尝试重新解析之前未解析的位。

    至于必须手写词法分析器;不要,使用 lex 生成一个普通的解析器,然后通过手写 shim 读取它,让您返回并从缓存中提供解析器以及 lex 的内容。

    至于制作几个语法,在yacc文件上使用预处理器有点乐趣,你应该能够将它们全部从同一个原始源中制作出来

    【讨论】:

    • 我并不担心手写词法分析器,它并不难(实际上可能会稍微容易一些,因为我的语言有类似 Python 的缩进)。使用带有 YACC 的预处理器听起来可能可行,但是有什么方法可以更改开始符号?
    • 用 yacc 重新做一个预处理器,就是这样。定义完整的语法而不明确定义开始符号,然后换出一小部分文件(通过#include或#define之类的东西)来选择起点。一种方法是使用“Root ::= MacroRule;”形式的开始规则并将 MacroRule 替换为此版本所需的任何内容。
    猜你喜欢
    • 2010-10-22
    • 2010-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    相关资源
    最近更新 更多