Fixed problems with expressions being compiled to statements inside expression progns...
[clinton/parenscript.git] / src / js-macrology.lisp
CommitLineData
4a987e2b 1(in-package :parenscript)
5aa10005
RD
2
3;;;; The macrology of the basic Javascript-in-SEXPs language. Special forms and macros.
4
5;;; literals
4a987e2b 6(defmacro defpsliteral (name string)
45f8fec1
VS
7 `(define-ps-special-form ,name (expecting)
8 (declare (ignore expecting))
9 (list 'js-literal ,string)))
4a987e2b
VS
10
11(defpsliteral this "this")
12(defpsliteral t "true")
13(defpsliteral true "true")
14(defpsliteral false "false")
15(defpsliteral f "false")
16(defpsliteral nil "null")
17(defpsliteral undefined "undefined")
18
19(defmacro defpskeyword (name string)
45f8fec1
VS
20 `(define-ps-special-form ,name (expecting)
21 (declare (ignore expecting))
22 (list 'js-keyword ,string)))
4a987e2b
VS
23
24(defpskeyword break "break")
25(defpskeyword continue "continue")
26
27(define-ps-special-form array (expecting &rest values)
45f8fec1 28 (declare (ignore expecting))
4a987e2b
VS
29 (cons 'array-literal (mapcar (lambda (form) (compile-parenscript-form form :expecting :expression))
30 values)))
31
32(define-ps-special-form aref (expecting array &rest coords)
45f8fec1 33 (declare (ignore expecting))
4a987e2b
VS
34 (list 'js-aref (compile-parenscript-form array :expecting :expression)
35 (mapcar (lambda (form)
36 (compile-parenscript-form form :expecting :expression))
37 coords)))
38
39(define-ps-special-form {} (expecting &rest arrows)
45f8fec1 40 (declare (ignore expecting))
4a987e2b
VS
41 (cons 'object-literal (loop for (key value) on arrows by #'cddr
42 collect (cons key (compile-parenscript-form value :expecting :expression)))))
5aa10005
RD
43
44;;; operators
4a987e2b 45(define-ps-special-form incf (expecting x &optional (delta 1))
45f8fec1 46 (declare (ignore expecting))
4a987e2b
VS
47 (if (equal delta 1)
48 (list 'unary-operator "++" (compile-parenscript-form x :expecting :expression) :prefix t)
49 (list 'operator '+= (list (compile-parenscript-form x :expecting :expression)
50 (compile-parenscript-form delta :expecting :expression)))))
51
52(define-ps-special-form decf (expecting x &optional (delta 1))
45f8fec1 53 (declare (ignore expecting))
4a987e2b
VS
54 (if (equal delta 1)
55 (list 'unary-operator "--" (compile-parenscript-form x :expecting :expression) :prefix t)
56 (list 'operator '-= (list (compile-parenscript-form x :expecting :expression)
57 (compile-parenscript-form delta :expecting :expression)))))
58
59(define-ps-special-form - (expecting first &rest rest)
45f8fec1 60 (declare (ignore expecting))
5aa10005 61 (if (null rest)
4a987e2b
VS
62 (list 'unary-operator "-" (compile-parenscript-form first :expecting :expression) :prefix t)
63 (list 'operator '- (mapcar (lambda (val) (compile-parenscript-form val :expecting :expression))
64 (cons first rest)))))
65
66(define-ps-special-form not (expecting x)
45f8fec1 67 (declare (ignore expecting))
4a987e2b
VS
68 (let ((form (compile-parenscript-form x :expecting :expression))
69 (not-op nil))
70 (if (and (eql (first form) 'operator)
71 (= (length (third form)) 2)
72 (setf not-op (case (second form)
73 (== '!=)
74 (< '>=)
75 (> '<=)
76 (<= '>)
77 (>= '<)
78 (!= '==)
79 (=== '!==)
80 (!== '===)
81 (t nil))))
82 (list 'operator not-op (third form))
83 (list 'unary-operator "!" form :prefix t))))
84
85(define-ps-special-form ~ (expecting x)
45f8fec1 86 (declare (ignore expecting))
e0032a96 87 (list 'unary-operator "~" (compile-parenscript-form x :expecting :expression) :prefix t))
4a987e2b 88
839600e9
VS
89(defun flatten-blocks (body)
90 (when body
4a987e2b 91 (if (and (listp (car body))
839600e9
VS
92 (eql 'js-block (caar body)))
93 (append (third (car body)) (flatten-blocks (cdr body)))
94 (cons (car body) (flatten-blocks (cdr body))))))
4a987e2b
VS
95
96(define-ps-special-form progn (expecting &rest body)
b1017218
VS
97 (if (and (eql expecting :expression) (= 1 (length body)))
98 (compile-parenscript-form (car body) :expecting :expression)
99 (list 'js-block
e0032a96 100 expecting
13b8268e 101 (let* ((block (mapcar (lambda (form)
e0032a96 102 (compile-parenscript-form form :expecting expecting))
13b8268e
VS
103 body))
104 (clean-block (remove nil block))
105 (flat-block (flatten-blocks clean-block))
106 (reachable-block (append (remove-if #'constant-literal-form-p (butlast flat-block))
107 (last flat-block))))
108 reachable-block))))
5aa10005
RD
109
110;;; function definition
e0032a96
VS
111(defun compile-function-definition (args body)
112 (list (mapcar (lambda (arg) (compile-parenscript-form arg :expecting :symbol)) args)
113 (let ((*enclosing-lexical-block-declarations* ()))
114 ;; the first compilation will produce a list of variables we need to declare in the function body
115 (compile-parenscript-form `(progn ,@body) :expecting :statement)
116 ;; now declare and compile
117 (compile-parenscript-form `(progn ,@(loop for var in *enclosing-lexical-block-declarations* collect `(defvar ,var))
118 ,@body) :expecting :statement))))
119
4a987e2b 120(define-ps-special-form %js-lambda (expecting args &rest body)
45f8fec1 121 (declare (ignore expecting))
e0032a96 122 (cons 'js-lambda (compile-function-definition args body)))
4a987e2b
VS
123
124(define-ps-special-form %js-defun (expecting name args &rest body)
45f8fec1 125 (declare (ignore expecting))
e0032a96 126 (append (list 'js-defun name) (compile-function-definition args body)))
5aa10005
RD
127
128;;; object creation
4a987e2b 129(define-ps-special-form create (expecting &rest args)
45f8fec1 130 (declare (ignore expecting))
4a987e2b
VS
131 (list 'js-object (loop for (name val) on args by #'cddr collecting
132 (let ((name-expr (compile-parenscript-form name :expecting :expression)))
133 (assert (or (stringp name-expr)
134 (numberp name-expr)
135 (and (listp name-expr)
136 (or (eql 'js-variable (car name-expr))
137 (eql 'script-quote (car name-expr)))))
138 ()
139 "Slot ~s is not one of js-variable, keyword, string or number." name-expr)
140 (list name-expr (compile-parenscript-form val :expecting :expression))))))
141
142(define-ps-special-form %js-slot-value (expecting obj slot)
45f8fec1 143 (declare (ignore expecting))
4a987e2b
VS
144 (if (ps::ps-macroexpand slot)
145 (list 'js-slot-value (compile-parenscript-form obj :expecting :expression) (compile-parenscript-form slot))
146 (compile-parenscript-form obj :expecting :expression)))
147
148(define-ps-special-form cond (expecting &rest clauses)
0949f072
VS
149 (ecase expecting
150 (:statement (list 'js-cond-statement
151 (mapcar (lambda (clause)
152 (destructuring-bind (test &rest body)
153 clause
154 (list (compile-parenscript-form test :expecting :expression)
e0032a96 155 (compile-parenscript-form `(progn ,@body) :expecting :statement))))
0949f072
VS
156 clauses)))
157 (:expression (make-cond-clauses-into-nested-ifs clauses))))
158
159(defun make-cond-clauses-into-nested-ifs (clauses)
160 (if clauses
161 (destructuring-bind (test &rest body)
162 (car clauses)
163 (if (eq t test)
164 (compile-parenscript-form `(progn ,@body) :expecting :expression)
165 (list 'js-expression-if (compile-parenscript-form test :expecting :expression)
166 (compile-parenscript-form `(progn ,@body) :expecting :expression)
167 (make-cond-clauses-into-nested-ifs (cdr clauses)))))
168 (compile-parenscript-form nil :expecting :expression)))
4a987e2b
VS
169
170(define-ps-special-form if (expecting test then &optional else)
171 (ecase expecting
172 (:statement (list 'js-statement-if (compile-parenscript-form test :expecting :expression)
173 (compile-parenscript-form `(progn ,then))
174 (when else (compile-parenscript-form `(progn ,else)))))
175 (:expression (list 'js-expression-if (compile-parenscript-form test :expecting :expression)
176 (compile-parenscript-form then :expecting :expression)
5705b542 177 (compile-parenscript-form else :expecting :expression)))))
4a987e2b
VS
178
179(define-ps-special-form switch (expecting test-expr &rest clauses)
45f8fec1 180 (declare (ignore expecting))
4a987e2b
VS
181 (let ((clauses (mapcar (lambda (clause)
182 (let ((val (car clause))
5aa10005 183 (body (cdr clause)))
e0032a96 184 (cons (if (eql val 'default)
5aa10005 185 'default
4a987e2b 186 (compile-parenscript-form val :expecting :expression))
e0032a96
VS
187 (mapcar (lambda (statement) (compile-parenscript-form statement :expecting :statement))
188 body))))
5aa10005 189 clauses))
4a987e2b
VS
190 (expr (compile-parenscript-form test-expr :expecting :expression)))
191 (list 'js-switch expr clauses)))
5aa10005
RD
192
193;;; assignment
194(defun assignment-op (op)
195 (case op
196 (+ '+=)
197 (~ '~=)
198 (\& '\&=)
199 (\| '\|=)
200 (- '-=)
201 (* '*=)
202 (% '%=)
203 (>> '>>=)
204 (^ '^=)
205 (<< '<<=)
206 (>>> '>>>=)
207 (/ '/=)
208 (t nil)))
209
4a987e2b
VS
210(defun smart-setf (lhs rhs)
211 (if (and (listp rhs)
212 (eql 'operator (car rhs))
213 (member lhs (third rhs) :test #'equalp))
214 (let ((args-without (remove lhs (third rhs) :count 1 :test #'equalp))
215 (args-without-first (remove lhs (third rhs) :count 1 :end 1 :test #'equalp)))
216 (cond ((and (equal (car args-without) 1) (eql (second rhs) '+))
217 (list 'unary-operator "++" lhs :prefix nil))
218 ((and (equal (second args-without-first) 1) (eql (second rhs) '-))
219 (list 'unary-operator "--" lhs :prefix nil))
220 ((and (assignment-op (second rhs))
221 (member (second rhs) '(+ *))
222 (equalp lhs (first (third rhs))))
223 (list 'operator (assignment-op (second rhs))
224 (list lhs (list 'operator (second rhs) args-without-first))))
225 ((and (assignment-op (second rhs)) (equalp (first (third rhs)) lhs))
226 (list 'operator (assignment-op (second rhs))
227 (list lhs (list 'operator (second rhs) (cdr (third rhs))))))
228 (t (list 'js-assign lhs rhs))))
229 (list 'js-assign lhs rhs)))
230
231(define-ps-special-form setf1% (expecting lhs rhs)
45f8fec1 232 (declare (ignore expecting))
4a987e2b
VS
233 (smart-setf (compile-parenscript-form lhs :expecting :expression) (compile-parenscript-form rhs :expecting :expression)))
234
235(define-ps-special-form defvar (expecting name &rest value)
45f8fec1 236 (declare (ignore expecting))
f26808d8 237 (append (list 'js-defvar name)
4a987e2b
VS
238 (when value
239 (assert (= (length value) 1) () "Wrong number of arguments to defvar: ~s" `(defvar ,name ,@value))
240 (list (compile-parenscript-form (car value) :expecting :expression)))))
5aa10005
RD
241
242;;; iteration
243(defun make-for-vars (decls)
244 (loop for decl in decls
245 for var = (if (atom decl) decl (first decl))
4a987e2b
VS
246 for init-value = (if (atom decl) nil (second decl))
247 collect (cons (compile-parenscript-form var :expecting :symbol) (compile-parenscript-form init-value))))
5aa10005
RD
248
249(defun make-for-steps (decls)
250 (loop for decl in decls
251 when (= (length decl) 3)
4a987e2b 252 collect (compile-parenscript-form (third decl) :expecting :expression)))
5aa10005 253
4a987e2b 254(define-ps-special-form do (expecting decls termination-test &rest body)
45f8fec1 255 (declare (ignore expecting))
5aa10005
RD
256 (let ((vars (make-for-vars decls))
257 (steps (make-for-steps decls))
4a987e2b
VS
258 (test (compile-parenscript-form `(not ,(first termination-test)) :expecting :expression))
259 (body (compile-parenscript-form `(progn ,@body))))
260 (list 'js-for vars steps test body)))
261
262(define-ps-special-form doeach (expecting decl &rest body)
45f8fec1 263 (declare (ignore expecting))
4a987e2b 264 (list 'js-for-each
f26808d8 265 (first decl)
4a987e2b
VS
266 (compile-parenscript-form (second decl) :expecting :expression)
267 (compile-parenscript-form `(progn ,@body))))
268
269(define-ps-special-form while (expecting test &rest body)
45f8fec1 270 (declare (ignore expecting))
4a987e2b
VS
271 (list 'js-while (compile-parenscript-form test :expecting :expression)
272 (compile-parenscript-form `(progn ,@body))))
273
274(define-ps-special-form with (expecting expression &rest body)
45f8fec1 275 (declare (ignore expecting))
4a987e2b
VS
276 (list 'js-with (compile-parenscript-form expression :expecting :expression)
277 (compile-parenscript-form `(progn ,@body))))
278
279(define-ps-special-form try (expecting form &rest clauses)
45f8fec1 280 (declare (ignore expecting))
4a987e2b
VS
281 (let ((catch (cdr (assoc :catch clauses)))
282 (finally (cdr (assoc :finally clauses))))
5aa10005 283 (assert (not (cdar catch)) nil "Sorry, currently only simple catch forms are supported.")
839600e9
VS
284 (assert (or catch finally) ()
285 "Try form should have either a catch or a finally clause or both.")
4a987e2b
VS
286 (list 'js-try (compile-parenscript-form `(progn ,form))
287 :catch (when catch (list (compile-parenscript-form (caar catch) :expecting :symbol)
288 (compile-parenscript-form `(progn ,@(cdr catch)))))
289 :finally (when finally (compile-parenscript-form `(progn ,@finally))))))
290
291(define-ps-special-form regex (expecting regex)
45f8fec1 292 (declare (ignore expecting))
4a987e2b 293 (list 'js-regex (string regex)))
5aa10005
RD
294
295;;; TODO instanceof
4a987e2b 296(define-ps-special-form instanceof (expecting value type)
45f8fec1 297 (declare (ignore expecting))
4a987e2b
VS
298 (list 'js-instanceof (compile-parenscript-form value :expecting :expression)
299 (compile-parenscript-form type :expecting :expression)))
5aa10005
RD
300
301;;; single operations
4a987e2b 302(mapcar (lambda (op) (eval `(define-ps-special-form ,op (expecting value)
45f8fec1 303 (declare (ignore expecting))
4a987e2b
VS
304 (list 'js-named-operator ',op (compile-parenscript-form value)))))
305 '(throw delete void typeof new))
5aa10005 306
4a987e2b 307(define-ps-special-form return (expecting &optional value)
45f8fec1 308 (declare (ignore expecting))
4a987e2b 309 (list 'js-return (compile-parenscript-form value :expecting :expression)))
a2434734 310
5aa10005 311;;; conditional compilation
4a987e2b 312(define-ps-special-form cc-if (expecting test &rest body)
45f8fec1 313 (declare (ignore expecting))
4a987e2b 314 (list 'cc-if test (mapcar #'compile-parenscript-form body)))
5aa10005
RD
315
316;;; standard macros
4a987e2b 317(defpsmacro when (test &rest body)
5aa10005
RD
318 `(if ,test (progn ,@body)))
319
4a987e2b 320(defpsmacro unless (test &rest body)
5aa10005
RD
321 `(if (not ,test) (progn ,@body)))
322
4a987e2b 323(defpsmacro 1- (form)
5aa10005
RD
324 `(- ,form 1))
325
4a987e2b 326(defpsmacro 1+ (form)
5aa10005 327 `(+ ,form 1))