最近我在思考一个自己构建的算法。我把它叫做 替换编译.它的工作原理如下。
定义一种语言以及它的运算符的优先级,比如说:
(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>
;
然后扫描所有标记,直到到达文件的末端,找到优先级最高的操作,并 "打包 "操作。
<kw,store>(<kw,add><num,1><kw,to><2>)<kw,as><id,a><EOF>
把 "子语句",也就是括号里的表达式,替换成定义的替换。
<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)")
那么我的问题是:这个算法能不能用,有什么限制?这个算法是不是在简单的语言上效果更好?
这不会像现在这样工作,因为没有办法决定操作和关键词的优先级。不过 你基本上已经定义了解析(并在最后加入了解释步骤)。 这看起来非常接近于 运算符-前缀解析但我可能对你的设想细节有误。 解析算法的真正关键在于它读取代码的方向性precedence,决策是自上而下的(找出什么样的语句并应用规则)还是自下而上的(将小块的语句组合成较大的组件,直到语句的类型显而易见),以及语法是被编码为代码还是通用解析器的数据。 我可能忽略了一些东西,但这应该给你一个起点,让你从进一步的阅读中获得意义)。
更典型的是,代码一般是用一个 LR 技术(LL 如果它是自上而下的),它是由一个带有前瞻和下一步信息的状态机驱动的,但你也会发现偶尔会有一些 递归. 由于它们都在做非常相似的事情(只是实现方式不同),你的粗略算法可能会被改进成和它们中的任何一个很像。
对于大多数学习解析的人来说,递归降序是最好的方式,因为一切都在代码中,而不是为状态机定义构建一个相当于解释器的东西。 但大多数解析器生成器都会构建一个LL或LR编译器。
我显然是把这个领域过于简单化了,因为你可以在维基百科的页面底部看到,有一些相关的系统部分地围绕着你所拥有的语法。 但对于大多数语言来说,这些都是三大算法。
你所定义的是一个重写系统。https:/en.wikipedia.orgwikiRewriting(重写)
你可以做一个这样的编译器,但是很辛苦,运行速度也很慢,如果你做了很好的优化,那么你会得到传统的表驱动的解析器。 最后还是先了解一下这些,然后从那里开始就好了。
如果你实在不想使用解析器生成工具,那么最简单的方法通常是递归下降,手工写一个简单语言的解析器。https:/en.wikipedia.orgwikiRecursive_descent_parser(递归后裔解析器)