Last active
September 5, 2017 14:41
-
-
Save mourner/88286de4d89dd1306272ce6c50860d06 to your computer and use it in GitHub Desktop.
Fast GL JS expressions proof of concept
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
'use strict'; | |
function genFn(ctx, body) { | |
let id = ctx.cache[body]; | |
if (!id) { | |
id = ctx.id++; | |
ctx.cache[body] = id; | |
ctx.src += ` | |
function e${id}() { ${body} }`; | |
} | |
return id; | |
} | |
function genObj(ctx, obj) { | |
const keys = Object.keys(obj); | |
let body = ''; | |
for (let i = 0; i < keys.length; i++) { | |
if (i > 0) body += ', '; | |
body += `${JSON.stringify(keys[i])}: ${obj[keys[i]]}`; | |
} | |
let id = ctx.cache[body]; | |
if (!id) { | |
id = ctx.id++; | |
ctx.cache[body] = id; | |
ctx.src += ` | |
var o${id} = {${body}};`; | |
} | |
return id; | |
} | |
function genConstant(ctx, expr) { | |
return genFn(ctx, `return ${JSON.stringify(expr)};`); | |
} | |
function genArithmetic(ctx, expr) { | |
const argIds = []; | |
for (let i = 1; i < expr.length; i++) { | |
argIds.push(ctx.compile(expr[i])); | |
} | |
return genFn(ctx, `return ${argIds.map(argId => `${argId}()`).join(` ${expr[0]} `)};`); | |
} | |
function genGet(ctx, expr) { | |
return genFn(ctx, `return f.properties[${JSON.stringify(expr[1])}];`); | |
} | |
function genType(ctx, expr) { | |
const inputId = ctx.compile(expr[1]); | |
return genFn(ctx, `var v = ${inputId}(); return typeof v !== '${expr[0]}' ? panic('Expected a ${expr[0]}') : v;`); | |
} | |
function genMatch(ctx, expr) { | |
const inputId = ctx.compile(expr[1]); | |
const elseId = ctx.compile(expr[expr.length - 1]); | |
const lookup = {}; | |
for (let i = 2; i < expr.length - 1; i += 2) { | |
lookup[expr[i]] = ctx.compile(expr[i + 1]); | |
} | |
const lookupId = genObj(ctx, lookup); | |
return genFn(ctx, `return (o${lookupId}[${inputId}()] || ${elseId})();`); | |
} | |
const keywords = { | |
'get': genGet, | |
'string': genType, | |
'number': genType, | |
'boolean': genType, | |
'object': genType, | |
'+': genArithmetic, | |
'-': genArithmetic, | |
'/': genArithmetic, | |
'*': genArithmetic, | |
'%': genArithmetic, | |
'match': genMatch | |
} | |
class Expression { | |
constructor(expr) { | |
this.src = ` | |
var f, g; function panic(msg) { throw new Error(msg); } | |
`; | |
this.id = 0; | |
this.cache = {}; | |
const finalId = this.compile(expr); | |
this.src += ` | |
return function($f, $g) { f = $f; g = $g; var v = ${finalId}(); f = g = null; return v; };`; | |
this.fn = new Function(this.src)(); | |
} | |
compile(expr) { | |
let id; | |
const key = expr[0]; | |
if (!Array.isArray(expr)) { | |
id = genConstant(this, expr); | |
} else { | |
const genFn = keywords[expr[0]]; | |
if (!genFn) throw new Error('Unrecognized expression type: ' + expr[0]); | |
id = genFn(this, expr); | |
} | |
return `e${id}`; | |
} | |
} | |
module.exports = Expression; |
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
'use strict'; | |
class Expression { | |
constructor(expr) { | |
this.expr = expr; | |
} | |
exec(expr) { | |
if (!Array.isArray(expr)) return expr; | |
var key = expr[0]; | |
switch (key) { | |
case 'get': | |
var v = this.f.properties[expr[1]]; | |
return v; | |
case 'string': | |
var v = this.exec(expr[1]); | |
if (typeof v !== 'string') throw new Error('Expected string'); | |
return v; | |
case '/': | |
var v = this.exec(expr[1]); | |
for (var i = 2; i < expr.length; i++) { | |
v /= this.exec(expr[i]); | |
} | |
return v; | |
case '*': | |
var v = 1; | |
for (var i = 1; i < expr.length; i++) { | |
v *= this.exec(expr[i]); | |
} | |
return v; | |
case 'match': | |
var v = this.exec(expr[1]); | |
for (var i = 2; i < expr.length - 2; i++) { | |
if (v === expr[i]) return this.exec(expr[i + 1]); | |
} | |
return this.exec(expr[expr.length - 1]); | |
} | |
throw new Error('Unexpected expression'); | |
} | |
fn(feature, globalProperties) { | |
this.f = feature; | |
this.g = globalProperties; | |
var v = this.exec(this.expr); | |
this.f = this.g = null; | |
return v; | |
} | |
} | |
module.exports = Expression; |
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 f, g; function panic(msg) { throw new Error(msg); } | |
function e0() { return f.properties["class"]; } | |
function e1() { var v = e0(); return typeof v !== 'string' ? panic('Expected a string') : v; } | |
function e2() { return 3; } | |
function e3() { return f.properties["val"]; } | |
function e4() { return 1.2; } | |
function e5() { return e3() / e4(); } | |
function e6() { return e3() * e4() * e2(); } | |
var o7 = {"park": e5, "building": e6}; | |
function e8() { return (o7[e1()] || e2)(); } | |
return function($f, $g) { f = $f; g = $g; var v = e8(); f = g = null; return v; }; |
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
'use strict'; | |
const Expression = require('./'); | |
var expr = new Expression([ | |
"match", ["string", ["get", "class"]], | |
"park", ["/", ["get", "val"], 1.2], | |
"building", ["*", ["get", "val"], 1.2, 3], | |
3 | |
]); | |
console.log(expr.src); | |
function jsExpr(f, g) { | |
var klass = f.properties.class; | |
var val = f.properties.val; | |
if (typeof klass !== 'string') throw new Error('Expected string'); | |
return klass === 'park' ? val / 1.2 : | |
klass === 'building' ? val * 1.2 * 3 : 3; | |
} | |
var features = []; | |
for (var i = 0; i < 1000000; i++) { | |
var r = Math.random(); | |
features.push({properties: { | |
val: r, | |
class: r < 0.4 ? 'park' : r < 0.8 ? 'building' : 'other' | |
}}); | |
} | |
var globalProps = {}; | |
console.time('expr'); | |
var sum = 0; | |
for (i = 0; i < features.length; i++) sum += expr.fn(features[i], globalProps); | |
console.timeEnd('expr'); | |
// console.time('js'); | |
// var sum = 0; | |
// for (i = 0; i < features.length; i++) sum += jsExpr(features[i], globalProps); | |
// console.timeEnd('js'); | |
console.log(sum); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment