\i init.sql \i io.sql \i types.sql \i reader.sql \i printer.sql \i env.sql \i core.sql -- --------------------------------------------------------- -- step1_read_print.sql -- read CREATE FUNCTION READ(line varchar) RETURNS integer AS $$ BEGIN RETURN read_str(line); END; $$ LANGUAGE plpgsql; -- eval CREATE FUNCTION is_pair(ast integer) RETURNS boolean AS $$ BEGIN RETURN _sequential_Q(ast) AND _count(ast) > 0; END; $$ LANGUAGE plpgsql; CREATE FUNCTION quasiquote(ast integer) RETURNS integer AS $$ DECLARE a0 integer; a00 integer; BEGIN IF NOT is_pair(ast) THEN RETURN _list(ARRAY[_symbolv('quote'), ast]); ELSE a0 := _nth(ast, 0); IF _symbol_Q(a0) AND a0 = _symbolv('unquote') THEN RETURN _nth(ast, 1); ELSE a00 := _nth(a0, 0); IF _symbol_Q(a00) AND a00 = _symbolv('splice-unquote') THEN RETURN _list(ARRAY[_symbolv('concat'), _nth(a0, 1), quasiquote(_rest(ast))]); END IF; END IF; RETURN _list(ARRAY[_symbolv('cons'), quasiquote(_first(ast)), quasiquote(_rest(ast))]); END IF; END; $$ LANGUAGE plpgsql; CREATE FUNCTION is_macro_call(ast integer, env integer) RETURNS boolean AS $$ DECLARE a0 integer; f integer; result boolean = false; BEGIN IF _list_Q(ast) THEN a0 = _first(ast); IF _symbol_Q(a0) AND env_find(env, _valueToString(a0)) IS NOT NULL THEN f := env_get(env, a0); SELECT macro INTO result FROM collection WHERE collection_id = (SELECT collection_id FROM value WHERE value_id = f); END IF; END IF; RETURN result; END; $$ LANGUAGE plpgsql; CREATE FUNCTION macroexpand(ast integer, env integer) RETURNS integer AS $$ DECLARE mac integer; BEGIN WHILE is_macro_call(ast, env) LOOP mac := env_get(env, _first(ast)); ast := _apply(mac, _valueToArray(_rest(ast))); END LOOP; RETURN ast; END; $$ LANGUAGE plpgsql; CREATE FUNCTION eval_ast(ast integer, env integer) RETURNS integer AS $$ DECLARE type integer; symkey varchar; vid integer; k varchar; i integer; src_coll_id integer; dst_coll_id integer = NULL; e integer; result integer; BEGIN SELECT type_id INTO type FROM value WHERE value_id = ast; CASE WHEN type = 7 THEN BEGIN result := env_get(env, ast); END; WHEN type IN (8, 9, 10) THEN BEGIN src_coll_id := (SELECT collection_id FROM value WHERE value_id = ast); -- Create new value entry pointing to new collection dst_coll_id := COALESCE((SELECT Max(collection_id) FROM value)+1,0); INSERT INTO value (type_id, collection_id) VALUES (type, dst_coll_id) RETURNING value_id INTO result; FOR vid, k, i IN (SELECT value_id, key_string, idx FROM collection WHERE collection_id = src_coll_id) LOOP -- Evaluate each entry e := EVAL(vid, env); INSERT INTO collection (collection_id, key_string, idx, value_id) VALUES (dst_coll_id, k, i, e); END LOOP; END; ELSE result := ast; END CASE; RETURN result; END; $$ LANGUAGE plpgsql; CREATE FUNCTION EVAL(ast integer, env integer) RETURNS integer AS $$ DECLARE type integer; a0 integer; a0sym varchar; a1 integer; a2 integer; let_env 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 -- RAISE NOTICE 'EVAL: % [%]', pr_str(ast), ast; SELECT type_id INTO type FROM value WHERE value_id = ast; IF type <> 8 THEN RETURN eval_ast(ast, env); END IF; ast := macroexpand(ast, env); SELECT type_id INTO type FROM value WHERE value_id = ast; IF type <> 8 THEN RETURN eval_ast(ast, env); END IF; a0 := _first(ast); IF _symbol_Q(a0) THEN a0sym := (SELECT val_string FROM value WHERE value_id = a0); ELSE a0sym := '__<*fn*>__'; END IF; --RAISE NOTICE 'ast: %, a0sym: %', ast, a0sym; CASE WHEN a0sym = 'def!' THEN BEGIN RETURN env_set(env, _nth(ast, 1), EVAL(_nth(ast, 2), env)); END; WHEN a0sym = 'let*' THEN BEGIN let_env := env_new(env); a1 := _nth(ast, 1); binds := ARRAY(SELECT collection.value_id FROM collection INNER JOIN value ON collection.collection_id=value.collection_id WHERE value.value_id = a1 AND (collection.idx % 2) = 0 ORDER BY collection.idx); exprs := ARRAY(SELECT collection.value_id FROM collection INNER JOIN value ON collection.collection_id=value.collection_id WHERE value.value_id = a1 AND (collection.idx % 2) = 1 ORDER BY collection.idx); FOR idx IN array_lower(binds, 1) .. array_upper(binds, 1) LOOP PERFORM env_set(let_env, binds[idx], EVAL(exprs[idx], let_env)); END LOOP; env := let_env; ast := _nth(ast, 2); CONTINUE; -- TCO END; WHEN a0sym = 'quote' THEN BEGIN RETURN _nth(ast, 1); END; WHEN a0sym = 'quasiquote' THEN BEGIN ast := quasiquote(_nth(ast, 1)); CONTINUE; -- TCO END; WHEN a0sym = 'defmacro!' THEN BEGIN fn := EVAL(_nth(ast, 2), env); fn := _macro(fn); RETURN env_set(env, _nth(ast, 1), fn); END; WHEN a0sym = 'macroexpand' THEN BEGIN RETURN macroexpand(_nth(ast, 1), env); END; WHEN a0sym = 'try*' THEN BEGIN BEGIN RETURN EVAL(_nth(ast, 1), env); EXCEPTION WHEN OTHERS THEN IF _count(ast) >= 3 THEN a2 = _nth(ast, 2); IF _valueToString(_nth(a2, 0)) = 'catch*' THEN binds := ARRAY[_nth(a2, 1)]; exprs := ARRAY[_stringv(SQLERRM)]; env := env_new_bindings(env, _list(binds), exprs); RETURN EVAL(_nth(a2, 2), env); END IF; END IF; RAISE; END; END; WHEN a0sym = 'do' THEN BEGIN PERFORM eval_ast(_slice(ast, 1, _count(ast)-1), env); ast := _nth(ast, _count(ast)-1); CONTINUE; -- TCO END; WHEN a0sym = 'if' THEN BEGIN cond := EVAL(_nth(ast, 1), env); SELECT type_id INTO type FROM value WHERE value_id = cond; IF type = 0 OR type = 1 THEN -- nil or false IF _count(ast) > 3 THEN ast := _nth(ast, 3); CONTINUE; -- TCO ELSE RETURN 0; -- nil END IF; ELSE ast := _nth(ast, 2); CONTINUE; -- TCO END IF; END; WHEN a0sym = 'fn*' THEN BEGIN RETURN _function(_nth(ast, 2), _nth(ast, 1), env); END; ELSE BEGIN el := eval_ast(ast, env); SELECT type_id, collection_id, function_name INTO type, fn, fname FROM value WHERE value_id = _first(el); args := _restArray(el); IF type = 11 THEN EXECUTE format('SELECT %s($1);', fname) INTO result USING args; RETURN result; ELSIF type = 12 THEN SELECT value_id, params_id, env_id INTO fast, fparams, fenv FROM collection WHERE collection_id = fn; env := env_new_bindings(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 PRINT(exp integer) RETURNS varchar AS $$ BEGIN RETURN pr_str(exp); END; $$ LANGUAGE plpgsql; -- repl -- repl_env is environment 0 CREATE FUNCTION REP(line varchar) RETURNS varchar AS $$ BEGIN RETURN PRINT(EVAL(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_eval(args integer[]) RETURNS integer AS $$ BEGIN RETURN EVAL(args[1], 0); END; $$ LANGUAGE plpgsql; INSERT INTO value (type_id, function_name) VALUES (11, 'mal_eval'); SELECT env_vset(0, 'eval', (SELECT value_id FROM value WHERE function_name = 'mal_eval')) \g '/dev/null' -- *ARGV* values are set by RUN SELECT env_vset(0, '*ARGV*', READ('()')); -- core.mal: defined using the language itself SELECT REP('(def! *host-language* "plpqsql")') \g '/dev/null' SELECT REP('(def! not (fn* (a) (if a false true)))') \g '/dev/null' SELECT REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))') \g '/dev/null' SELECT 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' SELECT REP('(def! *gensym-counter* (atom 0))') \g '/dev/null' SELECT REP('(def! gensym (fn* [] (symbol (str "G__" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))') \g '/dev/null' SELECT REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))') \g '/dev/null' CREATE FUNCTION MAIN_LOOP(pwd varchar) RETURNS integer AS $$ DECLARE line varchar; output varchar; BEGIN PERFORM env_vset(0, '*PWD*', _stringv(pwd)); PERFORM REP('(println (str "Mal [" *host-language* "]"))'); WHILE true LOOP BEGIN line := readline('user> ', 0); IF line IS NULL THEN RETURN 0; END IF; IF line <> '' THEN output := REP(line); PERFORM writeline(output); END IF; EXCEPTION WHEN OTHERS THEN PERFORM writeline('Error: ' || SQLERRM); END; END LOOP; END; $$ LANGUAGE plpgsql; CREATE FUNCTION RUN(pwd varchar, argstring varchar) RETURNS void AS $$ DECLARE allargs integer; BEGIN allargs := READ(argstring); PERFORM env_vset(0, '*PWD*', _stringv(pwd)); PERFORM env_vset(0, '*ARGV*', _rest(allargs)); PERFORM REP('(load-file ' || pr_str(_first(allargs)) || ')'); PERFORM wait_flushed(1); RETURN; END; $$ LANGUAGE plpgsql;