编译器设计的算法?

An algorithm for compiler designing?

最近在想一个自己构造的算法。我称之为替换编译。 它的工作原理如下:

定义一种语言及其运算符的优先级,例如

(1) store <value> as <id>, replace with: var <id> = <value>, precedence: 1
(2) add <num> to <num>, replace with: <num> + <num>, precedence: 2

接受一行输入,例如store add 1 to 2 as a

对其进行标记:<kw,store><kw,add><num,1><kw,to><2><kw,as><id,a><EOF>

然后扫描所有标记,直到到达文件末尾,找到优先级最高的操作,然后"pack"操作:

<kw,store>(<kw,add><num,1><kw,to><2>)<kw,as><id,a><EOF>

将括号中的表达式 "sub-statement" 替换为定义的替换项:

<kw,store>(1 + 2)<kw,as><id,a><EOF>

重复直到没有更多的陈述:

(<kw,store>(1 + 2)<kw,as><id,a>)<EOF>
(var a = (1 + 2))

然后使用内置函数计算代码,eval()

eval("var a = (1 + 2)")

那么我的问题是:这个算法行得通吗,有什么局限性?这个算法在简单语言上效果更好吗?

这不会按原样工作,因为无法确定操作和关键字的优先级,但是您基本上已经定义了解析(并抛出解释步骤在末尾)。这看起来非常接近 operator-precedence parsing,但我在您的愿景细节上可能是错误的。构成解析算法的真正关键是 direction/precedence 它读取代码,决定是自上而下(找出什么样的语句并应用规则)还是自下而上(assemble 小块分成较大的组件,直到语句的类型显而易见),以及语法是编码为通用解析器的代码还是数据。 (我可能忽略了一些东西,但这应该给你一个起点,让你进一步阅读。)

更典型的是,代码通常使用 LR technique (LL if it's top-down) that's driven from a state machine with look-ahead and next-step information, but you'll also find the occasional recursive descent 进行解析。由于它们都在做非常相似的事情(只是实现方式不同),您的粗略算法可能会被改进为看起来很像它们中的任何一个。

对于大多数学习解析的人来说,递归下降是必经之路,因为一切都在代码中,而不是构建相当于状态机定义的解释器。但是大多数解析器生成器构建一个 LL 或 LR 编译器。

而且我显然过度简化了该领域,因为您可以在维基百科页面的底部看到一些相关系统,这些系统部分围绕您可用的语法类型。但对于大多数语言来说,这些是三大算法。

你定义的是重写系统:https://en.wikipedia.org/wiki/Rewriting

您可以制作这样的编译器,但它工作量大且运行缓慢,如果您在优化方面做得非常好,那么您将获得传统的 table 驱动的解析器。最后最好先了解这些然后从那里开始。

如果你真的不想使用解析器生成工具,那么手工编写简单语言的解析器的最简单方法通常是递归下降:https://en.wikipedia.org/wiki/Recursive_descent_parser