Commit | Line | Data |
---|---|---|
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)))) |