add Dart implementation
[jackhill/mal.git] / dart / step5_tco.dart
diff --git a/dart/step5_tco.dart b/dart/step5_tco.dart
new file mode 100644 (file)
index 0000000..b8dd03b
--- /dev/null
@@ -0,0 +1,161 @@
+import 'dart:io';
+
+import 'core.dart';
+import 'env.dart';
+import 'printer.dart' as printer;
+import 'reader.dart' as reader;
+import 'types.dart';
+
+final Env replEnv = new Env();
+
+void setupEnv() {
+  ns.forEach((sym, fun) => replEnv.set(sym, fun));
+
+  rep('(def! not (fn* (a) (if a false true)))');
+}
+
+MalType READ(String x) => reader.read_str(x);
+
+MalType eval_ast(MalType ast, Env env) {
+  if (ast is MalSymbol) {
+    var result = env.get(ast);
+    if (result == null) {
+      throw new NotFoundException(ast.value);
+    }
+    return result;
+  } else if (ast is MalList) {
+    return new MalList(ast.elements.map((x) => EVAL(x, env)).toList());
+  } else if (ast is MalVector) {
+    return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList());
+  } else if (ast is MalHashMap) {
+    var newMap = new Map<MalSymbol, MalType>.from(ast.value);
+    for (var key in newMap.keys) {
+      newMap[key] = EVAL(newMap[key], env);
+    }
+    return new MalHashMap(newMap);
+  } else {
+    return ast;
+  }
+}
+
+MalType EVAL(MalType ast, Env env) {
+  while (true) {
+    if (ast is! MalList) {
+      return eval_ast(ast, env);
+    } else {
+      if ((ast as MalList).elements.isEmpty) {
+        return ast;
+      } else {
+        var list = ast as MalList;
+        if (list.elements.first is MalSymbol) {
+          var symbol = list.elements.first as MalSymbol;
+          var args = list.elements.sublist(1);
+          if (symbol.value == "def!") {
+            MalSymbol key = args.first;
+            MalType value = EVAL(args[1], env);
+            env.set(key, value);
+            return value;
+          } else if (symbol.value == "let*") {
+            // TODO(het): If elements.length is not even, give helpful error
+            Iterable<List<MalType>> pairs(List<MalType> elements) sync* {
+              for (var i = 0; i < elements.length; i += 2) {
+                yield [elements[i], elements[i + 1]];
+              }
+            }
+
+            var newEnv = new Env(env);
+            MalIterable bindings = args.first;
+            for (var pair in pairs(bindings.elements)) {
+              MalSymbol key = pair[0];
+              MalType value = EVAL(pair[1], newEnv);
+              newEnv.set(key, value);
+            }
+            ast = args[1];
+            env = newEnv;
+            continue;
+          } else if (symbol.value == "do") {
+            for (var element in args.sublist(0, args.length - 1)) {
+              eval_ast(element, env);
+            }
+            ast = args.last;
+            continue;
+          } else if (symbol.value == "if") {
+            var condition = EVAL(args[0], env);
+            if (condition is MalNil ||
+                condition is MalBool && condition.value == false) {
+              // False side of branch
+              if (args.length < 3) {
+                return new MalNil();
+              }
+              ast = args[2];
+              continue;
+            } else {
+              // True side of branch
+              ast = args[1];
+              continue;
+            }
+          } else if (symbol.value == "fn*") {
+            var params = (args[0] as MalIterable)
+                .elements
+                .map((e) => e as MalSymbol)
+                .toList();
+            return new MalClosure(
+                params,
+                args[1],
+                env,
+                (List<MalType> funcArgs) =>
+                    EVAL(args[1], new Env(env, params, funcArgs)));
+          }
+        }
+        var newAst = eval_ast(ast, env) as MalList;
+        var f = newAst.elements.first;
+        var args = newAst.elements.sublist(1);
+        if (f is MalBuiltin) {
+          return f.call(args);
+        } else if (f is MalClosure) {
+          ast = f.ast;
+          env = new Env(f.env, f.params, args);
+          continue;
+        } else {
+          throw 'bad!';
+        }
+      }
+    }
+  }
+}
+
+String PRINT(MalType x) => printer.pr_str(x);
+
+String rep(String x) {
+  var parsed;
+  try {
+    parsed = READ(x);
+  } on reader.ParseException catch (e) {
+    return e.message;
+  }
+
+  var evaledAst;
+  try {
+    evaledAst = EVAL(parsed, replEnv);
+  } on NotFoundException catch (e) {
+    return "'${e.value}' not found";
+  }
+  return PRINT(evaledAst);
+}
+
+const prompt = 'user> ';
+main() {
+  setupEnv();
+  while (true) {
+    stdout.write(prompt);
+    var input = stdin.readLineSync();
+    if (input == null) return;
+    var output;
+    try {
+      output = rep(input);
+    } on reader.NoInputException {
+      continue;
+    }
+    stdout.writeln(output);
+  }
+}