Someone named Micah Cantor shared a blog post to Hacker News about their side project parsing a simple version of propositional logic in 33 lines of Racket.
This quick description is taken from Micah's post:
I'm just curious if we can achieve the same feat in the same or fewer lines of code. So here it is, 12 lines (comments and white space not included) of Prolog. No external library needed because most Prolog implementations come built-in with a definite clause grammar (DCG) parser. I'm not going to delve into how the code works. You can read a bit on the DCG parser here.
% Memoize the expr/1 goal to avoid left recursion.
% Requires SWI-Prolog 8 or above.
:- table expr//1.
expr([]) --> [].
% skipping whitespaces
expr(Expr) --> [' '], expr(Expr).
expr(Expr) --> expr(Expr), [' '].
% groupping things inside parentheses
expr(Expr) --> ['('], expr(Expr), [')'].
% symbols must be in the P -> Z range
expr(symbol(S)) --> [S], { 'P' @=< S, S @=< 'Z' }.
% unary operators
expr(not(Rest)) --> [~], expr(Rest).
% binary operators
expr(and(L, R)) --> expr(L), [^], expr(R).
expr(or(L, R)) --> expr(L), [v], expr(R).
expr(if(L, R)) --> expr(L), [-, >], expr(R).
expr(iff(L, R)) --> expr(L), [<, -, >], expr(R).
parse(Atom, Expression) :- atom_chars(Atom, Chars), phrase(expr(Expression), Chars).
Granted, their version is probably a bit more flexible because they have the lexing and parsing phases separated.
To use this, save the code into a file, maybe pros.prolog
then run the SWI-Prolog shell, load the script and run the parse
command. Here I use the example from Micah's post:
$ swipl
?- [pros.prolog]
?- parse('(P->Q) <-> ~R', Result).
Result = iff(if(symbol('P'), symbol('Q')), not(symbol('R'))).
What you get is a nice abstract syntax tree:
iff
if
symbol(P)
symbol(Q)
not
symbol(R)
: )