Commit | Line | Data |
---|---|---|
3934e3f8 HT |
1 | import 'dart:io'; |
2 | ||
3 | import 'core.dart'; | |
4 | import 'env.dart'; | |
5 | import 'printer.dart' as printer; | |
6 | import 'reader.dart' as reader; | |
7 | import 'types.dart'; | |
8 | ||
9 | final Env replEnv = new Env(); | |
10 | ||
11 | void setupEnv(List<String> argv) { | |
12 | // TODO(het): use replEnv#set once generalized tearoffs are implemented | |
13 | ns.forEach((sym, fun) => replEnv.set(sym, fun)); | |
14 | ||
15 | replEnv.set(new MalSymbol('eval'), | |
16 | new MalBuiltin((List<MalType> args) => EVAL(args.single, replEnv))); | |
17 | ||
18 | replEnv.set(new MalSymbol('*ARGV*'), | |
19 | new MalList(argv.map((s) => new MalString(s)).toList())); | |
20 | ||
21 | rep('(def! not (fn* (a) (if a false true)))'); | |
22 | rep("(def! load-file " | |
23 | "(fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); | |
24 | } | |
25 | ||
26 | MalType READ(String x) => reader.read_str(x); | |
27 | ||
28 | MalType eval_ast(MalType ast, Env env) { | |
29 | if (ast is MalSymbol) { | |
30 | var result = env.get(ast); | |
31 | if (result == null) { | |
32 | throw new NotFoundException(ast.value); | |
33 | } | |
34 | return result; | |
35 | } else if (ast is MalList) { | |
36 | return new MalList(ast.elements.map((x) => EVAL(x, env)).toList()); | |
37 | } else if (ast is MalVector) { | |
38 | return new MalVector(ast.elements.map((x) => EVAL(x, env)).toList()); | |
39 | } else if (ast is MalHashMap) { | |
40 | var newMap = new Map<MalType, MalType>.from(ast.value); | |
41 | for (var key in newMap.keys) { | |
42 | newMap[key] = EVAL(newMap[key], env); | |
43 | } | |
44 | return new MalHashMap(newMap); | |
45 | } else { | |
46 | return ast; | |
47 | } | |
48 | } | |
49 | ||
50 | MalType EVAL(MalType ast, Env env) { | |
51 | while (true) { | |
52 | if (ast is! MalList) { | |
53 | return eval_ast(ast, env); | |
54 | } else { | |
55 | if ((ast as MalList).elements.isEmpty) { | |
56 | return ast; | |
57 | } else { | |
58 | var list = ast as MalList; | |
59 | if (list.elements.first is MalSymbol) { | |
60 | var symbol = list.elements.first as MalSymbol; | |
61 | var args = list.elements.sublist(1); | |
62 | if (symbol.value == "def!") { | |
63 | MalSymbol key = args.first; | |
64 | MalType value = EVAL(args[1], env); | |
65 | env.set(key, value); | |
66 | return value; | |
67 | } else if (symbol.value == "let*") { | |
68 | // TODO(het): If elements.length is not even, give helpful error | |
69 | Iterable<List<MalType>> pairs(List<MalType> elements) sync* { | |
70 | for (var i = 0; i < elements.length; i += 2) { | |
71 | yield [elements[i], elements[i + 1]]; | |
72 | } | |
73 | } | |
74 | ||
75 | var newEnv = new Env(env); | |
76 | MalIterable bindings = args.first; | |
77 | for (var pair in pairs(bindings.elements)) { | |
78 | MalSymbol key = pair[0]; | |
79 | MalType value = EVAL(pair[1], newEnv); | |
80 | newEnv.set(key, value); | |
81 | } | |
82 | ast = args[1]; | |
83 | env = newEnv; | |
84 | continue; | |
85 | } else if (symbol.value == "do") { | |
86 | eval_ast(new MalList(args.sublist(0, args.length - 1)), env); | |
87 | ast = args.last; | |
88 | continue; | |
89 | } else if (symbol.value == "if") { | |
90 | var condition = EVAL(args[0], env); | |
91 | if (condition is MalNil || | |
92 | condition is MalBool && condition.value == false) { | |
93 | // False side of branch | |
94 | if (args.length < 3) { | |
95 | return new MalNil(); | |
96 | } | |
97 | ast = args[2]; | |
98 | continue; | |
99 | } else { | |
100 | // True side of branch | |
101 | ast = args[1]; | |
102 | continue; | |
103 | } | |
104 | } else if (symbol.value == "fn*") { | |
105 | var params = (args[0] as MalIterable) | |
106 | .elements | |
107 | .map((e) => e as MalSymbol) | |
108 | .toList(); | |
109 | return new MalClosure( | |
110 | params, | |
111 | args[1], | |
112 | env, | |
113 | (List<MalType> funcArgs) => | |
114 | EVAL(args[1], new Env(env, params, funcArgs))); | |
115 | } | |
116 | } | |
117 | var newAst = eval_ast(ast, env) as MalList; | |
118 | var f = newAst.elements.first; | |
119 | var args = newAst.elements.sublist(1); | |
120 | if (f is MalBuiltin) { | |
121 | return f.call(args); | |
122 | } else if (f is MalClosure) { | |
123 | ast = f.ast; | |
124 | env = new Env(f.env, f.params, args); | |
125 | continue; | |
126 | } else { | |
127 | throw 'bad!'; | |
128 | } | |
129 | } | |
130 | } | |
131 | } | |
132 | } | |
133 | ||
134 | String PRINT(MalType x) => printer.pr_str(x); | |
135 | ||
136 | String rep(String x) { | |
dd7a4f55 | 137 | return PRINT(EVAL(READ(x), replEnv)); |
3934e3f8 HT |
138 | } |
139 | ||
140 | const prompt = 'user> '; | |
141 | main(List<String> args) { | |
142 | setupEnv(args.isEmpty ? const <String>[] : args.sublist(1)); | |
143 | if (args.isNotEmpty) { | |
144 | rep("(load-file \"${args.first}\")"); | |
145 | return; | |
146 | } | |
147 | while (true) { | |
148 | stdout.write(prompt); | |
149 | var input = stdin.readLineSync(); | |
150 | if (input == null) return; | |
151 | var output; | |
152 | try { | |
153 | output = rep(input); | |
dd7a4f55 JM |
154 | } on reader.ParseException catch (e) { |
155 | stdout.writeln("Error: '${e.message}'"); | |
156 | continue; | |
157 | } on NotFoundException catch (e) { | |
158 | stdout.writeln("Error: '${e.value}' not found"); | |
159 | continue; | |
160 | } on MalException catch (e) { | |
161 | stdout.writeln("Error: ${printer.pr_str(e.value)}"); | |
162 | continue; | |
3934e3f8 HT |
163 | } on reader.NoInputException { |
164 | continue; | |
165 | } | |
166 | stdout.writeln(output); | |
167 | } | |
168 | } |