edgerender-yatl
Version:
Yet Another Template Language
267 lines • 9.93 kB
JavaScript
;
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