1 (in-package :parenscript.javascript
)
3 ;;;; The macrology of the basic Javascript-in-SEXPs language. Special forms and macros.
6 (defmacro defscriptliteral
(name string
)
7 "Define a Javascript literal that will expand to STRING."
8 `(define-script-special-form ,name
() (make-instance 'expression
:value
,string
)))
10 (defscriptliteral this
"this")
11 (defscriptliteral t
"true")
12 (defscriptliteral nil
"null")
13 (defscriptliteral false
"false")
14 (defscriptliteral undefined
"undefined")
16 (defmacro defscriptkeyword
(name string
)
17 "Define a Javascript keyword that will expand to STRING."
18 `(define-script-special-form ,name
() (make-instance 'statement
:value
,string
)))
20 (defscriptkeyword break
"break")
21 (defscriptkeyword continue
"continue")
24 (define-script-special-form array
(&rest values
)
25 (make-instance 'array-literal
26 :values
(mapcar #'compile-to-expression values
)))
28 (define-script-special-form aref
(array &rest coords
)
29 (make-instance 'js-aref
30 :array
(compile-to-expression array
)
31 :index
(mapcar #'compile-to-expression coords
)))
34 ;;; object literals (maps and hash-tables)
35 (define-script-special-form {} (&rest values
)
36 (make-instance 'object-literal
38 for
(key value
) on values by
#'cddr
39 collect
(cons key
(compile-to-expression value
)))))
42 (define-script-special-form ++ (x)
43 (make-instance 'one-op
:pre-p nil
:op
"++"
44 :value
(compile-to-expression x
)))
46 (define-script-special-form --
(x)
47 (make-instance 'one-op
:pre-p nil
:op
"--"
48 :value
(compile-to-expression x
)))
50 (define-script-special-form incf
(x &optional
(delta 1))
52 (make-instance 'one-op
:pre-p t
:op
"++"
53 :value
(compile-to-expression x
))
54 (make-instance 'op-form
56 :args
(mapcar #'compile-to-expression
59 (define-script-special-form decf
(x &optional
(delta 1))
61 (make-instance 'one-op
:pre-p t
:op
"--"
62 :value
(compile-to-expression x
))
63 (make-instance 'op-form
65 :args
(mapcar #'compile-to-expression
68 (define-script-special-form -
(first &rest rest
)
70 (make-instance 'one-op
73 :value
(compile-to-expression first
))
74 (make-instance 'op-form
76 :args
(mapcar #'compile-to-expression
79 (define-script-special-form not
(x)
80 (let ((value (compile-to-expression x
)))
81 (if (and (typep value
'op-form
)
82 (= (length (op-args value
)) 2))
83 (let ((new-op (case (operator value
)
94 (make-instance 'op-form
:operator new-op
95 :args
(op-args value
))
96 (make-instance 'one-op
:pre-p t
:op
"!"
98 (make-instance 'one-op
:pre-p t
:op
"!"
101 (define-script-special-form ~
(x)
102 (let ((expr (compile-to-expression x
)))
103 (make-instance 'one-op
:pre-p t
:op
"~" :value expr
)))
106 (define-script-special-form progn
(&rest body
)
107 (make-instance 'js-block
108 :statements
(mapcar #'compile-to-statement body
)))
110 (defmethod expression-precedence ((body js-block
))
111 (if (= (length (block-statements body
)) 1)
112 (expression-precedence (first (block-statements body
)))
113 (op-precedence 'comma
)))
115 ;;; function definition
116 (define-script-special-form lambda
(args &rest body
)
117 (make-instance 'js-lambda
118 :args
(mapcar #'compile-to-symbol args
)
119 :body
(make-instance 'js-block
121 :statements
(mapcar #'compile-to-statement body
))))
123 (define-script-special-form defun
(name args
&rest body
)
124 (make-instance 'js-defun
125 :name
(compile-to-symbol name
)
126 :args
(mapcar #'compile-to-symbol args
)
127 :body
(make-instance 'js-block
129 :statements
(mapcar #'compile-to-statement body
))))
132 (define-script-special-form create
(&rest args
)
133 (make-instance 'js-object
134 :slots
(loop for
(name val
) on args by
#'cddr
135 collect
(let ((name-expr (compile-to-expression name
)))
136 (assert (or (typep name-expr
'js-variable
)
137 (typep name-expr
'string-literal
)
138 (typep name-expr
'number-literal
)))
139 (list name-expr
(compile-to-expression val
))))))
142 (define-script-special-form slot-value
(obj slot
)
143 (make-instance 'js-slot-value
:object
(compile-to-expression obj
)
144 :slot
(compile-script-form slot
)))
147 (define-script-special-form cond
(&rest clauses
)
148 (make-instance 'js-cond
149 :tests
(mapcar (lambda (clause) (compile-to-expression (car clause
)))
151 :bodies
(mapcar (lambda (clause) (compile-to-block (cons 'progn
(cdr clause
)) :indent
" "))
155 (define-script-special-form if
(test then
&optional else
)
156 (make-instance 'js-if
:test
(compile-to-expression test
)
157 :then
(compile-to-block then
:indent
" ")
159 (compile-to-block else
:indent
" "))))
161 (defmethod expression-precedence ((if js-if
))
165 (define-script-special-form switch
(value &rest clauses
)
166 (let ((clauses (mapcar #'(lambda (clause)
167 (let ((val (first clause
))
169 (list (if (eql val
'default
)
171 (compile-to-expression val
))
172 (compile-to-block (cons 'progn body
) :indent
" "))))
174 (check (compile-to-expression value
)))
175 (make-instance 'js-switch
:value check
180 (defun assignment-op (op)
196 (defun make-js-test (lhs rhs
)
197 (if (and (typep rhs
'op-form
)
198 (member lhs
(op-args rhs
) :test
#'js-equal
))
199 (let ((args-without (remove lhs
(op-args rhs
)
200 :count
1 :test
#'js-equal
))
201 (args-without-first (remove lhs
(op-args rhs
)
204 (one (list (make-instance 'number-literal
:value
1))))
206 (format t
"OPERATOR: ~S, ARGS-WITHOUT: ~S, ARGS-WITHOUT-FIRST ~S~%"
210 (cond ((and (js-equal args-without one
)
211 (eql (operator rhs
) '+))
212 (make-instance 'one-op
:pre-p nil
:op
"++"
214 ((and (js-equal args-without-first one
)
215 (eql (operator rhs
) '-
))
216 (make-instance 'one-op
:pre-p nil
:op
"--"
218 ((and (assignment-op (operator rhs
))
219 (member (operator rhs
)
221 (js-equal lhs
(first (op-args rhs
))))
222 (make-instance 'op-form
223 :operator
(assignment-op (operator rhs
))
224 :args
(list lhs
(make-instance 'op-form
225 :operator
(operator rhs
)
226 :args args-without-first
))))
227 ((and (assignment-op (operator rhs
))
228 (js-equal (first (op-args rhs
)) lhs
))
229 (make-instance 'op-form
230 :operator
(assignment-op (operator rhs
))
231 :args
(list lhs
(make-instance 'op-form
232 :operator
(operator rhs
)
233 :args
(cdr (op-args rhs
))))))
234 (t (make-instance 'js-setf
:lhs lhs
:rhsides
(list rhs
)))))
235 (make-instance 'js-setf
:lhs lhs
:rhsides
(list rhs
))))
237 (define-script-special-form setf
(&rest args
)
238 (let ((assignments (loop for
(lhs rhs
) on args by
#'cddr
239 for rexpr
= (compile-to-expression rhs
)
240 for lexpr
= (compile-to-expression lhs
)
241 collect
(make-js-test lexpr rexpr
))))
242 (if (= (length assignments
) 1)
244 (make-instance 'js-block
:indent
"" :statements assignments
))))
246 (defmethod expression-precedence ((setf js-setf
))
250 (define-script-special-form defvar
(name &optional value
)
251 (make-instance 'js-defvar
:names
(list (compile-to-symbol name
))
252 :value
(when value
(compile-to-expression value
))))
255 (defun make-for-vars (decls)
256 (loop for decl in decls
257 for var
= (if (atom decl
) decl
(first decl
))
258 for init
= (if (atom decl
) nil
(second decl
))
259 collect
(make-instance 'js-defvar
:names
(list (compile-to-symbol var
))
260 :value
(compile-to-expression init
))))
262 (defun make-for-steps (decls)
263 (loop for decl in decls
264 when
(= (length decl
) 3)
265 collect
(compile-to-expression (third decl
))))
267 (define-script-special-form do
(decls termination
&rest body
)
268 (let ((vars (make-for-vars decls
))
269 (steps (make-for-steps decls
))
270 (check (compile-to-expression (list 'not
(first termination
))))
271 (body (compile-to-block (cons 'progn body
) :indent
" ")))
272 (make-instance 'js-for
278 (define-script-special-form doeach
(decl &rest body
)
279 (make-instance 'for-each
:name
(compile-to-symbol (first decl
))
280 :value
(compile-to-expression (second decl
))
281 :body
(compile-to-block (cons 'progn body
) :indent
" ")))
283 (define-script-special-form while
(check &rest body
)
284 (make-instance 'js-while
285 :check
(compile-to-expression check
)
286 :body
(compile-to-block (cons 'progn body
) :indent
" ")))
289 (define-script-special-form with
(statement &rest body
)
290 (make-instance 'js-with
291 :obj
(compile-to-expression statement
)
292 :body
(compile-to-block (cons 'progn body
) :indent
" ")))
296 (define-script-special-form try
(body &rest clauses
)
297 (let ((body (compile-to-block body
:indent
" "))
298 (catch (cdr (assoc :catch clauses
)))
299 (finally (cdr (assoc :finally clauses
))))
300 (assert (not (cdar catch
)) nil
"Sorry, currently only simple catch forms are supported.")
301 (make-instance 'js-try
303 :catch
(when catch
(list (compile-to-symbol (caar catch
))
304 (compile-to-block (cons 'progn
(cdr catch
))
306 :finally
(when finally
(compile-to-block (cons 'progn finally
)
309 (define-script-special-form regex
(regex)
310 (make-instance 'regex
:value
(string regex
)))
313 (define-script-special-form instanceof
(value type
)
314 (make-instance 'js-instanceof
315 :value
(compile-to-expression value
)
316 :type
(compile-to-expression type
)))
318 ;;; single operations
319 (defmacro define-parse-script-single-op
(name &optional
(superclass 'expression
))
320 (let ((script-name (intern (concatenate 'string
"JS-" (symbol-name name
)) #.
*package
*)))
321 `(define-script-special-form ,name
(value)
322 (make-instance ',script-name
:value
(compile-to-expression value
)))
325 (define-parse-script-single-op return statement
)
326 (define-parse-script-single-op throw statement
)
327 (define-parse-script-single-op delete
)
328 (define-parse-script-single-op void
)
329 (define-parse-script-single-op typeof
)
330 (define-parse-script-single-op new
)
332 ;;; conditional compilation
333 (define-script-special-form cc-if
(test &rest body
)
334 (make-instance 'cc-if
:test test
335 :body
(mapcar #'compile-script-form body
)))
338 (defscriptmacro with-slots
(slots object
&rest body
)
339 `(symbol-macrolet ,(mapcar #'(lambda (slot)
340 `(,slot
'(slot-value ,object
',slot
)))
344 (defscriptmacro when
(test &rest body
)
345 `(if ,test
(progn ,@body
)))
347 (defscriptmacro unless
(test &rest body
)
348 `(if (not ,test
) (progn ,@body
)))
350 (defscriptmacro 1-
(form)
353 (defscriptmacro 1+ (form)
357 (defscriptmacro floor
(expr)
358 `(*Math.floor
,expr
))
360 (defscriptmacro random
()
363 (defscriptmacro evenp
(num)
366 (defscriptmacro oddp
(num)
370 (define-script-special-form js
(&rest body
)
371 (make-instance 'string-literal
372 :value
(string-join (js-to-statement-strings
373 (compile-script-form (cons 'progn body
)) 0) " ")))
375 (define-script-special-form script-inline
(&rest body
)
376 (make-instance 'string-literal
380 (string-join (js-to-statement-strings
381 (compile-script-form (cons 'progn body
)) 0) " "))))
382 (defscriptmacro js-inline
(&rest body
)
383 `(script-inline ,@body
))