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