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(List argv) { // TODO(het): use replEnv#set once generalized tearoffs are implemented ns.forEach((sym, fun) => replEnv.set(sym, fun)); replEnv.set(new MalSymbol('eval'), new MalBuiltin((List args) => EVAL(args.single, replEnv))); replEnv.set(new MalSymbol('*ARGV*'), new MalList(argv.map((s) => new MalString(s)).toList())); rep('(def! not (fn* (a) (if a false true)))'); rep("(def! load-file " "(fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); } 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.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> pairs(List 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") { eval_ast(new MalList(args.sublist(0, args.length - 1)), 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 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(List args) { setupEnv(args.isEmpty ? const [] : args.sublist(1)); if (args.isNotEmpty) { rep("(load-file \"${args.first}\")"); return; } 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); } }