Skip to content

Instantly share code, notes, and snippets.

@mourner
Last active September 5, 2017 14:41
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 mourner/88286de4d89dd1306272ce6c50860d06 to your computer and use it in GitHub Desktop.
Save mourner/88286de4d89dd1306272ce6c50860d06 to your computer and use it in GitHub Desktop.
Fast GL JS expressions proof of concept
'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;
'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;
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; };
'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