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

2019-12-30 14:54:22王冬梅
.BuildIn("first", (args, scope) => { SList list = null; (args.Length == 1 && (list = (args[0].Evaluate(scope) as SList)) != null).OrThrows("<first> must apply to a list."); return list.First(); }) .BuildIn("rest", (args, scope) => { SList list = null; (args.Length == 1 && (list = (args[0].Evaluate(scope) as SList)) != null).OrThrows("<rest> must apply to a list."); return new SList(list.Skip(1)); })

又发现相当的重复逻辑——判断参数是否是一个合法的列表,重复代码很邪恶,所以这里把这段逻辑抽象为扩展方法:


public static SList RetrieveSList(this SExpression[] expressions, SScope scope, String operationName) {
  SList list = null;
  (expressions.Length == 1 && (list = (expressions[0].Evaluate(scope) as SList)) != null)
    .OrThrows("<" + operationName + "> must apply to a list");
  return list;
}

有了这个扩展方法,接下来的列表操作就很容易实现:


.BuildIn("first", (args, scope) => args.RetrieveSList(scope, "first").First())
.BuildIn("rest", (args, scope) => new SList(args.RetrieveSList(scope, "rest").Skip(1)))
.BuildIn("append", (args, scope) => {
  SList list0 = null, list1 = null;
  (args.Length == 2
    && (list0 = (args[0].Evaluate(scope) as SList)) != null
    && (list1 = (args[1].Evaluate(scope) as SList)) != null).OrThrows("Input must be two lists");
  return new SList(list0.Concat(list1));
})
.BuildIn("empty?", (args, scope) => args.RetrieveSList(scope, "empty?").Count() == 0)

测试

iScheme 的内置操作完成之后,就可以测试下初步成果了。

首先添加基于控制台的分析/求值(Parse/Evaluation)循环:


public static void KeepInterpretingInConsole(this SScope scope, Func<String, SScope, SObject> evaluate) {
  while (true) {
    try {
      Console.ForegroundColor = ConsoleColor.Gray;
      Console.Write(">> ");
      String code;
      if (!String.IsNullOrWhiteSpace(code = Console.ReadLine())) {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine(">> " + evaluate(code, scope));
      }
    } catch (Exception ex) {
      Console.ForegroundColor = ConsoleColor.Red;
      Console.WriteLine(">> " + ex.Message);
    }
  }
}

然后在 SExpression#Evaluate 中补充调用代码:


public override SObject Evaluate(SScope scope) {
  if (this.Children.Count == 0) {
    Int64 number;
    if (Int64.TryParse(this.Value, out number)) {
      return number;
    }
  } else {
    SExpression first = this.Children[0];
    if (SScope.BuiltinFunctions.ContainsKey(first.Value)) {
      var arguments = this.Children.Skip(1).Select(node => node.Evaluate(scope)).ToArray();
      return SScope.BuiltinFunctions[first.Value](arguments, scope);
    }
  }
  throw new Exception("THIS IS JUST TEMPORARY!");
}