Syntax parameters allow us to play around with hygiene in a principled fashion.
stxParam it = function() { throw new Error("must be rebound"); };
let aif = macro {
rule { ($cond ...) { $body ...} } => {
var it_inner = $cond ...;
if (it_inner) {
syntaxParameterize it (it_inner) {
$body ...
}
}
}
}
Not super happy about the syntax. In particular the Racket form is
more general letting syntaxParameterize
take syntax-rules
as a way
of matching more than just the identifier. Perhaps we only need to
start with this simplified version? The more general syntax gets ugly
really quick:
stxParam it = function() { throw new Error("must be rebound"); };
let aif = macro {
rule { ($cond ...) { $body ...} } => {
var it_inner = $cond ...;
if (it_inner) {
syntaxParameterize it {
rule { } => { it_inner }
} {
$body ...
}
}
}
}
Or something like that. Ugh!
We'll need to implement a few functions the build out syntax parameters.
define-syntax
in racket load an expression in the compiletime
environment and binds the name in the current scope. Basically it's
like let
but for compiletime values.
The define
prefix doesn't make sense for JS. Perhaps just syntax
?
This would just stick a function into the transformer environment:
syntax name = function(x) {
return x + 1;
}
This can also be used to define macros of course:
syntax id = macro {
rule { $x } => { $x }
}
Or just arbitrary values:
syntax compiletimeNumber = 42;
This if course slightly conflicts with the existing syntax {}
template but we can fix that up.
Alternately we could use stx
:
stx name = function(x) { return x };
stx id = macro {
rule { $x } => { $x }
}
stx compiletimeNumber = 42;
The nice things about this is it's the same number of characters as
var
and let
though maybe it is too terse (where have all the
vowels gone?).
Recursive bindings in macros could be handled like so:
stx rec = macro rec {
// ...
}
The nice thing here is that the default is not to bind inside of the macros. Usually what you want.
To implement this you'll need to add a form the TermTree
hierarchy
and handle recognizing this in enforest
. Something like:
function enforest() {
//...
if (head === "stx" &&
rest[0] === ident &&
rest[1] === "=" &&
rest[2] === expr) {
StxDef.create(head, rest[0], rest[1], rest[2]);
// ...
}
//...
}
Then in expandTermTree
you'll want to load any StxDef
term trees
into the compiletime environment (e.g. context.env
) and bind the
name appropriately. Basically do something similar to what
expandTermTree
does for let-bound macros currently.
syntax-local-value
in racket retrieves a value from the compiletime
environment.
For JS how about we call this function syntaxLocalValue()
? Was
worried before that this is too long but I think it works. It is used
like this:
stx id = function(x) { return x };
stx m = macro {
case { () } => {
var stxId = syntaxLocalValue(#{id});
letstx $x = [makeValue(stxId(42), #{here})];
return #{$x}
}
}
m();
// expands to:
// 42;
Should actually be pretty straightforward. Just add a function to the
object in loadMacroDef
called syntaxLocalValue
that takes a syntax
object (or a one element array for convenience since #{id}
returns
an array of one syntax object) and the retrieves the value stored in
context.env
with that syntax object (call getMacroInEnv
etc.).
The racket function syntax-local-get-shadower
returns the nearest
shadowing identifier syntax object or the given syntax object if
nothing is shadowing in the context of expansion.
In JS we can just camelCase the same name I think:
var foo = 42;
stx m = macro {
case {_ () } => {
letstx $id = [syntaxLocalGetShadower(#{foo})];
return #{$id}
}
}
function f(foo) {
m ();
// expands to:
// foo <- the `foo` bound to the function parameter, not 42
}
I think the way to make this work you need to get access to the
expansion context and then create a syntax object with the .value
of
the syntax object passed to syntaxLocalGetShadower
but with the
lexical context of the expansion context. How can we reify the
expansion context?
Might need this, not sure.
Adds the mark of the current expansion context to a syntax object.
Actually, we might not need this function to implement syntax parameters so ignore it for now.
The racket function make-rename-transformer
is sort of like a macro
that just swaps around names.
For JS I think we can just call it makeRenameTransformer
.
stxParm
is just a macro that expands to two syntax definitions:
stxParm name = function() { throw "must rebind"; };
Expands to:
stx name_internal = function() { throw "must rebind"; };
stx name = function(stx, context, prevStx, prevTerms) {
var shadower = syntaxLocalGetShadower(#{name_internal});
var shadowerValue = syntaxLocalValue(shadower);
return shadowerValue(stx, context, prevStx, prevTerms);
}
syntaxParameterize
is just a macro that expands to something like:
stxParm name = function() { throw "must rebind"; };
var myName = "foo";
syntaxParameterize name { rule { } => { myName }} {
name;
}
goes to:
stxParm name = function() { throw "must rebind"; };
var myName = "foo";
stx name = macro { rule {} => { myName }}
name;
Or maybe we don't even need syntaxParameterize
? All that's really
necessary is redefining what name
expands to.
Hello,
I got the implementation of the first part, defining the primitive part and store it in the env context.
Implementation part,
defined the syntax parameter as
Next I would be working on
Few question:
thanks
Vimal