Change quasiquote algorithm
[jackhill/mal.git] / impls / common-lisp / src / step8_macros.lisp
CommitLineData
c8ac1eda
IA
1(defpackage :mal
2 (:use :common-lisp
3 :types
4 :env
5 :reader
6 :printer
0795349b 7 :core)
774d5cf8
IA
8 (:import-from :cl-readline
9 :readline
10 :register-function)
0795349b
IA
11 (:import-from :genhash
12 :hashref
13 :hashmap)
14 (:import-from :utils
15 :listify
774d5cf8
IA
16 :getenv
17 :common-prefix)
c8ac1eda
IA
18 (:export :main))
19
20(in-package :mal)
21
baa3c3af 22(define-condition invalid-function (mal-runtime-exception)
c8ac1eda
IA
23 ((form :initarg :form :reader form)
24 (context :initarg :context :reader context))
25 (:report (lambda (condition stream)
26 (format stream
27 "Invalid function '~a' provided while ~a"
28 (printer:pr-str (form condition))
29 (if (string= (context condition) "apply")
30 "applying"
31 "defining macro")))))
32
33
34(defvar *repl-env* (env:create-mal-env))
35
36(dolist (binding core:ns)
baa3c3af 37 (env:set-env *repl-env* (car binding) (cdr binding)))
c8ac1eda
IA
38
39(defvar mal-def! (make-mal-symbol "def!"))
40(defvar mal-let* (make-mal-symbol "let*"))
41(defvar mal-do (make-mal-symbol "do"))
42(defvar mal-if (make-mal-symbol "if"))
43(defvar mal-fn* (make-mal-symbol "fn*"))
44(defvar mal-quote (make-mal-symbol "quote"))
45(defvar mal-quasiquote (make-mal-symbol "quasiquote"))
fbfe6784 46(defvar mal-quasiquoteexpand (make-mal-symbol "quasiquoteexpand"))
c8ac1eda
IA
47(defvar mal-unquote (make-mal-symbol "unquote"))
48(defvar mal-splice-unquote (make-mal-symbol "splice-unquote"))
fbfe6784 49(defvar mal-vec (make-mal-symbol "vec"))
c8ac1eda
IA
50(defvar mal-cons (make-mal-symbol "cons"))
51(defvar mal-concat (make-mal-symbol "concat"))
52(defvar mal-defmacro! (make-mal-symbol "defmacro!"))
53(defvar mal-macroexpand (make-mal-symbol "macroexpand"))
54
55(defun eval-sequence (sequence env)
56 (map 'list
57 (lambda (ast) (mal-eval ast env))
baa3c3af 58 (mal-data-value sequence)))
c8ac1eda
IA
59
60(defun eval-hash-map (hash-map env)
baa3c3af
IA
61 (let ((hash-map-value (mal-data-value hash-map))
62 (new-hash-table (make-mal-value-hash-table)))
c8ac1eda
IA
63 (genhash:hashmap (lambda (key value)
64 (setf (genhash:hashref (mal-eval key env) new-hash-table)
65 (mal-eval value env)))
66 hash-map-value)
baa3c3af 67 (make-mal-hash-map new-hash-table)))
c8ac1eda
IA
68
69(defun eval-ast (ast env)
70 (switch-mal-type ast
71 (types:symbol (env:get-env env ast))
72 (types:list (eval-sequence ast env))
73 (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env))))
74 (types:hash-map (eval-hash-map ast env))
75 (types:any ast)))
76
fbfe6784
NB
77(defun qq-reducer (elt acc)
78 (make-mal-list
79 (if (and (mal-list-p elt)
80 (mal-data-value= (first (mal-data-value elt)) mal-splice-unquote))
81 (list mal-concat (second (mal-data-value elt)) acc)
82 (list mal-cons (quasiquote elt) acc))))
83(defun qq-iter (elts)
84 (reduce #'qq-reducer elts :from-end t :initial-value (make-mal-list ())))
c8ac1eda 85(defun quasiquote (ast)
fbfe6784
NB
86 (switch-mal-type ast
87 (types:list (if (mal-data-value= (first (mal-data-value ast)) mal-unquote)
88 (second (mal-data-value ast))
89 (qq-iter (mal-data-value ast))))
90 (types:vector (make-mal-list (list mal-vec (qq-iter (listify (mal-data-value ast))))))
91 (types:hash-map (make-mal-list (list mal-quote ast)))
92 (types:symbol (make-mal-list (list mal-quote ast)))
93 (types:any ast)))
c8ac1eda
IA
94
95(defun is-macro-call (ast env)
baa3c3af
IA
96 (when (mal-list-p ast)
97 (let* ((func-symbol (first (mal-data-value ast)))
98 (func (when (mal-symbol-p func-symbol)
c8ac1eda
IA
99 (env:find-env env func-symbol))))
100 (and func
baa3c3af 101 (mal-fn-p func)
85657c96 102 (cdr (assoc :is-macro (mal-data-attrs func)))))))
c8ac1eda
IA
103
104(defun mal-macroexpand (ast env)
105 (loop
106 while (is-macro-call ast env)
baa3c3af 107 do (let* ((forms (mal-data-value ast))
c8ac1eda 108 (func (env:get-env env (first forms))))
baa3c3af 109 (setf ast (apply (mal-data-value func)
c8ac1eda
IA
110 (cdr forms)))))
111 ast)
112
113(defun mal-read (string)
114 (reader:read-str string))
115
116(defun mal-eval (ast env)
117 (loop
118 do (setf ast (mal-macroexpand ast env))
119 do (cond
baa3c3af
IA
120 ((null ast) (return mal-nil))
121 ((not (mal-list-p ast)) (return (eval-ast ast env)))
122 ((zerop (length (mal-data-value ast))) (return ast))
123 (t (let ((forms (mal-data-value ast)))
c8ac1eda 124 (cond
baa3c3af 125 ((mal-data-value= mal-quote (first forms))
c8ac1eda
IA
126 (return (second forms)))
127
fbfe6784
NB
128 ((mal-data-value= mal-quasiquoteexpand (first forms))
129 (return (quasiquote (second forms))))
130
baa3c3af 131 ((mal-data-value= mal-quasiquote (first forms))
c8ac1eda
IA
132 (setf ast (quasiquote (second forms))))
133
baa3c3af 134 ((mal-data-value= mal-macroexpand (first forms))
c8ac1eda
IA
135 (return (mal-macroexpand (second forms) env)))
136
baa3c3af 137 ((mal-data-value= mal-def! (first forms))
c8ac1eda
IA
138 (return (env:set-env env (second forms) (mal-eval (third forms) env))))
139
baa3c3af 140 ((mal-data-value= mal-defmacro! (first forms))
c8ac1eda 141 (let ((value (mal-eval (third forms) env)))
baa3c3af 142 (return (if (mal-fn-p value)
c8ac1eda
IA
143 (env:set-env env
144 (second forms)
145 (progn
85657c96 146 (setf (cdr (assoc :is-macro (mal-data-attrs value))) t)
c8ac1eda
IA
147 value))
148 (error 'invalid-function
149 :form value
150 :context "macro")))))
151
baa3c3af 152 ((mal-data-value= mal-let* (first forms))
c8ac1eda 153 (let ((new-env (env:create-mal-env :parent env))
baa3c3af 154 (bindings (utils:listify (mal-data-value (second forms)))))
c8ac1eda
IA
155
156 (mapcar (lambda (binding)
157 (env:set-env new-env
158 (car binding)
159 (mal-eval (or (cdr binding)
baa3c3af 160 mal-nil)
c8ac1eda
IA
161 new-env)))
162 (loop
163 for (symbol value) on bindings
164 by #'cddr
165 collect (cons symbol value)))
166 (setf ast (third forms)
167 env new-env)))
168
baa3c3af 169 ((mal-data-value= mal-do (first forms))
c8ac1eda
IA
170 (mapc (lambda (form) (mal-eval form env))
171 (butlast (cdr forms)))
172 (setf ast (car (last forms))))
173
baa3c3af 174 ((mal-data-value= mal-if (first forms))
c8ac1eda 175 (let ((predicate (mal-eval (second forms) env)))
baa3c3af
IA
176 (setf ast (if (or (mal-data-value= predicate mal-nil)
177 (mal-data-value= predicate mal-false))
c8ac1eda
IA
178 (fourth forms)
179 (third forms)))))
180
baa3c3af 181 ((mal-data-value= mal-fn* (first forms))
c8ac1eda
IA
182 (return (let ((arglist (second forms))
183 (body (third forms)))
baa3c3af
IA
184 (make-mal-fn (lambda (&rest args)
185 (mal-eval body (env:create-mal-env :parent env
186 :binds (listify (mal-data-value arglist))
187 :exprs args)))
85657c96
VS
188 :attrs (list (cons :params arglist)
189 (cons :ast body)
190 (cons :env env)
191 (cons :is-macro nil))))))
c8ac1eda
IA
192
193 (t (let* ((evaluated-list (eval-ast ast env))
194 (function (car evaluated-list)))
195 ;; If first element is a mal function unwrap it
baa3c3af
IA
196 (cond ((mal-fn-p function)
197 (let* ((attrs (mal-data-attrs function)))
85657c96
VS
198 (setf ast (cdr (assoc :ast attrs))
199 env (env:create-mal-env :parent (cdr (assoc :env attrs))
c8ac1eda
IA
200 :binds (map 'list
201 #'identity
85657c96 202 (mal-data-value (cdr (assoc :params attrs))))
c8ac1eda 203 :exprs (cdr evaluated-list)))))
baa3c3af
IA
204 ((mal-builtin-fn-p function)
205 (return (apply (mal-data-value function)
c8ac1eda
IA
206 (cdr evaluated-list))))
207 (t (error 'invalid-function
208 :form function
209 :context "apply")))))))))))
210
211(defun mal-print (expression)
212 (printer:pr-str expression))
213
214(defun rep (string)
215 (handler-case
baa3c3af
IA
216 (mal-print (mal-eval (mal-read string) *repl-env*))
217 (mal-error (condition)
218 (format nil "~a" condition))
c8ac1eda 219 (error (condition)
baa3c3af 220 (format nil "Internal error: ~a" condition))))
c8ac1eda
IA
221
222(env:set-env *repl-env*
baa3c3af
IA
223 (make-mal-symbol "eval")
224 (make-mal-builtin-fn (lambda (ast)
225 (mal-eval ast *repl-env*))))
c8ac1eda
IA
226
227(rep "(def! not (fn* (a) (if a false true)))")
e6d41de4 228(rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))")
c8ac1eda 229(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
c8ac1eda
IA
230
231(defvar *use-readline-p* nil)
232
774d5cf8
IA
233(defun complete-toplevel-symbols (input &rest ignored)
234 (declare (ignorable ignored))
235
236 (let (candidates)
237 (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*)
238 when (let ((pos (search input key))) (and pos (zerop pos)))
239 do (push key candidates))
240
241 (if (= 1 (length candidates))
242 (cons (car candidates) candidates)
243 (cons (apply #'utils:common-prefix candidates) candidates))))
244
c8ac1eda
IA
245(defun raw-input (prompt)
246 (format *standard-output* prompt)
247 (force-output *standard-output*)
248 (read-line *standard-input* nil))
249
250(defun mal-readline (prompt)
251 (if *use-readline-p*
774d5cf8 252 (rl:readline :prompt prompt :add-history t :novelty-check #'string/=)
c8ac1eda
IA
253 (raw-input prompt)))
254
255(defun mal-writeline (string)
256 (when string
257 (write-line string)
258 (force-output *standard-output*)))
259
260(defun repl ()
261 (loop do (let ((line (mal-readline "user> ")))
262 (if line
263 (mal-writeline (rep line))
264 (return)))))
265
266(defun run-file (file)
267 (rep (format nil "(load-file \"~a\")" file)))
268
269(defun main (&optional (argv nil argv-provided-p))
89676a9f 270
0795349b
IA
271 (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false")
272 (string= (utils:getenv "TERM") "dumb"))))
c8ac1eda 273
89676a9f
IA
274 ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort
275 ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment
276 ;; variable which the test runner sets causing `read-line' on *standard-input*
277 ;; to fail with an empty stream error. The following reinitializes the
278 ;; standard streams
279 ;;
280 ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html
281 #+clisp (setf *standard-input* (ext:make-stream :input)
282 *standard-output* (ext:make-stream :output :buffered t)
283 *error-output* (ext:make-stream :error :buffered t))
284
774d5cf8
IA
285 ;; CCL fails with a error while registering completion function
286 ;; See also https://github.com/mrkkrp/cl-readline/issues/5
287 #-ccl (rl:register-function :complete #'complete-toplevel-symbols)
288
c8ac1eda
IA
289 (let ((args (if argv-provided-p
290 argv
291 (cdr (utils:raw-command-line-arguments)))))
292 (env:set-env *repl-env*
baa3c3af 293 (make-mal-symbol "*ARGV*")
8202dbf8 294 (make-mal-list (mapcar #'make-mal-string (cdr args))))
c8ac1eda
IA
295 (if (null args)
296 (repl)
297 (run-file (car args)))))
033f64c4
IA
298
299;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an
300;;; image containing foreign libraries is restored. The extra messages cause the
301;;; MAL testcases to fail
302
303#+cmucl (progn
304 (defvar *old-standard-output* *standard-output*
305 "Keep track of current value standard output, this is restored after image restore completes")
306
307 (defun muffle-output ()
308 (setf *standard-output* (make-broadcast-stream)))
309
310 (defun restore-output ()
311 (setf *standard-output* *old-standard-output*))
312
313 (pushnew #'muffle-output ext:*after-save-initializations*)
314 (setf ext:*after-save-initializations*
315 (append ext:*after-save-initializations* (list #'restore-output))))