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