DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / plpgsql / step3_env.sql
1 -- ---------------------------------------------------------
2 -- step3_env.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
11 -- ---------------------------------------------------------
12
13 CREATE SCHEMA mal;
14
15 -- read
16 CREATE FUNCTION mal.READ(line varchar) RETURNS integer AS $$
17 BEGIN
18 RETURN reader.read_str(line);
19 END; $$ LANGUAGE plpgsql;
20
21 -- eval
22 CREATE FUNCTION mal.eval_ast(ast integer, env integer) RETURNS integer AS $$
23 DECLARE
24 type integer;
25 seq integer[];
26 eseq integer[];
27 hash hstore;
28 ehash hstore;
29 kv RECORD;
30 e integer;
31 result integer;
32 BEGIN
33 SELECT type_id INTO type FROM types.value WHERE value_id = ast;
34 CASE
35 WHEN type = 7 THEN
36 BEGIN
37 result := envs.get(env, ast);
38 END;
39 WHEN type IN (8, 9) THEN
40 BEGIN
41 SELECT val_seq INTO seq FROM types.value WHERE value_id = ast;
42 -- Evaluate each entry creating a new sequence
43 FOR i IN 1 .. COALESCE(array_length(seq, 1), 0) LOOP
44 eseq[i] := mal.EVAL(seq[i], env);
45 END LOOP;
46 INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq)
47 RETURNING value_id INTO result;
48 END;
49 WHEN type = 10 THEN
50 BEGIN
51 SELECT val_hash INTO hash FROM types.value WHERE value_id = ast;
52 -- Evaluate each value for every key/value
53 FOR kv IN SELECT * FROM each(hash) LOOP
54 e := mal.EVAL(CAST(kv.value AS integer), env);
55 IF ehash IS NULL THEN
56 ehash := hstore(kv.key, CAST(e AS varchar));
57 ELSE
58 ehash := ehash || hstore(kv.key, CAST(e AS varchar));
59 END IF;
60 END LOOP;
61 INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash)
62 RETURNING value_id INTO result;
63 END;
64 ELSE
65 result := ast;
66 END CASE;
67
68 RETURN result;
69 END; $$ LANGUAGE plpgsql;
70
71 CREATE FUNCTION mal.EVAL(ast integer, env integer) RETURNS integer AS $$
72 DECLARE
73 type integer;
74 a0 integer;
75 a0sym varchar;
76 a1 integer;
77 let_env integer;
78 idx integer;
79 binds integer[];
80 el integer;
81 fname varchar;
82 args integer[];
83 result integer;
84 BEGIN
85 -- PERFORM writeline(format('EVAL: %s [%s]', pr_str(ast), ast));
86 SELECT type_id INTO type FROM types.value WHERE value_id = ast;
87 IF type <> 8 THEN
88 RETURN mal.eval_ast(ast, env);
89 END IF;
90 IF types._count(ast) = 0 THEN
91 RETURN ast;
92 END IF;
93
94 a0 := types._first(ast);
95 IF types._symbol_Q(a0) THEN
96 a0sym := (SELECT val_string FROM types.value WHERE value_id = a0);
97 ELSE
98 a0sym := '__<*fn*>__';
99 END IF;
100
101 CASE
102 WHEN a0sym = 'def!' THEN
103 BEGIN
104 RETURN envs.set(env, types._nth(ast, 1),
105 mal.EVAL(types._nth(ast, 2), env));
106 END;
107 WHEN a0sym = 'let*' THEN
108 BEGIN
109 let_env := envs.new(env);
110 a1 := types._nth(ast, 1);
111 binds := (SELECT val_seq FROM types.value WHERE value_id = a1);
112 idx := 1;
113 WHILE idx < array_length(binds, 1) LOOP
114 PERFORM envs.set(let_env, binds[idx],
115 mal.EVAL(binds[idx+1], let_env));
116 idx := idx + 2;
117 END LOOP;
118 RETURN mal.EVAL(types._nth(ast, 2), let_env);
119 END;
120 ELSE
121 BEGIN
122 el := mal.eval_ast(ast, env);
123 SELECT val_string INTO fname FROM types.value
124 WHERE value_id = types._first(el);
125 args := types._restArray(el);
126 EXECUTE format('SELECT %s($1);', fname)
127 INTO result USING args;
128 RETURN result;
129 END;
130 END CASE;
131 END; $$ LANGUAGE plpgsql;
132
133 -- print
134 CREATE FUNCTION mal.PRINT(exp integer) RETURNS varchar AS $$
135 BEGIN
136 RETURN printer.pr_str(exp);
137 END; $$ LANGUAGE plpgsql;
138
139
140 -- repl
141
142 CREATE FUNCTION mal.intop(op varchar, args integer[]) RETURNS integer AS $$
143 DECLARE a integer; b integer; result integer;
144 BEGIN
145 SELECT val_int INTO a FROM types.value WHERE value_id = args[1];
146 SELECT val_int INTO b FROM types.value WHERE value_id = args[2];
147 EXECUTE format('INSERT INTO types.value (type_id, val_int)
148 VALUES (3, $1 %s $2)
149 RETURNING value_id;', op) INTO result USING a, b;
150 RETURN result;
151 END; $$ LANGUAGE plpgsql;
152
153 CREATE FUNCTION mal.add(args integer[]) RETURNS integer AS $$
154 BEGIN RETURN mal.intop('+', args); END; $$ LANGUAGE plpgsql;
155 CREATE FUNCTION mal.subtract(args integer[]) RETURNS integer AS $$
156 BEGIN RETURN mal.intop('-', args); END; $$ LANGUAGE plpgsql;
157 CREATE FUNCTION mal.multiply(args integer[]) RETURNS integer AS $$
158 BEGIN RETURN mal.intop('*', args); END; $$ LANGUAGE plpgsql;
159 CREATE FUNCTION mal.divide(args integer[]) RETURNS integer AS $$
160 BEGIN RETURN mal.intop('/', args); END; $$ LANGUAGE plpgsql;
161
162 -- repl_env is environment 0
163 INSERT INTO envs.env (env_id, outer_id, data)
164 VALUES (0, NULL, hstore(ARRAY['+', types._function('mal.add'),
165 '-', types._function('mal.subtract'),
166 '*', types._function('mal.multiply'),
167 '/', types._function('mal.divide')]));
168
169 CREATE FUNCTION mal.REP(line varchar) RETURNS varchar AS $$
170 BEGIN
171 RETURN mal.PRINT(mal.EVAL(mal.READ(line), 0));
172 END; $$ LANGUAGE plpgsql;
173
174 CREATE FUNCTION mal.MAIN(pwd varchar) RETURNS integer AS $$
175 DECLARE
176 line varchar;
177 output varchar;
178 BEGIN
179 WHILE true
180 LOOP
181 BEGIN
182 line := io.readline('user> ', 0);
183 IF line IS NULL THEN
184 PERFORM io.close(1);
185 RETURN 0;
186 END IF;
187 IF line NOT IN ('', E'\n') THEN
188 output := mal.REP(line);
189 PERFORM io.writeline(output);
190 END IF;
191
192 EXCEPTION WHEN OTHERS THEN
193 PERFORM io.writeline('Error: ' || SQLERRM);
194 END;
195 END LOOP;
196 END; $$ LANGUAGE plpgsql;