Skip to content

Instantly share code, notes, and snippets.

@disnet
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save disnet/0e04a2755b2d838caf86 to your computer and use it in GitHub Desktop.
Save disnet/0e04a2755b2d838caf86 to your computer and use it in GitHub Desktop.
thoughts about syntax parameter

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!

primitive functions

We'll need to implement a few functions the build out syntax parameters.

define-syntax

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.

implementation

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

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;

implementation

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.).

syntax-local-get-shadower

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
}

implementation

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?

syntax-local-introduce

Might need this, not sure.

Adds the mark of the current expansion context to a syntax object.

make-rename-transformer

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.

implementation of syntax parameters

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.

@VimalKumarS
Copy link

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

stx it => (function it(x) {
         if(x==null) {
            return "No value is initialized"
         }
           else {
               return x;
           }
         })

prototype.png

prototype.png

Next I would be working on
 var stxId = syntaxLocalValue(#{id});
  letstx $x = [makeValue(stxId(42), #{here})];
 return #{$x}

Few question:

  1. In function 'expandToTermTree' there is a method call named 'addToDefinitionCtx', what is this method for?
  2. Where in the code actual mapping takes place, in the case macro eg: $x mapped to 42, making call to function (stx$601, context$602, prevStx$603, prevTerms$604) which in turn call the transcribe method? I was thinking if i can push the anaphoric 'it' variable mapping in the scope with in pattern matching block?

thanks
Vimal

@VimalKumarS
Copy link

I got second part also working "syntaxLocalValue"

stx it => (function(x) {
         if(x==null) {
            return "No value is initialized"
         }
           else {
               return x;
           }
         })

macro m  {
    case { _ () } => {
    var stxId = syntaxLocalValue(#{it});
    letstx $x = [makeValue(stxId(42), #{here})];
    return #{$x}
    }
}

m()

result in 42;

In order to implement, can you brief me out the implementation of letStx,

@VimalKumarS
Copy link

what i am looking now is to push the syntax-parameter part of matchPatterns(rhs,args,context,true)

what i am doing currently...

   function makeSyntaxParam(val,mappedTo,stx){

        parser.syntaxParameter[stx[0].token.value]={
            "param" : val,
               "value" : mappedTo
            }
        }

and in transcribe stage

 function syntaxParamMatch(_inner,paramValue,env)
    {

        _.each(_inner, function(inner,key) {
                      if(inner.token.value == "()")
                          {
                             inner.token.inner=syntaxParamMatch(inner.token.inner,paramValue,env)
                          }
                      else if(inner.token.value == paramValue.param)
                          {
                               var last=_.last(_inner,_inner.length-key-1);
                                _inner=_.first(_inner,key);
                                push.apply(_inner,paramValue.value);
                                 push.apply(_inner,last);
                          }
                      return _inner;
        });


              return _inner;

    }

But if i were to push the syntax parameter as part of match pattern then i don't have to change the trascribe part.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment