8 (:import-from
:cl-readline
11 (:import-from
:genhash
22 (define-condition invalid-function
(mal-runtime-exception)
23 ((form :initarg
:form
:reader form
)
24 (context :initarg
:context
:reader context
))
25 (:report
(lambda (condition stream
)
27 "Invalid function '~a' provided while ~a"
28 (printer:pr-str
(form condition
))
29 (if (string= (context condition
) "apply")
33 (defvar *repl-env
* (env:create-mal-env
))
35 (dolist (binding core
:ns
)
36 (env:set-env
*repl-env
* (car binding
) (cdr binding
)))
38 (defvar mal-def
! (make-mal-symbol "def!"))
39 (defvar mal-let
* (make-mal-symbol "let*"))
40 (defvar mal-do
(make-mal-symbol "do"))
41 (defvar mal-if
(make-mal-symbol "if"))
42 (defvar mal-fn
* (make-mal-symbol "fn*"))
43 (defvar mal-quote
(make-mal-symbol "quote"))
44 (defvar mal-quasiquote
(make-mal-symbol "quasiquote"))
45 (defvar mal-unquote
(make-mal-symbol "unquote"))
46 (defvar mal-splice-unquote
(make-mal-symbol "splice-unquote"))
47 (defvar mal-cons
(make-mal-symbol "cons"))
48 (defvar mal-concat
(make-mal-symbol "concat"))
49 (defvar mal-defmacro
! (make-mal-symbol "defmacro!"))
50 (defvar mal-macroexpand
(make-mal-symbol "macroexpand"))
51 (defvar mal-try
* (make-mal-symbol "try*"))
52 (defvar mal-catch
* (make-mal-symbol "catch*"))
53 (defvar mal-throw
(make-mal-symbol "throw"))
55 (defun eval-sequence (sequence env
)
57 (lambda (ast) (mal-eval ast env
))
58 (mal-data-value sequence
)))
60 (defun eval-hash-map (hash-map env
)
61 (let ((hash-map-value (mal-data-value hash-map
))
62 (new-hash-table (make-mal-value-hash-table)))
63 (genhash:hashmap
(lambda (key value
)
64 (setf (genhash:hashref
(mal-eval key env
) new-hash-table
)
65 (mal-eval value env
)))
67 (make-mal-hash-map new-hash-table
)))
69 (defun eval-ast (ast env
)
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
))
77 (defun is-pair (value)
78 (and (or (mal-list-p value
)
80 (< 0 (length (mal-data-value value
)))))
82 (defun quasiquote (ast)
83 (if (not (is-pair ast
))
84 (make-mal-list (list mal-quote ast
))
85 (let ((forms (map 'list
#'identity
(mal-data-value ast
))))
87 ((mal-data-value= mal-unquote
(first forms
))
90 ((and (is-pair (first forms
))
91 (mal-data-value= mal-splice-unquote
92 (first (mal-data-value (first forms
)))))
93 (make-mal-list (list mal-concat
94 (second (mal-data-value (first forms
)))
95 (quasiquote (make-mal-list (cdr forms
))))))
97 (t (make-mal-list (list mal-cons
98 (quasiquote (first forms
))
99 (quasiquote (make-mal-list (cdr forms
))))))))))
101 (defun is-macro-call (ast env
)
102 (when (mal-list-p ast
)
103 (let* ((func-symbol (first (mal-data-value ast
)))
104 (func (when (mal-symbol-p func-symbol
)
105 (env:find-env env func-symbol
))))
108 (cdr (assoc :is-macro
(mal-data-attrs func
)))))))
110 (defun mal-macroexpand (ast env
)
112 while
(is-macro-call ast env
)
113 do
(let* ((forms (mal-data-value ast
))
114 (func (env:get-env env
(first forms
))))
115 (setf ast
(apply (mal-data-value func
)
119 (defun mal-read (string)
120 (reader:read-str string
))
122 (defun mal-eval (ast env
)
124 do
(setf ast
(mal-macroexpand ast env
))
126 ((null ast
) (return mal-nil
))
127 ((not (mal-list-p ast
)) (return (eval-ast ast env
)))
128 ((zerop (length (mal-data-value ast
))) (return ast
))
129 (t (let ((forms (mal-data-value ast
)))
131 ((mal-data-value= mal-quote
(first forms
))
132 (return (second forms
)))
134 ((mal-data-value= mal-quasiquote
(first forms
))
135 (setf ast
(quasiquote (second forms
))))
137 ((mal-data-value= mal-macroexpand
(first forms
))
138 (return (mal-macroexpand (second forms
) env
)))
140 ((mal-data-value= mal-def
! (first forms
))
141 (return (env:set-env env
(second forms
) (mal-eval (third forms
) env
))))
143 ((mal-data-value= mal-defmacro
! (first forms
))
144 (let ((value (mal-eval (third forms
) env
)))
145 (return (if (mal-fn-p value
)
149 (setf (cdr (assoc :is-macro
(mal-data-attrs value
))) t
)
151 (error 'invalid-function
153 :context
"macro")))))
155 ((mal-data-value= mal-let
* (first forms
))
156 (let ((new-env (env:create-mal-env
:parent env
))
157 (bindings (utils:listify
(mal-data-value (second forms
)))))
159 (mapcar (lambda (binding)
162 (mal-eval (or (cdr binding
)
166 for
(symbol value
) on bindings
168 collect
(cons symbol value
)))
169 (setf ast
(third forms
)
172 ((mal-data-value= mal-do
(first forms
))
173 (mapc (lambda (form) (mal-eval form env
))
174 (butlast (cdr forms
)))
175 (setf ast
(car (last forms
))))
177 ((mal-data-value= mal-if
(first forms
))
178 (let ((predicate (mal-eval (second forms
) env
)))
179 (setf ast
(if (or (mal-data-value= predicate mal-nil
)
180 (mal-data-value= predicate mal-false
))
184 ((mal-data-value= mal-fn
* (first forms
))
185 (return (let ((arglist (second forms
))
186 (body (third forms
)))
187 (make-mal-fn (lambda (&rest args
)
188 (mal-eval body
(env:create-mal-env
:parent env
189 :binds
(listify (mal-data-value arglist
))
191 :attrs
(list (cons :params arglist
)
194 (cons :is-macro nil
))))))
196 ((mal-data-value= mal-try
* (first forms
))
197 (if (not (third forms
))
198 (return (mal-eval (second forms
) env
))
200 (return (mal-eval (second forms
) env
))
202 (let ((catch-forms (mal-data-value (third forms
))))
203 (when (mal-data-value= mal-catch
*
205 (return (mal-eval (third catch-forms
)
206 (env:create-mal-env
:parent env
207 :binds
(list (second catch-forms
))
208 :exprs
(list (if (typep condition
'mal-user-exception
)
209 (mal-exception-data condition
)
210 (make-mal-string (format nil
"~a" condition
)))))))))))))
212 (t (let* ((evaluated-list (eval-ast ast env
))
213 (function (car evaluated-list
)))
214 ;; If first element is a mal function unwrap it
215 (cond ((mal-fn-p function
)
216 (let* ((attrs (mal-data-attrs function
)))
217 (setf ast
(cdr (assoc :ast attrs
))
218 env
(env:create-mal-env
:parent
(cdr (assoc :env attrs
))
221 (mal-data-value (cdr (assoc :params attrs
))))
222 :exprs
(cdr evaluated-list
)))))
223 ((mal-builtin-fn-p function
)
224 (return (apply (mal-data-value function
)
225 (cdr evaluated-list
))))
226 (t (error 'invalid-function
228 :context
"apply")))))))))))
230 (defun mal-print (expression)
231 (printer:pr-str expression
))
235 (mal-print (mal-eval (mal-read string
) *repl-env
*))
236 (mal-error (condition)
237 (format nil
"Error: ~a" condition
))
238 (mal-runtime-exception (condition)
239 (format nil
"Exception: ~a" condition
))
240 (mal-user-exception (condition)
241 (format nil
"Exception: ~a" (pr-str (mal-exception-data condition
))))
243 (format nil
"Internal error: ~a" condition
))))
245 (env:set-env
*repl-env
*
246 (make-mal-symbol "eval")
247 (make-mal-builtin-fn (lambda (ast)
248 (mal-eval ast
*repl-env
*))))
250 (env:set-env
*repl-env
*
251 (make-mal-symbol "*cl-implementation*")
252 (make-mal-string (lisp-implementation-type)))
254 (env:set-env
*repl-env
*
255 (make-mal-symbol "*cl-version*")
256 (make-mal-string (lisp-implementation-version)))
258 (rep "(def! not (fn* (a) (if a false true)))")
259 (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
260 (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)))))))")
261 (rep "(def! *host-language* \"common-lisp\")")
262 (rep "(def! inc (fn* [x] (+ x 1)))")
263 (rep "(def! gensym (let* [counter (atom 0)] (fn* [] (symbol (str \"G__\" (swap! counter inc))))))")
264 (rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))")
266 (defvar *use-readline-p
* nil
)
268 (defun complete-toplevel-symbols (input &rest ignored
)
269 (declare (ignorable ignored
))
272 (loop for key being the hash-keys of
(env:mal-env-bindings
*repl-env
*)
273 when
(let ((pos (search input key
))) (and pos
(zerop pos
)))
274 do
(push key candidates
))
276 (if (= 1 (length candidates
))
277 (cons (car candidates
) candidates
)
278 (cons (apply #'utils
:common-prefix candidates
) candidates
))))
280 (defun raw-input (prompt)
281 (format *standard-output
* prompt
)
282 (force-output *standard-output
*)
283 (read-line *standard-input
* nil
))
285 (defun mal-readline (prompt)
287 (rl:readline
:prompt prompt
:add-history t
:novelty-check
#'string
/=)
290 (defun mal-writeline (string)
293 (force-output *standard-output
*)))
296 (rep "(println (str \"Mal [\" *host-language* \"]\"))")
297 (loop do
(let ((line (mal-readline "user> ")))
299 (mal-writeline (rep line
))
302 (defun run-file (file)
303 (rep (format nil
"(load-file \"~a\")" file
)))
305 (defun main (&optional
(argv nil argv-provided-p
))
307 (setf *use-readline-p
* (not (or (string= (utils:getenv
"PERL_RL") "false")
308 (string= (utils:getenv
"TERM") "dumb"))))
310 ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort
311 ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment
312 ;; variable which the test runner sets causing `read-line' on *standard-input*
313 ;; to fail with an empty stream error. The following reinitializes the
316 ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html
317 #+clisp
(setf *standard-input
* (ext:make-stream
:input
)
318 *standard-output
* (ext:make-stream
:output
:buffered t
)
319 *error-output
* (ext:make-stream
:error
:buffered t
))
321 ;; CCL fails with a error while registering completion function
322 ;; See also https://github.com/mrkkrp/cl-readline/issues/5
323 #-ccl
(rl:register-function
:complete
#'complete-toplevel-symbols
)
325 (let ((args (if argv-provided-p
327 (cdr (utils:raw-command-line-arguments
)))))
328 (env:set-env
*repl-env
*
329 (make-mal-symbol "*ARGV*")
330 (make-mal-list (mapcar #'make-mal-string
(cdr args
))))
333 (run-file (car args
)))))
335 ;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an
336 ;;; image containing foreign libraries is restored. The extra messages cause the
337 ;;; MAL testcases to fail
340 (defvar *old-standard-output
* *standard-output
*
341 "Keep track of current value standard output, this is restored after image restore completes")
343 (defun muffle-output ()
344 (setf *standard-output
* (make-broadcast-stream)))
346 (defun restore-output ()
347 (setf *standard-output
* *old-standard-output
*))
349 (pushnew #'muffle-output ext
:*after-save-initializations
*)
350 (setf ext
:*after-save-initializations
*
351 (append ext
:*after-save-initializations
* (list #'restore-output
))))