+ (let* ((bindings (mapcar #'ensure-list bindings))
+ (variables (mapcar #'first bindings))
+ (cvalues (mapcar #'ls-compile (mapcar #'second bindings)))
+ (*environment* (extend-local-env (remove-if #'special-variable-p variables)))
+ (dynamic-bindings))
+ (concat "(function("
+ (join (mapcar (lambda (x)
+ (if (special-variable-p x)
+ (let ((v (gvarname x)))
+ (push (cons x v) dynamic-bindings)
+ v)
+ (translate-variable x)))
+ variables)
+ ",")
+ "){" *newline*
+ (let ((body (ls-compile-block body t)))
+ (indent (let-binding-wrapper dynamic-bindings body)))
+ "})(" (join cvalues ",") ")")))
+
+
+;;; Return the code to initialize BINDING, and push it extending the
+;;; current lexical environment if the variable is special.
+(defun let*-initialize-value (binding)
+ (let ((var (first binding))
+ (value (second binding)))
+ (if (special-variable-p var)
+ (concat (ls-compile `(setq ,var ,value)) ";" *newline*)
+ (let* ((v (gvarname var))
+ (b (make-binding var 'variable v)))
+ (prog1 (concat "var " v " = " (ls-compile value) ";" *newline*)
+ (push-to-lexenv b *environment* 'variable))))))
+
+;;; Wrap BODY to restore the symbol values of SYMBOLS after body. It
+;;; DOES NOT generate code to initialize the value of the symbols,
+;;; unlike let-binding-wrapper.
+(defun let*-binding-wrapper (symbols body)
+ (when (null symbols)
+ (return-from let*-binding-wrapper body))
+ (let ((store (mapcar (lambda (s) (cons s (gvarname s)))
+ (remove-if-not #'special-variable-p symbols))))
+ (concat
+ "try {" *newline*
+ (indent
+ (mapconcat (lambda (b)
+ (let ((s (ls-compile `(quote ,(car b)))))
+ (concat "var " (cdr b) " = " s ".value;" *newline*)))
+ store)
+ body)
+ "}" *newline*
+ "finally {" *newline*
+ (indent
+ (mapconcat (lambda (b)
+ (let ((s (ls-compile `(quote ,(car b)))))
+ (concat s ".value" " = " (cdr b) ";" *newline*)))
+ store))
+ "}" *newline*)))
+
+
+(define-compilation let* (bindings &rest body)
+ (let ((bindings (mapcar #'ensure-list bindings))
+ (*environment* (copy-lexenv *environment*)))
+ (js!selfcall
+ (let ((specials (remove-if-not #'special-variable-p (mapcar #'first bindings)))
+ (body (concat (mapconcat #'let*-initialize-value bindings)
+ (ls-compile-block body t))))
+ (let*-binding-wrapper specials body)))))
+
+
+(defvar *block-counter* 0)
+
+(define-compilation block (name &rest body)
+ (let ((tr (integer-to-string (incf *block-counter*))))
+ (let ((b (make-binding name 'block tr)))
+ (js!selfcall
+ "try {" *newline*
+ (let ((*environment* (extend-lexenv (list b) *environment* 'block)))
+ (indent "return " (ls-compile `(progn ,@body)) ";" *newline*))
+ "}" *newline*
+ "catch (cf){" *newline*
+ " if (cf.type == 'block' && cf.id == " tr ")" *newline*
+ " return cf.value;" *newline*
+ " else" *newline*
+ " throw cf;" *newline*
+ "}" *newline*))))
+
+(define-compilation return-from (name &optional value)
+ (let ((b (lookup-in-lexenv name *environment* 'block)))
+ (if b
+ (js!selfcall
+ "throw ({"
+ "type: 'block', "
+ "id: " (binding-value b) ", "
+ "value: " (ls-compile value) ", "
+ "message: 'Return from unknown block " (symbol-name name) ".'"
+ "})")
+ (error (concat "Unknown block `" (symbol-name name) "'.")))))
+
+
+(define-compilation catch (id &rest body)
+ (js!selfcall
+ "var id = " (ls-compile id) ";" *newline*
+ "try {" *newline*
+ (indent "return " (ls-compile `(progn ,@body))
+ ";" *newline*)
+ "}" *newline*
+ "catch (cf){" *newline*
+ " if (cf.type == 'catch' && cf.id == id)" *newline*
+ " return cf.value;" *newline*
+ " else" *newline*
+ " throw cf;" *newline*
+ "}" *newline*))
+
+(define-compilation throw (id value)
+ (js!selfcall
+ "throw ({"
+ "type: 'catch', "
+ "id: " (ls-compile id) ", "
+ "value: " (ls-compile value) ", "
+ "message: 'Throw uncatched.'"
+ "})"))
+
+
+(defvar *tagbody-counter* 0)
+(defvar *go-tag-counter* 0)
+
+(defun go-tag-p (x)
+ (or (integerp x) (symbolp x)))
+
+(defun declare-tagbody-tags (tbidx body)
+ (let ((bindings
+ (mapcar (lambda (label)
+ (let ((tagidx (integer-to-string (incf *go-tag-counter*))))
+ (make-binding label 'gotag (list tbidx tagidx))))
+ (remove-if-not #'go-tag-p body))))
+ (extend-lexenv bindings *environment* 'gotag)))
+
+(define-compilation tagbody (&rest body)
+ ;; Ignore the tagbody if it does not contain any go-tag. We do this
+ ;; because 1) it is easy and 2) many built-in forms expand to a
+ ;; implicit tagbody, so we save some space.
+ (unless (some #'go-tag-p body)
+ (return-from tagbody (ls-compile `(progn ,@body nil))))
+ ;; The translation assumes the first form in BODY is a label
+ (unless (go-tag-p (car body))
+ (push (gensym "START") body))
+ ;; Tagbody compilation
+ (let ((tbidx (integer-to-string *tagbody-counter*)))
+ (let ((*environment* (declare-tagbody-tags tbidx body))
+ initag)
+ (let ((b (lookup-in-lexenv (first body) *environment* 'gotag)))
+ (setq initag (second (binding-value b))))
+ (js!selfcall
+ "var tagbody_" tbidx " = " initag ";" *newline*
+ "tbloop:" *newline*
+ "while (true) {" *newline*
+ (indent "try {" *newline*
+ (indent (let ((content ""))
+ (concat "switch(tagbody_" tbidx "){" *newline*
+ "case " initag ":" *newline*
+ (dolist (form (cdr body) content)
+ (concatf content
+ (if (not (go-tag-p form))
+ (indent (ls-compile form) ";" *newline*)
+ (let ((b (lookup-in-lexenv form *environment* 'gotag)))
+ (concat "case " (second (binding-value b)) ":" *newline*)))))
+ "default:" *newline*
+ " break tbloop;" *newline*
+ "}" *newline*)))
+ "}" *newline*
+ "catch (jump) {" *newline*
+ " if (jump.type == 'tagbody' && jump.id == " tbidx ")" *newline*
+ " tagbody_" tbidx " = jump.label;" *newline*
+ " else" *newline*
+ " throw(jump);" *newline*
+ "}" *newline*)
+ "}" *newline*
+ "return " (ls-compile nil) ";" *newline*))))
+
+(define-compilation go (label)
+ (let ((b (lookup-in-lexenv label *environment* 'gotag))
+ (n (cond
+ ((symbolp label) (symbol-name label))
+ ((integerp label) (integer-to-string label)))))
+ (if b
+ (js!selfcall
+ "throw ({"
+ "type: 'tagbody', "
+ "id: " (first (binding-value b)) ", "
+ "label: " (second (binding-value b)) ", "
+ "message: 'Attempt to GO to non-existing tag " n "'"
+ "})" *newline*)
+ (error (concat "Unknown tag `" n "'.")))))
+
+(define-compilation unwind-protect (form &rest clean-up)
+ (js!selfcall
+ "var ret = " (ls-compile nil) ";" *newline*
+ "try {" *newline*
+ (indent "ret = " (ls-compile form) ";" *newline*)
+ "} finally {" *newline*
+ (indent (ls-compile-block clean-up))
+ "}" *newline*
+ "return ret;" *newline*))
+
+(define-compilation multiple-value-call (func-form &rest forms)
+ (let ((func (ls-compile func-form)))
+ (js!selfcall
+ "var args = [values];" *newline*
+ "values = function(){" *newline*
+ (indent "var result = [];" *newline*
+ "result['multiple-value'] = true;" *newline*
+ "for (var i=0; i<arguments.length; i++)" *newline*
+ (indent "result.push(arguments[i]);" *newline*)
+ "return result;" *newline*)
+ "}" *newline*
+ "var vs;" *newline*
+ (mapconcat (lambda (form)
+ (concat "vs = " (ls-compile form t) ";" *newline*
+ "if (typeof vs === 'object' && 'multiple-value' in vs)" *newline*
+ (indent "args = args.concat(vs);" *newline*)
+ "else" *newline*
+ (indent "args.push(vs);" *newline*)))
+ forms)
+ "return (" func ").apply(window, args);" *newline*)))
+
+