UNPKG

mframejs

Version:
508 lines (390 loc) 15.9 kB
import { ContainerValueConverters, ContainerClasses } from '../../container/exported'; import { getCorrectContext } from '../contextOfObject'; import { IBindingContext } from '../../interface/exported'; import { setValue } from '../setValue'; let fromView: any; const Traverser = class { /** * operator calls * */ public static operator = function (val: string, a: any, b: any) { switch (val) { case '+': return a + b; case '-': return b ? a - b : -a; case '*': return a * b; case '!': return !a; case '%': return a % b; case '>': return a > b; case '<': return a < b; case '>=': return a >= b; case '&&': return a && b; case '||': return a || b; case '<=': return a <= b; case '+=': return a += b; case '-=': return a -= b; case '*=': return a *= b; case '/=': return a /= b; case '===': return a === b; case '!==': return a !== b; case '/': return a / b; // tslint:disable-next-line:no-bitwise case '^': return a ^ b; } }; /** * finds out what arity it is * */ public static checkArity = function (ast: any, ctx: any, mainctx: any) { let value; if (ast && ast.arity === 'literal') { value = ast.value; } if (ast && ast.arity === 'variable') { value = Traverser.variable(ast, ctx, mainctx); } if (ast && ast.arity === 'binary') { value = Traverser.binary(ast, ctx, mainctx); } if (ast && ast.arity === 'unary') { value = Traverser.binary(ast, ctx, mainctx); } if (ast && ast.arity === 'ternary') { value = Traverser.ternary(ast, ctx, mainctx); } if (ast && ast.arity === 'valueConverter') { value = Traverser.valueConverter(ast, ctx, mainctx); } return value; }; /** * called if arity is variable * todo: this one is kinda messy atm * */ public static variable = function (ast: any, ctx: any, _mainctx: IBindingContext) { let result; try { switch (true) { case ast.value === 'null' && ast.arity === 'variable': return null; case ast.value === 'undefined' && ast.arity === 'variable': return undefined; case ast.value === '$this' && ast.arity === 'variable' && ast.root: return ctx.$context; case ast.value === '$event' && ast.arity === 'variable' && ast.root: return ctx.$event; case ast.arity === 'variable' && typeof ast.value === 'string' && ast.value.toUpperCase() === 'TRUE': return true; case ast.arity === 'variable' && typeof ast.value === 'string' && ast.value.toUpperCase() === 'FALSE': return false; default: } let curObj: any; let curCtx = ctx; if (ctx.__bindingContext) { curCtx = ctx.$context; } // check if it varibale starts with $ let localVariable = false; if (ast.value && ast.value[0] === 'string' && ast.value[0][0] !== '$') { localVariable = true; } if (localVariable) { curObj = curCtx[ast.value]; } else { if (ast.root) { curCtx = getCorrectContext(ast.value, ctx); if (curCtx.__bindingContext) { curCtx = curCtx.$context; } } curObj = curCtx[ast.value]; } result = curObj; } catch (e) { // we dont want to display this just nothing if there is no var // console.warn('failed in variable'); // nothing } return result; }; /** * called if arity is binary * */ public static binary = function (ast: any, ctx: any, mainctx: any) { let second: any; let value: any; let first: any; switch (true) { case ast.assignment === true: // maybe a bit weird, but works and is dynamic for simple expression assignments try { if (ast.first.arity === 'variable') { const $ctx = ctx.__bindingContext ? ctx.$context : ctx; if (ast.value === '+=' || ast.value === '-=') { $ctx[ast.first.value] = Traverser.operator( ast.value, $ctx[ast.first.value], Traverser.checkArity(ast.second, ctx, mainctx) ); } else { $ctx[ast.first.value] = Traverser.checkArity(ast.second, ctx, mainctx); } } else { // so it will return object let done = false; let astFirst = ast.first; const x: any = []; while (!done) { if (astFirst.second.arity !== 'binary') { x.push(astFirst.second.value); } else { x.push(`[${Traverser.checkArity(astFirst.second, ctx, mainctx)}]`); } if (astFirst.first.arity === 'variable') { x.push(astFirst.first.value); done = true; } else { astFirst = astFirst.first; } } x.reverse(); const objectString = x.join('.').split('.[').join('['); // obj.something.cool[5] if (ast.value === '=') { setValue(ctx, objectString, Traverser.checkArity(ast.second, ctx, mainctx)); } else { first = Traverser.checkArity(ast.first, ctx, mainctx); setValue(ctx, objectString, Traverser.operator( ast.value, first, Traverser.checkArity(ast.second, ctx, mainctx) )); } } } catch (x) { console.error('only simple assigments are supported, sorry', ast); } break; case ast.value === '{': const x = {}; ast.first.forEach((y: any) => { x[y.key] = Traverser.checkArity(y, ctx, mainctx); }); value = x; break; case ast.checkArray: first = Traverser.checkArity(ast.first, ctx, mainctx); if (first && ast.contextReset) { second = Traverser.checkArity(ast.second, mainctx, mainctx); second = first[second]; } else { second = Traverser.checkArity(ast.second, first, mainctx); } value = second ? second : first; break; case ast.isObject: first = Traverser.checkArity(ast.first, ctx, mainctx); if (first && ast.contextReset) { second = Traverser.checkArity(ast.second, mainctx, mainctx); second = first[second]; } else { second = Traverser.checkArity(ast.second, first, mainctx); } value = second; break; case !ast.isFunction: first = Traverser.checkArity(ast.first, ctx, mainctx); second = Traverser.checkArity(ast.second, ctx, mainctx); value = Traverser.operator(ast.value, first, second); break; default: // function calls const results: any[] = []; ast.second.forEach(function (res: any) { results.push(Traverser.evaluate(res, mainctx)); }); let curCtx = ctx.$context; let valueTrigger: any; if (ast.first.arity === 'binary') { valueTrigger = Traverser.binary(ast.first, ctx, mainctx); } else { valueTrigger = curCtx[ast.first.value]; } curCtx = getCorrectContext(ast.first.value, ctx); if (curCtx && curCtx.$context && typeof curCtx.$context[ast.first.value] === 'function') { valueTrigger = curCtx.$context[ast.first.value]; } if (valueTrigger) { const typeObj = Traverser.binary({ first: ast.first.first, checkArray: true }, ctx, mainctx); if (Array.isArray(typeObj)) { value = valueTrigger.apply(typeObj, results); } else { value = valueTrigger.apply(curCtx.$context, results); } } else { console.warn(`method does not exist:${ast.first.value}`); } } return value; }; /** * ternary * called if arity is ternary (conditional) * {condition ? expr1 : expr2}' */ public static ternary = function (ast: any, ctx: any, mainctx: any) { const first = Traverser.checkArity(ast.first, ctx, mainctx); const second = Traverser.checkArity(ast.second, ctx, mainctx); const third = Traverser.checkArity(ast.third, ctx, mainctx); const value: any = first ? second : third; return value; }; /** * called if arity is value converter * */ public static valueConverter = function (ast: any, ctx: any, mainctx: any) { let result; const args: any[] = []; // loop args ast.args.forEach((arg: any) => { args.push(Traverser.checkArity(arg, ctx, mainctx)); }); // find value converter const valueConverterExist = ContainerValueConverters.findConverter(ast.value); let _class: any; // if it exist, then we execute it if (valueConverterExist) { _class = ContainerClasses.get(valueConverterExist); try { result = fromView ? _class.fromView.apply(ctx, args) : _class.toView.apply(ctx, args); } catch (e) { console.warn('value converter failed:' + ast.value); } } else { console.warn('value converter does not exist:' + ast.value); } return result; }; /** * called to start evaluating ast (getting result) * */ public static evaluate = function (astInput: any, ctx: any) { fromView = false; const astArray = Array.isArray(astInput) ? astInput : [astInput]; const returnArray: any[] = []; for (let i = 0; i < astArray.length; i++) { const result = Traverser.checkArity(astArray[i], ctx, ctx); returnArray.push(result); } if (returnArray.length > 1) { return returnArray.join(''); } else { return returnArray[0]; } }; /** * handle value converter fromView only * */ public static traverseOnlyValueConverter = function (value: any, ast: any, ctx: any, mainctx: any) { let result; let args: any[] = []; // loop args ast.args.forEach((arg: any, i: number) => { if (i > 0) { args.push(Traverser.checkArity(arg, ctx, mainctx)); } }); args = [value, ...args]; // find value converter const valueConverterExist = ContainerValueConverters.findConverter(ast.value); let _class: any; // if it exist, then we execute it if (valueConverterExist) { _class = ContainerClasses.get(valueConverterExist); try { result = fromView ? _class.fromView.apply(ctx, args) : _class.toView.apply(ctx, args); } catch (e) { console.warn('value converter failed:' + ast.value); } } else { console.warn('value converter does not exist:' + ast.value); } return result; }; /** * evaluate ast * */ public static evaluateConverterFromViewOnly = function (astInput: any, value: any, ctx: any, mainctx: any) { // if more then its const astArray = Array.isArray(astInput) ? astInput : [astInput]; const returnArray: any[] = []; fromView = true; let valueConverted = false; for (let i = 0; i < astArray.length; i++) { let result; switch (true) { case astArray[i].arity === 'valueConverter': // resolve args valueConverted = true; result = Traverser.traverseOnlyValueConverter(value, astArray[i], ctx, mainctx); break; } if (result) { returnArray.push(result); } } if (returnArray.length > 1) { return returnArray.join(''); } else { if (valueConverted) { return returnArray[0]; } else { return value; } } }; /** * gets the behaviors from ast * */ public static getBehaviorFunctions = function (ast: any) { const astArray = Array.isArray(ast) ? ast : [ast]; const returnArray: any[] = []; for (let i = 0; i < astArray.length; i++) { let result; switch (true) { case astArray[i].arity === 'behavior': result = { name: astArray[i].value, args: astArray[i].args.map((a: any) => a.value) }; // this will be used outside this scope for now break; } if (result) { returnArray.push(result); } } return returnArray; }; }; // default for getting result export const traverseAST = function (ast: any, ctx: IBindingContext) { return Traverser.evaluate(ast, ctx); }; // used for getting valueConverter export const valueConvert = function (ast: any, ctx: IBindingContext, value: any) { return Traverser.evaluateConverterFromViewOnly(ast, value, ctx, ctx); }; // return behavior export const getBehavior = function (ast: any) { return Traverser.getBehaviorFunctions(ast); };