UNPKG

expexp

Version:

The express model io and express model and data representation.

275 lines (261 loc) 9.23 kB
// ////////////////////////////////////////////// // Syntax JSON format and tree traverse concept // This is a description of the concepts, ideas and guidelines behind // the form and structure of the parsed JSON. // The tree traverse function depends on that structure. So they are // described both together here. // // The traverse function keeps track and notifies in its events the // variables: ii, path and json // - json: Is the current part of the JSON that is in focus at the moment. // Initially this is the whole JSON. // - path: It is the chain of parent elements down to the root (the whole JSON) // When afterFct or beforeFct are called for the current element, // the path does not contain json. // - ii: Stands for identifier or index. It names the context of // the json object: If the parent is semantically an object, the // type of the top element at ii is string, providing the name of // the key. If the parent is semantically an array, the type of // the element is JS number (0 or positive and whole), providing // the index position. // // What does 'is semantically' mean? // Even though the JSON contains certainly arrays, they always are // represented as JS objects. This is to provide also a name for // the type of array. Whether json stands for an array can be checked // using the isArrSem(json) function. If so, there is - besides // the 't' only one more key 's' of type JS array holding the actual // array elements. The point is about having a format for tree unit // test data and at the same time human readable path positions in // the debug output. See the *.tlog files in the /test/units or // test/u* dirs. // // Remarks: // - The length of path and ii is identical. // - Neither json nor path do at any time contain a JS array. // Their content or elements are JS objects with a granted key 't' // of type string. // - The value of 't' unambiguously defines what other keys must // be present. // - Any object with 't':'*_dcl' (but 'sct_dcl') has also a key 'typ':{..} . // - The 't':'typ' as well as any aggregation type has a key 'spc' defining // the base type (of the elements) or being a reference. // - A key 'id' always stands for a JS string but for a JS object // in the following cases: // For 't':'a_atr_ids': // 'id':{t:'atr|qal_atr|qal_atr_rnmd', id:'..', ..} . // For 't':'drv|inv': // 'id':{t:'atr|qal_atr|qal_atr_rnmd', id:'..', ..} . // For 't':'cst_dcl': // 'id':{t:'cst', id:'..', ..} . // For 't':'a_prm_ids': // 'id':{t:'prm', id:'..', ..} . // For 't':'a_lcl_ids': // 'id':{t:'lcl', id:'..', ..} . // - The 'id' key of an operation {'t':'o_*'} is always a string. // - A key '*[Rr]ef' always stands for a JS object with {t:'*_ref', id:'..'} . // - A key '*[Rr]efs' always stands for a JS array containing JS // objects with {t:'*_ref', id:'..'} . function traverseContent(ii, path, json, afterFct, beforeFct, keyOrder) { if (Array.isArray(json)) { // Simple and plane iteration without any event notification. for (let i=0; i<json.length; i++) { ii.push(i) traverseContent(ii, path, json[i], afterFct, beforeFct, keyOrder) ii.pop() // i } } else { // json is object: {t:'..', ..} beforeFct(json, path, ii) path.push(json) // But we have to check for our special array-objects. if (isArrSem(json) == false) { // Establish the defined key order for iterating objects. const keys = Object.keys(json).filter(key => !IGNORE_KEY[key]) if (1 < keys.length) { // Initially the order was thought to be most-important-first sorted. // But to improve performance the whole thing is set up reverted. // We better sort only this short (even filtered) array instead // of the lengthy order array later. keys.sort() // alphanumerically if (Array.isArray(keyOrder) && 0 < keyOrder.length) { keys.reverse() // The reverted sorting causes order.length-i later. const order = keys.concat(keyOrder) // const order = DEFAULT_OBJECT_KEY_TRAVERSE_ORDER.concat(keys) // order.reverse() const key2idx = {} order.forEach((elt, i) => { key2idx[elt] = order.length-i }) // Index needs not be exact (missing -1). // A key, that is in the json as well as in the special array, // gets the lower index of the special array. keys.sort((a, b) => key2idx[a]-key2idx[b]) } } for (const key of keys) { const value = json[key] if (value !== null && typeof value == 'object' && Array.isArray(value) == false) { // Per design we do not have arrays here. ii.push(key) traverseContent(ii, path, value, afterFct, beforeFct, keyOrder) ii.pop() // key } } } else { // The special array-object is the event arg, and // already triggered here, while traverseContent(..) // only handles the true array (value.s). traverseContent(ii, path, json.s, afterFct, beforeFct, keyOrder) } path.pop() // json afterFct(json, path, ii) } } const II_ROOT = '≡' const IGNORE_KEY = {'p':true, 't':true} const DEFAULT_OBJECT_KEY_TRAVERSE_ORDER = [ // For most cases the order is a nice to have, but // for fct, xpr, itv { < < } and qry( <* | ): order src-var-cnd // the order fits op stack architecture and is functionally essential. 'arg0', 'qals0', 'arg', 'arg1', 'qals1', 'op', 'id1', // a compare relation op id 'arg2', 'id2', // a compare relation op id // Unfortunately there are some technical dependencies introduced // by the fact, that all must fit into one 'flat' order. // Through cnd(qry) also involved are: ntt and sct_dcl // Through whrs(ntt) also involved are: typ and rul // Through csts, lcls, ctts (rul) also: fun and prc // Through nttRef also involved are: inv, qal_atr, qal_atr_rnmd // Through ref(qal_atr_rnmd) also: ref_rnmd, enm_xtdd, slc_xtdd // Through refs(slc_xtdd) also: slc, use, ref, scm // Through xtd(enm_xtdd, slc_xtdd) also: enm, slc // Through vals(enm, enm_xtdd) also: agi, bif, coc 'src', // qry 'var', // qry, als, rpt 'id', // ntt, sct_dcl, .. and really many more, but good, this always comes first. 'spc', // typ, and really many more, but good, this comes after 'id' . 'xtd', // enm, enm_xtdd, slc, slc_xtdd 'nttOnly', // slc, slc_xtdd 'agrTyp', // inv 'agrBds', // inv 'nttRef', // sct_dcl, inv, qal_atr, qal_atr_rnmd 'ref', // qal_atr_rnmd, ref_rnmd, enm_xtdd, slc_xtdd, asg, als 'refs', // slc, slc_xtdd, use, t:ref, scm 'qals', // asg, als 'nttRefs', // rul 'invRef', // inv 'vals', // enm, enm_xtdd, agi, bif, coc 'abs', // ntt, sct_dcl 'ttlNttRefs', // sct_dcl 'cnd', // qry, ntt, sct_dcl, iff 'sprs', // ntt 'atrs', // ntt 'drvs', // ntt 'invs', // ntt 'unqs', // ntt 'itr', // rpt 'whl', // rpt 'utl', // rpt 'prms', // fun, prc 'res', // fun 'ctts', // rul, fun, prc (algorithm_head order) 'csts', // rul, scm, fun, prc (algorithm_head order) 'lcls', // rul, fun, prc (algorithm_head order) 'stms', // fun, prc, rul, cpd, als, rpt, iff 'whrs', // ntt, typ, rul 'elss', // iff 'slr', // cas 'caas', // cas 'oth', // cas 'xprs', // caa 'stm', // caa 'fr', // bds, itr 'to', // bds, itr 'by', // itr 'scts' // scm ] const HUMAN_READ_ORDER = ['t'].concat(DEFAULT_OBJECT_KEY_TRAVERSE_ORDER) const HUMAN_READ_IDX = {} HUMAN_READ_ORDER.forEach(key => { HUMAN_READ_IDX[key] = HUMAN_READ_ORDER.indexOf(key) + 1 // 0 is not good }) const HUMAN_READ_ORDER_FCT = function(a, b) { const aIdx = HUMAN_READ_IDX[a] // might be undefined const bIdx = HUMAN_READ_IDX[b] // might be undefined if (aIdx && bIdx) { return aIdx - bIdx } else if (!aIdx && !bIdx) { return a.localeCompare(b) // alphanumerical } else if (aIdx && !bIdx) { return -1 } else if (!aIdx && bIdx) { return 1 } } const noOp = function(){} function traverseExternal(json, afterFct, beforeFct=noOp, keyOrder=DEFAULT_OBJECT_KEY_TRAVERSE_ORDER, path=[], ii=[II_ROOT]) { if (!json) return if (Array.isArray(keyOrder)) { keyOrder = keyOrder.slice() // copy keyOrder.reverse() } traverseContent(ii, path, json, afterFct, beforeFct, keyOrder) } function isArrSem(json) { return json && json.t && json.t.startsWith('a_') } function path2str(ii, path, json) { let fullPath = path if (json) { fullPath = path.slice() fullPath.push(json) } const strs = fullPath.map(function(elt, i, arr){ let lbl = ii[i] if (Number.isInteger(lbl)) { const arrObj = arr[i-1] const pArrLenStrLength = ` ${arrObj.s.length}`.length lbl = String(lbl).padStart(pArrLenStrLength, ' ') lbl = `[${arrObj.t}${lbl}]` } else { lbl = '.' + lbl } let eltStr // undefined if (isArrSem(elt)) { if (i < fullPath.length-1) { eltStr = '' } else { // At the top. if (0 < elt.s.length) { eltStr = `[${elt.t}:${elt.s.length}]` } else { eltStr = `[]` } } } else { eltStr = elt.t if (elt.id) { eltStr += ' ' + elt.id } eltStr = `{${eltStr}}` } return lbl + eltStr }) return strs.join('').substr(1) } function DEFAULT_ORDER() { return DEFAULT_OBJECT_KEY_TRAVERSE_ORDER.slice() } function ALPHANUM_ORDER() { return [] } export { DEFAULT_ORDER, ALPHANUM_ORDER, HUMAN_READ_ORDER_FCT, traverseExternal as traverse, isArrSem, path2str }