Introduce types mal-runtime-exception and mal-user-exception types
[jackhill/mal.git] / common_lisp / types.lisp
CommitLineData
74fa635b 1(defpackage :types
23a2c88b
IA
2 (:use :common-lisp)
3 (:export :mal-value=
4 ;; Accessors
5 :mal-value
6 :mal-type
7 :mal-meta
1161823b 8 :mal-attrs
23a2c88b
IA
9 ;; Mal values
10 :number
11 :boolean
12 :nil
13 :string
14 :symbol
15 :keyword
16 :list
17 :vector
18 :hash-map
b9de97b6 19 :atom
579201bb 20 :fn
3747635d 21 :builtin-fn
a85bb5e2 22 :any
e844f5c8
IA
23 :mal-exception
24 ;; User exceptions
25 :mal-user-exception
26 ;; Exceptions raised by the runtime itself
27 :mal-runtime-exception
aa333009
IA
28 ;; Error
29 :mal-error
23a2c88b 30 ;; Helpers
623c5d2f 31 :wrap-value
23a2c88b 32 :apply-unwrapped-values
d584319a 33 :apply-unwrapped-values-prefer-bool
362360df 34 :switch-mal-type))
74fa635b
IA
35
36(in-package :types)
37
aa333009
IA
38(define-condition mal-error (error)
39 nil)
40
e844f5c8
IA
41(define-condition mal-exception (error)
42 nil)
43
44(define-condition mal-runtime-exception (mal-exception)
45 nil)
46
47(define-condition mal-user-exception (mal-exception)
48 ((data :accessor mal-exception-data :initarg :data)))
49
74fa635b
IA
50(defclass mal-type ()
51 ((value :accessor mal-value :initarg :value)
dc3f4a5d 52 (meta :accessor mal-meta :initarg :meta :initform nil)
1161823b
IA
53 (type :accessor mal-type :initarg :type)
54 (attrs :accessor mal-attrs :initarg :attrs)))
74fa635b 55
1e9ac59c 56(defmethod print-object ((obj mal-type) out)
1161823b
IA
57 (with-slots (value type meta attrs) obj
58 (format out "#<mal ~a: ~a (~a, ~a)>" type value meta attrs)))
33537539 59
74fa635b
IA
60(defmacro define-mal-type (type)
61 ;; Create a class for given type and a convenience constructor and also export
62 ;; them
63 (let ((name (intern (string-upcase (concatenate 'string
64 "mal-"
65 (symbol-name type)))))
66 (constructor (intern (string-upcase (concatenate 'string
67 "make-mal-"
24c07f4c
IA
68 (symbol-name type)))))
69 (predicate (intern (string-upcase (concatenate 'string
70 "mal-"
71 (symbol-name type)
72 "-p")))))
74fa635b
IA
73 `(progn (defclass ,name (mal-type)
74 ((type :accessor mal-type
75 :initarg :type
76 :initform ',type)))
77
1161823b 78 (defun ,constructor (value &key meta attrs)
74fa635b 79 (make-instance ',name
33537539 80 :value value
1161823b
IA
81 :meta meta
82 :attrs attrs))
24c07f4c 83 (defun ,predicate (value)
579201bb
IA
84 (when (typep value 'mal-type)
85 (equal (mal-type value) ',type)))
74fa635b
IA
86
87 (export ',name)
24c07f4c
IA
88 (export ',constructor)
89 (export ',predicate))))
74fa635b 90
74fa635b
IA
91(define-mal-type number)
92(define-mal-type symbol)
3ccd8eaa 93(define-mal-type keyword)
74fa635b 94(define-mal-type string)
e4d7c6ac 95;; TODO true, false and nil should ideally be singleton
74fa635b 96(define-mal-type boolean)
e4d7c6ac
IA
97(define-mal-type nil)
98
23a2c88b
IA
99(define-mal-type list)
100(define-mal-type vector)
101(define-mal-type hash-map)
e4d7c6ac 102
b9de97b6
IA
103(define-mal-type atom)
104
e4d7c6ac 105(define-mal-type fn)
3747635d 106(define-mal-type builtin-fn)
0baefce0 107
a85bb5e2 108;; Generic type
49b184f0 109(defvar any)
a85bb5e2 110
0baefce0 111(defmacro switch-mal-type (ast &body forms)
26451863 112 `(let ((type (types:mal-type ,ast)))
0baefce0
IA
113 (cond
114 ,@(mapcar (lambda (form)
a85bb5e2
IA
115 (list (if (or (equal (car form) t)
116 (equal (car form) 'any))
117 t
118 (list 'equal (list 'quote (car form)) 'type))
0baefce0
IA
119 (cadr form)))
120 forms))))
a85bb5e2 121
de8ef209
IA
122(defun mal-symbol= (value1 value2)
123 (string= (symbol-name (mal-value value1))
124 (symbol-name (mal-value value2))))
125
126(defun mal-sequence= (value1 value2)
127 (let ((sequence1 (map 'list #'identity (mal-value value1)))
128 (sequence2 (map 'list #'identity (mal-value value2))))
129 (when (= (length sequence1) (length sequence2))
130 (every #'identity
131 (loop
132 for x in sequence1
133 for y in sequence2
134 collect (mal-value= x y))))))
135
136(defun mal-hash-map= (value1 value2)
137 (let ((map1 (mal-value value1))
138 (map2 (mal-value value2)))
139 (when (= (hash-table-count map1) (hash-table-count map2))
140 (every #'identity
141 (loop
142 for key being the hash-keys of map1
143 collect (mal-value= (gethash key map1)
144 (gethash key map2)))))))
145
146(defun mal-value= (value1 value2)
fd53ac3c
IA
147 (when (and (typep value1 'mal-type)
148 (typep value2 'mal-type))
149 (if (equal (mal-type value1) (mal-type value2))
dc1a9c47
IA
150 (switch-mal-type value1
151 (number (= (mal-value value1) (mal-value value2)))
152 (boolean (equal (mal-value value1) (mal-value value2)))
153 (nil (equal (mal-value value1) (mal-value value2)))
154 (string (string= (mal-value value1) (mal-value value2)))
155 (symbol (mal-symbol= value1 value2))
156 (keyword (mal-symbol= value1 value2))
157 (list (mal-sequence= value1 value2))
158 (vector (mal-sequence= value1 value2))
159 (hash-map (mal-hash-map= value1 value2))
160 (any nil))
161 (when (or (and (mal-list-p value1) (mal-vector-p value2))
162 (and (mal-list-p value2) (mal-vector-p value1)))
fd53ac3c 163 (mal-sequence= value1 value2)))))
de8ef209
IA
164
165(defun hash-mal-value (value)
166 (sxhash (mal-value value)))
167
168#+sbcl (sb-ext:define-hash-table-test mal-value= hash-mal-value)
169#+clisp (ext:define-hash-table-test mal-value= mal-value= hash-mal-value)
170
130e1c94
IA
171(defun wrap-hash-value (value)
172 (let ((new-hash-table (make-hash-table :test 'mal-value=)))
173 (loop
174 for key being the hash-keys of value
175 do (setf (gethash (wrap-value key) new-hash-table)
176 (wrap-value (gethash key value))))
177 new-hash-table))
178
179(defun wrap-value (value &key booleanp listp)
180 (typecase value
181 (number (make-mal-number value))
182 ;; This needs to before symbol since nil is a symbol
183 (null (funcall (cond
184 (booleanp #'make-mal-boolean)
185 (listp #'make-mal-list)
186 (t #'make-mal-nil))
187 value))
188 ;; This needs to before symbol since t, nil are symbols
189 (boolean (make-mal-boolean value))
190 (symbol (make-mal-symbol value))
191 (keyword (make-mal-keyword value))
192 (string (make-mal-string value))
193 (list (make-mal-list (map 'list #'wrap-value value)))
194 (vector (make-mal-vector (map 'vector #'wrap-value value)))
195 (hash-table (make-mal-hash-map (wrap-hash-value value)))
196 (null (make-mal-nil value))))
623c5d2f 197
cc9b97ef
IA
198(defun unwrap-value (value)
199 (switch-mal-type value
200 (list (mapcar #'unwrap-value (mal-value value)))
201 (vector (map 'vector #'unwrap-value (mal-value value)))
202 (hash-map (let ((hash-table (make-hash-table))
203 (hash-map-value (mal-value value)))
204 (loop
205 for key being the hash-keys of hash-map-value
206 do (setf (gethash (mal-value key) hash-table)
207 (mal-value (gethash key hash-map-value))))
208 hash-table))
209 (any (mal-value value))))
210
24c07f4c 211(defun apply-unwrapped-values (op &rest values)
cc9b97ef 212 (wrap-value (apply op (mapcar #'unwrap-value values))))
d584319a
IA
213
214(defun apply-unwrapped-values-prefer-bool (op &rest values)
cc9b97ef 215 (wrap-value (apply op (mapcar #'unwrap-value values)) :booleanp t))