Merge pull request #440 from aasimk2000/add-nil-if-test
[jackhill/mal.git] / plpgsql / step8_macros.sql
1 -- ---------------------------------------------------------
2 -- step8_macros.sql
3
4 \i init.sql
5 \i io.sql
6 \i types.sql
7 \i reader.sql
8 \i printer.sql
9 \i envs.sql
10 \i core.sql
11
12 -- ---------------------------------------------------------
13
14 CREATE SCHEMA mal;
15
16 -- read
17 CREATE FUNCTION mal.READ(line varchar) RETURNS integer AS $$
18 BEGIN
19 RETURN reader.read_str(line);
20 END; $$ LANGUAGE plpgsql;
21
22 -- eval
23 CREATE FUNCTION mal.is_pair(ast integer) RETURNS boolean AS $$
24 BEGIN
25 RETURN types._sequential_Q(ast) AND types._count(ast) > 0;
26 END; $$ LANGUAGE plpgsql;
27
28 CREATE FUNCTION mal.quasiquote(ast integer) RETURNS integer AS $$
29 DECLARE
30 a0 integer;
31 a00 integer;
32 BEGIN
33 IF NOT mal.is_pair(ast) THEN
34 RETURN types._list(ARRAY[types._symbolv('quote'), ast]);
35 ELSE
36 a0 := types._nth(ast, 0);
37 IF types._symbol_Q(a0) AND a0 = types._symbolv('unquote') THEN
38 RETURN types._nth(ast, 1);
39 ELSE
40 a00 := types._nth(a0, 0);
41 IF types._symbol_Q(a00) AND
42 a00 = types._symbolv('splice-unquote') THEN
43 RETURN types._list(ARRAY[types._symbolv('concat'),
44 types._nth(a0, 1),
45 mal.quasiquote(types._rest(ast))]);
46 END IF;
47 END IF;
48 RETURN types._list(ARRAY[types._symbolv('cons'),
49 mal.quasiquote(types._first(ast)),
50 mal.quasiquote(types._rest(ast))]);
51 END IF;
52 END; $$ LANGUAGE plpgsql;
53
54 CREATE FUNCTION mal.is_macro_call(ast integer, env integer) RETURNS boolean AS $$
55 DECLARE
56 a0 integer;
57 f integer;
58 result boolean = false;
59 BEGIN
60 IF types._list_Q(ast) THEN
61 a0 = types._first(ast);
62 IF types._symbol_Q(a0) AND
63 envs.find(env, types._valueToString(a0)) IS NOT NULL THEN
64 f := envs.get(env, a0);
65 SELECT macro INTO result FROM types.value WHERE value_id = f;
66 END IF;
67 END IF;
68 RETURN result;
69 END; $$ LANGUAGE plpgsql;
70
71 CREATE FUNCTION mal.macroexpand(ast integer, env integer) RETURNS integer AS $$
72 DECLARE
73 mac integer;
74 BEGIN
75 WHILE mal.is_macro_call(ast, env)
76 LOOP
77 mac := envs.get(env, types._first(ast));
78 ast := types._apply(mac, types._valueToArray(types._rest(ast)));
79 END LOOP;
80 RETURN ast;
81 END; $$ LANGUAGE plpgsql;
82
83 CREATE FUNCTION mal.eval_ast(ast integer, env integer) RETURNS integer AS $$
84 DECLARE
85 type integer;
86 seq integer[];
87 eseq integer[];
88 hash hstore;
89 ehash hstore;
90 kv RECORD;
91 e integer;
92 result integer;
93 BEGIN
94 SELECT type_id INTO type FROM types.value WHERE value_id = ast;
95 CASE
96 WHEN type = 7 THEN
97 BEGIN
98 result := envs.get(env, ast);
99 END;
100 WHEN type IN (8, 9) THEN
101 BEGIN
102 SELECT val_seq INTO seq FROM types.value WHERE value_id = ast;
103 -- Evaluate each entry creating a new sequence
104 FOR i IN 1 .. COALESCE(array_length(seq, 1), 0) LOOP
105 eseq[i] := mal.EVAL(seq[i], env);
106 END LOOP;
107 INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq)
108 RETURNING value_id INTO result;
109 END;
110 WHEN type = 10 THEN
111 BEGIN
112 SELECT val_hash INTO hash FROM types.value WHERE value_id = ast;
113 -- Evaluate each value for every key/value
114 FOR kv IN SELECT * FROM each(hash) LOOP
115 e := mal.EVAL(CAST(kv.value AS integer), env);
116 IF ehash IS NULL THEN
117 ehash := hstore(kv.key, CAST(e AS varchar));
118 ELSE
119 ehash := ehash || hstore(kv.key, CAST(e AS varchar));
120 END IF;
121 END LOOP;
122 INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash)
123 RETURNING value_id INTO result;
124 END;
125 ELSE
126 result := ast;
127 END CASE;
128
129 RETURN result;
130 END; $$ LANGUAGE plpgsql;
131
132 CREATE FUNCTION mal.EVAL(ast integer, env integer) RETURNS integer AS $$
133 DECLARE
134 type integer;
135 a0 integer;
136 a0sym varchar;
137 a1 integer;
138 let_env integer;
139 idx integer;
140 binds integer[];
141 el integer;
142 fn integer;
143 fname varchar;
144 args integer[];
145 cond integer;
146 fast integer;
147 fparams integer;
148 fenv integer;
149 result integer;
150 BEGIN
151 LOOP
152 -- PERFORM writeline(format('EVAL: %s [%s]', pr_str(ast), ast));
153 SELECT type_id INTO type FROM types.value WHERE value_id = ast;
154 IF type <> 8 THEN
155 RETURN mal.eval_ast(ast, env);
156 END IF;
157
158 ast := mal.macroexpand(ast, env);
159 SELECT type_id INTO type FROM types.value WHERE value_id = ast;
160 IF type <> 8 THEN
161 RETURN mal.eval_ast(ast, env);
162 END IF;
163 IF types._count(ast) = 0 THEN
164 RETURN ast;
165 END IF;
166
167 a0 := types._first(ast);
168 IF types._symbol_Q(a0) THEN
169 a0sym := (SELECT val_string FROM types.value WHERE value_id = a0);
170 ELSE
171 a0sym := '__<*fn*>__';
172 END IF;
173
174 CASE
175 WHEN a0sym = 'def!' THEN
176 BEGIN
177 RETURN envs.set(env, types._nth(ast, 1),
178 mal.EVAL(types._nth(ast, 2), env));
179 END;
180 WHEN a0sym = 'let*' THEN
181 BEGIN
182 let_env := envs.new(env);
183 a1 := types._nth(ast, 1);
184 binds := (SELECT val_seq FROM types.value WHERE value_id = a1);
185 idx := 1;
186 WHILE idx < array_length(binds, 1) LOOP
187 PERFORM envs.set(let_env, binds[idx],
188 mal.EVAL(binds[idx+1], let_env));
189 idx := idx + 2;
190 END LOOP;
191 env := let_env;
192 ast := types._nth(ast, 2);
193 CONTINUE; -- TCO
194 END;
195 WHEN a0sym = 'quote' THEN
196 BEGIN
197 RETURN types._nth(ast, 1);
198 END;
199 WHEN a0sym = 'quasiquote' THEN
200 BEGIN
201 ast := mal.quasiquote(types._nth(ast, 1));
202 CONTINUE; -- TCO
203 END;
204 WHEN a0sym = 'defmacro!' THEN
205 BEGIN
206 fn := mal.EVAL(types._nth(ast, 2), env);
207 fn := types._macro(fn);
208 RETURN envs.set(env, types._nth(ast, 1), fn);
209 END;
210 WHEN a0sym = 'macroexpand' THEN
211 BEGIN
212 RETURN mal.macroexpand(types._nth(ast, 1), env);
213 END;
214 WHEN a0sym = 'do' THEN
215 BEGIN
216 PERFORM mal.eval_ast(types._slice(ast, 1, types._count(ast)-1), env);
217 ast := types._nth(ast, types._count(ast)-1);
218 CONTINUE; -- TCO
219 END;
220 WHEN a0sym = 'if' THEN
221 BEGIN
222 cond := mal.EVAL(types._nth(ast, 1), env);
223 SELECT type_id INTO type FROM types.value WHERE value_id = cond;
224 IF type = 0 OR type = 1 THEN -- nil or false
225 IF types._count(ast) > 3 THEN
226 ast := types._nth(ast, 3);
227 CONTINUE; -- TCO
228 ELSE
229 RETURN 0; -- nil
230 END IF;
231 ELSE
232 ast := types._nth(ast, 2);
233 CONTINUE; -- TCO
234 END IF;
235 END;
236 WHEN a0sym = 'fn*' THEN
237 BEGIN
238 RETURN types._malfunc(types._nth(ast, 2), types._nth(ast, 1), env);
239 END;
240 ELSE
241 BEGIN
242 el := mal.eval_ast(ast, env);
243 SELECT type_id, val_string, ast_id, params_id, env_id
244 INTO type, fname, fast, fparams, fenv
245 FROM types.value WHERE value_id = types._first(el);
246 args := types._restArray(el);
247 IF type = 11 THEN
248 EXECUTE format('SELECT %s($1);', fname)
249 INTO result USING args;
250 RETURN result;
251 ELSIF type = 12 THEN
252 env := envs.new(fenv, fparams, args);
253 ast := fast;
254 CONTINUE; -- TCO
255 ELSE
256 RAISE EXCEPTION 'Invalid function call';
257 END IF;
258 END;
259 END CASE;
260 END LOOP;
261 END; $$ LANGUAGE plpgsql;
262
263 -- print
264 CREATE FUNCTION mal.PRINT(exp integer) RETURNS varchar AS $$
265 BEGIN
266 RETURN printer.pr_str(exp);
267 END; $$ LANGUAGE plpgsql;
268
269
270 -- repl
271
272 -- repl_env is environment 0
273
274 CREATE FUNCTION mal.REP(line varchar) RETURNS varchar AS $$
275 BEGIN
276 RETURN mal.PRINT(mal.EVAL(mal.READ(line), 0));
277 END; $$ LANGUAGE plpgsql;
278
279 -- core.sql: defined using SQL (in core.sql)
280 -- repl_env is created and populated with core functions in by core.sql
281 CREATE FUNCTION mal.mal_eval(args integer[]) RETURNS integer AS $$
282 BEGIN
283 RETURN mal.EVAL(args[1], 0);
284 END; $$ LANGUAGE plpgsql;
285 INSERT INTO types.value (type_id, val_string) VALUES (11, 'mal.mal_eval');
286
287 SELECT envs.vset(0, 'eval',
288 (SELECT value_id FROM types.value
289 WHERE val_string = 'mal.mal_eval')) \g '/dev/null'
290 -- *ARGV* values are set by RUN
291 SELECT envs.vset(0, '*ARGV*', mal.READ('()')) \g '/dev/null'
292
293
294 -- core.mal: defined using the language itself
295 SELECT mal.REP('(def! not (fn* (a) (if a false true)))') \g '/dev/null'
296 SELECT mal.REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))') \g '/dev/null'
297 SELECT mal.REP('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list ''if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons ''cond (rest (rest xs)))))))') \g '/dev/null'
298
299 CREATE FUNCTION mal.MAIN(pwd varchar, argstring varchar DEFAULT NULL)
300 RETURNS integer AS $$
301 DECLARE
302 line varchar;
303 output varchar;
304 allargs integer;
305 BEGIN
306 PERFORM envs.vset(0, '*PWD*', types._stringv(pwd));
307
308 IF argstring IS NOT NULL THEN
309 allargs := mal.READ(argstring);
310 PERFORM envs.vset(0, '*ARGV*', types._rest(allargs));
311 PERFORM mal.REP('(load-file ' ||
312 printer.pr_str(types._first(allargs)) || ')');
313 PERFORM io.close(1);
314 PERFORM io.wait_flushed(1);
315 RETURN 0;
316 END IF;
317
318 WHILE true
319 LOOP
320 BEGIN
321 line := io.readline('user> ', 0);
322 IF line IS NULL THEN
323 PERFORM io.close(1);
324 RETURN 0;
325 END IF;
326 IF line NOT IN ('', E'\n') THEN
327 output := mal.REP(line);
328 PERFORM io.writeline(output);
329 END IF;
330
331 EXCEPTION WHEN OTHERS THEN
332 PERFORM io.writeline('Error: ' || SQLERRM);
333 END;
334 END LOOP;
335 END; $$ LANGUAGE plpgsql;