All: TCO let* and quasiquote.
[jackhill/mal.git] / cs / step7_quote.cs
CommitLineData
5a159ae7
JM
1using System;
2using System.IO;
3using System.Collections;
4using System.Collections.Generic;
5using Mal;
6using MalVal = Mal.types.MalVal;
7using MalString = Mal.types.MalString;
8using MalSymbol = Mal.types.MalSymbol;
9using MalInteger = Mal.types.MalInteger;
10using MalList = Mal.types.MalList;
11using MalVector = Mal.types.MalVector;
12using MalHashMap = Mal.types.MalHashMap;
13using MalFunction = Mal.types.MalFunction;
14using Env = Mal.env.Env;
15
16namespace Mal {
8cb5cda4 17 class step7_quote {
5a159ae7
JM
18 // read
19 static MalVal READ(string str) {
20 return reader.read_str(str);
21 }
22
23 // eval
24 public static bool is_pair(MalVal x) {
25 return x is MalList && ((MalList)x).size() > 0;
26 }
27
28 public static MalVal quasiquote(MalVal ast) {
29 if (!is_pair(ast)) {
30 return new MalList(new MalSymbol("quote"), ast);
31 } else {
32 MalVal a0 = ((MalList)ast)[0];
33 if ((a0 is MalSymbol) &&
34 (((MalSymbol)a0).getName() == "unquote")) {
35 return ((MalList)ast)[1];
36 } else if (is_pair(a0)) {
37 MalVal a00 = ((MalList)a0)[0];
38 if ((a00 is MalSymbol) &&
39 (((MalSymbol)a00).getName() == "splice-unquote")) {
40 return new MalList(new MalSymbol("concat"),
41 ((MalList)a0)[1],
42 quasiquote(((MalList)ast).rest()));
43 }
44 }
45 return new MalList(new MalSymbol("cons"),
46 quasiquote(a0),
47 quasiquote(((MalList)ast).rest()));
48 }
49 }
50
51 static MalVal eval_ast(MalVal ast, Env env) {
52 if (ast is MalSymbol) {
53 MalSymbol sym = (MalSymbol)ast;
54 return env.get(sym.getName());
55 } else if (ast is MalList) {
56 MalList old_lst = (MalList)ast;
57 MalList new_lst = ast.list_Q() ? new MalList()
58 : (MalList)new MalVector();
59 foreach (MalVal mv in old_lst.getValue()) {
60 new_lst.conj_BANG(EVAL(mv, env));
61 }
62 return new_lst;
63 } else if (ast is MalHashMap) {
64 var new_dict = new Dictionary<string, MalVal>();
65 foreach (var entry in ((MalHashMap)ast).getValue()) {
66 new_dict.Add(entry.Key, EVAL((MalVal)entry.Value, env));
67 }
68 return new MalHashMap(new_dict);
69 } else {
70 return ast;
71 }
72 }
73
74
75 static MalVal EVAL(MalVal orig_ast, Env env) {
76 MalVal a0, a1, a2, res;
77 MalList el;
78
79 while (true) {
80
81 //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
82 if (!orig_ast.list_Q()) {
83 return eval_ast(orig_ast, env);
84 }
85
86 // apply list
87 MalList ast = (MalList)orig_ast;
88 if (ast.size() == 0) { return ast; }
89 a0 = ast[0];
90
91 String a0sym = a0 is MalSymbol ? ((MalSymbol)a0).getName()
92 : "__<*fn*>__";
93
94 switch (a0sym) {
95 case "def!":
96 a1 = ast[1];
97 a2 = ast[2];
98 res = EVAL(a2, env);
99 env.set(((MalSymbol)a1).getName(), res);
100 return res;
101 case "let*":
102 a1 = ast[1];
103 a2 = ast[2];
104 MalSymbol key;
105 MalVal val;
106 Env let_env = new Env(env);
107 for(int i=0; i<((MalList)a1).size(); i+=2) {
108 key = (MalSymbol)((MalList)a1)[i];
109 val = ((MalList)a1)[i+1];
110 let_env.set(key.getName(), EVAL(val, let_env));
111 }
6301e0b6
JM
112 orig_ast = a2;
113 env = let_env;
114 break;
5a159ae7
JM
115 case "quote":
116 return ast[1];
117 case "quasiquote":
6301e0b6
JM
118 orig_ast = quasiquote(ast[1]);
119 break;
5a159ae7
JM
120 case "do":
121 eval_ast(ast.slice(1, ast.size()-1), env);
122 orig_ast = ast[ast.size()-1];
123 break;
124 case "if":
125 a1 = ast[1];
126 MalVal cond = EVAL(a1, env);
127 if (cond == Mal.types.Nil || cond == Mal.types.False) {
128 // eval false slot form
129 if (ast.size() > 3) {
130 orig_ast = ast[3];
131 } else {
132 return Mal.types.Nil;
133 }
134 } else {
135 // eval true slot form
136 orig_ast = ast[2];
137 }
138 break;
139 case "fn*":
140 MalList a1f = (MalList)ast[1];
141 MalVal a2f = ast[2];
142 Env cur_env = env;
143 return new MalFunction(a2f, env, a1f,
144 args => EVAL(a2f, new Env(cur_env, a1f, args)) );
145 default:
146 el = (MalList)eval_ast(ast, env);
147 var f = (MalFunction)el[0];
148 MalVal fnast = f.getAst();
149 if (fnast != null) {
150 orig_ast = fnast;
151 env = f.genEnv(el.rest());
152 } else {
153 return f.apply(el.rest());
154 }
155 break;
156 }
157
158 }
159 }
160
161 // print
162 static string PRINT(MalVal exp) {
163 return printer._pr_str(exp, true);
164 }
165
86b689f3 166 // repl
5a159ae7
JM
167 static MalVal RE(Env env, string str) {
168 return EVAL(READ(str), env);
169 }
5a159ae7
JM
170
171 static void Main(string[] args) {
172 string prompt = "user> ";
173
8cb5cda4
JM
174 // core.cs: defined using C#
175 var repl_env = new env.Env(null);
176 foreach (var entry in core.ns) {
177 repl_env.set(entry.Key, entry.Value);
5a159ae7 178 }
8cb5cda4 179 repl_env.set("eval", new MalFunction(a => EVAL(a[0], repl_env)));
86b689f3
JM
180 MalList _argv = new MalList();
181 for (int i=1; i < args.Length; i++) {
182 _argv.conj_BANG(new MalString(args[i]));
183 }
184 repl_env.set("*ARGV*", _argv);
5a159ae7 185
8cb5cda4 186 // core.mal: defined using the language itself
5a159ae7
JM
187 RE(repl_env, "(def! not (fn* (a) (if a false true)))");
188 RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
189
190 int fileIdx = 0;
191 if (args.Length > 0 && args[0] == "--raw") {
192 Mal.readline.mode = Mal.readline.Mode.Raw;
193 fileIdx = 1;
194 }
195 if (args.Length > fileIdx) {
86b689f3 196 RE(repl_env, "(load-file \"" + args[fileIdx] + "\")");
5a159ae7
JM
197 return;
198 }
86b689f3
JM
199
200 // repl loop
5a159ae7
JM
201 while (true) {
202 string line;
203 try {
204 line = Mal.readline.Readline(prompt);
205 if (line == null) { break; }
206 } catch (IOException e) {
207 Console.WriteLine("IOException: " + e.Message);
208 break;
209 }
210 try {
211 Console.WriteLine(PRINT(RE(repl_env, line)));
212 } catch (Mal.types.MalContinue) {
213 continue;
5a159ae7
JM
214 } catch (Exception e) {
215 Console.WriteLine("Error: " + e.Message);
216 Console.WriteLine(e.StackTrace);
217 continue;
218 }
219 }
220 }
221 }
222}