Merge pull request #377 from asarhaddon/fix-runtests-pre-eval
[jackhill/mal.git] / common-lisp / src / step5_tco.lisp
CommitLineData
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))))