Skip to content

Instantly share code, notes, and snippets.

@trxcllnt
Last active August 29, 2015 14:04
Show Gist options
  • Save trxcllnt/969101a24184ce16f54e to your computer and use it in GitHub Desktop.
Save trxcllnt/969101a24184ce16f54e to your computer and use it in GitHub Desktop.
Sweet.js macros for optional/default params, and a macro to unroll tail-recursive functions into imperative loops.
macro parameters {
rule { ($arguments ...) } => { (params ($arguments ...)) }
}
macro params {
// group matchers
rule { ($x:ident = $y:expr , $rest:params (,) ...) } => { $x , $rest (,) ... }
rule { ($x:ident , $rest:params (,) ...) } => { $x , $rest (,) ... }
rule { ($x:ident = $y:expr) } => { $x }
rule { ($x:ident) } => { $x }
// inside matchers.
// rule { $x:ident = $y:expr , $rest:params (,) ... } => { $x , $rest (,) ... }
// rule { $x:ident , $rest:params (,) ... } => { $x , $rest (,) ... }
rule { $x:ident = $y:expr } => { $x }
rule { $x:ident } => { $x }
rule { } => { }
}
macro assign {
// group matchers
rule { ($x:ident = $y:expr , $rest:assign (,) ...) } => {
$x = $x === undefined ? $y : $x ;
$rest ...
}
rule { ($x:ident , $rest:assign (,) ...) } => {
$rest ...
}
rule { ($x:ident = $y:expr) } => {
$x = $x === undefined ? $y : $x ;
}
rule { ($x:ident) } => {
}
// inside matchers.
rule { $x:ident = $y:expr } => {
$x = $x === undefined ? $y : $x ;
}
rule { $x:ident } => {
}
rule { } => { }
}
let function = macro {
// anonymous functions
rule { ($x:ident = $y:expr , $rest:params (,) ...) { $body ... } } => {
(function ($x, $rest (,) ...) {
assign ($x = $y, $rest (,) ...)
$body ...
})
}
rule { ($x:ident , $rest:params (,) ...) { $body ... } } => {
(function ($x, $rest (,) ...) {
assign ($rest (,) ...)
$body ...
})
}
rule { ($x:ident = $y:expr) { $body ... } } => {
(function ($x) {
assign ($x = $y)
$body ...
})
}
rule { ($x:ident) { $body ... } } => {
(function ($x) {
$body ...
})
}
rule { ($x ... ) { $body ... } } => {
(function ($x ... ) {
$body ...
})
}
rule { $name () { $body ... } } => {
function $name () {
$body ...
}
}
// We only need a single named function template.
// Not sure if it's a bug in Sweet or not, but it
// won't let me call the parameters macro after
// the function keyword.
rule { $name ($x ...) { $body ... } } => {
function $name parameters ($x ...) {
assign ($x ...)
$body ...
}
}
rule { }
}
export function;
let var = macro {
rule { $name:ident = tailrec ($x ...) { $body ... } } => {
var $name = tailrec $name function ($x ...) { $body ... }
}
// normal `var` behavior.
rule { $x:ident = $y:expr , $rest (,) ... } => { var $x = $y , $rest (,) ... }
rule { $x:ident , $rest (,) ... } => { var $x , $rest (,) ... }
rule { $x:ident = $y:expr } => { var $x = $y }
rule { $x:ident } => { var $x }
// object destructuring
rule { {$x:ident (,) ...} = $y:expr } => { var o = $value $(, $x = o.$x) ... }
// array destructuring
rule { [$x:ident (,) ...] = $y:expr } => { var a = $y, i = 0 $(, $x = a[i++]) ... }
rule { }
}
export var;
macro undefined {
rule { } => { void 0 }
}
export undefined;
macro tailrec {
case { $def $name:ident ($x ...) { $option ($body ...) } } => {
function log(x) {
var a = arguments;
var n = a.length;
var xs = a[n - 1];
if(Array.isArray(xs) && xs) {
n = n - 1;
} else {
xs = [];
}
console.log.apply(console, Array.prototype.slice.call(a, 0, n).concat(xs.map(function(stx, i) {
return i + ": (" + stx.token.value + ", " + (stx.context && stx.context.instNum || null) + ")";
})));
}
function returnFactory(exprs, ctx) {
return exprs;
}
function breakFactory(exprs, ctx) {
return #{
break $name;
}
}
function findIndex(arr, predicate, startIndex) {
for (var i = (startIndex || 0) - 1, n = arr.length; ++i < n;) {
if (predicate(arr[i])) {
return i;
}
}
return -1;
}
function findValue(arr, predicate, startIndex) {
return arr[findIndex(arr, predicate, startIndex)];
}
function isIdentifier(stx) {
return stx.token.type == parser.Token.Identifier;
}
function isSemicolon(stx) {
return stx.token.type === parser.Token.Punctuator && stx.token.value === ";";
}
function isReturnStatement(stx) {
return stx &&
stx.token.type === parser.Token.Keyword &&
stx.token.value === "return";
}
function isFunctionParams(stx) {
return stx &&
stx.token.type === parser.Token.Delimiter &&
stx.token.value === "()" &&
stx.token.inner;
}
function isRecursiveCall(stx, name) {
return stx && stx.token.value === name;
}
// Is there a Sweet.js method that does this?
function castSyntax(ctx, stx) {
// ctx = null;
var type = stx.token.type;
var value = stx.token.value;
var inner = stx.token.inner;
switch(type) {
case parser.Token.EOF:
return makeValue("\n", ctx);
case parser.Token.BooleanLiteral:
return makeValue(Boolean(value), ctx);
case parser.Token.NullLiteral:
return makeValue(null, ctx);
case parser.Token.NumericLiteral:
return makeValue(Number(value), ctx);
case parser.Token.StringLiteral:
return makeValue(value, ctx);
case parser.Token.Delimiter:
return makeDelim(value, inner, ctx);
case parser.Token.Identifier:
return makeIdent(value, ctx);
case parser.Token.Keyword:
return makeKeyword(value, ctx);
case parser.Token.Punctuator:
return makePunc(value, ctx);
}
return stx;
}
return [#{ $x ... }].reduce(function(xs, params) {
function foldExpressionsList(xs, stx) {
var len = xs.length;
var terms = xs[len - 1] || (xs[len++] = []);
if(stx.token.value === ",") {
xs[len++] = [];
} else {
terms[terms.length] = castSyntax(params[len - 1], stx);
}
return xs;
}
function foldExpressionsToAssignments(xs, terms, i) {
letstx $param = [params[i]];
letstx $terms = terms;
return xs.concat(#{
$param = $terms;
});
}
function unroll(context, stxs, name, params, stacks, depth) {
letstx $depth = [depth];
return stxs.reduce(function(expressions, stx, index, stxs) {
var nextIndex = (expressions.index || (expressions.index = -1));
var returnIndex, semiIndex, paramExprs, inner;
// Ignore processing syntax elements at indicies that
// are before the next index. This is to mimick Array.splice.
if(index <= nextIndex) {
return expressions;
}
// If we found a recursive call, replace or unroll it.
if(isReturnStatement(stx)) {
returnIndex = index;
if(isRecursiveCall(stxs[returnIndex + 1], name)) {
expressions.index++;
} else {
// Ignore the next `n` elements in the outer fold.
semiIndex = findIndex(stxs, isSemicolon, returnIndex);
if(semiIndex === -1) {
semiIndex = stxs.length;
}
expressions.index = semiIndex;
// Find the invocation expression.
paramExprs = stxs.slice(returnIndex, semiIndex);
// Since this is a return statement without
// a recursive invocation, convert it to a break
// or return expression depending on how the macro
// was invoked.
paramExprs = exit(paramExprs, context);
expressions.push.apply(expressions, paramExprs);
}
}
// If we are self-recursive, replace the recursive call
// with assignment expressions and a continue statement.
else if(isRecursiveCall(stx, name)) {
// Ignore the next `n` elements in the outer fold.
semiIndex = findIndex(stxs, isSemicolon, index);
if(semiIndex === -1) {
semiIndex = stxs.length;
}
expressions.index = semiIndex;
paramExprs = stxs.slice(index, semiIndex);
paramExprs = findValue(paramExprs, isFunctionParams);
if(paramExprs && paramExprs.token.type === parser.Token.Delimiter) {
paramExprs.expose();
}
paramExprs = paramExprs && paramExprs.token.inner || [];
// Reduce the inner expressions to a list of inner expression lists.
// At this point, paramExprs is a flattened list of syntax objects.
// Reduce it to a list of individual inner expressions, split by a
// comma delimiter. Essentially:
// ```
// var exprs = ("a = 1, b = 2, c = 3").split(", ").map(function(x) {
// return x.split(" ");
// });
// ```
paramExprs = paramExprs.
reduce(foldExpressionsList, []).
// Reduce the list of inner expression lists
// into a list of assignment expressions.
reduce(foldExpressionsToAssignments, []);
returnIndex = index - 1;
if(!isReturnStatement(stxs[returnIndex])) {
paramExprs = stacks.reduce(function(xs, stack, i) {
letstx $depth = [depth];
letstx $stack = [stack];
letstx $param = [params[i]];
return xs.concat(#{ $stack[$depth] = $param; });
}, [])
.concat(#{ $depth++; })
.concat(paramExprs);
}
paramExprs = paramExprs.concat(
makeKeyword("continue", context),
makeIdent(name, context),
makePunc(";", context)
);
expressions.push.apply(expressions, paramExprs);
} else {
// Otherwise, attempt to unroll this expression to look
// for more return statements. If it can't be unrolled,
// just add it to the list of expressions and continue.
if((inner = stx.token.inner) != null) {
stx.token.inner = unroll(context, inner, name, params, stacks, depth);
}
expressions[expressions.length] = stx;
expressions.index++;
}
return expressions;
}, []);
}
params = params.filter(isIdentifier);
var context = #{ $name };
var option = unwrapSyntax(#{ $option });
var exit = option === "break" ? breakFactory : returnFactory;
var stacks = params.map(function(x) {
return makeIdent(x.token.value + "Stack", context);
});
var body = #{ $body ... };
var name = unwrapSyntax(#{ $name });
var depth = makeIdent("depth", context);
return withSyntax (
$depth = [depth],
$stacks = stacks.reduce(function(stxs, stack, i) {
letstx $stack = [stack];
letstx $param = [params[i]];
return stxs.concat(#{ var $stack = [$param]; });
}, []),
$body = unroll(context, body, name, params, stacks, depth)
) #{
var $depth = 0;
$stacks
$name : while($depth >= 0) {
$body
}
}
}, []);
}
case { $def function $name:ident ($x ...) { $body ... } } => {
return #{
function $name ($x ...) {
tailrec $name ($x ...) {
return ($body ...)
}
}
}
}
case { $def $name:ident function ($x ...) { $body ... } } => {
return #{
(function ($x ...) {
assign ($x ...)
tailrec $name ($x ...) {
return ($body ...)
}
})
}
}
case { $def $name:ident $args:($x:ident = $y:expr , $rest:params (,) ...) { $body ... } } => {
return #{
var $args ;
tailrec $name ($x, $rest (,) ...) {
break ($body ...)
}
}
}
case { $def $name:ident $args:($x:ident , $rest:params (,) ...) { $body ... } } => {
return #{
var $args ;
tailrec $name ($x, $rest (,) ...) {
break ($body ...)
}
}
}
case { $def $name:ident ($x:ident = $y:expr) { $body ... } } => {
return #{
var $x = $y ;
tailrec $name ($x) {
break ($body ...)
}
}
}
case { $def $name:ident ($x:ident) { $body ... } } => {
return #{
var $x ;
tailrec $name ($x) {
break ($body ...)
}
}
}
case { $def $name:ident ($x ... ) { $body ... } } => {
return #{
var $x ... ;
tailrec $name ($x ... ) {
break ($body ...)
}
}
}
}
export tailrec;
// Express the tail-recursive function as a while-loop.
tailrec fac(n = 10, acc = 1) {
if(n < 1) {
return acc;
} else {
return fac(n - 1, acc * n);
}
}
console.log(acc);
// Hoist the tail-recursive function.
tailrec function fac2(n, acc = 1) {
if(n < 1) {
return acc;
}
return fac2(n - 1, acc * n);
}
console.log(fac2(10));
// Create an anonymous function.
var fac3 = tailrec(n, acc = 1) {
if(n < 1) {
return acc;
}
return fac3(n - 1, acc * n);
}
console.log(fac3(10, 1));
params (a);
params (b, c);
params (d = 1, e, f);
params (g, h = 2, i);
params (j, k, l = 3);
params (m = 3, n = 4, o);
params (p, q = 5, r = 6);
params (s = 7, t, u = 8);
params (v = 9, w = 10, x = 11, y = 12);
params (z = 13);
assign (a)
assign (b, c)
assign (d = 1, e, f)
assign (g, h = 2, i)
assign (j, k, l = 3)
assign (m = 3, n = 4, o)
assign (p, q = 5, r = 6)
assign (s = 7, t, u = 8)
assign (v = 9, w = 10, x = 11, y = 12)
assign (z = 13)
function () { }
function (a) { }
function (b, c) { }
function (d = 1, e, f) { }
function (g, h = 2, i) { }
function (j, k, l = 3) { }
function (m = 3, n = 4, o) { }
function (p, q = 5, r = 6) { }
function (s = 7, t, u = 8) { }
function (v = 9, w = 10, x = 11, y = 12) { }
function (z = 13) { }
function A() { }
function a(a) { }
function b(b, c) { }
function d(d = 1, e, f) { }
function g(g, h = 2, i) { }
function j(j, k, l = 3) { }
function m(m = 3, n = 4, o) { }
function p(p, q = 5, r = 6) { }
function s(s = 7, t, u = 8) { }
function v(v = 9, w = 10, x = 11, y = 12) { }
function z(z = 13) { }
/*
Output:
// Express the tail-recursive function as a while-loop.
var n = 10, acc = 1;
var depth = 0;
var nStack = [];
var accStack = [];
fac:
while (depth >= 0) {
if (n < 1) {
break fac;
} else {
n = n - 1;
acc = acc * n;
continue fac;
}
}
console.log(acc);
// Hoist the tail-recursive function.
function fac2(n$2, acc$2) {
acc$2 = acc$2 === void 0 ? 1 : acc$2;
var depth$2 = 0;
var nStack$2 = [];
var accStack$2 = [];
fac2:
while (depth$2 >= 0) {
if (n$2 < 1) {
return acc$2;
}
n$2 = n$2 - 1;
acc$2 = acc$2 * n$2;
continue fac2;
}
}
console.log(fac2(10));
// Create an anonymous function.
var fac3 = function (n$2, acc$2) {
acc$2 = acc$2 === void 0 ? 1 : acc$2;
var depth$2 = 0;
var nStack$2 = [];
var accStack$2 = [];
fac3$1105:
while (depth$2 >= 0) {
if (n$2 < 1) {
return acc$2;
}
n$2 = n$2 - 1;
acc$2 = acc$2 * n$2;
continue fac3$1105;
}
};
console.log(fac3(10, 1));
a;
b, c;
d, e, f;
g, h, i;
j, k, l;
m, n, o;
p, q, r;
s, t, u;
v, w, x, y;
z;
d = d === void 0 ? 1 : d;
h = h === void 0 ? 2 : h;
l = l === void 0 ? 3 : l;
m = m === void 0 ? 3 : m;
n = n === void 0 ? 4 : n;
q = q === void 0 ? 5 : q;
r = r === void 0 ? 6 : r;
s = s === void 0 ? 7 : s;
u = u === void 0 ? 8 : u;
v = v === void 0 ? 9 : v;
w = w === void 0 ? 10 : w;
x = x === void 0 ? 11 : x;
y = y === void 0 ? 12 : y;
z = z === void 0 ? 13 : z;
(function () {
}(function (a) {
})(function (b, c$2) {
})(function (d, e$2, f$2) {
d = d === void 0 ? 1 : d;
})(function (g, h$2, i$2) {
})(function (j, k$2, l$2) {
})(function (m, n$2, o$2) {
m = m === void 0 ? 3 : m;
})(function (p, q$2, r$2) {
})(function (s, t$2, u$2) {
s = s === void 0 ? 7 : s;
})(function (v, w$2, x$2, y$2) {
v = v === void 0 ? 9 : v;
})(function (z) {
z = z === void 0 ? 13 : z;
}));
function A() {
}
function a(a) {
}
function b(b, c) {
}
function d(d, e, f) {
d = d === void 0 ? 1 : d;
}
function g(g, h, i) {
h = h === void 0 ? 2 : h;
}
function j(j, k, l) {
l = l === void 0 ? 3 : l;
}
function m(m, n$2, o) {
m = m === void 0 ? 3 : m;
n$2 = n$2 === void 0 ? 4 : n$2;
}
function p(p, q, r) {
q = q === void 0 ? 5 : q;
r = r === void 0 ? 6 : r;
}
function s(s, t, u) {
s = s === void 0 ? 7 : s;
u = u === void 0 ? 8 : u;
}
function v(v, w, x, y) {
v = v === void 0 ? 9 : v;
w = w === void 0 ? 10 : w;
x = x === void 0 ? 11 : x;
y = y === void 0 ? 12 : y;
}
function z(z) {
z = z === void 0 ? 13 : z;
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment