Last active
August 29, 2015 14:04
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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