90分钟实现一门编程语言(极简解释器教程)

2019-12-30 14:54:22王冬梅

高阶函数

和 Scheme 一样,函数在 iScheme 中是头等对象,这意味着:

可以定义一个变量为函数。 函数可以接受一个函数作为参数。 函数返回一个函数。

iScheme 的高阶函数示例


; Defines a multiply function.
(def mul (func (a b) (* a b)))
; Defines a list map function.
(def map (func (f alist)
  (if (empty? alist)
    (list )
    (append (list (f (first alist))) (map f (rest alist)))
    )))
; Doubles a list using map and mul.
>> (map (mul 2) (list 1 2 3))
>> (list 2 4 6)

小结

对 iScheme 的介绍就到这里——事实上这就是 iScheme 的所有元素,会不会太简单了? -_-

接下来进入正题——从头开始构造 iScheme 的解释程序。

解释器构造
iScheme 解释器主要分为两部分,解析(Parse)和求值(Evaluation):

 1、解析(Parse):解析源程序,并生成解释器可以理解的中间(Intermediate)结构。这部分包含词法分析,语法分析,语义分析,生成语法树。
2、求值(Evaluation):执行解析阶段得到的中介结构然后得到运行结果。这部分包含作用域,类型系统设计和语法树遍历。
词法分析

词法分析负责把源程序解析成一个个词法单元(Lex),以便之后的处理。

iScheme 的词法分析极其简单——由于 iScheme 的词法元素只包含括号,空白,数字和变量名,因此C#自带的 String#Split 就足够。

iScheme的词法分析及测试


public static String[] Tokenize(String text) {
  String[] tokens = text.Replace("(", " ( ").Replace(")", " ) ").Split(" trn".ToArray(), StringSplitOptions.RemoveEmptyEntries);
  return tokens;
}
// Extends String.Join for a smooth API.
public static String Join(this String separator, IEnumerable<Object> values) {
  return String.Join(separator, values);
}
// Displays the lexes in a readable form.
public static String PrettyPrint(String[] lexes) {
  return "[" + ", ".Join(lexes.Select(s => "'" + s + "'") + "]";
}
// Some tests
>> PrettyPrint(Tokenize("a"))
>> ['a']
>> PrettyPrint(Tokenize("(def a 3)"))
>> ['(', 'def', 'a', '3', ')']
>> PrettyPrint(Tokenize("(begin (def a 3) (* a a))"))
>> ['begin', '(', 'def', 'a', '3', ')', '(', '*', 'a', 'a', ')', ')']

注意

个人不喜欢 String.Join 这个静态方法,所以这里使用C#的扩展方法(Extension Methods)对String类型做了一个扩展。 相对于LINQ Syntax,我个人更喜欢LINQ Extension Methods,接下来的代码也都会是这种风格。 不要以为词法分析都是这么离谱般简单!vczh的词法分析教程给出了一个完整编程语言的词法分析教程。