Last active
August 29, 2015 14:00
-
-
Save jcmoore/4aa88a93ece687122682 to your computer and use it in GitHub Desktop.
Fennel.js prototype -- useful for making "curries"
This file contains 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
var Fennel = (function FennelMake () { | |
var Curry = function Curry () { | |
}; | |
Curry.prototype.method = {}; | |
Curry.prototype.context = {}; | |
Curry.prototype.params = (function paramsMake () { | |
var cache = [{}]; | |
return function params (remaining) { | |
var count = cache.length; | |
while (count < remaining) { | |
count = cache.push({}); | |
} | |
return cache[remaining - 1]; | |
}; | |
}) (); | |
var universal = new Curry(); | |
var extra = null; | |
var adapt = function adapt () { | |
var list = extra, | |
count = list ? list.length : 0, | |
index = 0, | |
dish = null, | |
factory = null; | |
extra = null; | |
for (index = 0; index < count; index++) { | |
dish = list[index]; | |
factory = (Function (dish.recipe)); | |
dish.recipe = null; | |
dish.spoon = factory()(dish.curry); | |
} | |
}; | |
var fallback = (function () { | |
var gen = (function * fallback (curry) { | |
var amount = 0, | |
index = 0, | |
method = null, | |
_method = null, | |
context = null, | |
_context = null, | |
params = null, | |
_params = null, | |
arg = null, | |
delegate = function delegate () { | |
return _method && _method.apply(_context, _params); | |
}; | |
do { | |
method = yield curry.method; | |
context = yield curry.context; | |
for (index = 0; index < amount; index++) { | |
arg = yield curry.params(amount - index); | |
if (!params) { | |
params = [arg]; | |
} else { | |
params.push(arg); | |
} | |
} | |
yield curry.params(0); | |
arg = null; | |
index = 0; | |
_method = method, | |
method = null, | |
_context = context, | |
context = null, | |
_params = params, | |
params = null, | |
amount = yield delegate; | |
_method = null; | |
_context = null; | |
_params = null; | |
} while (!null); | |
}) (universal); | |
gen.next(0); // amount... | |
gen.next(null); // method | |
gen.next(null); // context | |
gen.next(null); // queue | |
return gen; | |
}) (); | |
var Ingredient = function () { | |
this.flavor = null; | |
this.after = null; | |
}; | |
Ingredient.prototype.reset = function (flavor, after) { | |
this.flavor = flavor; | |
this.after = after || null; | |
return this; | |
}; | |
var unpool = (function (Ingredient) { | |
var collection = null, | |
count = 0; | |
Ingredient.prototype.repool = function (flavor, after) { | |
this.reset.apply(this, arguments); | |
if (!collection) { | |
collection = [this]; | |
count = 1; | |
} else if (count < collection.length) { | |
collection[count++] = this; | |
} else { | |
count = collection.push(this); | |
} | |
}; | |
return function unpool (flavor, before) { | |
var ingredient = null; | |
if ((count > 0) && | |
(collection.length > (count - 1))) { | |
ingredient = collection[--count]; | |
collection[count] = null; | |
} else { | |
ingredient = new Ingredient(); | |
} | |
ingredient.flavor = flavor; | |
before && (before.after = ingredient); | |
return ingredient; | |
}; | |
}) (Ingredient); | |
var Meal = function (curry, dish) { | |
var meal = this, | |
head = null, | |
tail = null, | |
amount = dish.amount, | |
progress = 0; | |
this.next = null; | |
this.next = function Meal_next () { | |
var result = null, | |
count = arguments.length, | |
index = 0, | |
_head = null, | |
_progress = null; | |
for (index = 0; index < count; index++) { | |
progress++; | |
tail = unpool(arguments[index], tail); | |
head = head || tail; | |
} | |
if (progress > amount + 3) { | |
tail = null; | |
_head = head; | |
head = null; | |
_progress = progress; | |
progress = 0; | |
result = dish.consume(meal, _head, _progress); | |
_head = null; | |
_progress = 0; | |
} | |
return result; | |
}; | |
}; | |
var delicacy = null; | |
// treat.next() probably has to return the same class every invocation... | |
var Treat = function () { | |
var treat = this, | |
head = null, | |
tail = null, | |
progress = 0; | |
this.next = null; | |
this.next = function Treat_next () { | |
var result = null, | |
managed = this === delicacy, | |
initial = null, | |
limit = head ? head.flavor : null, | |
temp = null, | |
count = arguments.length, | |
index; | |
if (progress === null) { | |
temp = head; | |
head = head.after; | |
result = temp.flavor; | |
temp.repool(); | |
if (head === null) { | |
tail = null; | |
progress = 0; | |
treat.repool(); | |
} | |
} else { | |
index = 0; | |
if (count > 0) { | |
if (progress === 0) { | |
progress = 1; | |
index = 1; | |
initial = !null; | |
if (managed) { | |
head = tail = unpool(null, tail); | |
} else { | |
head = tail = unpool(arguments[0] | 0, tail); | |
} | |
} | |
} | |
limit = head.flavor; | |
if (managed && | |
!initial) { | |
limit = count + progress - 1; | |
head.flavor = limit; | |
} | |
while (!null) { | |
if ((limit !== null) && | |
(limit < progress)) { | |
progress = null; | |
break; | |
} else if (index < count) { | |
progress++; | |
tail = unpool(arguments[index++], tail); | |
} else { | |
break; | |
} | |
} | |
} | |
return result; | |
}; | |
}; | |
var api = (function (Treat) { | |
var collection = null, | |
count = 0; | |
Treat.prototype.repool = function () { | |
if (!collection) { | |
collection = [this]; | |
count = 1; | |
} else if (count < collection.length) { | |
collection[count++] = this; | |
} else { | |
count = collection.push(this); | |
} | |
}; | |
return function Fennel () { | |
var treat = null; | |
if ((count > 0) && | |
(collection.length > (count - 1))) { | |
treat = collection[--count]; | |
collection[count] = null; | |
} else { | |
treat = new Treat(); | |
} | |
return treat; | |
}; | |
}) (Treat); | |
Treat.prototype.share = function () { | |
var amount = this.next(null), | |
count = arguments.length, | |
index = 0, | |
result = api(), | |
treat = null, | |
value = amount; | |
do { | |
result.next(value); | |
for (index = 0; index < count; index++) { | |
treat = arguments[index]; | |
treat.next(value); | |
} | |
} while ((amount > 0) && amount-- && ((value = this.next(null)), !null)); | |
return result; | |
}; | |
delicacy = new Treat(); | |
var _prologue = [ | |
("return function * (curry) {"), | |
(" var delegate = function delegate () {"), | |
(" return method && method.call(context"), | |
].join("\n"); | |
var _act1 = [""]; | |
var _intermission = [ | |
(" );"), | |
(" };"), | |
(" do {"), | |
(" var method = yield curry.method;"), | |
(" var context = yield curry.context;"), | |
].join("\n"); | |
var _act2 = [""]; | |
var _epilogue = [ | |
(" yield curry.params(0);"), | |
(" yield delegate;"), | |
(" } while (!null);"), | |
("};"), | |
].join("\n"); | |
var Dish = function Dish (count, curry) { | |
var dish = this, | |
index; | |
for (index = _act1.length - 1; index < count; index++) { | |
_act1.push(" , _"+index); | |
} | |
for (index = _act2.length - 1; index < count; index++) { | |
_act2.push(" var _"+index+" = yield curry.params("+(count-index)+");"); | |
} | |
this.recipe = [ | |
_prologue, | |
_act1.join("\n"), | |
_intermission, | |
_act2.join("\n"), | |
_epilogue, | |
].join("\n"); | |
this.amount = count; | |
this.curry = curry; | |
this.spoon = null; | |
this.bouy = 0; | |
this.pool = null; | |
if (extra) { | |
extra.push(this); | |
} else { | |
extra = [this]; | |
setTimeout(adapt); | |
} | |
}; | |
Dish.prototype.serve = function serve () { | |
var result = null; | |
if (this.bouy > 0) { | |
result = this.pool[--this.bouy]; | |
this.pool[this.bouy] = null; | |
} else { | |
result = new Meal(this.curry, this); | |
} | |
result.next(this.amount); | |
return result; | |
}; | |
Dish.prototype.clean = function clean (meal) { | |
if (!this.pool) { | |
this.pool = [meal]; | |
this.bouy = 1; | |
} else if (this.bouy < this.pool.length) { | |
this.pool[this.bouy++] = meal; | |
} else { | |
this.bouy = this.pool.push(meal); | |
} | |
}; | |
Dish.prototype.consume = function consume (meal, ingredient, progress) { | |
var result = null, | |
temp = ingredient, | |
spoon = this.spoon || fallback, | |
count = 0, | |
index = 0; | |
this.clean(meal); | |
count = this.amount + 3; | |
for (index = 0; index < count; index++) { | |
spoon.next(temp.flavor); | |
temp = temp.after; | |
} | |
count = progress; | |
for (index = 0; index < count; index++) { | |
temp = ingredient; | |
ingredient = ingredient.after; | |
temp.repool(); | |
} | |
result = spoon.next(null).value(); | |
return result; | |
}; | |
var cook = api.cook = (function (Dish, curry) { | |
var reserved = 20; | |
var cache = [new Dish(0, curry)]; | |
var cook = function cook (amount) { | |
var count = cache.length; | |
amount++; | |
while (count < amount) { | |
count = cache.push(new Dish(count, curry)); | |
} | |
amount--; | |
if (amount < 0) { | |
amount = 0; | |
} | |
return cache[amount].serve(); | |
}; | |
var meal = cook(reserved); | |
adapt(); | |
meal.next(null); // method | |
meal.next(null); // context | |
while (reserved > 0) { | |
reserved--; | |
meal.next(null); | |
} | |
meal.next(null); // result | |
return cook; | |
}) (Dish, universal); | |
var dessert = api.dessert = function dessert (filling) { | |
return function () { | |
var treat = api(), | |
sauce = cook(arguments.length + 1); | |
treat.next.call(delicacy, null); | |
sauce.next(filling, this, treat); | |
sauce.next.apply(sauce, arguments); | |
sauce.next(null); | |
treat.next.call(delicacy); | |
return treat; | |
}; | |
}; | |
var dinner = api.dinner = function dinner (main) { | |
var courses = dessert(main), | |
amount = main.length ? (main.length - 1) : null; | |
return function supper () { | |
var result = null, | |
range = amount, | |
count = arguments.length, | |
index = 0, | |
sauce = null, | |
treat = null, | |
total = 0, | |
outs = 0, | |
skip = 0; | |
sauce = cook(range); | |
sauce.next(courses, this); | |
for (index = 0; (index < range) && (index < count); index++) { | |
sauce.next(arguments[index]); | |
} | |
for (index = count; index < range; index++) { | |
sauce.next(null); | |
} | |
treat = sauce.next(null); | |
total = treat.next(null); | |
outs = total + count - range; | |
if (outs > 0) { | |
sauce = cook(outs - 2); | |
skip = 1; | |
sauce.next(treat.next(null)); | |
if (outs > 1) { | |
skip = 2; | |
sauce.next(treat.next(null)); | |
} else { | |
sauce.next(null); | |
} | |
for (index = skip; index < total; index++) { | |
sauce.next(treat.next(null)); | |
} | |
for (index = range; index < count; index++) { | |
sauce.next(arguments[index]); | |
} | |
result = sauce.next(null); | |
} else { | |
for (index = 0; index < total; index++) { | |
treat.next(null); | |
} | |
} | |
return result; | |
}; | |
}; | |
return api; | |
}) (); | |
var sweet = Fennel.dinner(function (snack, position, method, context) { | |
snack.next(method, context, 123 + position, null); | |
}); | |
function printStatus(fn) { | |
switch(%GetOptimizationStatus(fn)) { | |
case 1: console.log("Function is optimized"); break; | |
case 2: console.log("Function is not optimized"); break; | |
case 3: console.log("Function is always optimized"); break; | |
case 4: console.log("Function is never optimized"); break; | |
case 6: console.log("Function is maybe deoptimized"); break; | |
} | |
} | |
//Fill type-info | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { a:1, b:2 }); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { c:1, d:2 }, 1); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { e:1, f:2 }, 1, 2); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { g:1, h:2 }, 1, 2, 3); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { i:1, j:2 }, 1, 2, 3, 4); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { k:1, l:2 }, 1, 2, 3, 4, 5); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { m:1, n:2 }, 1, 2, 3, 4, 5, 6); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { o:1, p:2 }, 1, 2, 3, 4, 5, 6, 7); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { q:1, r:2 }, 1, 2, 3, 4, 5, 6, 7, 8); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { s:1, t:2 }, 1, 2, 3, 4, 5, 6, 7, 8, 9); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { u:1, v:2 }, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0); | |
//Check | |
printStatus(sweet); | |
%OptimizeFunctionOnNextCall(sweet); | |
//The next call | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { w:1, x:2 }, "true"); | |
//Check | |
printStatus(sweet); | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, [1, 2, 3, 4], true, false, 6); | |
%OptimizeFunctionOnNextCall(sweet); | |
//The next call | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments.length === 4; | |
}, { y:1, z:2 }, "true", "false"); | |
//Check | |
printStatus(sweet); | |
%OptimizeFunctionOnNextCall(sweet); | |
//The next call | |
sweet(789, function (address, particle, truthy, falsey, fireengine) { | |
return arguments[0] > 12; | |
}, { _:1, $:2 }, "true", "false", 12, 34); | |
//Check | |
printStatus(sweet); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment