Hi @egallesio !
I'm opening this issue so we can track the problems with the hygienic macro system implementation.
This is what I can see...
Macros should remember their environment
(define-syntax f
(syntax-rules ()
((f) a)))
(f)
The above should trigger an error, because the macro did not remember its environment. The "a" that it was referencing was "a in current module", and the local a included by the let should not be visible (this is a new local environment that did not exist a the time the macro was created).
Interestingly, Scheme9, Gambit, Cyclone and Bigloo also behave like that (but it's not according to the standard).
(define-syntax f
(syntax-rules ()
((f a b) (- a b))))
(let ((f +)) (f 2 3)) ;; => should be 5; *** STklos returns -1
Now Gambit does return 5... Cyclone and Bigloo return -1 (but the correct is 5).
(let ((x 'outer))
(let-syntax ((m (syntax-rules ()
((m a) (list x a)))))
(let ((x 'inner))
(cons x (m 17)))))
;; Should be (inner outer 17)
;; STklos result is (inner inner 17)
I think this problem is related to the eval issue (#410), since we'd have to eval the macro expander in the module where the macro was created.
STklos does not accept internal define-syntax
This is easy to fix, I suppose, since let-syntax already works.
Hygiene
This can be dealt with later, but I think it would be possible to either implement ER macros (there are issues with it, but I think we can circumvent them), or syntax-case (but I think a lexically-scoped define-macro along with gensym, syntax-rules and hygiene is as powerful as syntax-case, and perhaps easier -- then syntax-case can be implemented on top of this?)
The syntax-rules matcher
It's quite old (from Macros by Example), and there are some glitches. I can re-write it if it's ok.
In particular, the current implementation doesn't handle improper lists as patterns correctly (it complains about the length not being calculable). I think this is not hard to re-implement in a better way. -- fixed
It does not support templates beginning with ellipses.
Aliases, for SRFI 212
That SRFI specifies an alias macro that will work for any symbol - even those bound to macros.
STklos already has %symbol-alias, which does the right thing for global symbols (including macros!)
stklos> (define-macro (f . whatever) -1)
;; f
stklos> (f 1)
-1
stklos> (%symbol-alias 'g 'f)
stklos> (g 2)
-1
But it would be nice to be able to alias local symbols. For that, the scope structure in the compiler would have to be slightly adapted (I can do that):
Keep variable scopes and macro scopes separated as they are currently, but also create a third type of scope for aliases;
Both locals and mlocals in structure scope would be association lists:
- ALIASES: the
CAR is the symbol, and the CDR points to the location
where location is either some other entry for a variable in this scope (down the scope stack), or a binding to a specific symbol in a specific module. This works for both macros and variables.
- MACROS: the
CDR is the syntax object:
- LOCAL VARS: no need for a
CDR
This would give us a way to implement that SRFI, and would repect lexical scope for both variables, macros and aliases. :)
This is not difficult to do, I can help (I can make a PR soon if it's ok).
Tests for all the above
I have some tests... I'll organize them and make a draft PR for reference.
Hi @egallesio !
I'm opening this issue so we can track the problems with the hygienic macro system implementation.
This is what I can see...
Macros should remember their environment
The above should trigger an error, because the macro did not remember its environment. The "a" that it was referencing was "a in current module", and the local
aincluded by theletshould not be visible (this is a new local environment that did not exist a the time the macro was created).Interestingly, Scheme9, Gambit, Cyclone and Bigloo also behave like that (but it's not according to the standard).
Now Gambit does return 5... Cyclone and Bigloo return -1 (but the correct is 5).
I think this problem is related to the
evalissue (#410), since we'd have to eval the macro expander in the module where the macro was created.STklos does not accept internal
define-syntaxThis is easy to fix, I suppose, since
let-syntaxalready works.Hygiene
This can be dealt with later, but I think it would be possible to either implement ER macros (there are issues with it, but I think we can circumvent them), or syntax-case (but I think a lexically-scoped
define-macroalong withgensym,syntax-rulesand hygiene is as powerful assyntax-case, and perhaps easier -- thensyntax-casecan be implemented on top of this?)The
syntax-rulesmatcherIt's quite old (from Macros by Example), and there are some glitches. I can re-write it if it's ok.
In particular, the current implementation doesn't handle improper lists as patterns correctly (it complains about the length not being calculable). I think this is not hard to re-implement in a better way.-- fixedIt does not support templates beginning with ellipses.
Aliases, for SRFI 212
That SRFI specifies an
aliasmacro that will work for any symbol - even those bound to macros.STklos already has
%symbol-alias, which does the right thing for global symbols (including macros!)But it would be nice to be able to alias local symbols. For that, the
scopestructure in the compiler would have to be slightly adapted (I can do that):Keep variable scopes and macro scopes separated as they are currently, but also create a third type of scope for aliases;
Both
localsandmlocalsin structurescopewould be association lists:CARis the symbol, and theCDRpoints to the locationCDRis the syntax object:CDRThis would give us a way to implement that SRFI, and would repect lexical scope for both variables, macros and aliases. :)
This is not difficult to do, I can help (I can make a PR soon if it's ok).
Tests for all the above
I have some tests... I'll organize them and make a draft PR for reference.