+ (js!selfcall
+ "throw ({"
+ "type: 'catch', "
+ "id: " (ls-compile id env) ", "
+ "value: " (ls-compile value env) ", "
+ "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 (env tbidx body)
+ (let ((bindings
+ (mapcar (lambda (label)
+ (let ((tagidx (integer-to-string (incf *go-tag-counter*))))
+ (make-binding label 'gotag (list tbidx tagidx) t)))
+ (remove-if-not #'go-tag-p body))))
+ (extend-lexenv bindings env '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) env)))
+ ;; 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 ((env (declare-tagbody-tags env tbidx body))
+ initag)
+ (let ((b (lookup-in-lexenv (first body) env 'gotag)))
+ (setq initag (second (binding-translation 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 env) ";" *newline*)
+ (let ((b (lookup-in-lexenv form env 'gotag)))
+ (concat "case " (second (binding-translation 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 env 'gotag))
+ (n (cond
+ ((symbolp label) (symbol-name label))
+ ((integerp label) (integer-to-string label)))))
+ (if b
+ (js!selfcall
+ "throw ({"
+ "type: 'tagbody', "
+ "id: " (first (binding-translation b)) ", "
+ "label: " (second (binding-translation 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 env) ";" *newline*)
+ "} finally {" *newline*
+ (indent (ls-compile-block clean-up env))
+ "}" *newline*
+ "return ret;" *newline*))