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