1 (in-package :parenscript
)
3 ;;;; The macrology of the Parenscript language. Special forms and macros.
5 ;;; parenscript gensyms
6 (defvar *gen-script-name-counter
* 0)
8 (defun gen-script-name-string (&key
(prefix "_js_"))
9 "Generates a unique valid javascript identifier ()"
11 prefix
(princ-to-string (incf *gen-script-name-counter
*))))
13 (defun gen-script-name (&key
(prefix "_ps_"))
14 "Generate a new javascript identifier."
15 (intern (gen-script-name-string :prefix prefix
)
18 (defmacro gen-ps-name
(&rest args
)
19 `(gen-script-name ,@args
))
21 (defmacro with-unique-ps-names
(symbols &body body
)
22 "Evaluate BODY with the variables on SYMBOLS bound to new javascript identifiers.
24 Each element of SYMBOLS is either a symbol or a list of (symbol
26 `(let* ,(mapcar (lambda (symbol)
27 (destructuring-bind (symbol &optional prefix
)
32 `(,symbol
(gen-script-name :prefix
,prefix
))
33 `(,symbol
(gen-script-name)))))
37 (defvar *var-counter
* 0)
39 (defun script-gensym (&optional
(name "js"))
40 (intern (format nil
"tmp-~A-~A" name
(incf *var-counter
*)) #.
*package
*))
43 (defscriptmacro list
(&rest values
)
46 (defscriptmacro make-array
(&rest inits
)
47 `(new (*array
,@inits
)))
50 (define-script-special-form eval-when
(&rest args
)
51 "(eval-when form-language? (situation*) form*)
53 The given forms are evaluated only during the given SITUATION in the specified
54 FORM-LANGUAGE (either :lisp or :parenscript, def, defaulting to :lisp during
55 -toplevel and :parenscript during :execute). The accepted SITUATIONS are :execute,
56 :scan-toplevel. :scan-toplevel is the phase of compilation when function definitions
57 and the like are being added to the compilation environment. :execute is the phase when
58 the code is being evaluated by a Javascript engine."
59 (multiple-value-bind (body-language situations subforms
)
60 (process-eval-when-args args
)
61 ; (format t "~A~%~A~%"
62 ; (and (compiler-in-situation-p *compilation-environment* :compile-toplevel)
63 ; (find :compile-toplevel situations))
64 ; (compiler-in-situation-p *compilation-environment* :execute)
65 ; (find :execute situations))
67 ((and (compiler-in-situation-p *compilation-environment
* :compile-toplevel
)
68 (find :compile-toplevel situations
))
69 (error "Should never be processing eval-when :COMPILE-TOPLEVEL forms from here."))
71 ((and (compiler-in-situation-p *compilation-environment
* :execute
)
72 (find :execute situations
))
73 (when (eql body-language
:parenscript
)
74 (let ((form `(progn ,@subforms
)))
75 ; (format t "Form: ~A~%" form)
76 (compile-to-statement form
)))))))
79 (defscriptmacro defpackage
(name &rest options
)
80 "Defines a Parenscript package."
81 (labels ((opt-name (opt) (if (listp opt
) (car opt
) opt
)))
82 (let ((nicknames nil
) (lisp-package nil
) (secondary-lisp-packages nil
)
83 (exports nil
) (used-packages nil
) (documentation nil
))
86 (:lisp-package
(setf lisp-package
(second opt
)))
87 (:nicknames
(setf nicknames
(rest opt
)))
88 (:secondary-lisp-packages secondary-lisp-packages t
)
89 (:export
(setf exports
(rest opt
)))
90 (:use
(setf used-packages
(rest opt
)))
91 (:documentation
(setf documentation
(second opt
)))
92 (t (error "Unknown option in DEFPACKAGE: ~A" (opt-name opt
)))))
93 ; (format t "Exports: ~A~%" exports)
94 (create-script-package
95 *compilation-environment
*
98 :secondary-lisp-packages secondary-lisp-packages
99 :used-packages used-packages
100 :lisp-package lisp-package
102 :documentation documentation
)))
105 (defscriptmacro in-package
(package-designator)
106 "Changes the current script package in the parenscript compilation environment. This mostly
107 affects the reader and how it interns non-prefixed symbols"
108 (let ((script-package
109 (find-script-package package-designator
*compilation-environment
*)))
110 (when (null script-package
)
111 (error "~A does not designate any script package. Available script package: ~A"
113 (mapcar #'script-package-name
(comp-env-script-packages *compilation-environment
*))))
114 (setf (comp-env-current-package *compilation-environment
*)
118 (defscriptmacro case
(value &rest clauses
)
119 (labels ((make-clause (val body more
)
121 (append (mapcar #'list
(butlast val
))
122 (make-clause (first (last val
)) body more
)))
123 ((member val
'(t otherwise
))
124 (make-clause 'default body more
))
125 (more `((,val
,@body break
)))
126 (t `((,val
,@body
))))))
127 `(switch ,value
,@(mapcon #'(lambda (x)
128 (make-clause (car (first x
))
134 (define-script-special-form let
(decls &rest body
)
135 (let ((defvars (mapcar #'(lambda (decl)
137 (make-instance 'ps-js
::js-defvar
138 :names
(list (compile-to-symbol decl
))
140 (let ((name (first decl
))
141 (value (second decl
)))
142 (make-instance 'ps-js
::js-defvar
143 :names
(list (compile-to-symbol name
))
144 :value
(compile-to-expression value
)))))
146 (make-instance 'ps-js
::js-sub-block
148 :statements
(nconc defvars
149 (mapcar #'compile-to-statement body
)))))
152 (defscriptmacro dotimes
(iter &rest body
)
153 (let ((var (first iter
))
154 (times (second iter
)))
155 `(do ((,var
0 (1+ ,var
)))
159 (defscriptmacro dolist
(i-array &rest body
)
160 (let ((var (first i-array
))
161 (array (second i-array
))
162 (arrvar (script-gensym "arr"))
163 (idx (script-gensym "i")))
164 `(let ((,arrvar
,array
))
165 (do ((,idx
0 (1+ ,idx
)))
166 ((>= ,idx
(slot-value ,arrvar
'length
)))
167 (let ((,var
(aref ,arrvar
,idx
)))
171 (defmacro with-temp-macro-environment
((var) &body body
)
172 `(let* ((,var
(make-macro-env-dictionary))
173 (*script-macro-env
* (cons ,var
*script-macro-env
*)))
176 (define-script-special-form macrolet
(macros &body body
)
177 (with-temp-macro-environment (macro-env-dict)
178 (dolist (macro macros
)
179 (destructuring-bind (name arglist
&body body
)
181 (setf (get-macro-spec name macro-env-dict
)
182 (cons nil
(let ((args (gensym "ps-macrolet-args-")))
183 (compile nil
`(lambda (&rest
,args
)
184 (destructuring-bind ,arglist
187 (compile-script-form `(progn ,@body
))))
189 (define-script-special-form symbol-macrolet
(symbol-macros &body body
)
190 (with-temp-macro-environment (macro-env-dict)
191 (dolist (macro symbol-macros
)
192 (destructuring-bind (name &body expansion
)
194 (setf (get-macro-spec name macro-env-dict
)
195 (cons t
(compile nil
`(lambda () ,@expansion
))))))
196 (compile-script-form `(progn ,@body
))))
198 (defscriptmacro defmacro
(name args
&body body
)
199 `(lisp (defscriptmacro ,name
,args
,@body
) nil
))
201 (defscriptmacro lisp
(&body forms
)
202 "Evaluates the given forms in Common Lisp at ParenScript
203 macro-expansion time. The value of the last form is treated as a
204 ParenScript expression and is inserted into the generated Javascript
205 \(use nil for no-op)."
206 (eval (cons 'progn forms
)))
209 (defscriptmacro rebind
(variables expression
)
210 "Creates a new js lexical environment and copies the given
211 variable(s) there. Executes the body in the new environment. This
212 has the same effect as a new (let () ...) form in lisp but works on
213 the js side for js closures."
214 (unless (listp variables
)
215 (setf variables
(list variables
)))
217 (let ((new-context (new *object
)))
218 ,@(loop for variable in variables
219 do
(setf variable
(symbol-to-js variable
))
220 collect
`(setf (slot-value new-context
,variable
) (slot-value this
,variable
)))
222 (return ,expression
))))))