├── README.md └── lisprolog.pl /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Interpreter for a simple Lisp, written in Prolog 3 | 4 | Some online books show how to implement simple "Prolog" engines in 5 | Lisp. These engines typically assume a representation of Prolog 6 | programs that is convenient from a Lisp perspective, and can't even 7 | parse a single proper Prolog term. Instead, they require you to 8 | manually translate Prolog programs to Lisp forms that are no longer 9 | valid Prolog syntax. With this approach, implementing a simple "Lisp" 10 | in Prolog is even easier ("Lisp in Prolog in zero lines"): Manually 11 | translate each Lisp function to a Prolog predicate with one additional 12 | argument to hold the original function's return value. Done. This is 13 | possible since a function is a special case of a relation, and 14 | functional programming is a restricted form of logic programming. 15 | 16 | Here is a bit beyond that: [**`lisprolog.pl`**](lisprolog.pl) 17 | 18 | These 165 lines of Prolog code give you an interpreter for a simple 19 | Lisp, *including* a parser to let you write Lisp code in its 20 | natural form. 21 | 22 | Internally, Prolog [**Definite Clause 23 | Grammars**](https://www.metalevel.at/prolog/dcg) are used for parsing 24 | Lisp code, and 25 | [semicontext notation](https://www.metalevel.at/prolog/dcg#semicontext) 26 | is used to implicitly thread through certain arguments. This 27 | Prolog feature is very similar to Haskell's monads. 28 | 29 | Read [**The Power of Prolog**](https://www.metalevel.at/prolog) for 30 | more information about Prolog. 31 | 32 | 33 | Sample queries, using Scryer Prolog: 34 | 35 | 36 | Append: 37 | 38 |
39 | ?- run(" \
40 | \
41 | (defun append (x y) \
42 | (if x \
43 | (cons (car x) (append (cdr x) y)) \
44 | y)) \
45 | \
46 | (append '(a b) '(3 4 5)) \
47 | \
48 | ", Vs).
49 | Vs = [append,[a,b,3,4,5]].
50 |
51 |
52 |
56 | ?- time(run(" \
57 | \
58 | (defun fib (n) \
59 | (if (= 0 n) \
60 | 0 \
61 | (if (= 1 n) \
62 | 1 \
63 | (+ (fib (- n 1)) (fib (- n 2)))))) \
64 | (fib 24) \
65 | \
66 | ", Vs)).
67 | % CPU time: 6.414s
68 | Vs = [fib,46368].
69 |
70 |
71 |
75 | ?- time(run(" \
76 | \
77 | (defun fib (n) \
78 | (if (= 0 n) 0 (fib1 0 1 1 n))) \
79 | \
80 | (defun fib1 (f1 f2 i to) \
81 | (if (= i to) \
82 | f2 \
83 | (fib1 f2 (+ f1 f2) (+ i 1) to))) \
84 | \
85 | (fib 250) \
86 | \
87 | ", Vs)).
88 |
89 | % CPU time: 0.020s
90 | Vs = [fib,fib1,7896325826131730509282738943634332893686268675876375].
91 |
92 |
93 |
97 | ?- time(run(" \
98 | \
99 | (defun fib (n) \
100 | (setq f (cons 0 1)) \
101 | (setq i 0) \
102 | (while (< i n) \
103 | (setq f (cons (cdr f) (+ (car f) (cdr f)))) \
104 | (setq i (+ i 1))) \
105 | (car f)) \
106 | \
107 | (fib 350) \
108 | \
109 | ", Vs)).
110 |
111 | % CPU time: 0.021s
112 | Vs = [fib,6254449428820551641549772190170184190608177514674331726439961915653414425].
113 |
114 |
115 |
119 | ?- run(" \
120 | \
121 | (defun map (f xs) \
122 | (if xs \
123 | (cons (eval (list f (car xs))) (map f (cdr xs))) \
124 | ())) \
125 | \
126 | (defun plus1 (x) (+ 1 x)) \
127 | \
128 | (map 'plus1 '(1 2 3)) \
129 | \
130 | ", Vs).
131 | Vs = [map,plus1,[2,3,4]].
132 |
133 |
134 | More information about this interpreter is available at:
135 |
136 | [**https://www.metalevel.at/lisprolog/**](https://www.metalevel.at/lisprolog/)
137 |
--------------------------------------------------------------------------------
/lisprolog.pl:
--------------------------------------------------------------------------------
1 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2 | Lisprolog -- Interpreter for a simple Lisp. Written in Prolog.
3 | Written Nov. 26th, 2006 by Markus Triska (triska@metalevel.at).
4 | Public domain code. https://www.metalevel.at/lisprolog/
5 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
6 |
7 | :- use_module(library(charsio)).
8 | :- use_module(library(dcgs)).
9 | :- use_module(library(lists)).
10 | :- use_module(library(assoc)).
11 |
12 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
13 | Parsing. See https://www.metalevel.at/prolog/dcg for more.
14 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
15 |
16 | parsing(String, Exprs) :- phrase(expressions(Exprs), String).
17 |
18 | expressions([E|Es]) -->
19 | ws, expression(E), ws,
20 | !, % single solution: longest input match
21 | expressions(Es).
22 | expressions([]) --> [].
23 |
24 | ws --> [W], { char_type(W, whitespace) }, ws.
25 | ws --> [].
26 |
27 | % A number N is represented as n(N), a symbol S as s(S).
28 |
29 | expression(s(A)) --> symbol(Cs), { atom_chars(A, Cs) }.
30 | expression(n(N)) --> number(Cs), { number_chars(N, Cs) }.
31 | expression(List) --> "(", expressions(List), ")".
32 | expression([s(quote),Q]) --> "'", expression(Q).
33 |
34 | number([D|Ds]) --> digit(D), number(Ds).
35 | number([D]) --> digit(D).
36 |
37 | digit(D) --> [D], { char_type(D, decimal_digit) }.
38 |
39 | symbol([A|As]) -->
40 | [A],
41 | { memberchk(A, "+/-*><=") ; char_type(A, alpha) },
42 | symbolr(As).
43 |
44 | symbolr([A|As]) -->
45 | [A],
46 | { memberchk(A, "+/-*><=") ; char_type(A, alnum) },
47 | symbolr(As).
48 | symbolr([]) --> [].
49 |
50 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
51 | Interpretation
52 | --------------
53 |
54 | Declaratively, execution of a Lisp form is a relation between the
55 | (function and variable) binding environment before its execution
56 | and the environment after its execution. A Lisp program is a
57 | sequence of Lisp forms, and its result is the sequence of their
58 | results. The environment is represented as a pair of association
59 | lists Fs-Vs, associating function names with argument names and
60 | bodies, and variables with values. DCGs are used to implicitly
61 | thread the environment state through.
62 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
63 |
64 | run(Program, Values) :-
65 | parsing(Program, Forms0),
66 | empty_assoc(E),
67 | compile_all(Forms0, Forms),
68 | phrase(eval_all(Forms, Values0), [E-E], _),
69 | maplist(unfunc, Values0, Values).
70 |
71 | unfunc(s(S), S).
72 | unfunc(t, t).
73 | unfunc(n(N), N).
74 | unfunc([], []).
75 | unfunc([Q0|Qs0], [Q|Qs]) :- unfunc(Q0, Q), unfunc(Qs0, Qs).
76 |
77 | fold([], _, V, n(V)).
78 | fold([n(F)|Fs], Op, V0, V) :- E =.. [Op,V0,F], V1 is E, fold(Fs, Op, V1, V).
79 |
80 | compile_all(Fs0, Fs) :- maplist(compile, Fs0, Fs).
81 |
82 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
83 | compile/2 marks (with "user/1") calls of user-defined functions.
84 | This eliminates an otherwise defaulty representation of function
85 | calls and thus allows for first argument indexing in eval//3.
86 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
87 |
88 | compile(F0, F) :-
89 | ( F0 = n(_) -> F = F0
90 | ; F0 = s(t) -> F = t
91 | ; F0 = s(nil) -> F = []
92 | ; F0 = s(_) -> F = F0
93 | ; F0 = [] -> F = []
94 | ; F0 = [s(quote),Arg] -> F = [quote,Arg]
95 | ; F0 = [s(setq),s(Var),Val0] -> compile(Val0, Val), F = [setq,Var,Val]
96 | ; F0 = [s(Op)|Args0],
97 | memberchk(Op, [+,-,*,equal,if,>,<,=,progn,eval,list,car,cons,
98 | cdr,while,not]) ->
99 | compile_all(Args0, Args),
100 | F = [Op|Args]
101 | ; F0 = [s(defun),s(Name),Args0|Body0] ->
102 | compile_all(Body0, Body),
103 | maplist(arg(1), Args0, Args),
104 | F = [defun,Name,Args|Body]
105 | ; F0 = [s(Op)|Args0] -> compile_all(Args0, Args), F = [user(Op)|Args]
106 | ).
107 |
108 | eval_all([], []) --> [].
109 | eval_all([A|As], [B|Bs]) --> eval(A, B), eval_all(As, Bs).
110 |
111 | eval(n(N), n(N)) --> [].
112 | eval(t, t) --> [].
113 | eval([], []) --> [].
114 | eval(s(A), V), [Fs-Vs] --> [Fs-Vs], { get_assoc(A, Vs, V) }.
115 | eval([L|Ls], Value) --> eval(L, Ls, Value).
116 |
117 | eval(quote, [Q], Q) --> [].
118 | eval(+, As0, V) --> eval_all(As0, As), { fold(As, +, 0, V) }.
119 | eval(-, As0, V) --> eval_all(As0, [n(V0)|Vs0]), { fold(Vs0, -, V0, V) }.
120 | eval(*, As0, V) --> eval_all(As0, Vs), { fold(Vs, *, 1, V) }.
121 | eval(car, [A], C) --> eval(A, V), { V == [] -> C = [] ; V = [C|_] }.
122 | eval(cdr, [A], C) --> eval(A, V), { V == [] -> C = [] ; V = [_|C] }.
123 | eval(list, Ls0, Ls) --> eval_all(Ls0, Ls).
124 | eval(not, [A], V) --> eval(A, V0), goal_truth(V0=[], V).
125 | eval(>, [A,B], V) --> eval(A, n(V1)), eval(B, n(V2)), goal_truth(V1>V2, V).
126 | eval(<, [A,B], V) --> eval(>, [B,A], V).
127 | eval(=, [A,B], V) --> eval(A, n(V1)), eval(B, n(V2)), goal_truth(V1=:=V2, V).
128 | eval(progn, Ps, V) --> eval_all(Ps, Vs), { last(Vs, V) }.
129 | eval(eval, [A], V) --> eval(A, F0), { compile(F0, F1) }, eval(F1, V).
130 | eval(equal, [A,B], V) --> eval(A, V1), eval(B, V2), goal_truth(V1=V2, V).
131 | eval(cons, [A,B], [V0|V1]) --> eval(A, V0), eval(B, V1).
132 | eval(while, [Cond|Bs], []) -->
133 | ( eval(Cond, []) -> []
134 | ; eval_all(Bs, _),
135 | eval(while, [Cond|Bs], _)
136 | ).
137 | eval(defun, [F,As|Body], s(F)), [Fs-Vs0] -->
138 | [Fs0-Vs0],
139 | { put_assoc(F, Fs0, As-Body, Fs) }.
140 | eval(user(F), As0, V), [Fs-Vs] -->
141 | eval_all(As0, As1),
142 | [Fs-Vs],
143 | { empty_assoc(E),
144 | get_assoc(F, Fs, As-Body),
145 | bind_arguments(As, As1, E, Bindings),
146 | phrase(eval_all(Body, Results), [Fs-Bindings], _),
147 | last(Results, V) }.
148 | eval(setq, [Var,V0], V), [Fs0-Vs] -->
149 | eval(V0, V),
150 | [Fs0-Vs0],
151 | { put_assoc(Var, Vs0, V, Vs) }.
152 | eval(if, [Cond,Then|Else], Value) -->
153 | ( eval(Cond, []) -> eval_all(Else, Values), { last(Values, Value) }
154 | ; eval(Then, Value)
155 | ).
156 |
157 | goal_truth(Goal, T) --> { Goal -> T = t ; T = [] }.
158 |
159 | bind_arguments([], [], Bs, Bs).
160 | bind_arguments([A|As], [V|Vs], Bs0, Bs) :-
161 | put_assoc(A, Bs0, V, Bs1),
162 | bind_arguments(As, Vs, Bs1, Bs).
163 |
164 | last(Ls, L) :- reverse(Ls, [L|_]).
165 |
--------------------------------------------------------------------------------