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

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

作用域

作用域决定程序的运行环境。iScheme使用嵌套作用域。

以下面的程序为例


>> (def x 1)
>> 1
>> (def y (begin (def x 2) (* x x)))
>> 4
>> x
>> 1

c#,极简解释器

利用C#提供的 Dictionary<TKey, TValue> 类型,我们可以很容易的实现 iScheme 的作用域 SScope :

iScheme的作用域实现


public class SScope {
  public SScope Parent { get; private set; }
  private Dictionary<String, SObject> variableTable;
  public SScope(SScope parent) {
    this.Parent = parent;
    this.variableTable = new Dictionary<String, SObject>();
  }
  public SObject Find(String name) {
    SScope current = this;
    while (current != null) {
      if (current.variableTable.ContainsKey(name)) {
        return current.variableTable[name];
      }
      current = current.Parent;
    }
    throw new Exception(name + " is not defined.");
  }
  public SObject Define(String name, SObject value) {
    this.variableTable.Add(name, value);
    return value;
  }
}

类型实现

iScheme 的类型系统极其简单——只有数值,Bool,列表和函数,考虑到他们都是 iScheme 里面的值对象(Value Object),为了便于对它们进行统一处理,这里为它们设置一个统一的父类型 SObject :

public class SObject { }

数值类型

iScheme 的数值类型只是对 .Net 中 Int64 (即 C# 里的 long )的简单封装:


public class SNumber : SObject {
  private readonly Int64 value;
  public SNumber(Int64 value) {
    this.value = value;
  }
  public override String ToString() {
    return this.value.ToString();
  }
  public static implicit operator Int64(SNumber number) {
    return number.value;
  }
  public static implicit operator SNumber(Int64 value) {
    return new SNumber(value);
  }
}

注意这里使用了 C# 的隐式操作符重载,这使得我们可以:


SNumber foo = 30;
SNumber bar = 40;
SNumber foobar = foo * bar;

而不必:


SNumber foo = new SNumber(value: 30);
SNumber bar = new SNumber(value: 40);
SNumber foobar = new SNumber(value: foo.Value * bar.Value);

为了方便,这里也为 SObject 增加了隐式操作符重载(尽管 Int64 可以被转换为 SNumber 且 SNumber 继承自 SObject ,但 .Net 无法直接把 Int64 转化为 SObject ):


public class SObject {
  ...
  public static implicit operator SObject(Int64 value) {
    return (SNumber)value;
  }
}

Bool类型

由于 Bool 类型只有 True 和 False,所以使用静态对象就足矣。