\i init.sql
\i types.sql
\i reader.sql
\i printer.sql

-- ---------------------------------------------------------
-- step1_read_print.sql

-- read
CREATE OR REPLACE FUNCTION READ(line varchar) RETURNS integer AS $$
BEGIN
    RETURN read_str(line);
END; $$ LANGUAGE plpgsql;

-- eval
CREATE OR REPLACE FUNCTION just_add(args integer[]) RETURNS integer AS $$
BEGIN RETURN args[1] + args[2]; END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION eval_ast(ast integer, env integer) RETURNS integer AS $$
DECLARE
    type           integer;
    symkey         varchar;
    vid            integer;
    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
        symkey := _vstring(ast);
        SELECT e.value_id FROM env e INTO result
            WHERE e.env_id = env
            AND e.key = symkey;
        IF result IS NULL THEN
            RAISE EXCEPTION '''%'' not found', symkey;
        END IF;
    END;
    WHEN type = 8 OR type = 9 THEN
    BEGIN
        src_coll_id := (SELECT collection_id FROM value WHERE value_id = ast);
        FOR vid, i IN (SELECT value_id, idx FROM collection
                       WHERE collection_id = src_coll_id)
        LOOP
            e := EVAL(vid, env);
            IF dst_coll_id IS NULL THEN
                dst_coll_id := COALESCE((SELECT Max(collection_id)
                                         FROM collection)+1,0);
            END IF;
            -- Evaluated each entry
            INSERT INTO collection (collection_id, idx, value_id)
                VALUES (dst_coll_id, i, e);
        END LOOP;
        -- Create value entry pointing to new collection
        INSERT INTO value (type_id, collection_id)
            VALUES (type, dst_coll_id)
            RETURNING value_id INTO result;
    END;
    ELSE
        result := ast;
    END CASE;

    RETURN result;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION EVAL(ast integer, env integer) RETURNS integer AS $$
DECLARE
    type    integer;
    el      integer;
    fname   varchar;
    args    integer[];
    result  integer;
BEGIN
    SELECT type_id INTO type FROM value WHERE value_id = ast;
    IF type <> 8 THEN
        RETURN eval_ast(ast, env);
    END IF;

    el := eval_ast(ast, env);
    SELECT function_name INTO fname FROM value WHERE value_id = _first(el);
    args := _restArray(el);
    -- RAISE NOTICE 'fname: %, args: %', fname, args;
    EXECUTE format('SELECT %s($1);', fname)
        INTO result USING args;
    RETURN result;
END; $$ LANGUAGE plpgsql;

-- print
CREATE OR REPLACE FUNCTION PRINT(exp integer) RETURNS varchar AS $$
BEGIN
    RETURN pr_str(exp);
END; $$ LANGUAGE plpgsql;


-- repl

-- env table
CREATE TABLE env (
    env_id    integer NOT NULL,
    key       varchar NOT NULL,
    value_id  integer NOT NULL
);

CREATE OR REPLACE FUNCTION env_vset(env integer, name varchar, val integer)
    RETURNS void AS $$
BEGIN
    INSERT INTO env (env_id, key, value_id) VALUES (env, name, val);
END; $$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION mal_intop(op varchar, args integer[]) RETURNS integer AS $$
DECLARE a integer; b integer; result integer;
BEGIN
    SELECT val_int INTO a FROM value WHERE value_id = args[1];
    SELECT val_int INTO b FROM value WHERE value_id = args[2];
    EXECUTE format('INSERT INTO value (type_id, val_int) VALUES (3, $1 %s $2)
                    RETURNING value_id;', op) INTO result USING a, b;
    RETURN result;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION mal_add(args integer[]) RETURNS integer AS $$
BEGIN RETURN mal_intop('+', args); END; $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION mal_subtract(args integer[]) RETURNS integer AS $$
BEGIN RETURN mal_intop('-', args); END; $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION mal_multiply(args integer[]) RETURNS integer AS $$
BEGIN RETURN mal_intop('*', args); END; $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION mal_divide(args integer[]) RETURNS integer AS $$
BEGIN RETURN mal_intop('/', args); END; $$ LANGUAGE plpgsql;

INSERT INTO value (type_id, function_name) VALUES (11, 'mal_add');
INSERT INTO value (type_id, function_name) VALUES (11, 'mal_subtract');
INSERT INTO value (type_id, function_name) VALUES (11, 'mal_multiply');
INSERT INTO value (type_id, function_name) VALUES (11, 'mal_divide');

-- repl_env is environment 0
SELECT env_vset(0, '+', (SELECT value_id FROM value WHERE function_name = 'mal_add'));
SELECT env_vset(0, '-', (SELECT value_id FROM value WHERE function_name = 'mal_subtract'));
SELECT env_vset(0, '*', (SELECT value_id FROM value WHERE function_name = 'mal_multiply'));
SELECT env_vset(0, '/', (SELECT value_id FROM value WHERE function_name = 'mal_divide'));


CREATE OR REPLACE FUNCTION REP(line varchar) RETURNS varchar AS $$
DECLARE
    output varchar;
BEGIN
    -- RAISE NOTICE 'line is %', line;
    -- output := 'line: ' || line;
    RETURN PRINT(EVAL(READ(line), 0));
END; $$ LANGUAGE plpgsql;
