-- --------------------------------------------------------- -- reader.sql CREATE SCHEMA reader; CREATE FUNCTION reader.tokenize(str varchar) RETURNS varchar[] AS $$ DECLARE re varchar = E'[[:space:] ,]*(~@|[\\[\\]{}()\'`~@]|"(?:[\\\\].|[^\\\\"])*"|;[^\n]*|[^\\s \\[\\]{}()\'"`~@,;]*)'; BEGIN RETURN ARRAY(SELECT tok FROM (SELECT (regexp_matches(str, re, 'g'))[1] AS tok) AS x WHERE tok <> '' AND tok NOT LIKE ';%'); END; $$ LANGUAGE plpgsql IMMUTABLE; -- read_atom: -- takes a tokens array and position -- returns new position and value_id CREATE FUNCTION reader.read_atom(tokens varchar[], INOUT pos integer, OUT result integer) AS $$ DECLARE str_id integer; str varchar; token varchar; BEGIN token := tokens[pos]; pos := pos + 1; -- RAISE NOTICE 'read_atom: %', token; IF token = 'nil' THEN -- nil result := 0; ELSIF token = 'false' THEN -- false result := 1; ELSIF token = 'true' THEN -- true result := 2; ELSIF token ~ '^-?[0-9][0-9]*$' THEN -- integer -- integer INSERT INTO types.value (type_id, val_int) VALUES (3, CAST(token AS integer)) RETURNING value_id INTO result; ELSIF token ~ '^".*"' THEN -- string -- string str := substring(token FROM 2 FOR (char_length(token)-2)); str := replace(str, '\\', chr(CAST(x'7f' AS integer))); str := replace(str, '\"', '"'); str := replace(str, '\n', E'\n'); str := replace(str, chr(CAST(x'7f' AS integer)), E'\\'); result := types._stringv(str); ELSIF token ~ '^:.*' THEN -- keyword -- keyword result := types._keywordv(substring(token FROM 2 FOR (char_length(token)-1))); ELSE -- symbol result := types._symbolv(token); END IF; END; $$ LANGUAGE plpgsql; -- read_seq: -- takes a tokens array, type (8, 9, 10), first and last characters -- and position -- returns new position and value_id for a list (8), vector (9) or -- hash-map (10) CREATE FUNCTION reader.read_seq(tokens varchar[], first varchar, last varchar, INOUT p integer, OUT items integer[]) AS $$ DECLARE token varchar; key varchar = NULL; item_id integer; BEGIN token := tokens[p]; p := p + 1; IF token <> first THEN RAISE EXCEPTION 'expected ''%''', first; END IF; items := ARRAY[]::integer[]; LOOP IF p > array_length(tokens, 1) THEN RAISE EXCEPTION 'expected ''%''', last; END IF; token := tokens[p]; IF token = last THEN EXIT; END IF; SELECT * FROM reader.read_form(tokens, p) INTO p, item_id; items := array_append(items, item_id); END LOOP; p := p + 1; END; $$ LANGUAGE plpgsql; -- read_form: -- takes a tokens array and position -- returns new position and value_id CREATE FUNCTION reader.read_form(tokens varchar[], INOUT pos integer, OUT result integer) AS $$ DECLARE vid integer; meta integer; token varchar; BEGIN token := tokens[pos]; -- peek CASE WHEN token = '''' THEN BEGIN pos := pos + 1; SELECT * FROM reader.read_form(tokens, pos) INTO pos, vid; result := types._list(ARRAY[types._symbolv('quote'), vid]); END; WHEN token = '`' THEN BEGIN pos := pos + 1; SELECT * FROM reader.read_form(tokens, pos) INTO pos, vid; result := types._list(ARRAY[types._symbolv('quasiquote'), vid]); END; WHEN token = '~' THEN BEGIN pos := pos + 1; SELECT * FROM reader.read_form(tokens, pos) INTO pos, vid; result := types._list(ARRAY[types._symbolv('unquote'), vid]); END; WHEN token = '~@' THEN BEGIN pos := pos + 1; SELECT * FROM reader.read_form(tokens, pos) INTO pos, vid; result := types._list(ARRAY[types._symbolv('splice-unquote'), vid]); END; WHEN token = '^' THEN BEGIN pos := pos + 1; SELECT * FROM reader.read_form(tokens, pos) INTO pos, meta; SELECT * FROM reader.read_form(tokens, pos) INTO pos, vid; result := types._list(ARRAY[types._symbolv('with-meta'), vid, meta]); END; WHEN token = '@' THEN BEGIN pos := pos + 1; SELECT * FROM reader.read_form(tokens, pos) INTO pos, vid; result := types._list(ARRAY[types._symbolv('deref'), vid]); END; -- list WHEN token = ')' THEN RAISE EXCEPTION 'unexpected '')'''; WHEN token = '(' THEN BEGIN SELECT p, types._list(items) FROM reader.read_seq(tokens, '(', ')', pos) INTO pos, result; END; -- vector WHEN token = ']' THEN RAISE EXCEPTION 'unexpected '']'''; WHEN token = '[' THEN BEGIN SELECT p, types._vector(items) FROM reader.read_seq(tokens, '[', ']', pos) INTO pos, result; END; -- hash-map WHEN token = '}' THEN RAISE EXCEPTION 'unexpected ''}'''; WHEN token = '{' THEN BEGIN SELECT p, types._hash_map(items) FROM reader.read_seq(tokens, '{', '}', pos) INTO pos, result; END; -- ELSE SELECT * FROM reader.read_atom(tokens, pos) INTO pos, result; END CASE; END; $$ LANGUAGE plpgsql; -- read_str: -- takes a string -- returns a new value_id CREATE FUNCTION reader.read_str(str varchar) RETURNS integer AS $$ DECLARE tokens varchar[]; pos integer; ast integer; BEGIN tokens := reader.tokenize(str); -- RAISE NOTICE 'read_str first: %', tokens[1]; pos := 1; SELECT * FROM reader.read_form(tokens, pos) INTO pos, ast; -- RAISE NOTICE 'pos after read_atom: %', pos; RETURN ast; END; $$ LANGUAGE plpgsql;