I have it broken up into three stages: a parser that reads a string from
stdin and emits (Prolog) AST to stdout; an interpreter of sorts that
reads AST from stdin, evaluates it, and then emits the AST of the stack
on stdout; and a printer that reads AST on stdin and prints Joy-ish code
to stdout.
I say Joy-ish because currently math is not evaluated and results of
math appear as expressions, not values.
This is because GNU Prolog doesn't have unbounded integers (it's numbers
are machine integers) so literals that are larger than the machine word
are converted into atoms! To keep things simple, I made all ints into
atoms, but then you can't evaluate them: '1'+'2' is not '3' (it might be
'12' though.)
So I print them out at expressions:
$ echo "1 2 3 4 [+ sub /] i" | ./joy_to_ast | ./thun | ./printer
(1 div (2-(4+3)))
You could almost feed that to, say, Python to evaluate, eh? Or dc with
proper formatting? (man dc; "Desk Calculator".)
Anyway, it's a start. The Prolog interpreter is more for things like
type checking and inference, optimizing, compiling, etc. Symbolic stuff
that's a PITA to express in other languages. (The old type inference
code in Python was pages long, in Prolog it's just the thun/3 & thun/4
predicates themselves. At least so far. There are things we will want
to do eventually that might be a PITA to express in Prolog, eh?
On a lark I implemented it in recursive style, but I'm not going to keep
it that way. I have to implement next_term() first and then I'll
uncomment i_joy_combinator().
I'm pretty happy with this. It's iterative rather than recursive so you
won't blow out the call stack if you want to parse a million brackets
(intermediate results are stored on (another) little stack.) It scans
the string and builds lists and sublists as it goes, without wasting
cons cells.
So I was memset'ing the hash table and string table /after/ setting up
the left- and right-bracket tokens! So then when I tried to print the
token list and ht_lookup() dutifully set the error code when it couldn't
find the strings in the hash table, the system properly quit printing
and halted. D'oh! That was a subtle one. Obvious in hindsight.
It took all expletive-deleted day but I finally nailed it down. In the
end the last bug was I was decrementing a stack pointer /after/ trying
to load the item at the (empty) top of the stack. Classic.
I still need to make it not re-allocate strings that it has already
interned, but beyond that I think it's fine.
Clunky but now you only have to change the font name four time in one
place rather than N times in N places, eh?
Writing C again for the first time in ages (this and the Joy
interpreter) the using the preprocessor is like stone-age
meta-programming, from the lens of lisp it's like, "you do what to your
source code?".