SIGN IN SIGN UP
-- ---------------------------------------------------------
-- step9_try.sql
2016-03-24 21:10:10 -05:00
\i init.sql
\i io.sql
2016-03-24 21:10:10 -05:00
\i types.sql
\i reader.sql
\i printer.sql
\i envs.sql
2016-03-24 21:10:10 -05:00
\i core.sql
-- ---------------------------------------------------------
CREATE SCHEMA mal;
2016-03-24 21:10:10 -05:00
-- read
CREATE FUNCTION mal.READ(line varchar) RETURNS integer AS $$
2016-03-24 21:10:10 -05:00
BEGIN
RETURN reader.read_str(line);
2016-03-24 21:10:10 -05:00
END; $$ LANGUAGE plpgsql;
-- eval
CREATE FUNCTION mal.eval_debug(ast integer, env integer) RETURNS void AS $$
DECLARE
val constant integer := envs.get(env, 'DEBUG-EVAL');
BEGIN
IF val IS NOT NULL THEN
IF (SELECT type_id FROM types.value WHERE value_id = val) NOT IN (0, 1)
THEN
PERFORM io.writeline(format('EVAL: %s [%s]', mal.PRINT(ast), ast));
END IF;
END IF;
END; $$ LANGUAGE plpgsql;
CREATE FUNCTION mal.qq_loop(elt integer, acc integer) RETURNS integer AS $$
DECLARE
a0 integer;
2016-03-24 21:10:10 -05:00
BEGIN
IF types._list_Q(elt) AND types._count(elt) = 2 THEN
a0 := types._first(elt);
IF types._symbol_Q(a0) AND a0 = types._symbolv('splice-unquote') THEN
RETURN types._list(ARRAY[types._symbolv('concat'), types._nth(elt, 1), acc]);
END IF;
END IF;
RETURN types._list(ARRAY[types._symbolv('cons'), mal.quasiquote(elt), acc]);
END; $$ LANGUAGE plpgsql;
CREATE FUNCTION mal.qq_foldr(xs integer) RETURNS integer AS $$
DECLARE
elt integer;
acc integer := types._list(ARRAY[]::integer[]);
BEGIN
FOREACH elt IN ARRAY types.array_reverse(types._valueToArray(xs)) LOOP
acc := mal.qq_loop(elt, acc);
END LOOP;
RETURN acc;
2016-03-24 21:10:10 -05:00
END; $$ LANGUAGE plpgsql;
CREATE FUNCTION mal.quasiquote(ast integer) RETURNS integer AS $$
2016-03-24 21:10:10 -05:00
BEGIN
CASE type_id FROM types.value WHERE value_id = ast
WHEN 8 THEN -- list
DECLARE
a0 integer;
BEGIN
IF types._count(ast) = 2 THEN
a0 := types._first(ast);
IF types._symbol_Q(a0) AND a0 = types._symbolv('unquote') THEN
RETURN types._nth(ast, 1);
2016-03-24 21:10:10 -05:00
END IF;
END IF;
RETURN mal.qq_foldr(ast);
END;
WHEN 9 THEN -- vector
RETURN types._list(ARRAY[types._symbolv('vec'), mal.qq_foldr(ast)]);
WHEN 7, 10 THEN -- symbol or map
RETURN types._list(ARRAY[types._symbolv('quote'), ast]);
ELSE
RETURN ast;
END CASE;
2016-03-24 21:10:10 -05:00
END; $$ LANGUAGE plpgsql;
CREATE FUNCTION mal.eval_symbol(ast integer, env integer) RETURNS integer
AS $$
DECLARE
symkey constant varchar := types._valueToString(ast);
result constant integer := envs.get(env, symkey);
2016-03-24 21:10:10 -05:00
BEGIN
IF result IS NULL THEN
RAISE EXCEPTION '''%'' not found', symkey;
END IF;
RETURN result;
2016-03-24 21:10:10 -05:00
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION mal.eval_vector(ast integer, env integer) RETURNS integer
AS $$
DECLARE
seq constant integer[] := types._valueToArray(ast);
eseq integer[];
result integer;
2016-03-24 21:10:10 -05:00
BEGIN
-- 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 (9, eseq)
2016-03-25 23:11:40 -05:00
RETURNING value_id INTO result;
RETURN result;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION mal.eval_map(ast integer, env integer) RETURNS integer
AS $$
DECLARE
hash hstore;
ehash hstore;
kv RECORD;
e integer;
result integer;
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;
2016-03-24 21:10:10 -05:00
END LOOP;
INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash)
RETURNING value_id INTO result;
RETURN result;
2016-03-24 21:10:10 -05:00
END;
$$ LANGUAGE plpgsql;
2016-03-24 21:10:10 -05:00
CREATE FUNCTION mal.EVAL(ast integer, env integer) RETURNS integer AS $$
2016-03-24 21:10:10 -05:00
DECLARE
a0 integer;
BEGIN
LOOP
PERFORM mal.eval_debug(ast, env);
CASE type_id FROM types.value WHERE value_id = ast
WHEN 7 THEN RETURN mal.eval_symbol(ast, env);
WHEN 8 THEN NULL; -- List, proceed after this case statement.
WHEN 9 THEN RETURN mal.eval_vector(ast, env);
WHEN 10 THEN RETURN mal.eval_map(ast, env);
ELSE RETURN ast;
END CASE;
IF types._count(ast) = 0 THEN
RETURN ast;
2016-03-24 21:10:10 -05:00
END IF;
a0 := types._first(ast);
IF types._symbol_Q(a0) THEN
2016-03-24 21:10:10 -05:00
CASE val_string FROM types.value WHERE value_id = a0
WHEN 'def!' THEN
RETURN envs.set(env, types._nth(ast, 1),
mal.EVAL(types._nth(ast, 2), env));
WHEN 'let*' THEN
DECLARE
let_env constant integer := envs.new(env);
binds constant integer[] := types._valueToArray(types._nth(ast, 1));
2016-03-24 21:10:10 -05:00
BEGIN
FOR idx IN 1 .. array_length(binds, 1) BY 2 LOOP
PERFORM envs.set(let_env, binds[idx],
mal.EVAL(binds[idx+1], let_env));
2016-03-24 21:10:10 -05:00
END LOOP;
env := let_env;
ast := types._nth(ast, 2);
2016-03-24 21:10:10 -05:00
CONTINUE; -- TCO
END;
WHEN 'quote' THEN
RETURN types._nth(ast, 1);
WHEN 'quasiquote' THEN
2016-03-24 21:10:10 -05:00
BEGIN
ast := mal.quasiquote(types._nth(ast, 1));
2016-03-24 21:10:10 -05:00
CONTINUE; -- TCO
END;
WHEN 'defmacro!' THEN
RETURN envs.set(env, types._nth(ast, 1),
types._macro(mal.EVAL(types._nth(ast, 2), env)));
WHEN 'try*' THEN
DECLARE
a1 constant integer := types._nth(ast, 1);
a2 integer;
2016-03-24 21:10:10 -05:00
BEGIN
IF types._count(ast) >= 3 THEN
a2 = types._nth(ast, 2);
IF types._valueToString(types._nth(a2, 0)) = 'catch*' THEN
BEGIN
RETURN mal.EVAL(a1, env);
EXCEPTION WHEN OTHERS THEN
env := envs.new(env);
PERFORM envs.set(env, types._nth(a2, 1),
types._stringv(SQLERRM));
ast := types._nth(a2, 2);
CONTINUE; -- TCO
END;
END IF;
END IF;
ast := a1;
CONTINUE; -- TCO
2016-03-24 21:10:10 -05:00
END;
WHEN 'do' THEN
DECLARE
ignored integer;
2016-03-24 21:10:10 -05:00
BEGIN
FOR i IN 1 .. types._count(ast) - 2 LOOP
ignored := mal.EVAL(types._nth(ast, i), env);
END LOOP;
ast := types._nth(ast, types._count(ast)-1);
2016-03-24 21:10:10 -05:00
CONTINUE; -- TCO
END;
WHEN 'if' THEN
IF (SELECT type_id FROM types.value
WHERE value_id = mal.EVAL(types._nth(ast, 1), env))
IN (0, 1)
THEN -- nil or false
IF types._count(ast) > 3 THEN
ast := types._nth(ast, 3);
2016-03-24 21:10:10 -05:00
CONTINUE; -- TCO
ELSE
RETURN 0; -- nil
END IF;
ELSE
ast := types._nth(ast, 2);
2016-03-24 21:10:10 -05:00
CONTINUE; -- TCO
END IF;
WHEN 'fn*' THEN
RETURN types._malfunc(types._nth(ast, 2), types._nth(ast, 1), env);
2016-03-24 21:10:10 -05:00
ELSE
NULL;
END CASE;
END IF;
-- Apply phase.
DECLARE
type integer;
fname varchar;
fast integer;
fparams integer;
fenv integer;
fmacro boolean;
args integer[] := ARRAY[]::integer[];
result integer;
evda0 constant integer := mal.EVAL(a0, env);
2016-03-24 21:10:10 -05:00
BEGIN
SELECT type_id, val_string, ast_id, params_id, env_id, macro
INTO type, fname, fast, fparams, fenv, fmacro
FROM types.value WHERE value_id = evda0;
IF fmacro THEN
ast := types._apply(evda0, types._restArray(ast));
CONTINUE; -- TCO
END IF;
FOR i in 1 .. types._count(ast) - 1 LOOP
args[i] := mal.EVAL(types._nth(ast, i), env);
END LOOP;
2016-03-24 21:10:10 -05:00
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);
2016-03-24 21:10:10 -05:00
ast := fast;
CONTINUE; -- TCO
ELSE
RAISE EXCEPTION 'Invalid function call';
END IF;
END;
END LOOP;
END; $$ LANGUAGE plpgsql;
-- print
CREATE FUNCTION mal.PRINT(exp integer) RETURNS varchar AS $$
2016-03-24 21:10:10 -05:00
BEGIN
RETURN printer.pr_str(exp);
2016-03-24 21:10:10 -05:00
END; $$ LANGUAGE plpgsql;
-- repl
-- repl_env is environment 0
CREATE FUNCTION mal.REP(line varchar) RETURNS varchar AS $$
2016-03-24 21:10:10 -05:00
BEGIN
RETURN mal.PRINT(mal.EVAL(mal.READ(line), 0));
2016-03-24 21:10:10 -05:00
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 $$
2016-03-24 21:10:10 -05:00
BEGIN
RETURN mal.EVAL(args[1], 0);
2016-03-24 21:10:10 -05:00
END; $$ LANGUAGE plpgsql;
INSERT INTO types.value (type_id, val_string) VALUES (11, 'mal.mal_eval');
2016-03-24 21:10:10 -05:00
SELECT envs.vset(0, 'eval',
(SELECT value_id FROM types.value
WHERE val_string = 'mal.mal_eval')) \g '/dev/null'
2016-03-24 21:10:10 -05:00
-- *ARGV* values are set by RUN
SELECT envs.vset(0, '*ARGV*', mal.READ('()')) \g '/dev/null'
2016-03-24 21:10:10 -05:00
-- 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) "\nnil)")))))') \g '/dev/null'
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'
2016-03-24 21:10:10 -05:00
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;