vega-interpreter
Version:
CSP-compliant interpreter for Vega expressions.
316 lines (304 loc) • 9.04 kB
JavaScript
import { ascending, isString } from 'vega-util';
function adjustSpatial (item, encode, swap) {
let t;
if (encode.x2) {
if (encode.x) {
if (swap && item.x > item.x2) {
t = item.x;
item.x = item.x2;
item.x2 = t;
}
item.width = item.x2 - item.x;
} else {
item.x = item.x2 - (item.width || 0);
}
}
if (encode.xc) {
item.x = item.xc - (item.width || 0) / 2;
}
if (encode.y2) {
if (encode.y) {
if (swap && item.y > item.y2) {
t = item.y;
item.y = item.y2;
item.y2 = t;
}
item.height = item.y2 - item.y;
} else {
item.y = item.y2 - (item.height || 0);
}
}
if (encode.yc) {
item.y = item.yc - (item.height || 0) / 2;
}
}
var Constants = {
NaN: NaN,
E: Math.E,
LN2: Math.LN2,
LN10: Math.LN10,
LOG2E: Math.LOG2E,
LOG10E: Math.LOG10E,
PI: Math.PI,
SQRT1_2: Math.SQRT1_2,
SQRT2: Math.SQRT2,
MIN_VALUE: Number.MIN_VALUE,
MAX_VALUE: Number.MAX_VALUE
};
var Ops = {
'*': (a, b) => a * b,
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'/': (a, b) => a / b,
'%': (a, b) => a % b,
'>': (a, b) => a > b,
'<': (a, b) => a < b,
'<=': (a, b) => a <= b,
'>=': (a, b) => a >= b,
'==': (a, b) => a == b,
'!=': (a, b) => a != b,
'===': (a, b) => a === b,
'!==': (a, b) => a !== b,
'&': (a, b) => a & b,
'|': (a, b) => a | b,
'^': (a, b) => a ^ b,
'<<': (a, b) => a << b,
'>>': (a, b) => a >> b,
'>>>': (a, b) => a >>> b
};
var Unary = {
'+': a => +a,
'-': a => -a,
'~': a => ~a,
'!': a => !a
};
const slice = Array.prototype.slice;
const apply = (m, args, cast) => {
const obj = cast ? cast(args[0]) : args[0];
return obj[m].apply(obj, slice.call(args, 1));
};
const datetime = function (yearOrTimestring) {
let m = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
let d = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
let H = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
let M = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
let S = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0;
let ms = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 0;
return isString(yearOrTimestring) ? new Date(yearOrTimestring) : new Date(yearOrTimestring, m, d, H, M, S, ms);
};
var Functions = {
// math functions
isNaN: Number.isNaN,
isFinite: Number.isFinite,
abs: Math.abs,
acos: Math.acos,
asin: Math.asin,
atan: Math.atan,
atan2: Math.atan2,
ceil: Math.ceil,
cos: Math.cos,
exp: Math.exp,
floor: Math.floor,
log: Math.log,
max: Math.max,
min: Math.min,
pow: Math.pow,
random: Math.random,
round: Math.round,
sin: Math.sin,
sqrt: Math.sqrt,
tan: Math.tan,
clamp: (a, b, c) => Math.max(b, Math.min(c, a)),
// date functions
now: Date.now,
utc: Date.UTC,
datetime: datetime,
date: d => new Date(d).getDate(),
day: d => new Date(d).getDay(),
year: d => new Date(d).getFullYear(),
month: d => new Date(d).getMonth(),
hours: d => new Date(d).getHours(),
minutes: d => new Date(d).getMinutes(),
seconds: d => new Date(d).getSeconds(),
milliseconds: d => new Date(d).getMilliseconds(),
time: d => new Date(d).getTime(),
timezoneoffset: d => new Date(d).getTimezoneOffset(),
utcdate: d => new Date(d).getUTCDate(),
utcday: d => new Date(d).getUTCDay(),
utcyear: d => new Date(d).getUTCFullYear(),
utcmonth: d => new Date(d).getUTCMonth(),
utchours: d => new Date(d).getUTCHours(),
utcminutes: d => new Date(d).getUTCMinutes(),
utcseconds: d => new Date(d).getUTCSeconds(),
utcmilliseconds: d => new Date(d).getUTCMilliseconds(),
// sequence functions
length: x => x.length,
join: function () {
return apply('join', arguments);
},
indexof: function () {
return apply('indexOf', arguments);
},
lastindexof: function () {
return apply('lastIndexOf', arguments);
},
slice: function () {
return apply('slice', arguments);
},
reverse: x => x.slice().reverse(),
sort: x => x.slice().sort(ascending),
// string functions
parseFloat: parseFloat,
parseInt: parseInt,
upper: x => String(x).toUpperCase(),
lower: x => String(x).toLowerCase(),
substring: function () {
return apply('substring', arguments, String);
},
split: function () {
return apply('split', arguments, String);
},
replace: function () {
return apply('replace', arguments, String);
},
trim: x => String(x).trim(),
// Base64 encode/decode
// Convert binary string to base64-encoded ascii
btoa: x => btoa(x),
// Convert base64-encoded ascii to binary string
atob: x => atob(x),
// regexp functions
regexp: RegExp,
test: (r, t) => RegExp(r).test(t)
};
const EventFunctions = ['view', 'item', 'group', 'xy', 'x', 'y'];
const DisallowedMethods = new Set([Function, eval, setTimeout, setInterval]);
if (typeof setImmediate === 'function') DisallowedMethods.add(setImmediate);
const Visitors = {
Literal: ($, n) => n.value,
Identifier: ($, n) => {
const id = n.name;
return $.memberDepth > 0 ? id : id === 'datum' ? $.datum : id === 'event' ? $.event : id === 'item' ? $.item : Constants[id] || $.params['$' + id];
},
MemberExpression: ($, n) => {
const d = !n.computed,
o = $(n.object);
if (d) $.memberDepth += 1;
const p = $(n.property);
if (d) $.memberDepth -= 1;
if (DisallowedMethods.has(o[p])) {
// eslint-disable-next-line no-console
console.error(`Prevented interpretation of member "${p}" which could lead to insecure code execution`);
return;
}
return o[p];
},
CallExpression: ($, n) => {
const args = n.arguments;
let name = n.callee.name;
// handle special internal functions used by encoders
// re-route to corresponding standard function
if (name.startsWith('_')) {
name = name.slice(1);
}
// special case "if" due to conditional evaluation of branches
return name === 'if' ? $(args[0]) ? $(args[1]) : $(args[2]) : ($.fn[name] || Functions[name]).apply($.fn, args.map($));
},
ArrayExpression: ($, n) => n.elements.map($),
BinaryExpression: ($, n) => Ops[n.operator]($(n.left), $(n.right)),
UnaryExpression: ($, n) => Unary[n.operator]($(n.argument)),
ConditionalExpression: ($, n) => $(n.test) ? $(n.consequent) : $(n.alternate),
LogicalExpression: ($, n) => n.operator === '&&' ? $(n.left) && $(n.right) : $(n.left) || $(n.right),
ObjectExpression: ($, n) => n.properties.reduce((o, p) => {
$.memberDepth += 1;
const k = $(p.key);
$.memberDepth -= 1;
if (DisallowedMethods.has($(p.value))) {
// eslint-disable-next-line no-console
console.error(`Prevented interpretation of property "${k}" which could lead to insecure code execution`);
} else {
o[k] = $(p.value);
}
return o;
}, {})
};
function interpret (ast, fn, params, datum, event, item) {
const $ = n => Visitors[n.type]($, n);
$.memberDepth = 0;
$.fn = Object.create(fn);
$.params = params;
$.datum = datum;
$.event = event;
$.item = item;
// route event functions to annotated vega event context
EventFunctions.forEach(f => $.fn[f] = function () {
return event.vega[f](...arguments);
});
return $(ast);
}
var expression = {
/**
* Parse an expression used to update an operator value.
*/
operator(ctx, expr) {
const ast = expr.ast,
fn = ctx.functions;
return _ => interpret(ast, fn, _);
},
/**
* Parse an expression provided as an operator parameter value.
*/
parameter(ctx, expr) {
const ast = expr.ast,
fn = ctx.functions;
return (datum, _) => interpret(ast, fn, _, datum);
},
/**
* Parse an expression applied to an event stream.
*/
event(ctx, expr) {
const ast = expr.ast,
fn = ctx.functions;
return event => interpret(ast, fn, undefined, undefined, event);
},
/**
* Parse an expression used to handle an event-driven operator update.
*/
handler(ctx, expr) {
const ast = expr.ast,
fn = ctx.functions;
return (_, event) => {
const datum = event.item && event.item.datum;
return interpret(ast, fn, _, datum, event);
};
},
/**
* Parse an expression that performs visual encoding.
*/
encode(ctx, encode) {
const {
marktype,
channels
} = encode,
fn = ctx.functions,
swap = marktype === 'group' || marktype === 'image' || marktype === 'rect';
return (item, _) => {
const datum = item.datum;
let m = 0,
v;
for (const name in channels) {
v = interpret(channels[name].ast, fn, _, datum, undefined, item);
if (item[name] !== v) {
item[name] = v;
m = 1;
}
}
if (marktype !== 'rule') {
adjustSpatial(item, channels, swap);
}
return m;
};
}
};
export { expression as expressionInterpreter };
//# sourceMappingURL=vega-interpreter.js.map