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