I am away from a lispy computer, but here are the problem areas in js.lisp:
(defun js-expand-form (expr)
"Expand a javascript form."
(cond ((atom expr)
(multiple-value-bind (js-macro macro-env)
(lookup-macro expr)
(if js-macro
(js-expand-form (let ((*js-macro-env* macro-env))
(funcall js-macro)))
expr)))
;;; macros
(define-js-compiler-macro macrolet (macros &rest body)
(let* ((macro-env (make-hash-table :test 'equal))
(*js-macro-env* (cons macro-env *js-macro-env*)))
(dolist (macro macros)
(destructuring-bind (name arglist &rest body) macro
(setf (gethash (symbol-name name) macro-env)
(compile nil `(lambda ,arglist ,@body)))))
(js-compile `(progn ,@body))))
(defjsmacro symbol-macrolet (macros &rest body)
`(macrolet ,(mapcar #'(lambda (macro)
`(,(first macro) () ,@(rest macro))) macros)
,@body))
The problem is that symbol-macrolets use the same machinery as regular macrolets. A solution is to maintain parallel symbol and regular macro environments...
;; new variables
(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar *js-symbol-macro-toplevel* (make-hash-table :test 'equal)
"Toplevel of symbol macro expansion, holds all the toplevel javascript macros.")
(defvar *js-symbol-macro-env* (list js-symbol-macro-toplevel*)
"Current symbol macro environment."))
;; amend (lookup-macro ..) to take a :type argument.
(defun lookup-macro (name &keyword (type :lambda))
"Lookup the macro NAME in the current macro expansion
environment. Returns the macro and the parent macro environment of
this macro."
(unless (symbolp name)
(return-from lookup-macro nil))
(do ((env (case type
(:lambda *js-macro-env*)
(:symbol *js-symbol-macro-env*)
(t (error "Invalid macro type ~A" type)))
(cdr env)))
((null env) nil)
(let ((val (gethash (symbol-name name) (car env))))
(when val
(return-from lookup-macro
(values val (or (cdr env)
(list
(case type
(:lambda *js-macro-toplevel*)
(:symbol *js-symbol-macro-toplevel*)
(t (error "Invalid macro type ~A" type))))))))))
;; get rid of (defjsmacro symbol-macrolet ...) and replace it with
;; a compiler macro:
(define-js-compiler-macro symbol-macrolet (macros &rest body)
(let* ((macro-env (make-hash-table :test 'equal))
(*js-symbol-macro-env* (cons macro-env *js-symbol-macro-env*)))
(dolist (macro macros)
(setf (gethash (symbol-name (first macro)) macro-env)
(compile nil `(lambda () ,@(rest macro)))))
(js-compile `(progn ,@body))))
;; change expand-form
(defun js-expand-form (expr)
"Expand a javascript form."
(cond ((atom expr)
(multiple-value-bind (js-macro macro-env)
(lookup-macro expr :type :symbol)
(if js-macro
(js-expand-form (let ((*js-symbol-macro-env* macro-env))
(funcall js-macro)))
expr)))
((js-compiler-macro-form-p expr) expr)
((equal (first expr) 'quote) expr)
(t (let ((js-macro (lookup-macro (car expr))))
(if js-macro
(js-expand-form (apply js-macro (cdr expr)))
expr)))))
This should at least give an idea of the relevant areas of code.
-Red