UNPKG

expexp

Version:

The express model io and express model and data representation.

1,036 lines (963 loc) 32.7 kB
import {traverseSimpleExpression, traverseExpression, traverseSimpleFactor} from './mniTree.js' import {VariableScope} from './variableScope.js' // // TODO distinguish string from enum value (create a malicious test first) // TODO how to know for operations with two aggregate operands what is the // type of result set,bag,list,array? e.g. in the handleInOp // The HIINDEX, LOINDEX, HIBOUND, LOBOUND depend on that. export function Evaluator(model, inst, scope) { const me = {} const vs = [[]] // stack of current value(s) // The first dim grows with argument lists, parenthesis. // The second dim grows and shrinks while evaluating term or simple_expression. const ss = [scope] // scope stack (only grows with function calls or query expressions) const errors = [] // List of error messages let ignore = false // If set, this references the json, that ends the ignore state. me.selfValue = null const setup = function() { // Do nothing. } // Pushes / starts an empty new array on the value stack. const pushNewArr = function() { vs.push([]) } // Most of the time this should not be used directly. // It is more internal for the others stack operations. // But there are certain sensible exceptions: When a infrastructure array // becomes a data array may be, and others. const curArr = function() { return vs[vs.length-1] } const curVal = function(off = 0) { const curAr = curArr() return curAr[curAr.length-1+off] // most of the time curArr[0] } const pushVal = function(value) { curArr().push(value) } const chgVal = function(value) { const curAr = curArr() if (0 < curAr.length) { curAr[curAr.length-1] = value } else { curAr.push(value) } } const popVal = function() { return curArr().pop() // Is undefined if array is empty. } const popArr = function() { return vs.pop() } const popTransfer = function() { const actVal = curVal() vs.pop() pushVal(actVal) } const is = function(path) { return new Array(path.length * 2 + 1).join(' ') } const nice = function(val) { // Similar to instance.toStringRep if (typeof val == 'string') { val = '"'+val+'"' } else if (val instanceof Uint8Array) { val = `Uint8Array(${val.length})[..]` } return val } const path2str = function(path, json, idx) { const strs = path.concat([json]).map(function(elt, i, arr){ let parent // undefined if (0 < i) { parent = arr[i-1] } let eltStr // undefined if (Array.isArray(elt)) { eltStr = ':' } else { // object eltStr = `{${elt.t}}` } if (Array.isArray(parent)) { return `[${parent.indexOf(elt)}]${eltStr}` } else if (parent) { // parent is object const attr = Object.entries(parent).find(kv => (kv[1] === elt)) if (attr) { return `.${attr[0]}${eltStr}` } else { return `??${eltStr}` } } else { // first elt return eltStr } }) // TODO also add json and idx return strs.join('') } const beforeFct = function(json, path, idx) { if (ignore) return // const parent = path[path.length-1] // We rarely have to something before (rather after). // TODO also those with arg0 and arg1 need a current value stack push. switch (json.t) { case 'expr': case 'bifct': case 'func_call': pushNewArr() console.log(is(path),'bf', json.t, 'PUSH') break case 'factor': handleBeforeFactor(json, path, idx) break case 'query': ignore = json.cond // ignore everything until after condition // Also json.src is not visited / handled. console.log(is(path),'bf', json.t, 'IGNORE START (until af expr)') break case 'aggr': // aggregate initializer pushNewArr() // This sturcture array will become a data array in afterFct. console.log(is(path),'bf', json.t, 'PUSH') break default: console.log(is(path),'bf', json.t) } } const afterFct = function(json, path, idx) { if (ignore === json) { ignore = false console.log(is(path),'af', json.t, 'IGNORE END') return } else if (ignore) { return } // const parent = path[path.length-1] switch (json.t) { case 'integer': case 'real': case 'string': console.log(is(path),'af', json.t, json.value) pushVal(json.value) break case 'var_ref': handleVarRef(json, path, idx) break // case 'type_ref': This comes before enum_value and is ingored here. case 'func_call': handleFuncCall(json, path, idx) break case 'bifct': handleBiFct(json, path, idx) break case 'biconst': handleBiConst(json, path, idx) break case 'expr': handleExpr(json, path, idx) break case 'factor': handleAfterFactor(json, path, idx) break case 'query': handleAfterQuery(json, path, idx) break case 'aggr': handleAfterAggr(json, path, idx) break case 'relopext': case 'addlikeop': case 'multlikeop': console.log(is(path),'af', json.t, json.id) break case 'attr_ref': // Old term attr_qual. handleAfterAttrRef(json, path, idx) break case 'entity_ref': handleAfterEntityRef(json, path, idx) break case 'idx_qual': handleAfterIdxQual(json, path, idx) break case 'enum_value': handleAfterEnumValue(json, path, idx) break default: console.log(is(path),'af', json.t) } } const beforeArrFct = function(json, path, idx) { if (ignore) return if (0<json.length) { const parent = 0<path.length?path[path.length-1]:null if (parent) { if (Array.isArray(parent)) { // is term handleBeforeTerm(json, path, idx) } else if (parent.t == 'expr') { // is simple expression console.log(is(path),'bA', json.length, 'simple_expression PUSH') pushNewArr() } else { console.log(is(path),'bA', json.length) } } else { console.log(is(path),'bA', json.length) } } // ignore empty arrays } const afterArrFct = function(json, path, idx) { if (ignore === json) { ignore = false console.log(is(path),'aA', json.length, 'IGNORE END') return } else if (ignore) { return } if (0<json.length) { const parent = 0<path.length?path[path.length-1]:null if (parent) { if (Array.isArray(parent)) { // is term handleAfterTerm(json, path, idx) } else if (parent.t == 'expr') { // is simple expression console.log(is(path),'aA', json.length, 'simple_expression POP') popTransfer() } else if (parent.t == 'bifct' || parent.t == 'func_call') { console.log(is(path),'aA', json.length, 'args') } else { console.log(is(path),'aA', json.length) } } else { console.log(is(path),'bA', json.length) } } // ignore empty arrays } const handleVarRef = function(json, path, idx) { const actVal = ss[ss.length-1].get(json.id) console.log(is(path),'af', json.t, json.id, actVal) pushVal(actVal) } const handleFuncCall = function(json, path, idx) { const actParams = curArr() popArr() console.log(is(path),'af', json.t, json.id, actParams.length, 'POP') //const funMdl = model.functionById(json.id) // .params, .locals, .result // TODO handle visibility by checking: // funMdl.parent == null || me.selfFunc ?===? funMdl.parent || or the funMdl.parent's parent ... const res = inst.evalFuncSave(json.id, actParams) if (res.result) { pushVal(res.value) } else { // TODO Is it one or several messages? pushVal(INDETERMINATE) errors.push.apply(errors, res.messages) } } const handleBiFct = function(json, path, idx) { console.log(is(path),'af', json.t, json.id, json.values.length, 'POP') let actVal // undefined switch (json.id) { case 'exists': actVal = evalExists(curVal(), json, path, idx) break case 'sizeof': actVal = evalSizeOf(curVal(), json, path, idx) break case 'length': actVal = evalLength(curVal(), json, path, idx) break case 'blength': actVal = evalBlength(curVal(), json, path, idx) break case 'typeof': actVal = evalTypeof(curVal(), json, path, idx) break case 'usedin': handleAfterUsedIn(json, path, idx) // chgVal with result array actVal = curVal() break default: throw new Error('Unknown built in function ' + json.id) } popArr() pushVal(actVal) } // This one behaves like an operation function: It chgVal just the result array. const handleAfterUsedIn = function(json, path, idx) { handleTwoOperands( 'usedIn', (a)=>(inst.isEntity(a)?'entity token':false), (b)=>((b == '' || model.resolveRoleName(b))?'valid role name or empty string':false), (a,b)=>(inst.usedIn(a, b)), json, path, idx // Take result array of tokens from usedIn unchanged. // Defaults to INDETERMINATE. ) } const evalTypeof = function(val, json, path, idx) { if (isIndeterminate(val)) { return INDETERMINATE } if (inst.isToken(val)) { const scId = model.schemaId() const dta = inst.rawData(val) // We only need this to distinguish and the model ids. let chn // undefined if (dta.nttId) { chn = model.entityIdChainById(dta.nttId) } else { // dta.typeId chn = model.typeIdChainById(dta.typeId) // TODO base type or aggregation might be missing // TODO If it is REAL or INTEGER it is also NUMBER. } const res = chn.map(eltId => scId.toUpperCase() + '.' + eltId.toUpperCase()) return res } else { errors.push(`The ${json.t} ${json.id} cannot determine the type strings of invalid value ${nice(val)} for ${path2str(path, json, idx)}.`) return INDETERMINATE } } const evalExists = function(val, json, path, idx) { if (isIndeterminate(val)) { return false // Not INDETERMINATE . } if (isUnknown(val) == false) { return true } else { // UNKNOWN is a valid value, that exists. return true } } const evalSizeOf = function(val, json, path, idx) { if (isIndeterminate(val)) { return INDETERMINATE } if (Array.isArray(val)) { return val.length } else { errors.push(`The ${json.t} ${json.id} cannot take size of value ${nice(val)} for ${path2str(path, json, idx)}.`) return INDETERMINATE } } const evalLength = function(val, json, path, idx) { if (isIndeterminate(val)) { return INDETERMINATE } if (typeof val == 'string') { // TODO distinguish string from enum value return val.length } else { errors.push(`The ${json.t} ${json.id} cannot take length of value ${nice(val)} for ${path2str(path, json, idx)}.`) return INDETERMINATE } } const evalBlength = function(val, json, path, idx) { if (isIndeterminate(val)) { return INDETERMINATE } if (val instanceof Uint8Array) { return val.length * 8 // TODO that is not exact by the last bits - what to do? } else { errors.push(`The ${json.t} ${json.id} cannot take length of binary value ${nice(val)} for ${path2str(path, json, idx)}.`) return INDETERMINATE } } const handleLogicalOp = function(caseStr, dcnMx, json, path, idx) { if (grantTwoSameOperands(caseStr, toLogicalTypeStr, json, path, idx) == false) { chgVal(INDETERMINATE) // Change or push. (More errors might follow.) return } let left = curVal(-1) let right = curVal() if (isIndeterminate(left)) left = UNKNOWN if (isIndeterminate(right)) right = UNKNOWN chgVal(dcnMx[left][right]) } const handleInOp = function(json, path, idx) { handleTwoOperands( 'membership (in)', function(value){ // Is only called, if two operands are granted. const right = curVal() // But the right value is not yet granted its type. if (toArrayTypeStr(right)) { // TODO check value is of base type of aggregation (array) return 'ok' // TODO return false if the check fails } else { // The whole op will be invalid and result in INDETERMINATE because // the richt value is not valid. So we do not matter about the left. return 'ok' } }, toArrayTypeStr, (a,b)=>(b.includes(a)), json, path, idx, x=>x, UNKNOWN ) } const handleCompareOp = function(caseStr, cmpFct, json, path, idx, doTestAggrAndEntity = false) { if (grantTwoOperands(caseStr, json, path, idx) == false) { chgVal(INDETERMINATE) // Change or push. (More errors might follow.) return } const left = curVal(-1) const right = curVal() const cmp = isIndeterminate(left)?right:left switch (typeof cmp) { case 'number': chgVal(cmpFct(left, right)) break case 'string': // TODO how distinguish from enum value? chgVal(cmpFct(left, right)) // compare lexicograhically is ok break case 'null': // UNKNOWN case 'boolean': chgVal(cmpFct(left, right)) // TODO check that is correct? break default: if (doTestAggrAndEntity && Array.isArray(cmp)) { // TODO aggregation compare } else if (cmp instanceof Uint8Array) { // TODO binary compare } else if (doTestAggrAndEntity && false) { // TODO use isToken(cmp) instead of false // TODO entity compare } else if (isIndeterminate(left) || isIndeterminate(right)) { chgVal(UNKNOWN) // not an error case and not INDETERMINATE } else { const tLeft = typeof left const tRight = typeof right errors.push(`From neither type of operand (neither left ${tLeft} nor right ${tRight}) the type of ${caseStr} operation can be detremined at ${path2str(path, json, idx)}.`) chgVal(INDETERMINATE) } } } const handlePlusOp = function(json, path, idx) { if (grantTwoOperands('plus', json, path, idx) == false) { chgVal(INDETERMINATE) // Change or push. (More errors might follow.) return } const left = curVal(-1) const right = curVal() const cmp = isIndeterminate(left)?right:left switch (typeof cmp) { case 'number': handleTwoSameOperands('addition', toNumberTypeStr, (a,b)=>(a+b), json, path, idx) // no need to fix result break case 'string': // TODO how distinguish from enum value? handleTwoSameOperands('string concat', toStringTypeStr, (a,b)=>(a+b), json, path, idx) // no need to fix result break default: if (Array.isArray(cmp)) { // TODO aggregation union } else if (cmp instanceof Uint8Array) { // TODO concat binary } else if (isIndeterminate(left) || isIndeterminate(right)) { chgVal(INDETERMINATE) // not an error case } else { const tLeft = typeof left const tRight = typeof right errors.push(`From neither type of operand (neither left ${tLeft} nor right ${tRight}) the type of plus operation can be detremined at ${path2str(path, json, idx)}.`) chgVal(INDETERMINATE) } } } const handleMinusOp = function(json, path, idx) { handleMinusOrAsteriskOp( 'minus', 'subtraction', toNumberTypeStr, (a,b)=>(a-b), 'difference', TODO=>TODO, TODO=>TODO, json, path, idx ) } const handleAsteriskOp = function(json, path, idx) { handleMinusOrAsteriskOp( 'asterisk', 'multiplication', toNumberTypeStr, (a,b)=>(a*b), 'intersect', TODO=>TODO, TODO=>TODO, json, path, idx ) } const handleMinusOrAsteriskOp = function( caseStr, numCaseStr, numTypeFct, numFct, aggrCaseStr, aggrTypeFct, aggrFct, // TODO hope that is all needed json, path, idx ){ if (grantTwoOperands(caseStr, json, path, idx) == false) { chgVal(INDETERMINATE) // Change or push. (More errors might follow.) return } const left = curVal(-1) const right = curVal() const cmp = isIndeterminate(left)?right:left if (typeof cmp == 'number') { handleTwoSameOperands(numCaseStr, numTypeFct, numFct, json, path, idx) // no need to fix result } else if (Array.isArray(cmp)) { // TODO aggregation difference resp. intersect (use aggrCaseStr, aggrTypeFct, aggrFct somehow) } else if (isIndeterminate(left) || isIndeterminate(right)) { chgVal(INDETERMINATE) // not an error case } else { const tLeft = typeof left const tRight = typeof right errors.push(`From neither type of operand (neither left ${tLeft} nor right ${tRight}) the type of ${caseStr} operation can be detremined at ${path2str(path, json, idx)}.`) chgVal(INDETERMINATE) } } const toNumberTypeStr = function(value) { if (typeof value == 'number') { return Number.isInteger(value)?'int':'real' } return false } const toStringTypeStr = function(value) { const res = typeof value return (res == 'string')?res:false } const toArrayTypeStr = function(value) { const res = Array.isArray(value) return res?'array':false } const toLogicalTypeStr = function(value) { const t = typeof value switch (t) { case 'boolean': case 'null': // UNKNOWN case 'undefined': // INDETERMINATE (is also valid, for it is treated as UNKNOWN) return 'logical' break default: return false } } const handleTwoOperands = function(caseStr, leftTypeFct, rightTypeFct, fct, json, path, idx, fixResultFct = x=>x, defaultValue = INDETERMINATE) { if (grantTwoOperandsOfTypes(caseStr, leftTypeFct, rightTypeFct, json, path, idx) == false) { chgVal(INDETERMINATE) // Change or push. (More errors might follow.) return } handleTwoGrantedOpsOrDefault(fct, fixResultFct, defaultValue) } const handleTwoSameOperands = function(caseStr, typeFct, fct, json, path, idx, fixResultFct = x=>x, defaultValue = INDETERMINATE) { if (grantTwoSameOperands(caseStr, typeFct, json, path, idx) == false) { chgVal(INDETERMINATE) // Change or push. (More errors might follow.) return } handleTwoGrantedOpsOrDefault(fct, fixResultFct, defaultValue) } // Both operands on the stack must already be granted to be valid (This // comprises potential INDETERMINATE value). const handleTwoGrantedOpsOrDefault = function(fct, fixResultFct = x=>x, defaultValue = INDETERMINATE) { const left = curVal(-1) const right = curVal() if (isIndeterminate(left) == false && isIndeterminate(right) == false) { chgVal(fixResultFct(fct(left, right))) } else { chgVal(defaultValue) } } const grantTwoSameOperands = function(caseStr, typeFct, json, path, idx) { return grantTwoOperandsOfTypes(caseStr, typeFct, undefined, json, path, idx) } // A *TypeFct takes a value as its sole argument and returns the type as // string or false if the value is of invalid type. When talking of type, that // does not mean JS types (returned by typeof). (JS typeof is only used for // the error messages to provide a better hint of what is wrong.) // If rightTypeFct is undefined, leftTypeFct is applied to both and // additionally their result granted to be equal. // Any operand INDETERMINATE is ok and results in true (even if the other // one is invalid). const grantTwoOperandsOfTypes = function(caseStr, leftTypeFct, rightTypeFct, json, path, idx) { if (grantTwoOperands(caseStr, json, path, idx) == false) { chgVal(INDETERMINATE) // Change or push. (More errors might follow.) return } const left = curVal(-1) const right = curVal() // If any is INDETERMINATE, that is just ok. // Even if the left is INDETERMINATE and a possible *TypeFct(right) == false. if (isIndeterminate(left) || isIndeterminate(right)) { return true } const tLeft = leftTypeFct(left) const tRight = rightTypeFct?rightTypeFct(right):leftTypeFct(right) if (tLeft && tRight) { if (rightTypeFct == null) { // Just check if that param is passed. // Grant that the types are the same. if (tLeft != tRight) { errors.push(`The first/left argument/operand is of type ${tLeft} and the second/right one of type ${tRight}, but they should be of the same type. This is invalid for ${caseStr} at ${path2str(path, json, idx)}.`) return false } } return true } else { // One or both operands are invalid. const errFct = function(side, tWrong) { errors.push(`The ${side} argument/operand of type ${tWrong} is invalid for ${caseStr} at ${path2str(path, json, idx)}.`) } if (tLeft == false) { errFct('first/left', typeof left) } if (tRight == false) { errFct('second/right', typeof right) } return false } } const grantTwoOperands = function(caseStr, json, path, idx) { const curAr = curArr() const res = 1 < curAr.length if (res == false) { errors.push(`There is only ${curAr.length} operand for ${caseStr} at ${path2str(path, json, idx)}.`) } return res } const handleBiConst = function(json, path, idx) { console.log(is(path),'af', json.t, json.id) let actVal // undefined switch (json.id) { case 'self': actVal = me.selfValue break case 'true': actVal = true break case 'false': actVal = false break case 'unknown': actVal = null // The third logical value. break case 'const_e': actVal = Math.E break case 'pi': actVal = Math.PI break case 'question_mark': actVal = INDETERMINATE break default: throw new Error('Unknown built in constant ' + json.id) } pushVal(actVal) } const handleExpr = function(json, path, idx) { if (json.op) { console.log(is(path),'af', json.t, json.op.id, 'POP') switch(json.op.id) { case 'eq': handleCompareOp('equal', (a,b)=>(a==b), json, path, idx, true) break case 'ne': handleCompareOp('not-equal', (a,b)=>(a!=b), json, path, idx, true) break case 'lt': handleCompareOp('less-than', (a,b)=>(a<b), json, path, idx) break case 'le': handleCompareOp('less-than-or-equal', (a,b)=>(a<=b), json, path, idx) break case 'gt': handleCompareOp('greater-than', (a,b)=>(a>b), json, path, idx) break case 'ge': handleCompareOp('greater-than-or-equal', (a,b)=>(a>=b), json, path, idx) break case 'seq': // TODO what is with entity tokens? chgVal(curVal(-1) === curVal()) break case 'sne': // TODO what is with entity tokens? chgVal(curVal(-1) !== curVal()) break case 'in': handleInOp(json, path, idx) break // case 'like': // // TODO // break default: throw new Error('Unknown rel_op_extended ' + json.op.id) } } else { console.log(is(path),'af', json.t, 'nop POP') } // console.log(curArr(), vs.length) popTransfer() } const handleBeforeFactor = function(json, path, idx) { const parent = path[path.length-1] // Check if it is a series of AND and the result is already false. if (0 < idx && idx % 2 == 0) { // console.log('check false AND .. :', curVal(), parent[idx-2], parent[idx-1], json) // It is actually very special to decide according to the current value // whether to continue evaluation or not. const multLikeOp = parent[idx-1] if ((multLikeOp.id == 'and' && curVal() == false) == false) { pushNewArr() console.log(is(path),'bf', json.t, 'PUSH') } else { // Do not push and start skipping all until handleAfterFactor. pushVal(false) // Just repeat the false value. ignore = json console.log(is(path),'bf', json.t, 'IGNORE START') } } else { pushNewArr() console.log(is(path),'bf', json.t, 'PUSH') } } const handleAfterFactor = function(json, path, idx) { const parent = path[path.length-1] console.log(is(path),'af', json.t, idx, 'POP') // TODO not only arg0 but also arg1 and quals0 and quals1 // TODO somehow use this: // handleTwoSameOperands( // 'exponentiation', toNumberTypeStr, (a,b)=>(a ** b), // json, path, idx, // x=>(Number.isFinite(x)?x:INDETERMINATE) // better fix the result // ) popTransfer() // Do the term calculation already here (especially for 'and'): if (0 < idx && idx % 2 == 0) { // console.log('calc:', parent[idx-2], parent[idx-1], json) const multLikeOp = parent[idx-1] switch (multLikeOp.id) { case 'and': handleLogicalOp('and', AND_OP_MX, json, path, idx) break case 'asterisk': handleAsteriskOp(json, path, idx) break case 'divis': handleTwoSameOperands( 'division', toNumberTypeStr, (a,b)=>(a/b), json, path, idx, x=>(Number.isFinite(x)?x:INDETERMINATE) // fix division by zero ) break case 'div': // TODO more sophisticated chgVal(Math.floor(curVal(-1) / curVal())) break case 'mod': // TODO more sophisticated chgVal(curVal(-1) % curVal()) break // TODO || (entity construction) default: throw new Error('Unknown multiplication_like_op ' + multLikeOp.id) } } } const handleBeforeTerm = function(json, path, idx) { const parent = 0<path.length?path[path.length-1]:null // Check if it is a series of OR and the result is already true. if (0 < idx && idx % 2 == 0) { // console.log('check true OR .. :', curVal(), parent[idx-2], parent[idx-1], json) // It is actually very special to decide according to the current value // whether to continue evaluation or not. const addLikeOp = parent[idx-1] if ((addLikeOp.id == 'or' && curVal() == true) == false) { console.log(is(path),'bA', json.length, 'term PUSH') pushNewArr() } else { // Do not push and start skipping all until handleAfterTerm. pushVal(true) // Just repeat the true value. ignore = json console.log(is(path),'bA', json.length, 'term IGNORE START') } } else { console.log(is(path),'bA', json.length, 'term PUSH') pushNewArr() } } const handleAfterTerm = function(json, path, idx) { const parent = 0<path.length?path[path.length-1]:null console.log(is(path),'aA', json.length, idx, 'term POP') popTransfer() // Do the simple_expression calculation already here (especially for 'or'): if (0 < idx && idx % 2 == 0) { // console.log('calc:', parent[idx-2], parent[idx-1], json) const addLikeOp = parent[idx-1] switch (addLikeOp.id) { case 'or': handleLogicalOp('or', OR_OP_MX, json, path, idx) break case 'xor': handleLogicalOp('xor', XOR_OP_MX, json, path, idx) break case 'plus': handlePlusOp(json, path, idx) // arithmetic, string, binary or aggregation break case 'minus': handleMinusOp(json, path, idx) // arithmetic or aggregation break default: throw new Error('Unknown add_like_op ' + addLikeOp.id) } } } const handleAfterQuery = function(json, path, idx) { const parent = path[path.length-1] console.log(is(path),'af', json.t, idx) // Catch up the ignored json.src traversal. traverseSimpleExpression(json.src, afterFct, beforeFct, afterArrFct, beforeArrFct, path) const arr = curVal() // curVal(-1) before the popVal() // Start / continue evaluation for every p of arr. // Start a traverseExpression(json.cond, ..) for every element of arr. if (isIndeterminate(arr) == false) { if (0 < arr.length) { const res = [] for (const value of arr) { const scp = VariableScope(ss[ss.length-1]) ss.push(scp) scp.regVal(json.var, value) // The way scope works, the value of json.var could be changed, but // the way the query impl is this is not possible. traverseExpression(json.cond, afterFct, beforeFct, afterArrFct, beforeArrFct, path) const cond = popVal() // recently added by traverseExpression if (cond == true) { // INDETERMINATE or UNKNOWN is just ignored (like false). res.push(value) } ss.pop() } chgVal(res) // swap original full array with filtered one } // Just keep null or empty array } else { chgVal(INDETERMINATE) } } // Aggregate initializer // The type of the result ist aggregate of generic. const handleAfterAggr = function(json, path, idx) { // Make internal structure array become a data array. const arr = popArr() // It is very exceptional to use that return value. pushVal(arr) // The whole array is the new value. console.log(is(path),'af', json.t, 'POP') } const handleAfterAttrRef = function(json, path, idx) { // In case of self the entity_ref is already replaced by the token. if (curVal() === me.selfValue) { // Same behaviour as var_ref. handleVarRef(json, path, idx) } else { const nttTok = curVal() if (isIndeterminate(nttTok) == false) { // For evaluating that value on the other entity, we do not need to pass // the current scope there as parent. // This could be an explicit attribute, derived or inverse const attrVal = inst.getNttValue(nttTok, json.id) chgVal(attrVal) console.log(is(path),'af', json.t, nttTok, '.' + json.id, '=', attrVal) // That entity instance is not validated itself. Validation does not recur. } else { // Just let indeterminate on the stack. console.log(is(path),'af', json.t, json.id, ' = INDET') } } } const handleAfterIdxQual = function(json, path, idx) { // console.log(curArr()) // The latest stage contains an array followed by an integer index. const arr = curVal(-1) const idxVal = popVal() if (Array.isArray(arr) && Number.isInteger(idxVal)) { // TODO handle the indexing according to the actual type of the aggregation // The indexing of an ARRAY may even be specified to start with a negative index. const value = arr[idxVal-1] // Meaning of index is 1 = first element. console.log(is(path),'af', json.t, idxVal, value) chgVal(value) } else { console.log(is(path),'af', json.t, 'INDET') chgVal(INDETERMINATE) } } const handleAfterEntityRef = function(json, path, idx) { console.log(is(path),'af', json.t, '! TODO !') // console.log(curArr()) // The latest stage most probably contains an object followed by an entity_ref. // TODO // Outside of expressions entity_ref may also occur in other circumstances. // TODO Check if inside a rule there is cofusion between entity_ref and // that arrays of all objects of an entity. } const handleAfterEnumValue = function(json, path, idx) { // console.log(curArr()) // The latest stage does not provide any value to operate on. // The value pushed here only stems from the meta data enum type definition. const factor = path[path.length-2] // Parent is a qualifier array within a 'factor'. // Question is: Does this enum_value appear in quals0 or quals1 of the 'factor'. let typeRef // undefined if (!factor.quals1 || factor.quals0[0] === json) { typeRef = factor.arg0 // Presumed to be a type_ref. } else { // factor.quals1 typeRef = factor.arg1 // Presumed to be a type_ref. } const enumTypeId = typeRef.id const enumValue = json.id // Model already checked that model.hasType(typeId) - an enum - and // that enumValue belongs to it. const enumType = model.typeById(enumTypeId) // console.log(type.spec.t, type.spec.values.map(elt => elt.id).join(',')) pushVal(enumValue, enumType) console.log(is(path),'af', json.t, enumTypeId, enumValue) } const evalToCurrentOrErrors = function() { if (errors.length == 0) { return {result:true, value:curVal()} } else { return {result:false, messages:errors} } } me.evalSimpleExpr = function(json) { traverseSimpleExpression(json, afterFct, beforeFct, afterArrFct, beforeArrFct) return evalToCurrentOrErrors() } me.evalExpr = function(json) { traverseExpression(json, afterFct, beforeFct, afterArrFct, beforeArrFct) return evalToCurrentOrErrors() } setup() return me } // Logical export const TRUE = true export const FALSE = false export const UNKNOWN = null // Setup logical OP matrices. const AND_OP_MX = {} const OR_OP_MX = {} const XOR_OP_MX = {} for (const mx of [AND_OP_MX, OR_OP_MX, XOR_OP_MX]) { mx[true] = {} // true converted to 'true' mx[false] = {} // false converted to 'false' mx[UNKNOWN] = {} // null converted to 'null' } // and (false is strongest, then UNKNOWN, else true) AND_OP_MX[false][false] = false AND_OP_MX[true][false] = false AND_OP_MX[false][true] = false AND_OP_MX[UNKNOWN][false] = false AND_OP_MX[false][UNKNOWN] = false AND_OP_MX[UNKNOWN][UNKNOWN] = UNKNOWN AND_OP_MX[true][UNKNOWN] = UNKNOWN AND_OP_MX[UNKNOWN][true] = UNKNOWN AND_OP_MX[true][true] = true // or (true is strongest, then UNKNOWN, else false) OR_OP_MX[true][true] = true OR_OP_MX[true][false] = true OR_OP_MX[false][true] = true OR_OP_MX[true][UNKNOWN] = true OR_OP_MX[UNKNOWN][true] = true OR_OP_MX[UNKNOWN][UNKNOWN] = UNKNOWN OR_OP_MX[UNKNOWN][false] = UNKNOWN OR_OP_MX[false][UNKNOWN] = UNKNOWN OR_OP_MX[false][false] = false // xor (any UNKNOWN is so, without UNKNOWN its just xor) XOR_OP_MX[true][false] = true XOR_OP_MX[false][true] = true XOR_OP_MX[true][true] = false XOR_OP_MX[false][false] = false XOR_OP_MX[UNKNOWN][UNKNOWN] = UNKNOWN XOR_OP_MX[true][UNKNOWN] = UNKNOWN XOR_OP_MX[UNKNOWN][true] = UNKNOWN XOR_OP_MX[UNKNOWN][false] = UNKNOWN XOR_OP_MX[false][UNKNOWN] = UNKNOWN export const INDETERMINATE = undefined export function isIndeterminate(val) { return val === undefined } export function isUnknown(val) { return val == null }