Commit | Line | Data |
---|---|---|
626e3a1f IA |
1 | (defpackage :mal |
2 | (:use :common-lisp | |
3 | :types | |
4 | :env | |
5 | :reader | |
6 | :printer | |
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) | |
626e3a1f IA |
18 | (:export :main)) |
19 | ||
20 | (in-package :mal) | |
21 | ||
22 | (defvar *repl-env* (env:create-mal-env)) | |
23 | ||
24 | (dolist (binding core:ns) | |
baa3c3af | 25 | (env:set-env *repl-env* (car binding) (cdr binding))) |
626e3a1f IA |
26 | |
27 | (defvar mal-def! (make-mal-symbol "def!")) | |
28 | (defvar mal-let* (make-mal-symbol "let*")) | |
29 | (defvar mal-do (make-mal-symbol "do")) | |
30 | (defvar mal-if (make-mal-symbol "if")) | |
31 | (defvar mal-fn* (make-mal-symbol "fn*")) | |
32 | ||
33 | (defun eval-sequence (sequence env) | |
34 | (map 'list | |
35 | (lambda (ast) (mal-eval ast env)) | |
36 | (mal-data-value sequence))) | |
37 | ||
38 | (defun eval-hash-map (hash-map env) | |
baa3c3af IA |
39 | (let ((hash-map-value (mal-data-value hash-map)) |
40 | (new-hash-table (make-mal-value-hash-table))) | |
626e3a1f IA |
41 | (genhash:hashmap (lambda (key value) |
42 | (setf (genhash:hashref (mal-eval key env) new-hash-table) | |
43 | (mal-eval value env))) | |
44 | hash-map-value) | |
baa3c3af | 45 | (make-mal-hash-map new-hash-table))) |
626e3a1f IA |
46 | |
47 | (defun eval-ast (ast env) | |
48 | (switch-mal-type ast | |
49 | (types:symbol (env:get-env env ast)) | |
50 | (types:list (eval-sequence ast env)) | |
51 | (types:vector (make-mal-vector (apply 'vector (eval-sequence ast env)))) | |
52 | (types:hash-map (eval-hash-map ast env)) | |
53 | (types:any ast))) | |
54 | ||
55 | (defun mal-read (string) | |
56 | (reader:read-str string)) | |
57 | ||
58 | (defun mal-eval (ast env) | |
59 | (loop | |
60 | do (cond | |
baa3c3af IA |
61 | ((null ast) (return mal-nil)) |
62 | ((not (mal-list-p ast)) (return (eval-ast ast env))) | |
626e3a1f IA |
63 | ((zerop (length (mal-data-value ast))) (return ast)) |
64 | (t (let ((forms (mal-data-value ast))) | |
65 | (cond | |
66 | ((mal-data-value= mal-def! (first forms)) | |
67 | (return (env:set-env env (second forms) (mal-eval (third forms) env)))) | |
68 | ||
69 | ((mal-data-value= mal-let* (first forms)) | |
70 | (let ((new-env (env:create-mal-env :parent env)) | |
baa3c3af | 71 | (bindings (utils:listify (mal-data-value (second forms))))) |
626e3a1f IA |
72 | |
73 | (mapcar (lambda (binding) | |
74 | (env:set-env new-env | |
75 | (car binding) | |
76 | (mal-eval (or (cdr binding) | |
baa3c3af | 77 | mal-nil) |
626e3a1f IA |
78 | new-env))) |
79 | (loop | |
80 | for (symbol value) on bindings | |
81 | by #'cddr | |
82 | collect (cons symbol value))) | |
83 | (setf ast (third forms) | |
84 | env new-env))) | |
85 | ||
86 | ((mal-data-value= mal-do (first forms)) | |
87 | (mapc (lambda (form) (mal-eval form env)) | |
88 | (butlast (cdr forms))) | |
89 | (setf ast (car (last forms)))) | |
90 | ||
91 | ((mal-data-value= mal-if (first forms)) | |
92 | (let ((predicate (mal-eval (second forms) env))) | |
baa3c3af IA |
93 | (setf ast (if (or (mal-data-value= predicate mal-nil) |
94 | (mal-data-value= predicate mal-false)) | |
626e3a1f IA |
95 | (fourth forms) |
96 | (third forms))))) | |
97 | ||
98 | ((mal-data-value= mal-fn* (first forms)) | |
99 | (return (let ((arglist (second forms)) | |
100 | (body (third forms))) | |
baa3c3af IA |
101 | (make-mal-fn (lambda (&rest args) |
102 | (mal-eval body (env:create-mal-env :parent env | |
103 | :binds (listify (mal-data-value arglist)) | |
104 | :exprs args))) | |
85657c96 VS |
105 | :attrs (list (cons :params arglist) |
106 | (cons :ast body) | |
107 | (cons :env env)))))) | |
626e3a1f IA |
108 | |
109 | (t (let* ((evaluated-list (eval-ast ast env)) | |
110 | (function (car evaluated-list))) | |
111 | ;; If first element is a mal function unwrap it | |
baa3c3af | 112 | (if (not (mal-fn-p function)) |
626e3a1f IA |
113 | (return (apply (mal-data-value function) |
114 | (cdr evaluated-list))) | |
baa3c3af | 115 | (let* ((attrs (mal-data-attrs function))) |
85657c96 VS |
116 | (setf ast (cdr (assoc :ast attrs)) |
117 | env (env:create-mal-env :parent (cdr (assoc :env attrs)) | |
626e3a1f IA |
118 | :binds (map 'list |
119 | #'identity | |
85657c96 | 120 | (mal-data-value (cdr (assoc :params attrs)))) |
626e3a1f IA |
121 | :exprs (cdr evaluated-list))))))))))))) |
122 | ||
123 | (defun mal-print (expression) | |
124 | (printer:pr-str expression)) | |
125 | ||
126 | (defun rep (string) | |
127 | (handler-case | |
baa3c3af | 128 | (mal-print (mal-eval (mal-read string) *repl-env*)) |
626e3a1f | 129 | (error (condition) |
baa3c3af | 130 | (format nil "~a" condition)))) |
626e3a1f IA |
131 | |
132 | (rep "(def! not (fn* (a) (if a false true)))") | |
133 | ||
134 | (defvar *use-readline-p* nil) | |
135 | ||
774d5cf8 IA |
136 | (defun complete-toplevel-symbols (input &rest ignored) |
137 | (declare (ignorable ignored)) | |
138 | ||
139 | (let (candidates) | |
140 | (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*) | |
141 | when (let ((pos (search input key))) (and pos (zerop pos))) | |
142 | do (push key candidates)) | |
143 | ||
144 | (if (= 1 (length candidates)) | |
145 | (cons (car candidates) candidates) | |
146 | (cons (apply #'utils:common-prefix candidates) candidates)))) | |
147 | ||
626e3a1f IA |
148 | (defun raw-input (prompt) |
149 | (format *standard-output* prompt) | |
150 | (force-output *standard-output*) | |
151 | (read-line *standard-input* nil)) | |
152 | ||
153 | (defun mal-readline (prompt) | |
154 | (if *use-readline-p* | |
774d5cf8 | 155 | (rl:readline :prompt prompt :add-history t :novelty-check #'string/=) |
626e3a1f IA |
156 | (raw-input prompt))) |
157 | ||
158 | (defun mal-writeline (string) | |
159 | (when string | |
160 | (write-line string) | |
161 | (force-output *standard-output*))) | |
162 | ||
163 | (defun main (&optional (argv nil argv-provided-p)) | |
164 | (declare (ignorable argv argv-provided-p)) | |
89676a9f | 165 | |
0795349b IA |
166 | (setf *use-readline-p* (not (or (string= (utils:getenv "PERL_RL") "false") |
167 | (string= (utils:getenv "TERM") "dumb")))) | |
89676a9f IA |
168 | |
169 | ;; In GNU CLISP's batch mode the standard-input seems to be set to some sort | |
170 | ;; of input string-stream, this interacts wierdly with the PERL_RL enviroment | |
171 | ;; variable which the test runner sets causing `read-line' on *standard-input* | |
172 | ;; to fail with an empty stream error. The following reinitializes the | |
173 | ;; standard streams | |
174 | ;; | |
175 | ;; See http://www.gnu.org/software/clisp/impnotes/streams-interactive.html | |
176 | #+clisp (setf *standard-input* (ext:make-stream :input) | |
177 | *standard-output* (ext:make-stream :output :buffered t) | |
178 | *error-output* (ext:make-stream :error :buffered t)) | |
179 | ||
774d5cf8 IA |
180 | ;; CCL fails with a error while registering completion function |
181 | ;; See also https://github.com/mrkkrp/cl-readline/issues/5 | |
182 | #-ccl (rl:register-function :complete #'complete-toplevel-symbols) | |
183 | ||
626e3a1f IA |
184 | (loop do (let ((line (mal-readline "user> "))) |
185 | (if line (mal-writeline (rep line)) (return))))) | |
033f64c4 IA |
186 | |
187 | ;;; Workaround for CMUCL's printing of "Reloaded library ... " messages when an | |
188 | ;;; image containing foreign libraries is restored. The extra messages cause the | |
189 | ;;; MAL testcases to fail | |
190 | ||
191 | #+cmucl (progn | |
192 | (defvar *old-standard-output* *standard-output* | |
193 | "Keep track of current value standard output, this is restored after image restore completes") | |
194 | ||
195 | (defun muffle-output () | |
196 | (setf *standard-output* (make-broadcast-stream))) | |
197 | ||
198 | (defun restore-output () | |
199 | (setf *standard-output* *old-standard-output*)) | |
200 | ||
201 | (pushnew #'muffle-output ext:*after-save-initializations*) | |
202 | (setf ext:*after-save-initializations* | |
203 | (append ext:*after-save-initializations* (list #'restore-output)))) |