UNPKG

edgerender-yatl

Version:

Yet Another Template Language

267 lines 9.93 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const async_iter_1 = require("../async_iter"); const utils_1 = require("../utils"); class Evaluator { constructor(context, functions) { this.op_equals = (value, args) => async_iter_1.async_every(args, async (a) => utils_1.smart_equals(value, await this.evaluate(a))); this.op_not_equals = (value, args) => async_iter_1.async_every(args, async (a) => !utils_1.smart_equals(value, await this.evaluate(a))); this.op_and = async (value, args) => !!(value && (await async_iter_1.async_every(args, this.evaluate))); this.op_or = async (value, args) => !!(value || (await async_iter_1.async_any(args, this.evaluate))); this.context = context; this.functions = functions; this.evaluate = this.evaluate.bind(this); this.operator_functions = { '|': (value, args) => async_iter_1.async_reduce(args, (a, b) => this.filter_run(a, b), value), '*': (value, args) => this.op_mult_div(value, args, '*'), '/': (value, args) => this.op_mult_div(value, args, '/'), '+': this.op_add.bind(this), '-': this.op_subtract.bind(this), in: this.op_in.bind(this), '!in': async (value, args) => !(await this.op_in(value, args)), '==': this.op_equals.bind(this), '!=': this.op_not_equals.bind(this), '&&': this.op_and.bind(this), '||': this.op_or.bind(this), }; } async evaluate(c) { switch (c.type) { case 'var': return this.var(c); case 'str': case 'num': case 'bool': return c.value; case 'list': return await async_iter_1.async_map(c.elements, this.evaluate); case 'func': return await this.func_run(c); case 'mod': return await this.modifiers(c); case 'operator': return await this.operation(c); default: utils_1.shouldnt_happen(c); } } var(v) { return this.lookup_value(v, this.context, 'context'); } async func_run(f) { const func = this.func_get(f); const args = await async_iter_1.async_map(f.args, this.evaluate); const r = await func(...args); if (typeof r == 'function') { throw Error('filter functions may not be called directly'); } return r; } func_get(f) { const func = this.lookup_value(f.var, this.functions, 'functions'); if (typeof func == 'function') { return func; } else if (func) { throw Error(`"${f.var.symbol}" an object, not a function`); } else { throw Error(`function "${f.var.symbol}" not found`); } } async operation(op) { const func = this.operator_functions[op.operator]; return await func(await this.evaluate(op.args[0]), op.args.slice(1)); } async filter_run(a, filter) { let func; if (filter.type == 'var') { func = this.func_get({ type: 'func', var: filter, args: [] }); } else { const outer_func = this.func_get(filter); const args = await async_iter_1.async_map(filter.args, this.evaluate); const func_ = await outer_func(...args); if (typeof func_ != 'function') { throw Error('filter functions must return another function'); } func = func_; } const r = await func(a); if (typeof r == 'function') { throw Error('filters may not return a function'); } return r; } async op_mult_div(value, args, op) { if (typeof value != 'number') { throw TypeError(`arithmetic operation ${op} only possible on numbers, got ${typeof value}`); } for (const arg of args) { if (!value) { return value; } const arg_num = await this.evaluate(arg); if (typeof arg_num == 'number') { if (op == '*') { value *= arg_num; } else { value /= arg_num; } } else { throw TypeError(`arithmetic operation "${op}" only possible on numbers, got ${typeof arg_num}`); } } return value; } async op_add(value, args) { const value_type = utils_1.smart_typeof(value); switch (value_type) { case utils_1.SmartType.Number: return await async_iter_1.async_reduce(args, this.add_numbers.bind(this), value); case utils_1.SmartType.Array: return await async_iter_1.async_reduce(args, this.add_arrays.bind(this), value); case utils_1.SmartType.Object: return await async_iter_1.async_reduce(args, this.add_objects.bind(this), value); default: throw TypeError(`unable to add ${value_type}s`); } } async add_numbers(a, b) { const v = await this.evaluate(b); if (typeof v != 'number') { throw TypeError(`only number can be added to number, not ${typeof b}`); } return a + v; } async add_arrays(a, b) { const v = await this.evaluate(b); if (!Array.isArray(v)) { throw TypeError(`only arrays can be added to arrays, not ${typeof v}`); } return [...a, ...v]; } async add_objects(a, b) { const v = await this.evaluate(b); if (typeof v != 'object' || !v) { throw TypeError(`only objects can be added to objects, not ${typeof v}`); } return { ...a, ...v }; } async op_subtract(value, args) { if (typeof value != 'number') { throw TypeError(`only numbers can be subtracted, not ${typeof value}`); } return await async_iter_1.async_reduce(args, this.subtract_numbers.bind(this), value); } async subtract_numbers(a, b) { const v = await this.evaluate(b); if (typeof v != 'number') { throw TypeError(`only numbers can be subtracted, not ${typeof b}`); } return a - v; } async op_in(value, args) { const container = await this.evaluate(args[0]); const container_type = utils_1.smart_typeof(container); if (container_type == utils_1.SmartType.Object) { if (typeof value != 'string') { return false; } else { return utils_1.has_property(container, value); } } else if (container_type == utils_1.SmartType.Array) { return container.includes(value); } else if (container_type == utils_1.SmartType.String) { if (typeof value != 'string') { return false; } else { return container.includes(value); } } else { throw TypeError(`"in" is only possible for objects, arrays and strings, not "${container_type}`); } } async modifiers(mod) { if (mod.mod == '!') { return !(await this.evaluate(mod.element)); } else { // minus const v = await this.evaluate(mod.element); if (typeof v == 'number') { return -v; } else { throw Error(`negative of ${typeof v} is not valid`); } } } lookup_value(v, namespace, namespace_name) { let value = namespace[v.symbol]; if (value === undefined) { throw Error(`"${v.symbol}" not found in ${namespace_name}`); } const steps = []; const name = () => v.symbol + steps.map(s => `[${JSON.stringify(s)}]`).join(''); function return_or_error(c, key) { if (c.op == '.?') { return undefined; } else { steps.pop(); throw Error(`"${key}" not found in "${name()}"`); } } for (const c of v.chain) { let key = c.lookup; if (c.type == 'symbol') { const _key = this.context[c.lookup]; if (_key === undefined) { throw Error(`lookup "${c.lookup}" not found`); } else if (typeof _key != 'string' && typeof _key != 'number') { throw Error(`"${c.lookup}" must be a string or number`); } key = _key; } steps.push(key); if (typeof key == 'number') { if (Array.isArray(value)) { if (key >= 0 && key < value.length) { value = value[key]; } else { return return_or_error(c, key); } } else { throw Error(`${name()}: numeric lookups are only allowed on arrays, not ${typeof value}`); } } else { if (utils_1.is_object(value)) { if (key in value) { value = value[key]; } else { return return_or_error(c, key); } } else { throw Error(`${name()}: string lookups are only allowed on objects, not ${typeof value}`); } } } return value; } } exports.default = Evaluator; //# sourceMappingURL=evaluate.js.map