如何写一个解释器(1):编译原理

最近在看DSL的东西,对于外部DSL,写一个解释器是必不可少的。我试图概括一下我学到的,以写一个解释器为目标,讲一下若是来实现一个可用的解释器。一个解释器一般能够分为一下几个阶段:前端

  1. 词法分析(Lexer)
  2. 语法分析(Parser, BNF, CFG, AST)
  3. 语义分析(AST的处理, annotated AST)
  4. 目标语言生成(stack-based)

这里的解释器不包括目标语言的执行和运行时环境,若是须要相似于python/ruby的解析执行器的话,还须要bytecode-compiler, bytecode-interpreter和runtime。咱们这里只谈解释的部分,不谈执行的部分。做为DSL咱们大多都有宿主语言,一般也就咱们生成的目标语言,有关执行这部分,咱们交给目标语言去解决。换个说法,咱们只讨论一个完整解释执行器的前端。python

当前编译技术已经获得极大发展,对于词法分析和语法分析,咱们能够借助成熟的工具或库很快的完成,极大简化了写解释器的难度。对于一个解释器来讲咱们须要本身处理的部分基本上集中于语义的分析,即AST的处理和目标语言的生成。其实这里的AST的处理结果至关于一个与执行环境无关的中间语言表示,而目标语言的生成就是针对目标语言的特性,将annotated AST转换为目标语言以方便在目标语言的运行环境中运行。ruby

继续扯扯淡,Lisp语言的语法基本上就是直接写AST,直接用AST的表示法来表示程序,因此Lisp语法是最贴近于编译器的中间表示,开始可能根本就不是给通常人用的,谁知道这么多人喜欢用。Lisp之因此叫List Processer那是由于Lisp直接处理AST的数据结构表示即Nested List,因此才叫List Processor,一点都没有谦虚的意思。在数据结构里,表示树这种数据结构的,最简单直接的办法就是Nested List(经过Linked-List了来实现)。并且这种结构利于递归遍历,方便Stack-based执行或者代码的生成。数据结构

1,词法分析

词法分析是将源代码转换成tokens,这些tokens有些事关键字,有些是变量,有些是字面量,有些是语法结构,等等。好比:工具

var s = 5;flex

if(s > 0){code

  print “greater than 5”orm

}递归

输出的tokens是[var, s, 5, ;, if, (, s, >, 0, ), {, print, “greater than 5”, }],这些tokens是有顺序的,可是没有具体的语法和语义。咱们的词法分析器就是将源代码做为输入,输出就是这个tokens的序列。token

词法分析中咱们会遇到一些问题,好比当咱们看到whe的时候咱们不知道这个是个变量名仍是when关键字,这个须要看连续的4个字符才能够知道,这个就是LL(4),LL表明从左边依次读取,从最左边的w->wh-whe->when依次来推导出这个是关键字仍是变量。

这个咱们这里很少讲,细节能够找本编译器的书来看看。有不少现成的工具和类库帮咱们完成这样的工做,好比lex, flex, ply,等等。

2,语法分析

语法分析是帮咱们分析这个token序列是否符合咱们的语法,而且将语法分析结果用AST表示出来。既然是检查语法和按语法抽取出AST,那么咱们就须要表示语法,一般咱们使用Backs-Naur Format,也就是咱们常说的BNF表示法。不过可笑的是BNF自己是个规范,不是具体的实现。也就是说一样BNF,不一样的实现有不一样的表示。好比:

statement ::= statement | empty

这个表示称为statement的产生式,也就是statement的语法规则,一样的有的BNF表示法是这样表示的

statement –> statement or empty

其中::=和->, |和or,是同样的意思。

语法分析的结果就是符合语法的AST,也能够说是符合语法的一个实例。

语法分析阶段咱们也须要处理一些问题,好比为了解决算术的二义性,咱们须要引入优先级(precedence),和结合性(associative)。

语法分析也有现成的工具和类库可使用好比yacc, bison, antlr, ply等等。

3,语义分析

符合语法的源代码不必定有意义,好比a/0,这个就是没有意义的,这个咱们须要在语义处理阶段解决。

待续…

相关文章
相关标签/搜索