From 18c2fc758b229414b48b56dfd0e6004426ccb0b4 Mon Sep 17 00:00:00 2001 From: Nicolas Boulenguez Date: Thu, 24 Oct 2024 19:51:07 +0200 Subject: [PATCH] plpgsql: merge evalASt {quasiquote,macro}expand, add DEBUG-EVAL env.get: loop instead of a recursion, string parameter (merged with vget), do not raise an exception when the key is missing. Split eval_ast in eval_{symbol,vector,map} in order to reduce the diff and improve readability of EVAL. Use existing blocks to declare variables near their use (several were unused). Simplify try*, perform TCO. --- impls/plpgsql/core.sql | 2 +- impls/plpgsql/envs.sql | 38 +--- impls/plpgsql/step2_eval.sql | 84 +++++---- impls/plpgsql/step3_env.sql | 135 ++++++++------ impls/plpgsql/step4_if_fn_do.sql | 173 ++++++++++-------- impls/plpgsql/step5_tco.sql | 172 ++++++++++-------- impls/plpgsql/step6_file.sql | 172 ++++++++++-------- impls/plpgsql/step7_quote.sql | 205 +++++++++++---------- impls/plpgsql/step8_macros.sql | 261 ++++++++++++--------------- impls/plpgsql/step9_try.sql | 299 ++++++++++++++----------------- impls/plpgsql/stepA_mal.sql | 299 ++++++++++++++----------------- 11 files changed, 926 insertions(+), 914 deletions(-) diff --git a/impls/plpgsql/core.sql b/impls/plpgsql/core.sql index eb9f0b7176..a989154d7e 100644 --- a/impls/plpgsql/core.sql +++ b/impls/plpgsql/core.sql @@ -131,7 +131,7 @@ DECLARE BEGIN fname := types._valueToString(args[1]); IF fname NOT LIKE '/%' THEN - fname := types._valueToString(envs.vget(0, '*PWD*')) || '/' || fname; + fname := types._valueToString(envs.get(0, '*PWD*')) || '/' || fname; END IF; tmp := CAST(round(random()*1000000) AS varchar); diff --git a/impls/plpgsql/envs.sql b/impls/plpgsql/envs.sql index b856ba2071..b2b977fd71 100644 --- a/impls/plpgsql/envs.sql +++ b/impls/plpgsql/envs.sql @@ -91,43 +91,21 @@ BEGIN RETURN envs.vset(env, symkey, val); END; $$ LANGUAGE plpgsql; --- envs.find -CREATE FUNCTION envs.find(env integer, symkey varchar) RETURNS integer AS $$ +-- envs.get +CREATE FUNCTION envs.get(env integer, symkey varchar) RETURNS integer AS $$ DECLARE outer_id integer; d hstore; - val integer; BEGIN + LOOP SELECT e.data, e.outer_id INTO d, outer_id FROM envs.env e WHERE e.env_id = env; IF d ? symkey THEN - RETURN env; - ELSIF outer_id IS NOT NULL THEN - RETURN envs.find(outer_id, symkey); - ELSE - RETURN NULL; + RETURN (SELECT data -> symkey FROM envs.env WHERE env_id = e); END IF; -END; $$ LANGUAGE plpgsql; - - --- envs.vget -CREATE FUNCTION envs.vget(env integer, symkey varchar) RETURNS integer AS $$ -DECLARE - result integer; - e integer; -BEGIN - e := envs.find(env, symkey); - --RAISE NOTICE 'envs.find env: %, symkey: % -> e: %', env, symkey, e; - IF e IS NULL THEN - RAISE EXCEPTION '''%'' not found', symkey; - ELSE - SELECT data -> symkey INTO result FROM envs.env WHERE env_id = e; + env := outer_id; + IF env IS NULL THEN + RETURN NULL; END IF; - RETURN result; -END; $$ LANGUAGE plpgsql; - --- envs.get -CREATE FUNCTION envs.get(env integer, key integer) RETURNS integer AS $$ -BEGIN - RETURN envs.vget(env, types._valueToString(key)); + END LOOP; END; $$ LANGUAGE plpgsql; diff --git a/impls/plpgsql/step2_eval.sql b/impls/plpgsql/step2_eval.sql index ba818ec27f..4bb48c3b30 100644 --- a/impls/plpgsql/step2_eval.sql +++ b/impls/plpgsql/step2_eval.sql @@ -18,40 +18,45 @@ BEGIN END; $$ LANGUAGE plpgsql; -- eval -CREATE FUNCTION mal.eval_ast(ast integer, env hstore) RETURNS integer AS $$ -DECLARE - type integer; - symkey varchar; - 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 + +CREATE FUNCTION mal.eval_symbol(ast integer, env hstore) RETURNS integer +AS $$ + DECLARE + symkey constant varchar := types._valueToString(ast); BEGIN - symkey := types._valueToString(ast); IF env ? symkey THEN - result := env -> symkey; + RETURN env -> symkey; ELSE RAISE EXCEPTION '''%'' not found', symkey; END IF; END; - WHEN type IN (8, 9) THEN +$$ LANGUAGE plpgsql; + +CREATE FUNCTION mal.eval_vector(ast integer, env hstore) RETURNS integer +AS $$ + DECLARE + seq constant integer[] := types._valueToArray(ast); + eseq integer[]; + result integer; 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 + FOR i IN 1 .. array_length(seq, 1) LOOP eseq[i] := mal.EVAL(seq[i], env); END LOOP; - INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq) + INSERT INTO types.value (type_id, val_seq) VALUES (9, eseq) RETURNING value_id INTO result; + RETURN result; END; - WHEN type = 10 THEN +$$ LANGUAGE plpgsql; + +CREATE FUNCTION mal.eval_map(ast integer, env hstore) 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 @@ -63,36 +68,39 @@ BEGIN ehash := ehash || hstore(kv.key, CAST(e AS varchar)); END IF; END LOOP; - INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash) + INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash) RETURNING value_id INTO result; + RETURN result; END; - ELSE - result := ast; - END CASE; - - RETURN result; -END; $$ LANGUAGE plpgsql; +$$ LANGUAGE plpgsql; CREATE FUNCTION mal.EVAL(ast integer, env hstore) RETURNS integer AS $$ DECLARE - type integer; - el integer; + a0 integer; fname varchar; args integer[]; + evda0 integer; result integer; BEGIN - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - IF type <> 8 THEN - RETURN mal.eval_ast(ast, env); - END IF; + 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; END IF; - el := mal.eval_ast(ast, env); + a0 := types._first(ast); + evda0 := mal.EVAL(a0, env); SELECT val_string INTO fname FROM types.value - WHERE value_id = types._first(el); - args := types._restArray(el); + WHERE value_id = a0; + FOR i in 0 .. types._count(ast) - 2 LOOP + args[i] := mal.EVAL(types._nth(ast, i+1), env); + END LOOP; EXECUTE format('SELECT %s($1);', fname) INTO result USING args; RETURN result; END; $$ LANGUAGE plpgsql; diff --git a/impls/plpgsql/step3_env.sql b/impls/plpgsql/step3_env.sql index 085c41b598..9ec3f3fa55 100644 --- a/impls/plpgsql/step3_env.sql +++ b/impls/plpgsql/step3_env.sql @@ -19,34 +19,57 @@ BEGIN END; $$ LANGUAGE plpgsql; -- eval -CREATE FUNCTION mal.eval_ast(ast integer, env integer) RETURNS integer AS $$ + +CREATE FUNCTION mal.eval_debug(ast integer, env integer) RETURNS void AS $$ DECLARE - type integer; - seq integer[]; - eseq integer[]; - hash hstore; - ehash hstore; - kv RECORD; - e integer; - result integer; + val constant integer := envs.get(env, 'DEBUG-EVAL'); BEGIN - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - CASE - WHEN type = 7 THEN + 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.eval_symbol(ast integer, env integer) RETURNS integer +AS $$ + DECLARE + symkey constant varchar := types._valueToString(ast); + result constant integer := envs.get(env, symkey); BEGIN - result := envs.get(env, ast); + IF result IS NULL THEN + RAISE EXCEPTION '''%'' not found', symkey; + END IF; + RETURN result; END; - WHEN type IN (8, 9) THEN +$$ 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; 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 + FOR i IN 1 .. array_length(seq, 1) LOOP eseq[i] := mal.EVAL(seq[i], env); END LOOP; - INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq) + INSERT INTO types.value (type_id, val_seq) VALUES (9, eseq) RETURNING value_id INTO result; + RETURN result; END; - WHEN type = 10 THEN +$$ 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 @@ -58,76 +81,70 @@ BEGIN ehash := ehash || hstore(kv.key, CAST(e AS varchar)); END IF; END LOOP; - INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash) + INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash) RETURNING value_id INTO result; + RETURN result; END; - ELSE - result := ast; - END CASE; - - RETURN result; -END; $$ LANGUAGE plpgsql; +$$ 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; - fname varchar; - args integer[]; - result integer; BEGIN - -- 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; + 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; 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 + 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)); - END; - WHEN a0sym = 'let*' THEN + + WHEN 'let*' THEN + DECLARE + let_env constant integer := envs.new(env); + binds constant integer[] := types._valueToArray(types._nth(ast, 1)); 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 + 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)); - idx := idx + 2; END LOOP; RETURN mal.EVAL(types._nth(ast, 2), let_env); END; ELSE + NULL; + END CASE; + END IF; + -- Apply phase. + DECLARE + fname varchar; + args integer[]; + result integer; + evda0 constant integer := mal.EVAL(a0, env); BEGIN - el := mal.eval_ast(ast, env); SELECT val_string INTO fname FROM types.value - WHERE value_id = types._first(el); - args := types._restArray(el); + WHERE value_id = a0; + FOR i in 0 .. types._count(ast) - 2 LOOP + args[i] := mal.EVAL(types._nth(ast, i+1), env); + END LOOP; EXECUTE format('SELECT %s($1);', fname) INTO result USING args; RETURN result; END; - END CASE; END; $$ LANGUAGE plpgsql; -- print diff --git a/impls/plpgsql/step4_if_fn_do.sql b/impls/plpgsql/step4_if_fn_do.sql index 904e44126e..09f4cf12cb 100644 --- a/impls/plpgsql/step4_if_fn_do.sql +++ b/impls/plpgsql/step4_if_fn_do.sql @@ -20,34 +20,57 @@ BEGIN END; $$ LANGUAGE plpgsql; -- eval -CREATE FUNCTION mal.eval_ast(ast integer, env integer) RETURNS integer AS $$ + +CREATE FUNCTION mal.eval_debug(ast integer, env integer) RETURNS void AS $$ DECLARE - type integer; - seq integer[]; - eseq integer[]; - hash hstore; - ehash hstore; - kv RECORD; - e integer; - result integer; + val constant integer := envs.get(env, 'DEBUG-EVAL'); BEGIN - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - CASE - WHEN type = 7 THEN + 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.eval_symbol(ast integer, env integer) RETURNS integer +AS $$ + DECLARE + symkey constant varchar := types._valueToString(ast); + result constant integer := envs.get(env, symkey); BEGIN - result := envs.get(env, ast); + IF result IS NULL THEN + RAISE EXCEPTION '''%'' not found', symkey; + END IF; + RETURN result; END; - WHEN type IN (8, 9) THEN +$$ 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; 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 + FOR i IN 1 .. array_length(seq, 1) LOOP eseq[i] := mal.EVAL(seq[i], env); END LOOP; - INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq) + INSERT INTO types.value (type_id, val_seq) VALUES (9, eseq) RETURNING value_id INTO result; + RETURN result; END; - WHEN type = 10 THEN +$$ 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 @@ -59,80 +82,66 @@ BEGIN ehash := ehash || hstore(kv.key, CAST(e AS varchar)); END IF; END LOOP; - INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash) + INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash) RETURNING value_id INTO result; + RETURN result; END; - ELSE - result := ast; - END CASE; - - RETURN result; -END; $$ LANGUAGE plpgsql; +$$ 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 - -- 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; + 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; 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 + 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)); - END; - WHEN a0sym = 'let*' THEN + + WHEN 'let*' THEN + DECLARE + let_env constant integer := envs.new(env); + binds constant integer[] := types._valueToArray(types._nth(ast, 1)); 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 + 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)); - idx := idx + 2; END LOOP; RETURN mal.EVAL(types._nth(ast, 2), let_env); END; - WHEN a0sym = 'do' THEN + + WHEN 'do' THEN + DECLARE + result integer; BEGIN - el := mal.eval_ast(types._rest(ast), env); - RETURN types._nth(el, types._count(el)-1); + FOR i IN 1 .. types._count(ast) - 1 LOOP + result := mal.EVAL(types._nth(ast, i), env); + END LOOP; + RETURN result; 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 + + 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 RETURN mal.EVAL(types._nth(ast, 3), env); ELSE @@ -141,18 +150,31 @@ BEGIN ELSE RETURN mal.EVAL(types._nth(ast, 2), env); END IF; - END; - WHEN a0sym = 'fn*' THEN - BEGIN + + WHEN 'fn*' THEN RETURN types._malfunc(types._nth(ast, 2), types._nth(ast, 1), env); - END; + ELSE + NULL; + END CASE; + END IF; + -- Apply phase. + DECLARE + type integer; + fname varchar; + fast integer; + fparams integer; + fenv integer; + args integer[]; + result integer; + evda0 constant integer := mal.EVAL(a0, env); 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); + FROM types.value WHERE value_id = evda0; + FOR i in 0 .. types._count(ast) - 2 LOOP + args[i] := mal.EVAL(types._nth(ast, i+1), env); + END LOOP; IF type = 11 THEN EXECUTE format('SELECT %s($1);', fname) INTO result USING args; @@ -163,7 +185,6 @@ BEGIN RAISE EXCEPTION 'Invalid function call'; END IF; END; - END CASE; END; $$ LANGUAGE plpgsql; -- print diff --git a/impls/plpgsql/step5_tco.sql b/impls/plpgsql/step5_tco.sql index 20737be2c2..42bfb18efc 100644 --- a/impls/plpgsql/step5_tco.sql +++ b/impls/plpgsql/step5_tco.sql @@ -20,34 +20,57 @@ BEGIN END; $$ LANGUAGE plpgsql; -- eval -CREATE FUNCTION mal.eval_ast(ast integer, env integer) RETURNS integer AS $$ + +CREATE FUNCTION mal.eval_debug(ast integer, env integer) RETURNS void AS $$ DECLARE - type integer; - seq integer[]; - eseq integer[]; - hash hstore; - ehash hstore; - kv RECORD; - e integer; - result integer; + val constant integer := envs.get(env, 'DEBUG-EVAL'); BEGIN - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - CASE - WHEN type = 7 THEN + 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.eval_symbol(ast integer, env integer) RETURNS integer +AS $$ + DECLARE + symkey constant varchar := types._valueToString(ast); + result constant integer := envs.get(env, symkey); BEGIN - result := envs.get(env, ast); + IF result IS NULL THEN + RAISE EXCEPTION '''%'' not found', symkey; + END IF; + RETURN result; END; - WHEN type IN (8, 9) THEN +$$ 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; 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 + FOR i IN 1 .. array_length(seq, 1) LOOP eseq[i] := mal.EVAL(seq[i], env); END LOOP; - INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq) + INSERT INTO types.value (type_id, val_seq) VALUES (9, eseq) RETURNING value_id INTO result; + RETURN result; END; - WHEN type = 10 THEN +$$ 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 @@ -59,84 +82,71 @@ BEGIN ehash := ehash || hstore(kv.key, CAST(e AS varchar)); END IF; END LOOP; - INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash) + INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash) RETURNING value_id INTO result; + RETURN result; END; - ELSE - result := ast; - END CASE; - - RETURN result; -END; $$ LANGUAGE plpgsql; +$$ 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; + + 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; 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 + 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)); - END; - WHEN a0sym = 'let*' THEN + + WHEN 'let*' THEN + DECLARE + let_env constant integer := envs.new(env); + binds constant integer[] := types._valueToArray(types._nth(ast, 1)); 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 + 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)); - idx := idx + 2; END LOOP; env := let_env; ast := types._nth(ast, 2); CONTINUE; -- TCO END; - WHEN a0sym = 'do' THEN + + WHEN 'do' THEN + DECLARE + ignored integer; BEGIN - PERFORM mal.eval_ast(types._slice(ast, 1, types._count(ast)-1), env); + 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); 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 + + 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); CONTINUE; -- TCO @@ -147,18 +157,31 @@ BEGIN ast := types._nth(ast, 2); CONTINUE; -- TCO END IF; - END; - WHEN a0sym = 'fn*' THEN - BEGIN + + WHEN 'fn*' THEN RETURN types._malfunc(types._nth(ast, 2), types._nth(ast, 1), env); - END; + ELSE + NULL; + END CASE; + END IF; + -- Apply phase. + DECLARE + type integer; + fname varchar; + fast integer; + fparams integer; + fenv integer; + args integer[]; + result integer; + evda0 constant integer := mal.EVAL(a0, env); 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); + FROM types.value WHERE value_id = evda0; + FOR i in 0 .. types._count(ast) - 2 LOOP + args[i] := mal.EVAL(types._nth(ast, i+1), env); + END LOOP; IF type = 11 THEN EXECUTE format('SELECT %s($1);', fname) INTO result USING args; @@ -171,7 +194,6 @@ BEGIN RAISE EXCEPTION 'Invalid function call'; END IF; END; - END CASE; END LOOP; END; $$ LANGUAGE plpgsql; diff --git a/impls/plpgsql/step6_file.sql b/impls/plpgsql/step6_file.sql index 3115021b3b..532d645e79 100644 --- a/impls/plpgsql/step6_file.sql +++ b/impls/plpgsql/step6_file.sql @@ -20,34 +20,57 @@ BEGIN END; $$ LANGUAGE plpgsql; -- eval -CREATE FUNCTION mal.eval_ast(ast integer, env integer) RETURNS integer AS $$ + +CREATE FUNCTION mal.eval_debug(ast integer, env integer) RETURNS void AS $$ DECLARE - type integer; - seq integer[]; - eseq integer[]; - hash hstore; - ehash hstore; - kv RECORD; - e integer; - result integer; + val constant integer := envs.get(env, 'DEBUG-EVAL'); BEGIN - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - CASE - WHEN type = 7 THEN + 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.eval_symbol(ast integer, env integer) RETURNS integer +AS $$ + DECLARE + symkey constant varchar := types._valueToString(ast); + result constant integer := envs.get(env, symkey); BEGIN - result := envs.get(env, ast); + IF result IS NULL THEN + RAISE EXCEPTION '''%'' not found', symkey; + END IF; + RETURN result; END; - WHEN type IN (8, 9) THEN +$$ 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; 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 + FOR i IN 1 .. array_length(seq, 1) LOOP eseq[i] := mal.EVAL(seq[i], env); END LOOP; - INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq) + INSERT INTO types.value (type_id, val_seq) VALUES (9, eseq) RETURNING value_id INTO result; + RETURN result; END; - WHEN type = 10 THEN +$$ 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 @@ -59,84 +82,71 @@ BEGIN ehash := ehash || hstore(kv.key, CAST(e AS varchar)); END IF; END LOOP; - INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash) + INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash) RETURNING value_id INTO result; + RETURN result; END; - ELSE - result := ast; - END CASE; - - RETURN result; -END; $$ LANGUAGE plpgsql; +$$ 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; + + 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; 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 + 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)); - END; - WHEN a0sym = 'let*' THEN + + WHEN 'let*' THEN + DECLARE + let_env constant integer := envs.new(env); + binds constant integer[] := types._valueToArray(types._nth(ast, 1)); 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 + 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)); - idx := idx + 2; END LOOP; env := let_env; ast := types._nth(ast, 2); CONTINUE; -- TCO END; - WHEN a0sym = 'do' THEN + + WHEN 'do' THEN + DECLARE + ignored integer; BEGIN - PERFORM mal.eval_ast(types._slice(ast, 1, types._count(ast)-1), env); + 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); 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 + + 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); CONTINUE; -- TCO @@ -147,18 +157,31 @@ BEGIN ast := types._nth(ast, 2); CONTINUE; -- TCO END IF; - END; - WHEN a0sym = 'fn*' THEN - BEGIN + + WHEN 'fn*' THEN RETURN types._malfunc(types._nth(ast, 2), types._nth(ast, 1), env); - END; + ELSE + NULL; + END CASE; + END IF; + -- Apply phase. + DECLARE + type integer; + fname varchar; + fast integer; + fparams integer; + fenv integer; + args integer[]; + result integer; + evda0 constant integer := mal.EVAL(a0, env); 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); + FROM types.value WHERE value_id = evda0; + FOR i in 0 .. types._count(ast) - 2 LOOP + args[i] := mal.EVAL(types._nth(ast, i+1), env); + END LOOP; IF type = 11 THEN EXECUTE format('SELECT %s($1);', fname) INTO result USING args; @@ -171,7 +194,6 @@ BEGIN RAISE EXCEPTION 'Invalid function call'; END IF; END; - END CASE; END LOOP; END; $$ LANGUAGE plpgsql; diff --git a/impls/plpgsql/step7_quote.sql b/impls/plpgsql/step7_quote.sql index 3bd6e913d5..9b1c9bcc00 100644 --- a/impls/plpgsql/step7_quote.sql +++ b/impls/plpgsql/step7_quote.sql @@ -21,6 +21,18 @@ 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; @@ -46,13 +58,11 @@ BEGIN END; $$ LANGUAGE plpgsql; CREATE FUNCTION mal.quasiquote(ast integer) RETURNS integer AS $$ -DECLARE - type integer; - a0 integer; BEGIN - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - CASE - WHEN type = 8 THEN -- list + 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); @@ -62,49 +72,53 @@ BEGIN END IF; RETURN mal.qq_foldr(ast); END; - WHEN type = 9 THEN -- vector - BEGIN + WHEN 9 THEN -- vector RETURN types._list(ARRAY[types._symbolv('vec'), mal.qq_foldr(ast)]); - END; - WHEN type in (7, 10) THEN -- symbol or map - BEGIN + WHEN 7, 10 THEN -- symbol or map RETURN types._list(ARRAY[types._symbolv('quote'), ast]); - END; ELSE - BEGIN RETURN ast; - END; END CASE; 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 +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); BEGIN - result := envs.get(env, ast); + IF result IS NULL THEN + RAISE EXCEPTION '''%'' not found', symkey; + END IF; + RETURN result; END; - WHEN type IN (8, 9) THEN +$$ 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; 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 + FOR i IN 1 .. array_length(seq, 1) LOOP eseq[i] := mal.EVAL(seq[i], env); END LOOP; - INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq) + INSERT INTO types.value (type_id, val_seq) VALUES (9, eseq) RETURNING value_id INTO result; + RETURN result; END; - WHEN type = 10 THEN +$$ 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 @@ -116,95 +130,80 @@ BEGIN ehash := ehash || hstore(kv.key, CAST(e AS varchar)); END IF; END LOOP; - INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash) + INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash) RETURNING value_id INTO result; + RETURN result; END; - ELSE - result := ast; - END CASE; - - RETURN result; -END; $$ LANGUAGE plpgsql; +$$ 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; + + 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; 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 + 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)); - END; - WHEN a0sym = 'let*' THEN + + WHEN 'let*' THEN + DECLARE + let_env constant integer := envs.new(env); + binds constant integer[] := types._valueToArray(types._nth(ast, 1)); 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 + 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)); - idx := idx + 2; END LOOP; env := let_env; ast := types._nth(ast, 2); CONTINUE; -- TCO END; - WHEN a0sym = 'quote' THEN - BEGIN + + WHEN 'quote' THEN RETURN types._nth(ast, 1); - END; - WHEN a0sym = 'quasiquoteexpand' THEN - RETURN mal.quasiquote(types._nth(ast, 1)); - WHEN a0sym = 'quasiquote' THEN + + WHEN 'quasiquote' THEN BEGIN ast := mal.quasiquote(types._nth(ast, 1)); CONTINUE; -- TCO END; - WHEN a0sym = 'do' THEN + + WHEN 'do' THEN + DECLARE + ignored integer; BEGIN - PERFORM mal.eval_ast(types._slice(ast, 1, types._count(ast)-1), env); + 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); 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 + + 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); CONTINUE; -- TCO @@ -215,18 +214,31 @@ BEGIN ast := types._nth(ast, 2); CONTINUE; -- TCO END IF; - END; - WHEN a0sym = 'fn*' THEN - BEGIN + + WHEN 'fn*' THEN RETURN types._malfunc(types._nth(ast, 2), types._nth(ast, 1), env); - END; + ELSE + NULL; + END CASE; + END IF; + -- Apply phase. + DECLARE + type integer; + fname varchar; + fast integer; + fparams integer; + fenv integer; + args integer[]; + result integer; + evda0 constant integer := mal.EVAL(a0, env); 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); + FROM types.value WHERE value_id = evda0; + FOR i in 0 .. types._count(ast) - 2 LOOP + args[i] := mal.EVAL(types._nth(ast, i+1), env); + END LOOP; IF type = 11 THEN EXECUTE format('SELECT %s($1);', fname) INTO result USING args; @@ -239,7 +251,6 @@ BEGIN RAISE EXCEPTION 'Invalid function call'; END IF; END; - END CASE; END LOOP; END; $$ LANGUAGE plpgsql; diff --git a/impls/plpgsql/step8_macros.sql b/impls/plpgsql/step8_macros.sql index c5a5d110dc..452a627197 100644 --- a/impls/plpgsql/step8_macros.sql +++ b/impls/plpgsql/step8_macros.sql @@ -21,6 +21,18 @@ 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; @@ -46,13 +58,11 @@ BEGIN END; $$ LANGUAGE plpgsql; CREATE FUNCTION mal.quasiquote(ast integer) RETURNS integer AS $$ -DECLARE - type integer; - a0 integer; BEGIN - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - CASE - WHEN type = 8 THEN -- list + 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); @@ -62,78 +72,53 @@ BEGIN END IF; RETURN mal.qq_foldr(ast); END; - WHEN type = 9 THEN -- vector - BEGIN + WHEN 9 THEN -- vector RETURN types._list(ARRAY[types._symbolv('vec'), mal.qq_foldr(ast)]); - END; - WHEN type in (7, 10) THEN -- symbol or map - BEGIN + WHEN 7, 10 THEN -- symbol or map RETURN types._list(ARRAY[types._symbolv('quote'), ast]); - END; ELSE - BEGIN RETURN ast; - END; END CASE; END; $$ LANGUAGE plpgsql; -CREATE FUNCTION mal.is_macro_call(ast integer, env integer) RETURNS boolean AS $$ -DECLARE - a0 integer; - f integer; - result boolean = false; -BEGIN - IF types._list_Q(ast) THEN - a0 = types._first(ast); - IF types._symbol_Q(a0) AND - envs.find(env, types._valueToString(a0)) IS NOT NULL THEN - f := envs.get(env, a0); - SELECT macro INTO result FROM types.value WHERE value_id = f; - END IF; - END IF; - RETURN result; -END; $$ LANGUAGE plpgsql; - -CREATE FUNCTION mal.macroexpand(ast integer, env integer) RETURNS integer AS $$ -DECLARE - mac integer; -BEGIN - WHILE mal.is_macro_call(ast, env) - LOOP - mac := envs.get(env, types._first(ast)); - ast := types._apply(mac, types._valueToArray(types._rest(ast))); - END LOOP; - RETURN ast; -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 +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); BEGIN - result := envs.get(env, ast); + IF result IS NULL THEN + RAISE EXCEPTION '''%'' not found', symkey; + END IF; + RETURN result; END; - WHEN type IN (8, 9) THEN +$$ 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; 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 + FOR i IN 1 .. array_length(seq, 1) LOOP eseq[i] := mal.EVAL(seq[i], env); END LOOP; - INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq) + INSERT INTO types.value (type_id, val_seq) VALUES (9, eseq) RETURNING value_id INTO result; + RETURN result; END; - WHEN type = 10 THEN +$$ 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 @@ -145,111 +130,84 @@ BEGIN ehash := ehash || hstore(kv.key, CAST(e AS varchar)); END IF; END LOOP; - INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash) + INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash) RETURNING value_id INTO result; + RETURN result; END; - ELSE - result := ast; - END CASE; - - RETURN result; -END; $$ LANGUAGE plpgsql; +$$ 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; - ast := mal.macroexpand(ast, env); - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - IF type <> 8 THEN - RETURN mal.eval_ast(ast, env); - END IF; + 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; 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 + 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)); - END; - WHEN a0sym = 'let*' THEN + + WHEN 'let*' THEN + DECLARE + let_env constant integer := envs.new(env); + binds constant integer[] := types._valueToArray(types._nth(ast, 1)); 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 + 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)); - idx := idx + 2; END LOOP; env := let_env; ast := types._nth(ast, 2); CONTINUE; -- TCO END; - WHEN a0sym = 'quote' THEN - BEGIN + + WHEN 'quote' THEN RETURN types._nth(ast, 1); - END; - WHEN a0sym = 'quasiquoteexpand' THEN - RETURN mal.quasiquote(types._nth(ast, 1)); - WHEN a0sym = 'quasiquote' THEN + + WHEN 'quasiquote' THEN BEGIN ast := mal.quasiquote(types._nth(ast, 1)); CONTINUE; -- TCO END; - WHEN a0sym = 'defmacro!' THEN - BEGIN - fn := mal.EVAL(types._nth(ast, 2), env); - fn := types._macro(fn); - RETURN envs.set(env, types._nth(ast, 1), fn); - END; - WHEN a0sym = 'macroexpand' THEN - BEGIN - RETURN mal.macroexpand(types._nth(ast, 1), env); - END; - WHEN a0sym = 'do' THEN + + WHEN 'defmacro!' THEN + RETURN envs.set(env, types._nth(ast, 1), + types._macro(mal.EVAL(types._nth(ast, 2), env))); + + WHEN 'do' THEN + DECLARE + ignored integer; BEGIN - PERFORM mal.eval_ast(types._slice(ast, 1, types._count(ast)-1), env); + 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); 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 + + 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); CONTINUE; -- TCO @@ -260,18 +218,36 @@ BEGIN ast := types._nth(ast, 2); CONTINUE; -- TCO END IF; - END; - WHEN a0sym = 'fn*' THEN - BEGIN + + WHEN 'fn*' THEN RETURN types._malfunc(types._nth(ast, 2), types._nth(ast, 1), env); - END; + ELSE + NULL; + END CASE; + END IF; + -- Apply phase. + DECLARE + type integer; + fname varchar; + fast integer; + fparams integer; + fenv integer; + macro boolean; + args integer[]; + result integer; + evda0 constant integer := mal.EVAL(a0, env); 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); + SELECT type_id, val_string, ast_id, params_id, env_id, macro + INTO type, fname, fast, fparams, fenv, macro + FROM types.value WHERE value_id = evda0; + IF macro THEN + ast := types._apply(evda0, types._restArray(ast)); + CONTINUE; -- TCO + END IF; + FOR i in 0 .. types._count(ast) - 2 LOOP + args[i] := mal.EVAL(types._nth(ast, i+1), env); + END LOOP; IF type = 11 THEN EXECUTE format('SELECT %s($1);', fname) INTO result USING args; @@ -284,7 +260,6 @@ BEGIN RAISE EXCEPTION 'Invalid function call'; END IF; END; - END CASE; END LOOP; END; $$ LANGUAGE plpgsql; diff --git a/impls/plpgsql/step9_try.sql b/impls/plpgsql/step9_try.sql index b4dce3a5a3..d327153024 100644 --- a/impls/plpgsql/step9_try.sql +++ b/impls/plpgsql/step9_try.sql @@ -21,6 +21,18 @@ 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; @@ -46,13 +58,11 @@ BEGIN END; $$ LANGUAGE plpgsql; CREATE FUNCTION mal.quasiquote(ast integer) RETURNS integer AS $$ -DECLARE - type integer; - a0 integer; BEGIN - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - CASE - WHEN type = 8 THEN -- list + 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); @@ -62,78 +72,53 @@ BEGIN END IF; RETURN mal.qq_foldr(ast); END; - WHEN type = 9 THEN -- vector - BEGIN + WHEN 9 THEN -- vector RETURN types._list(ARRAY[types._symbolv('vec'), mal.qq_foldr(ast)]); - END; - WHEN type in (7, 10) THEN -- symbol or map - BEGIN + WHEN 7, 10 THEN -- symbol or map RETURN types._list(ARRAY[types._symbolv('quote'), ast]); - END; ELSE - BEGIN RETURN ast; - END; END CASE; END; $$ LANGUAGE plpgsql; -CREATE FUNCTION mal.is_macro_call(ast integer, env integer) RETURNS boolean AS $$ -DECLARE - a0 integer; - f integer; - result boolean = false; -BEGIN - IF types._list_Q(ast) THEN - a0 = types._first(ast); - IF types._symbol_Q(a0) AND - envs.find(env, types._valueToString(a0)) IS NOT NULL THEN - f := envs.get(env, a0); - SELECT macro INTO result FROM types.value WHERE value_id = f; - END IF; - END IF; - RETURN result; -END; $$ LANGUAGE plpgsql; - -CREATE FUNCTION mal.macroexpand(ast integer, env integer) RETURNS integer AS $$ -DECLARE - mac integer; -BEGIN - WHILE mal.is_macro_call(ast, env) - LOOP - mac := envs.get(env, types._first(ast)); - ast := types._apply(mac, types._valueToArray(types._rest(ast))); - END LOOP; - RETURN ast; -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 +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); BEGIN - result := envs.get(env, ast); + IF result IS NULL THEN + RAISE EXCEPTION '''%'' not found', symkey; + END IF; + RETURN result; END; - WHEN type IN (8, 9) THEN +$$ 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; 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 + FOR i IN 1 .. array_length(seq, 1) LOOP eseq[i] := mal.EVAL(seq[i], env); END LOOP; - INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq) + INSERT INTO types.value (type_id, val_seq) VALUES (9, eseq) RETURNING value_id INTO result; + RETURN result; END; - WHEN type = 10 THEN +$$ 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 @@ -145,130 +130,107 @@ BEGIN ehash := ehash || hstore(kv.key, CAST(e AS varchar)); END IF; END LOOP; - INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash) + INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash) RETURNING value_id INTO result; + RETURN result; END; - ELSE - result := ast; - END CASE; - - RETURN result; -END; $$ LANGUAGE plpgsql; +$$ LANGUAGE plpgsql; CREATE FUNCTION mal.EVAL(ast integer, env integer) RETURNS integer AS $$ DECLARE - type integer; a0 integer; - a0sym varchar; - a1 integer; - a2 integer; - let_env integer; - idx integer; - binds integer[]; - exprs 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; - ast := mal.macroexpand(ast, env); - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - IF type <> 8 THEN - RETURN mal.eval_ast(ast, env); - END IF; + 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; 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 + 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)); - END; - WHEN a0sym = 'let*' THEN + + WHEN 'let*' THEN + DECLARE + let_env constant integer := envs.new(env); + binds constant integer[] := types._valueToArray(types._nth(ast, 1)); 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 + 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)); - idx := idx + 2; END LOOP; env := let_env; ast := types._nth(ast, 2); CONTINUE; -- TCO END; - WHEN a0sym = 'quote' THEN - BEGIN + + WHEN 'quote' THEN RETURN types._nth(ast, 1); - END; - WHEN a0sym = 'quasiquoteexpand' THEN - RETURN mal.quasiquote(types._nth(ast, 1)); - WHEN a0sym = 'quasiquote' THEN + + WHEN 'quasiquote' THEN BEGIN ast := mal.quasiquote(types._nth(ast, 1)); CONTINUE; -- TCO END; - WHEN a0sym = 'defmacro!' THEN - BEGIN - fn := mal.EVAL(types._nth(ast, 2), env); - fn := types._macro(fn); - RETURN envs.set(env, types._nth(ast, 1), fn); - END; - WHEN a0sym = 'macroexpand' THEN - BEGIN - RETURN mal.macroexpand(types._nth(ast, 1), env); - END; - WHEN a0sym = 'try*' THEN + + 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; BEGIN - BEGIN - RETURN mal.EVAL(types._nth(ast, 1), env); - EXCEPTION WHEN OTHERS THEN - IF types._count(ast) >= 3 THEN - a2 = types._nth(ast, 2); - IF types._valueToString(types._nth(a2, 0)) = 'catch*' THEN - binds := ARRAY[types._nth(a2, 1)]; - exprs := ARRAY[types._stringv(SQLERRM)]; - env := envs.new(env, types._list(binds), exprs); - RETURN mal.EVAL(types._nth(a2, 2), env); - END IF; - END IF; - RAISE; - END; + 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 END; - WHEN a0sym = 'do' THEN + + WHEN 'do' THEN + DECLARE + ignored integer; BEGIN - PERFORM mal.eval_ast(types._slice(ast, 1, types._count(ast)-1), env); + 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); 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 + + 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); CONTINUE; -- TCO @@ -279,18 +241,36 @@ BEGIN ast := types._nth(ast, 2); CONTINUE; -- TCO END IF; - END; - WHEN a0sym = 'fn*' THEN - BEGIN + + WHEN 'fn*' THEN RETURN types._malfunc(types._nth(ast, 2), types._nth(ast, 1), env); - END; + ELSE + NULL; + END CASE; + END IF; + -- Apply phase. + DECLARE + type integer; + fname varchar; + fast integer; + fparams integer; + fenv integer; + macro boolean; + args integer[]; + result integer; + evda0 constant integer := mal.EVAL(a0, env); 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); + SELECT type_id, val_string, ast_id, params_id, env_id, macro + INTO type, fname, fast, fparams, fenv, macro + FROM types.value WHERE value_id = evda0; + IF macro THEN + ast := types._apply(evda0, types._restArray(ast)); + CONTINUE; -- TCO + END IF; + FOR i in 0 .. types._count(ast) - 2 LOOP + args[i] := mal.EVAL(types._nth(ast, i+1), env); + END LOOP; IF type = 11 THEN EXECUTE format('SELECT %s($1);', fname) INTO result USING args; @@ -303,7 +283,6 @@ BEGIN RAISE EXCEPTION 'Invalid function call'; END IF; END; - END CASE; END LOOP; END; $$ LANGUAGE plpgsql; diff --git a/impls/plpgsql/stepA_mal.sql b/impls/plpgsql/stepA_mal.sql index fc1e593829..7f7ed164e7 100644 --- a/impls/plpgsql/stepA_mal.sql +++ b/impls/plpgsql/stepA_mal.sql @@ -21,6 +21,18 @@ 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; @@ -46,13 +58,11 @@ BEGIN END; $$ LANGUAGE plpgsql; CREATE FUNCTION mal.quasiquote(ast integer) RETURNS integer AS $$ -DECLARE - type integer; - a0 integer; BEGIN - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - CASE - WHEN type = 8 THEN -- list + 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); @@ -62,78 +72,53 @@ BEGIN END IF; RETURN mal.qq_foldr(ast); END; - WHEN type = 9 THEN -- vector - BEGIN + WHEN 9 THEN -- vector RETURN types._list(ARRAY[types._symbolv('vec'), mal.qq_foldr(ast)]); - END; - WHEN type in (7, 10) THEN -- symbol or map - BEGIN + WHEN 7, 10 THEN -- symbol or map RETURN types._list(ARRAY[types._symbolv('quote'), ast]); - END; ELSE - BEGIN RETURN ast; - END; END CASE; END; $$ LANGUAGE plpgsql; -CREATE FUNCTION mal.is_macro_call(ast integer, env integer) RETURNS boolean AS $$ -DECLARE - a0 integer; - f integer; - result boolean = false; -BEGIN - IF types._list_Q(ast) THEN - a0 = types._first(ast); - IF types._symbol_Q(a0) AND - envs.find(env, types._valueToString(a0)) IS NOT NULL THEN - f := envs.get(env, a0); - SELECT macro INTO result FROM types.value WHERE value_id = f; - END IF; - END IF; - RETURN result; -END; $$ LANGUAGE plpgsql; - -CREATE FUNCTION mal.macroexpand(ast integer, env integer) RETURNS integer AS $$ -DECLARE - mac integer; -BEGIN - WHILE mal.is_macro_call(ast, env) - LOOP - mac := envs.get(env, types._first(ast)); - ast := types._apply(mac, types._valueToArray(types._rest(ast))); - END LOOP; - RETURN ast; -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 +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); BEGIN - result := envs.get(env, ast); + IF result IS NULL THEN + RAISE EXCEPTION '''%'' not found', symkey; + END IF; + RETURN result; END; - WHEN type IN (8, 9) THEN +$$ 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; 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 + FOR i IN 1 .. array_length(seq, 1) LOOP eseq[i] := mal.EVAL(seq[i], env); END LOOP; - INSERT INTO types.value (type_id, val_seq) VALUES (type, eseq) + INSERT INTO types.value (type_id, val_seq) VALUES (9, eseq) RETURNING value_id INTO result; + RETURN result; END; - WHEN type = 10 THEN +$$ 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 @@ -145,130 +130,107 @@ BEGIN ehash := ehash || hstore(kv.key, CAST(e AS varchar)); END IF; END LOOP; - INSERT INTO types.value (type_id, val_hash) VALUES (type, ehash) + INSERT INTO types.value (type_id, val_hash) VALUES (10, ehash) RETURNING value_id INTO result; + RETURN result; END; - ELSE - result := ast; - END CASE; - - RETURN result; -END; $$ LANGUAGE plpgsql; +$$ LANGUAGE plpgsql; CREATE FUNCTION mal.EVAL(ast integer, env integer) RETURNS integer AS $$ DECLARE - type integer; a0 integer; - a0sym varchar; - a1 integer; - a2 integer; - let_env integer; - idx integer; - binds integer[]; - exprs 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; - ast := mal.macroexpand(ast, env); - SELECT type_id INTO type FROM types.value WHERE value_id = ast; - IF type <> 8 THEN - RETURN mal.eval_ast(ast, env); - END IF; + 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; 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 + 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)); - END; - WHEN a0sym = 'let*' THEN + + WHEN 'let*' THEN + DECLARE + let_env constant integer := envs.new(env); + binds constant integer[] := types._valueToArray(types._nth(ast, 1)); 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 + 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)); - idx := idx + 2; END LOOP; env := let_env; ast := types._nth(ast, 2); CONTINUE; -- TCO END; - WHEN a0sym = 'quote' THEN - BEGIN + + WHEN 'quote' THEN RETURN types._nth(ast, 1); - END; - WHEN a0sym = 'quasiquoteexpand' THEN - RETURN mal.quasiquote(types._nth(ast, 1)); - WHEN a0sym = 'quasiquote' THEN + + WHEN 'quasiquote' THEN BEGIN ast := mal.quasiquote(types._nth(ast, 1)); CONTINUE; -- TCO END; - WHEN a0sym = 'defmacro!' THEN - BEGIN - fn := mal.EVAL(types._nth(ast, 2), env); - fn := types._macro(fn); - RETURN envs.set(env, types._nth(ast, 1), fn); - END; - WHEN a0sym = 'macroexpand' THEN - BEGIN - RETURN mal.macroexpand(types._nth(ast, 1), env); - END; - WHEN a0sym = 'try*' THEN + + 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; BEGIN - BEGIN - RETURN mal.EVAL(types._nth(ast, 1), env); - EXCEPTION WHEN OTHERS THEN - IF types._count(ast) >= 3 THEN - a2 = types._nth(ast, 2); - IF types._valueToString(types._nth(a2, 0)) = 'catch*' THEN - binds := ARRAY[types._nth(a2, 1)]; - exprs := ARRAY[types._stringv(SQLERRM)]; - env := envs.new(env, types._list(binds), exprs); - RETURN mal.EVAL(types._nth(a2, 2), env); - END IF; - END IF; - RAISE; - END; + 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 END; - WHEN a0sym = 'do' THEN + + WHEN 'do' THEN + DECLARE + ignored integer; BEGIN - PERFORM mal.eval_ast(types._slice(ast, 1, types._count(ast)-1), env); + 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); 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 + + 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); CONTINUE; -- TCO @@ -279,18 +241,36 @@ BEGIN ast := types._nth(ast, 2); CONTINUE; -- TCO END IF; - END; - WHEN a0sym = 'fn*' THEN - BEGIN + + WHEN 'fn*' THEN RETURN types._malfunc(types._nth(ast, 2), types._nth(ast, 1), env); - END; + ELSE + NULL; + END CASE; + END IF; + -- Apply phase. + DECLARE + type integer; + fname varchar; + fast integer; + fparams integer; + fenv integer; + macro boolean; + args integer[]; + result integer; + evda0 constant integer := mal.EVAL(a0, env); 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); + SELECT type_id, val_string, ast_id, params_id, env_id, macro + INTO type, fname, fast, fparams, fenv, macro + FROM types.value WHERE value_id = evda0; + IF macro THEN + ast := types._apply(evda0, types._restArray(ast)); + CONTINUE; -- TCO + END IF; + FOR i in 0 .. types._count(ast) - 2 LOOP + args[i] := mal.EVAL(types._nth(ast, i+1), env); + END LOOP; IF type = 11 THEN EXECUTE format('SELECT %s($1);', fname) INTO result USING args; @@ -303,7 +283,6 @@ BEGIN RAISE EXCEPTION 'Invalid function call'; END IF; END; - END CASE; END LOOP; END; $$ LANGUAGE plpgsql;