UNPKG

expexp

Version:

The express model io and express model and data representation.

679 lines (631 loc) 22.8 kB
import {traverse} from './tree.js' export function Model(rawJson, trust = false) { const me = {} // Operations on the model would go here. const name2Id = {} const tpId2Tp = {} const nttId2Ntt = {} const funcId2Func = {} const procId2Proc = {} const rlId2Rl = {} me.schemaId = function() { return rawJson.id } me.ALL_LEVELS = -999999 me.entities = function(lvl=me.ALL_LEVELS, noAbs = false) { if (lvl == me.ALL_LEVELS && noAbs == false) { return Object.values(nttId2Ntt) } // TODO for 0 get all root (in hierarchy), for 1 only childs of first level, etc. // TODO for -1 get all children without descendents, for -2 the ones that have no grandchildren, etc. } me.types = function(lvl=me.ALL_LEVELS) { if (lvl == me.ALL_LEVELS) { return Object.values(tpId2Tp) } // if (lvl == me.ALL_LEVELS) { // // TODO return array of all // } // TODO for 0 get all root (in hierarchy), for 1 only childs of first level, etc. // TODO for -1 get all children without descendents, for -2 the ones that have no grandchildren, etc. } me.hasType = function(name) { const id = name2Id[name] return id in tpId2Tp } me.hasEntity = function(name) { const id = name2Id[name] return id in nttId2Ntt } me.hasFunction = function(name) { const id = name2Id[name] return id in funcId2Func } me.typeById = function(name) { const id = name2Id[name] return tpId2Tp[id] } me.entityById = function(name) { const id = name2Id[name] return nttId2Ntt[id] } me.functionById = function(name) { const id = name2Id[name] return funcId2Func[id] } me.procById = function(name) { const id = name2Id[name] return procId2Proc[id] } me.ruleById = function(name) { const id = name2Id[name] return rlId2Rl[id] } me.typeIdChainById = function(name) { const id = name2Id[name] return typeSpecChainById(id).map(tp => tp.id) } me.entityIdChainById = function(name) { const id = name2Id[name] return nttSuperChainById(id).map(ntt => ntt.id) } // It returns: // [ {ctxId:'', id:'', type:{t:'',..}, opt:true/false}, .. ] // By the way: At least one of the returned params has opt:true . me.procParamListById = function(name) { const id = name2Id[name] // TODO } // It returns: // [ {ctxId:'', id:'', type:{t:'',..}, var:true/false}, .. ] me.funcParamListById = function(name) { const id = name2Id[name] const func = funcId2Func[id] const res = [] if (func.prms == null) return for (const rawElt of func.prms.s) { for (const eltIdObj of rawElt.ids.s) { res.push({ ctxId: id, // function id id: eltIdObj.id, type: rawElt.typ, var: false // TODO later for proc this can also be true // There is no opt nor omit attribute. }) } } return res } // The result of this function is a flattened list of all attributes with // the additional info about what entity in the hierarchy it belongs to. // It returns: // [ {ctxId:'', id:'', type:{t:'',..}, opt:true/false}, .. ] me.fullAttrListById = function(name) { const id = name2Id[name] return fullAttrListById(id) } // Resolve all possible types or entities for a type reference. // Select types are resolved (recursively if necessary). // The ref of: // {t:'type_ref', id:'..'} // Returns for example: // {'NttPerson':'entity', 'NttOrg':'entity', 'TpLabel':'type', ..} me.possibleTypesOfRef = function(ref) { const res = {} resolveType(res, ref) return res } me.nttEvalOrder = function(name) { const id = name2Id[name] return nttEvalOrder(id) // TODO cache result? } me.funcEvalOrder = function(name) { const id = name2Id[name] return funcEvalOrder(id) // TODO cache result? } me.procEvalOrder = function(name) { const id = name2Id[name] return procEvalOrder(id) // TODO cache result? } // Resolve all possible types or entities for a select type. // Select types are resolved (recursively if necessary). // Inheritence of entities and types is ignored. Also of types because their // super types normally have less constraints. // Finally possibilities looks like that for example: // {'NttPerson':'entity', 'NttOrg':'entity', 'TpLabel':'type', ..} // me.possibleTypesOfSelect = function(selectId) { // if (selectId in tpId2Tp) { // const type = tpId2Tp[selectId] // if (type.spec.t == 'select'){ // const res = {} // for (let subRef of type.spec.refs) { // resolveType(res, subRef) // recur // } // return res // } // } // throw new Error(`The id "${selectId}" is not of a select type.`) // } // The role name like 'SCHEMA_NAME.ENTITY_NAME.ATTR_NAME'. // Returns {scmId: .., nttId: .., attrId: ..} or undefined. me.resolveRoleName = function(roleName) { if (typeof roleName == 'string') { const prts = roleName.split('.') const scIdUp = rawJson.id.toUpperCase() if (prts.length == 3 && prts[0] == scIdUp) { // console.log('prts', prts) const eltName = scIdUp + '.' + prts[1] const eltId = name2Id[eltName] if (eltId && nttId2Ntt.hasOwnProperty(eltId)) { // console.log('eltId', eltId) const attrName = prts[2] const attrs = fullAttrListById(eltId) // Is an entity id. const res = attrs.find(elt => (elt.id.toUpperCase() == attrName)) // console.log(attrName, attrs.length, res) if (res) { // console.log(rawJson.id, eltId, res.id) return {scmId: rawJson.id, nttId: eltId, attrId: res.id} } } } } } const setup = function() { if (trust == false) { // TODO validate the rawJson (from the parser). // But not as long as we only use validated input files. // - uniqueness of various ids // - referenced ids exist // - ranges are sensible (only static ones can be checked) // - inverse redundances // - circular declarations of select / subtype // - default values of locals are valid if enum type declared // - In expressions enum values are valid according to scope. // - } if (rawJson.scts == null) return for (let e of rawJson.scts.s) { switch(e.t) { case 'typ': registerName(tpId2Tp, e) break case 'ntt': registerName(nttId2Ntt, e) break case 'sct_dcl': registerName({}, e) // TODO not {} break case 'fun': registerName(funcId2Func, e) break case 'prc': registerName(procId2Proc, e) break case 'rul': registerName(rlId2Rl, e) break default: throw new Error(`Unknown model element of type ${e.t}.`) } } } const registerName = function(idToElt, elt) { const scIdUp = rawJson.id.toUpperCase() const name = scIdUp + '.' + elt.id.toUpperCase() if (name2Id.hasOwnProperty(name) == false) { name2Id[name] = elt.id idToElt[elt.id] = elt } else { throw new Error(`The element ${elt.id} maps to name ${name}, which is already registered.`) } } // Resolve all possible types or entities for a type reference. // Select types are resolved (recursively if necessary). // Inheritence of entities and types is ignored. Also of types because their // super types normally have less constraints. // The ref of: // {t:'type_ref', id:'..'} // Finally possibilities looks like that for example: // {'NttPerson':'entity', 'NttOrg':'entity', 'TpLabel':'type', ..} const resolveType = function(possibilities, ref) { if (ref.id in nttId2Ntt) { possibilities[ref.id] = 'entity' } else if (ref.id in tpId2Tp) { const type = tpId2Tp[ref.id] if (type.spc.t != 'slc') { // a select // final candidate possibilities[ref.id] = 'type' // if (type.spec.t != 'type_ref') { // // final candidate (E.g: type TpInt of INTEGER, or ENUM, or aggregation) // possibilities[ref.id] = 'type' // } else { // type.spec.t == 'type_ref' (E.g. TpCompass in the test data) // resolveType(possibilities, type.spec) // } } else { // type.spc.t == 'slc' for (let subRef of type.spc.refs.s) { resolveType(possibilities, subRef) // recur } } } else { throw new Error(`Type reference to ${ref.id} cannot be resolved.`) } } // The result of this function is a flattened list of all attributes with // the additional info about what entity in the hierarchy it belongs to. // It returns: // [ {ctxId:'', id:'', type:{t:'',..}, opt:true/false}, .. ] const fullAttrListById = function(id) { // The raw model has per attribute several ids possible and there is no // hint per attribute what enitity in the hierarchy it belongs to. const inheritence = nttSuperChainById(id) const attrs = [] for (let ntt of inheritence) { const nttId = ntt.id if (!!ntt.atrs) { for (let rawAttr of ntt.atrs.s) { for (let attrIdObj of rawAttr.ids.s) { attrs.push({ ctxId: nttId, id: attrIdObj.id, type: rawAttr.typ, omit: false, opt: rawAttr.opt }) } } } // Use the qual_attr within the ntt.derives to determine the omitted boolean on attr to be true. if (ntt.drvs) { for (let drv of ntt.drvs.s) { if (drv.id.t == 'qal_atr') { // Search within attrs and set omit true. for (let attr of attrs) { if (attr.ctxId == drv.id.nttRef.id && attr.id == drv.id.id) { attr.omit = true } } } } } //attrs.push(...ntt.attrs) is too simple } return attrs } const nttSuperChainById = function(id) { let cur = nttId2Ntt[id] const res = [cur] while (!!cur.sprs && cur.sprs.s[0].t == 'ntt_ref') { // super const spr = cur.sprs.s[0] if (spr.id in nttId2Ntt) { cur = nttId2Ntt[spr.id] res.push(cur) } else { throw new Error(`When resolving super chain for entity ${id} on level ${cur.id}, the type ref ${spr.id} does not exist.`) } } res.reverse() return res } const typeSpecChainById = function(id) { let cur = tpId2Tp[id] const res = [cur] while (cur.spc.t == 'any_ref') { if (cur.spc.id in tpId2Tp) { cur = tpId2Tp[cur.spc.id] res.push(cur) } else { throw new Error(`When resolving spec chain for type ${id} on level ${cur.id}, the type ref ${cur.spc.id} does not exist.`) } } res.reverse() return res } // This function provides the order (at least one of several possible) in // which the elements of a function can be evaluated savely, that means avoiding // the situation that part of the expression is not yet defined (but could // be so later). // The order respects these different kind of elements from the function: // - param definitions not consisting of aggregation or of aggregations with only static bounds // - local definitions not consisting of aggregation or of aggregations with only static bounds // - param definitions consisting of aggregations with dynamic bounds // - local definitions consisting of aggregations with dynamic bounds // - param value expressions // - local value expressions // The functions return value initialisation (bound spec with var_ref) is // excluded from that order, and presumed to be the very last to be // evaluated and calculated only at the very moment of return. // The basis for the evaluation order is the values given by parameters that // are not aggregations or aggregations with constant/static values for the // bounds. const funcEvalOrder = function(id) { const func = funcId2Func[id] const nameSpace = [] if (func.prms != null) { nameSpace.push.apply(nameSpace, func.prms.s) } if (func.lcls != null) { nameSpace.push.apply(nameSpace, func.lcls.s) } const circleMessageFct = function(eltsWithCircles, idsWithCirclesStr){ return `The ${func.id}'s parameters and locals most probably form at least one circular dependency including ${idsWithCirclesStr}.'` } // Pass func as additional element to evaluate also func.result's dependencies. return evalOrderOfNameSpace(nameSpace, circleMessageFct, [func]) } // This function provides the order (at least one of several possible) in // which the elements of a procedure can be evaluated savely, that means avoiding // the situation that part of the expression is not yet defined (but could // be so later). // The order respects these different kind of elements from the procedure: // - param definitions not consisting of aggregation or of aggregations with only static bounds // - local definitions not consisting of aggregation or of aggregations with only static bounds // - param definitions consisting of aggregations with dynamic bounds // - local definitions consisting of aggregations with dynamic bounds // - param value expressions // - local value expressions // The basis for the evaluation order is the values given by parameters that // are not aggregations or aggregations with constant/static values for the // bounds. const procEvalOrder = function(id) { const proc = procId2Proc[id] const nameSpace = [] if (proc.prms != null) { nameSpace.push.apply(nameSpace, proc.prms.s) } if (proc.lcls != null) { nameSpace.push.apply(nameSpace, proc.lcls.s) } const circleMessageFct = function(eltsWithCircles, idsWithCirclesStr){ return `The ${proc.id}'s parameters and locals most probably form at least one circular dependency including ${idsWithCirclesStr}.'` } return evalOrderOfNameSpace(nameSpace, circleMessageFct) // no additional elements } // This function provides the order (at least one of several possible) in // which the elements of an entity can be evaluated savely, that means avoiding // the situation that part of the expression is not yet defined (but could // be so later). // The order respects these different kind of elements from the entity: // - attribute definitions not consisting of aggregation or of aggregations with only static bounds // - derived definitions not consisting of aggregation or of aggregations with only static bounds // - attribute definitions consisting of aggregations with dynamic bounds // - derived definitions consisting of aggregations with dynamic bounds // - derived value expressions // - inverse value expressions // - expressions within where constraints // The basis for the evaluation order is the values given by attributes that // are not aggregations or aggregations with constant/static values for the // bounds. const nttEvalOrder = function(id) { const inheritence = nttSuperChainById(id) // The inheritence is starting with the root entity first. // The use of me.fullAttrListById here would not be appropriate, since // derives and the others would be missing. const nameSpace = [] const wheres = [] for (const ntt of inheritence) { if (ntt.atrs != null) { nameSpace.push.apply(nameSpace, ntt.atrs.s) } if (ntt.drvs != null) { nameSpace.push.apply(nameSpace, ntt.drvs.s) } if (ntt.invs != null) { nameSpace.push.apply(nameSpace, ntt.invs.s) } if (ntt.whrs != null) { wheres.push.apply(wheres, ntt.whrs.s) } } const circleMessageFct = function(eltsWithCircles, idsWithCirclesStr){ let ntt = nttId2Ntt[id] return `The ${ntt.id}'s attributes, derives and inverse attributes most probably form at least one circular dependency including ${idsWithCirclesStr}. (Inheritence might veil the reason.)'` } return evalOrderOfNameSpace(nameSpace, circleMessageFct, wheres) } const evalOrderOfNameSpace = function(nameSpace, circleMessageFct, additionalElts = []) { const idToElt = setupId2Elt(nameSpace) // Eventual renaming and shadowing is handled. const elts = [] // actual elements const eltSet = new WeakMap() // Just a temp variable for uniqueness. for (const elt of Object.values(idToElt)) { if (eltSet.has(elt) == false) { elts.push(elt) eltSet.set(elt, true) } } elts.push.apply(elts, additionalElts) // Sorting is not necessary, but having a deterministic result helps write tests. elts.sort((a, b)=>idsOfElt(a)[0].localeCompare(idsOfElt(b)[0])) // We cannot use WeakMap bacause later we need to know the number of elements. // const deps = elts.map(ns => depsOfElt(ns, idToElt).reduce( // (acc, elt) => {return acc.set(elt, true)}, new WeakMap() // )) const deps = elts.map(ns => depsOfElt(ns, idToElt)) // Keep the refernce to the dependencies for the end to also return them. // Since each change to the elements of this array by filter later creates // a new array this reference to the full dependencies remains valid. const fullDeps = [] fullDeps.push.apply(fullDeps, deps) // for (let i=0;i<elts.length;i++) { // const elt = elts[i] // const dep = deps[i] // console.log(elt.id, elt.ids) // console.log('depends on:') // dep.forEach(function(delt){console.log(delt)}) // console.log('') // } // Resolve on elts and deps (the ones first that have no dependencies). const res = [] const resIdxs = [] const out = new WeakMap() let outCount = 0 while (outCount < elts.length) { const wave = [] const waveIdxs = [] // Setup current wave. for (let i=0;i<elts.length;i++) { const elt = elts[i] if (out.has(elt) == false) { // Ignore the ones already treated. const dep = deps[i] // an array of elements if (dep.length == 0) { // element without dependency wave.push(elt) waveIdxs.push(i) } } } // In all dependencies remove the ones, that have no dependencies left. for (let i=0;i<elts.length;i++) { const elt = elts[i] if (out.has(elt) == false) { // Ignore the ones already treated. deps[i] = deps[i].filter(welt=>wave.indexOf(welt)==-1) } } // Mark wave as out (and count its elements). for (const elt of wave) { out.set(elt, true) // object is key outCount++ } // Handle pathological case of empty wave. if (wave.length == 0) { // The remaining elements may have circular dependencies. const eltsWithCircles = elts.filter(elt=>out.has(elt)==false) const idsStrFct = function(elt) { return idsOfElt(elt).join(', ') } const idsWithCirclesStr = eltsWithCircles.map(idsStrFct).join(', ') return {result: false, message:circleMessageFct(eltsWithCircles, idsWithCirclesStr)} } res.push.apply(res, wave) resIdxs.push.apply(resIdxs, waveIdxs) } const directDeps = resIdxs.map(idx => fullDeps[idx]) const orderedDeps = directDeps.map(dep => collectDeps(elts, fullDeps, res, dep)) return {result:true, elements:res, dependencies:orderedDeps} } const collectDeps = function(elts, deps, order, base) { const already = new WeakMap() base.forEach(function(elt){already.set(elt, true)}) let next = base while (0 < next.length) { const cur = [] for (const elt of next) { const i = elts.indexOf(elt) const dep = deps[i] for (const dd of dep) { if (already.has(dd) == false) { cur.push(dd) already.set(dd, true) } } } next = cur } return order.filter(elt => already.has(elt)) } const idsOfElt = function(elt) { const ids = [] withIdsOfElt(elt, function(id){ids.push(id)}) return ids } const withIdsOfElt = function(elt, visitFct) { if (!!elt.ids) { // attributes, locals and parameters elt.ids.s.forEach(function(idObj){ visitFct(idObj.id, elt, idObj) }) } else { // derives, inverse attributes and entities if (typeof elt.id === 'object') { // elt.id.t is attr_id or qual_attr (for both derives and inverse) visitFct(elt.id.id, elt, elt.id) } else { // typeof ns.id === 'string' and elt is entity visitFct(elt.id, elt, null) } } } // Setup nameSpace lookup. // The nameSpace is an array of attributes, derives, inverse attributes, // parameters, locals or even entities (in case of rules.for). // The order of the elements in nameSpace matters as soon as there is the // same id twice or multiple time in the array. This may happen due to // inheritence of entities with redeclared_attribute or a derive shadowing // a super's attribute or derive. const setupId2Elt = function(nameSpace) { // A single id is either an attr_id or qual_attr (both having id as the // field to pick). But for entity id contains directly the id string. const res = {} for (const elt of nameSpace) { withIdsOfElt(elt, function(id, base){ res[id] = base }) } return res } // Get all dependencies of either a types where conditions, // an entities attribute, derived attribute, inverse attribute or where condition // or of a functions or procedures parameter or local variable declaration, // or of a rules where condition. // To get a functions return value's dependencies, pass the function as element. // Most important this function returns also dependencies arising from any // simple_expression used within aggregation bounds (of any level) within // the type declaration. const depsOfElt = function(elt, idToElt) { const depIdToElt = {} switch (elt.t) { case 'atr_dcl': depsOfType(elt.typ, idToElt, depIdToElt) // Here only deps are possible if elt.typ is aggregation // and in the bounds there are references. This is very rare. break case 'drv': depsOfType(elt.typ, idToElt, depIdToElt) depsOfExpr(elt.xpr, idToElt, depIdToElt) break case 'inv': // We actually never really have complicated expressions in there, but // just literal numbers or not set. depsOfBounds(elt.agrBds, idToElt, depIdToElt) break case 'whr': depsOfExpr(elt.xpr, idToElt, depIdToElt) break case 'prm_dcl': depsOfType(elt.typ, idToElt, depIdToElt) break case 'lcl_dcl': depsOfType(elt.typ, idToElt, depIdToElt) depsOfExpr(elt.xpr, idToElt, depIdToElt) break case 'fun': // stands for its return value depsOfType(elt.res, idToElt, depIdToElt) break default: throw new Error(`Unknown type ${elt.t} to extract dependencies from.`) } return Object.values(depIdToElt) } const depsOfBounds = function(bounds, idToElt, depIdToElt) { traverse(bounds, function(json, path, ii) { if (json.t == 'any_ref' && idToElt[json.id]) { depIdToElt[json.id] = idToElt[json.id] } }) } // The only way dependencies arise from a type declaration is because of // possible one or many bound_spec in there. const depsOfType = function(type, idToElt, depIdToElt) { traverse(type, function(json, path, ii) { if (json.t == 'any_ref' && idToElt[json.id]) { depIdToElt[json.id] = idToElt[json.id] } }) } // Dependencies must be determined either for an expression or most often // for a simple_expression. Thus expr can be either or. const depsOfExpr = function(expr, idToElt, depIdToElt) { traverse(expr, function(json, path, ii) { if (json.t == 'any_ref' && idToElt[json.id]) { depIdToElt[json.id] = idToElt[json.id] } }) } setup() return me }