mframejs
Version:
simple framework
508 lines (390 loc) • 15.9 kB
text/typescript
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);
};