UNPKG

theprogrammablemind

Version:

1,650 lines (1,503 loc) • 103 kB
// lookup = (name) => returns <config> const { Semantics, normalizeGenerator } = require('./semantics') const { Generators } = require('./generators') const { v4: uuidv4 } = require('uuid') const configHelpers = require('./configHelpers') const DigraphInternal = require('./digraph_internal') const helpers = require('./helpers') const { InitCalls } = require('./helpers') const { ecatch } = require('./helpers') const runtime = require('../runtime') const _ = require('lodash') const db = require('./debug') const debugBreak = () => { // debugger } const bags = [ 'generators', 'semantics' ] const indent = (string, indent) => { return string.replace(/^/gm, ' '.repeat(indent)) } const config_toServer = (config) => { // cant change things because copy breaks something } const updateHierarchy = (config) => { config.hierarchy = new DigraphInternal(config.config.hierarchy) } const initWords = (words) => { if (!words.literals) { words.literals = {} } if (!words.patterns) { words.patterns = [] } if (!words.hierarchy) { words.hierarchy = [] } return words } const debugPriority = (priority) => { if (global.pauseDebugging) { return } if (global.entodictonDebugPriority) { if (helpers.subPriority(entodictonDebugPriority, priority)) { // debug hierarchy hit debugger // eslint-disable-line no-debugger } } } const debugAssociation = (association) => { if (global.pauseDebugging) { return } if (global.entodictonDebugAssociation) { if (helpers.safeEquals(global.entodictonDebugAssociation, association)) { // debug association hit debugger // eslint-disable-line no-debugger } } } const debugWord = (word) => { if (global.pauseDebugging) { return } if (global.entodictonDebugWord) { if (helpers.safeEquals(global.entodictonDebugWord, word)) { // debug word hit debugger // eslint-disable-line no-debugger } } } const debugHierarchy = (pair) => { if (global.pauseDebugging) { return } if (global.entodictonDebugHierarchy) { if (helpers.safeEquals(global.entodictonDebugHierarchy, pair)) { // debug hierarchy hit debugger // eslint-disable-line no-debugger } } } const debugBridge = (bridge) => { if (global.pauseDebugging) { return } if (global.entodictonDebugBridge) { if (global.entodictonDebugBridge == bridge.id) { // debug hierarchy hit debugger // eslint-disable-line no-debugger } } } const debugOperator = (operator) => { if (global.pauseDebugging) { return } if (global.entodictonDebugOperator) { if ((operator.pattern || operator) === global.entodictonDebugOperator) { // debug operator hit debugger // eslint-disable-line no-debugger } } } const debugConfigProps = (config) => { if (global.pauseDebugging) { return } if (!config) { return } const checkProps = [ { property: 'priorities', check: (v) => debugPriority(v) }, { property: 'associations', check: (v) => debugAssociation(v) }, // TODO implement debugWords { property: 'words', check: (v) => debugWords(v) }, { property: 'hierarchy', check: (v) => debugHierarchy(v) }, { property: 'operators', check: (v) => debugOperator(v) }, { property: 'bridges', check: (v) => debugBridge(v) } ] for (const { property, check } of checkProps) { if (property == 'associations') { if (config[property]) { if (config[property].negative) { for (const value of config[property].negative) { check(value) } } if (config[property].positive) { for (const value of config[property].positive) { check(value) } } } } else if (property == 'words') { /* if (config[property]) { for (const value of config[property].literals) { check(value) } } */ } else if (config[property]) { for (const value of config[property]) { check(value) } } } } const validConfigProps = (config) => { const valid = [ 'hierarchy', 'objects', 'bridges', 'operators', 'words', 'priorities', 'associations', 'name', 'version', 'generatorp', 'generators', 'semantics', 'where', 'floaters', 'debug', // TODO Fix these from the test app 'implicits', 'convolution', 'expected_generated', 'expected_results', 'skipSemantics', 'description', 'contexts', 'utterances', 'flatten', 'namespaces', 'eqClasses' ] helpers.validProps(valid, config, 'config') // TODO add more type checks if (config.associations) { if (!config.associations.positive && !config.associations.negative) { throw new Error('Expected the \'associations\' property to be a hash with \'positive\' and or \'negative\' properties') } } } const setupInitializerFNArgs = (config, args) => { const aw = (word, def) => config.addWord(word, def, args.uuid) const ap = (pattern, def) => config.addPattern(pattern, def, args.uuid) const ag = (generator) => config.addGenerator(generator, args.uuid, config.name) const km = (name) => config.getConfig(name) const apis = (name) => config.getConfig(name).api return { ...args, addWord: aw, addPattern: ap, addGenerator: ag, config: config.getPseudoConfig(args.uuid, args.currentConfig), km, baseConfig: config, apis } } const operatorKey_valid = (key) => { if ( !_.isArray(key) || key.length != 2 || !_.isString(key[0]) || !_.isInteger(key[1]) || key[1] < 0 ) { let details = '' if (!_.isArray(key)) { details = 'Expected an array.' } else if (key.length != 2) { details = 'Expected an array of length two.' } else if (!_.isString(key[0])) { details = 'Expected element zero to be a string that is an operator id.' } else if (!_.isInteger(key[1])) { details = 'Expected element one to be a number that is an operator level.' } else if (key[1] < 0) { details = 'Expected element one to be a number that is an operator level which is greater than zero.' } throw new Error(`${JSON.stringify(key)} is not a valid operator key. Values are of the form [<operatorId>, <operatorLevel>]. ${details}`) } } const elist = (list, check, prefix) => { for ([index, element] of list.entries()) { try { check(element) } catch (e) { throw new Error(prefix(index, e)) } } } const priorities_valid = (cps) => { elist(cps, (cp) => priority_valid(cp), (index, e) => `priorities has an invalid priority at position ${index}. ${e}`) } const priority_valid = (cp) => { if (!cp.context) { throw new Error(`The priority ${JSON.stringify(cp)} is missing the "context" property. That is a list of the operator keys that are to be prioritized differently.`) } if (!_.isArray(cp.context)) { throw new Error(`The priority ${JSON.stringify(cp)} has an invalid "context" value. That is a list of the operator keys that are to be prioritized differently.`) } elist(cp.context, (element) => operatorKey_valid(element), (index, e) => `The priority ${JSON.stringify(cp)} has an invalid operator key at position ${index}. ${e}`) if (!_.isArray(cp.choose)) { throw new Error(`The priority ${JSON.stringify(cp)} has an invalid "choose" value. The value should be a list of the operators in the context to consider for prioritization.`) } elist(cp.choose, (element) => { if (!element && element !== 0) { throw new Error('The value should be an index into the "context" property of the operator that is to be considered for prioritization.') } if (!_.isInteger(element) || element < 0 || element >= cp.context.length) { throw new Error(`The value should be an index into the "context" property of the operator that is to be considered for prioritization. Valid values are between 0 and ${cp.context.length - 1}.`) } }, (index, e) => `The choose property in the priority ${JSON.stringify(cp)} has an invalid index at position ${index}. ${e}` ) } const handleBridgeProps = (config, bridge, { addFirst, uuid } = {}) => { ecatch(`While processing the bridge for ${bridge.id}#${bridge.level}`, () => { if (bridge.development && config.isModule) { return } if (false && !bridge.bridge) { bridge.bridge = '{ ...next(operator) }' } if (!bridge.level) { bridge.level = 0 } if (bridge.children) { for (const child of bridge.children) { if (child.child) { config.addHierarchy(child.child, bridge.id, child.instance) } else { config.addHierarchy(child, bridge.id) } } } if (bridge.operator) { config.addOperator(bridge.operator, uuid || config.uuid) } if (bridge.parents) { for (const parent of bridge.parents) { if (parent.parent) { config.addHierarchy(bridge.id, parent.parent, parent.instance) } else { config.addHierarchy(bridge.id, parent) } } } if (bridge.isA) { for (const parent of bridge.isA) { if (parent.parent) { config.addHierarchy(bridge.id, parent.parent, parent.instance) } else { config.addHierarchy(bridge.id, parent) } } } if (bridge.before) { for (let after of bridge.before) { if (typeof after === 'string') { after = [after, 0] } config.addPriority({ context: [[bridge.id, bridge.level], after], choose: [0] }) } } if (bridge.after) { for (let before of bridge.after) { if (typeof before === 'string') { before = [before, 0] } config.addPriority({ context: [before, [bridge.id, bridge.level]], choose: [0] }) } } if (bridge.words) { for (let def of bridge.words) { if (typeof def === 'string') { config.addWordInternal(def, { id: bridge.id, initial: `{ value: "${bridge.id}"}` }) } else { const word = def.word def = { initial: JSON.stringify(def), id: bridge.id, word } config.addWordInternal(word, def) } } } /* if (bridge.generator) { if (addFirst) { config.config.generators.unshift(bridge.generator) } else { config.config.generators.push(bridge.generator) } } */ const addUUID = (obj) => { return { ...obj, uuid: uuid || config.uuid } } if (bridge.generators) { const generators = [...bridge.generators] generators.reverse() for (const generator of generators) { if (addFirst) { config.config.generators.unshift(addUUID(generator)) } else { config.config.generators.push(addUUID(generator)) } } } if (bridge.generatorpr) { bridge.generatorp = bridge.generatorpr bridge.generatorr = bridge.generatorpr } if (bridge.generatorp) { const match = bridge.generatorp.match || (() => true) const apply = typeof bridge.generatorp === 'function' ? bridge.generatorp : bridge.generatorp.apply || bridge.generatorp const level = bridge.generatorp.level >= 0 ? bridge.generatorp.level : bridge.level + 1 const generator = { where: bridge.generatorp.where || bridge.where || helpers.where(4), match: async (args) => bridge.id == args.context.marker && args.context.level == level && args.context.paraphrase && await match(args), apply: (args) => apply(args), applyWrapped: apply, property: 'generatorp' } if (addFirst) { config.config.generators.unshift(addUUID(generator)) } else { config.config.generators.push(addUUID(generator)) } } if (bridge.generatorr) { const match = bridge.generatorr.match || (() => true) const apply = typeof bridge.generatorr === 'function' ? bridge.generatorr : bridge.generatorr.apply || bridge.generatorr const level = bridge.generatorr.level >= 0 ? bridge.generatorr.level : bridge.level + 1 const generator = { where: bridge.generatorr.where || bridge.where || helpers.where(4), match: async (args) => bridge.id == args.context.marker && args.context.level == level && !args.context.paraphrase && (args.context.response || args.context.isResponse) && await match(args), apply: (args) => apply(args), applyWrapped: apply, property: 'generatorr' } if (addFirst) { config.config.generators.unshift(addUUID(generator)) } else { config.config.generators.push(addUUID(generator)) } } const addSemantic = (semantic, evaluate) => { const match = semantic.match || (() => true) let apply = semantic // if I do apply == semantic.apply or semantic there is one function that has apply defined for some reason even though not explicitly set if (semantic.apply && typeof semantic !== 'function') { apply = semantic.apply } const semanticDef = { where: semantic.where || bridge.where || helpers.where(4), match: (args) => bridge.id == args.context.marker && !!args.context.evaluate == evaluate && match(args), apply: (args) => apply(args), applyWrapped: semantic, property: evaluate ? 'evaluator' : 'semantic' } if (addFirst) { config.config.semantics.unshift(addUUID(semanticDef)) } else { config.config.semantics.push(addUUID(semanticDef)) } } if (bridge.evaluator) { addSemantic(bridge.evaluator, true) } if (bridge.semantic) { addSemantic(bridge.semantic, false) } if (bridge.evaluators) { const evaluators = [...bridge.evaluators] evaluators.reverse() if (evaluators) { for (const evaluator of evaluators) { addSemantic(evaluator, true) } } } if (bridge.semantics) { const semantics = [...bridge.semantics] semantics.reverse() if (semantics) { for (const semantic of semantics) { addSemantic(semantic, false) } } } } ) } const handleCalculatedProps = (baseConfig, moreConfig, { addFirst, uuid } = {}) => { if (moreConfig.bridges) { moreConfig.bridges = moreConfig.bridges.map((bridge) => { bridge = { ...bridge } const valid = ['after', 'conditional', 'associations', 'before', 'bridge', 'development', 'skipable', 'return_type_selector', 'evaluator', 'evaluators', 'generatorp', 'generatorr', 'generatorpr', 'generators', 'operator', 'id', 'convolution', 'inverted', 'isA', 'children', 'parents', 'level', 'optional', 'selector', 'semantic', 'semantics', 'words', /Bridge$/, 'localHierarchy', 'levelSpecificHierarchy', 'where', 'uuid'] helpers.validProps(valid, bridge, 'bridge') handleBridgeProps(baseConfig, bridge, { addFirst, uuid }) return bridge }) } if (moreConfig.operators) { moreConfig.operators = moreConfig.operators.map((operator) => { if (typeof operator === 'string') { return { pattern: operator } } else { return operator } }) } } if (runtime.process.env.DEBUG_HIERARCHY) { global.entodictonDebugHierarchy = JSON.parse(runtime.process.env.DEBUG_HIERARCHY) } // i keep randomly doing one of the other so I will just make both work the same way if (runtime.process.env.DEBUG_PRIORITIES) { global.entodictonDebugPriority = JSON.parse(runtime.process.env.DEBUG_PRIORITIES) } if (runtime.process.env.DEBUG_PRIORITY) { global.entodictonDebugPriority = JSON.parse(runtime.process.env.DEBUG_PRIORITY) } if (runtime.process.env.DEBUG_CONTEXTUAL_PRIORITY) { global.entodictonDebugContextualPriority = JSON.parse(runtime.process.env.DEBUG_CONTEXTUAL_PRIORITY) } if (runtime.process.env.DEBUG_ASSOCIATION) { global.entodictonDebugAssociation = JSON.parse(runtime.process.env.DEBUG_ASSOCIATION) } if (runtime.process.env.DEBUG_WORD) { global.entodictonDebugWord = runtime.process.env.DEBUG_WORD } if (runtime.process.env.DEBUG_BRIDGE) { // id global.entodictonDebugBridge = runtime.process.env.DEBUG_BRIDGE global.entodictonDebugBridge[1] = parseInt(global.entodictonDebugBridge[1]) } if (runtime.process.env.DEBUG_OPERATOR) { // id/level global.entodictonDebugOperator = runtime.process.env.DEBUG_OPERATOR } const hierarchyCanonical = (element) => { if (element.child && element.parent) { return element } else { return { child: element[0], parent: element[1] } } } const isValidWordDef = (word, def, config) => { // TODO trie /* if (!def.id) { throw new Error(`In the KM "${config.name}", for the word ${word} the following definition is missing the "id" property: ${JSON.stringify(def)}`) } */ /* if (!def.initial) { throw `In the KM "${config.name}", for the word ${word} the following definition is missing the "initial" property: ${JSON.stringify(def)}` } */ } const hierarchyToCanonical = (edge) => { if (Array.isArray(edge)) { return { child: edge[0], parent: edge[1] } } return edge } const addWord = (config, uuid) => ({ word, id, initial }) => { if (!config.words) { config.words = { literals: {}, patterns: [], hierarchy: [] } } const literals = config.words.literals const def = { id, initial, uuid } if (literals[word]) { if (!literals[word].some((e) => helpers.safeEquals(e, def))) { literals[word].unshift(def) } } else { literals[word] = [def] } } const normalizeConfig = (config) => { if (config) { if (!config.objects) { config.objects = { namespaced: {}, } } for (const bag of bags) { if (config[bag]) { config[bag] = config[bag].map(normalizeGenerator) for (let i = 0; i < config[bag].length; ++i) { config[bag][i].index = i config[bag][i].km = config.name } } } if (config.bridges) { for (const bridge of config.bridges) { if (!bridge.level) { bridge.level = 0 } if (false && !bridge.bridge) { bridge.bridge = '{ ...next(operator) }' } } } if (config.semantics) { for (const semantic of config.semantics) { if (semantic.oneShot) { semantic.id = semantic.id || helpers.stableId('semantic') } } } if (config.operators) { for (let i = 0; i < config.operators.length; ++i) { if (typeof config.operators[i] === 'string') { config.operators[i] = { pattern: config.operators[i] } } } } } } /* function isLetter (char) { return (/[a-zA-Z]/).test(char) } */ async function configDup (config, options) { if (config instanceof Config) { return config.copy(options) } return _.cloneDeep(config) } function setWordsUUIDs (words, uuid) { const literals = words.literals for (const key in literals) { literals[key] = literals[key].map((o) => Object.assign(o, { uuid })) } const patterns = words.patterns for (const pattern of patterns) { pattern.defs.map((def) => Object.assign(def, { uuid })) } const hierarchy = words.hierarchy for (const pair of hierarchy || []) { pair.uuid = uuid } } function applyUUID (config, uuid) { if (config instanceof Config) { config = config.config } if (config.namespaces) { const keys = Object.keys(config.namespaces) if (keys.length > 1) { debugBreak() // throw 'wtf 23' } if (config.namespaces[keys[0]]) { config.namespaces[uuid] = config.namespaces[keys[0]] } } if (config.operators) { config.operators = config.operators.map((o) => Object.assign(o, { uuid })) } if (config.bridges) { config.bridges = config.bridges.map((o) => Object.assign(o, { uuid })) } if (config.generators) { config.generators = config.generators.map((o) => Object.assign(o, { uuid })) } if (config.semantics) { config.semantics = config.semantics.map((o) => Object.assign(o, { uuid })) } if (config.words) { setWordsUUIDs(config.words, uuid) } for (const property of bags) { if (config[property]) { config[property].forEach((bag) => { bag.uuid = uuid }) } } } /* function namespaceValid (namespace) { if (Array.isArray(namespace)) { return true } if (!namespace) { return true } return false } */ class KM { toNS (id) { if (this._namespace.length === 0) { return id } return `${this._namespace.join('#')}#${id}` } getName (config) { if (config instanceof Config) { return config.config.name } else { return config.name } } get name () { return this.toNS(this._name) } constructor ({ config, getCounter, namespace = [], uuid, isSelf = false }) { if (uuid) { this._uuid = uuid this._class = 'KM' this._config = config this._namespace = namespace this._name = this.getName(config) this._isSelf = true // new version the entry is for the containiner class but can't be that due to circularity } else { // this._uuid = uuidv4(); if (config instanceof Config) { config.valid() this._name = config.config.name // config.uuid = this._uuid this._uuid = config._uuid } else { // this._uuid = uuidv4() this._name = config.name this._uuid = getCounter(this._name) } this._class = 'KM' this._config = config // applyUUID(this._config, this._uuid) if (this._config instanceof Config) { this._config.valid() } this._namespace = namespace this._isSelf = false // old version the entry is for the containiner class but can't be that due to circularity } } valid () { if (this._config.initializerFn && !(this._config instanceof Config)) { debugBreak() return false } if (this._namespace && !Array.isArray(this._namespace)) { debugBreak() return false } if (this._config instanceof Config) { if (!this._config.valid()) { debugBreak() return false } } return true } async copy2 (options) { // greg -> add a flag to say don't init the api's const config = await configDup(this._config, options) const km = new KM({ config, getCounter: options.getCounter, name: this._name, _uuid: config._uuid, namespace: this._namespace, isSelf: this._isSelf }) return km // copy2() } async copy () { const km = new KM({ name: this._name, config: await configDup(this._config), // _uuid: uuidv4(), _uuid: this._config.getCounter(this._config._name), getCounter: (name) => this._config.getCounter(name), namespace: this._namespace, isSelf: this._isSelf }) if (km.config instanceof Config) { km.config.valid() } applyUUID(km._config, km._uuid) return km // copy() } get api () { return this._config.api } toString () { return `KM(${this._name}, ${JSON.stringify(this.config)}, ${this._namespace} isSelf(${this._isSelf}))` } get uuid () { return this._uuid } get config () { return this._config } set config (config) { this._config = config } get isSelf () { return this._isSelf } get namespace () { return this._namespace } set namespace (namespace) { this._namespace = namespace } } const multiApiImpl = (initializer) => { return new Object({ multiApi: true, // multi functions add: async (config, multiApi, api, apiConstructor) => { initializer(config, api) const name = api.getName() multiApi.apis[name] = api multiApi.apiConstructors[name] = apiConstructor multiApi.current = name }, initialize: ({ config, api: multiApi }) => { for (const apiName in multiApi.apis) { const api = multiApi.apis[apiName] initializer(config, api) } }, // "product1": apiInstance(testData1), apis: { }, apiConstructors: { }, // api functions api: (multiApi) => multiApi.apis[multiApi.current] }) } class Config { toServer (config) { return config_toServer(config) } async fixtures () { if (this.testConfig?.fixtures) { const args = {} args.uuid = this._uuid configHelpers.setupArgs(args, this) return this.testConfig.fixtures(args) } } getInfo () { const name = this.name const includes = this.configs.slice(1).map((km) => km.config.name) const visibleExamples = [] for (const test of this.tests) { if (!test.developerTest) { visibleExamples.push(test.query) } } const templateQueries = [] if (this.instances && this.instances.length > 0) { for (const query of this.instances.slice(-1)[0].configs) { if (typeof query === 'string') { templateQueries.push(query) } } } const info = { name, description: this.description, examples: visibleExamples, template: templateQueries, includes } return info } getPseudoConfig (uuid, config) { return { description: 'this is a pseudo config that has limited functionality due to being available in the initializer and fixtures function context', addAssociation: (...args) => this.addAssociation(...args), addAssociations: (...args) => this.addAssociations(...args), addBridge: (...args) => this.addBridge(...args, uuid), addGenerator: (...args) => this.addGenerator(...args, uuid, config.name), addHierarchy: (...args) => this.addHierarchy(...args), addOperator: (...args) => this.addOperator(...args, uuid), addPriority: (...args) => this.addPriority(...args), addPriorities: (...args) => this.addPriorities(...args), addSemantic: (...args) => this.addSemantic(...args, uuid, config.name), removeSemantic: (...args) => this.removeSemantic(...args, uuid, config.name), addWord: (...args) => this.addWord(...args, uuid), addPattern: (...args) => this.addPattern(...args, uuid), getHierarchy: (...args) => this.config.hierarchy, getBridges: (...args) => this.config.bridges, addArgs: (...args) => this.addArgs(...args), getBridge: (...args) => this.getBridge(...args), fragment: (...args) => this.fragment(...args), server: (...args) => this.server(...args), exists: (...args) => this.exists(...args), addAPI: (...args) => this.addAPI(...args) } } inDevelopmentMode (call) { config.developmentModeOn += 1 try { call() } finally { config.developmentModeOn -= 1 } } getCounter (maybeName = '') { const counter = this.configCounter this.configCounter += 1 return `${maybeName}${counter}` } setTestConfig (testConfig) { this.testConfig = testConfig } getTestConfig () { return this.testConfig } defaultConfig () { this.config = { operators: [], // TODO bridges: [], // Done hierarchy: [], // Done name: '', namespaces: { // "config id" : { namespace: [""], ids: set(ids) } }, eqClasses: [], priorities: [], // Done version: '3', debug: false, associations: { // Done negative: [], positive: [] }, objects: { // this is where the namespaced configs have their objects namespaced: {}, }, description: '', words: { literals: {}, patterns: [], hierarchy: [] }, // Done floaters: [], implicits: [], flatten: [], contexts: [], // TODO future expected_generated: [], expected_results: [], skipSemantics: false } for (const bag of bags) { this.config[bag] = [] } this.configCounter = 1 // this.wasInitialized = false } // applies only to config sent to the server watching () { const props = [ 'operators', 'bridges', 'hierarchy', 'namespaces', 'eqClasses', 'priorities', 'associations', 'words', 'floaters', 'implicits', 'flatten' ] const watching = {} for (const prop of props) { watching[prop] = this.config[prop] } return JSON.stringify(watching) } watch () { this.watchStart = this.watching() } wasChanged () { if (!this.watchStart) { return false } return this.watchStart !== this.watching() } exists (marker) { for (const bridge of this.config.bridges) { if (bridge.id == marker) { return true } } } get semantics () { return [...this.config.semantics] } getSemantics (logs = []) { return new Semantics(this.config.semantics, logs, { km: this.name }) } getGenerators (logs = []) { return new Generators(this.config.generators, logs, { km: this.name }) } warningNotEvaluated (log, value) { const description = 'WARNING: for semantics, implement an evaluations handler, set "value" property of the operator to the value.' const match = `({context}) => context.marker == '${value.marker}' && context.evaluate && <other conditions as you like>` const apply = '({context}) => <do stuff...>; context.value = <value>' const input = indent(JSON.stringify(value, null, 2), 2) const message = `${description}\nThe semantic would be\n match: ${match}\n apply: ${apply}\nThe input context would be:\n${input}\n` log.push(indent(message, 4)) } // value is in response field // TODO maybe generalize out query+evaluate along the lines of set value and set reference async getEvaluator (s, calls, log, context) { const instance = await s({ ...context, evaluate: true }) calls.touch(instance) if (!instance.evalue && !instance.verbatim && !instance.value) { this.warningNotEvaluated(log, context) } if (!instance.evalue) { instance.evalue = instance.value instance.edefault = true } delete instance.evaluate instance.instance = true return instance } fragmentInstantiator (args, contexts) { return new Object({ contexts: () => contexts, instantiate: async (mappings) => { const instantiated = _.cloneDeep(contexts) // const todo = [...instantiated] // const todo = [...instantiated] const todo = _.clone(instantiated) args = { ...args } while (todo.length > 0) { const context = todo.pop() args.context = context for (const mapping of mappings) { if (await mapping.match(args)) { await mapping.apply(args) } } for (const key of Object.keys(context)) { // if (['number', 'string', 'boolean'].includes(typeof (context[key]))) { if (!helpers.isCompound(context[key])) { continue } if (context[key].instantiated) { continue } todo.push(context[key]) } } return instantiated } }) } fragment (args, query) { for (const instance of (this.instances || [])) { for (const fragment of (instance.fragments || [])) { if (fragment.query === query) { return this.fragmentInstantiator(args, fragment.contexts) } } for (const fragment of (instance.resultss || [])) { if (fragment.isFragment && fragment.query === query) { return this.fragmentInstantiator(args, fragment.contexts) } } for (const fragment of (this.fragmentsBeingBuilt || [])) { if (fragment.query === query) { return this.fragmentInstantiator(args, fragment.contexts) } } } } // { rebuild: false, isModule: false } needsRebuild (template, instance, options) { if (options.rebuild) { return true } const toCanonical = (f) => { if (typeof f === 'string') { return { query: f } } else { return f } } const instanceFragments = (instance.fragments || []).map((fragment) => fragment.key || fragment.query).map(toCanonical) const templateFragments = (template.fragments || []).concat(this.dynamicFragments).map(toCanonical) const hasStop = template.configs && template.configs.find((config) => config.stop) // stop means fragments are not build so dont for a rebuild on the diff const sameFragments = hasStop || helpers.safeEquals(templateFragments, instanceFragments) const toCanonicalQuery = (queryOrConfig) => { if (typeof queryOrConfig === 'string') { const query = queryOrConfig return query } else if (typeof queryOrConfig === 'function') { if (options.isModule) { return { apply: 'function in the browser has webpack rewrites so can not be compared' } } else { return { apply: queryOrConfig.toString() } } } else if (queryOrConfig.apply) { if (options.isModule) { return { apply: 'function in the browser has webpack rewrites so can not be compared' } } else { return { apply: queryOrConfig.apply } } } else { const config = { ...queryOrConfig } delete config.where if (config.words && config.words.hierarchy) { config.words.hierarchy = config.words.hierarchy.map((value) => { value = { ...value } delete value.uuid return value }) } if (config.words && config.words.patterns) { config.words.patterns = config.words.patterns.map((value) => { value = { ...value } value.defs = value.defs.map((value) => { value = { ...value } delete value.uuid return value }) return value }) } config.operators = (config.operators || []).map((operator) => { if (typeof operator === 'string') { return { pattern: operator } } else { operator = { ...operator } delete operator.uuid return operator } }) config.bridges = (config.bridges || []).map((bridge) => { bridge = { ...bridge }, bridge.level = bridge.level || 0 delete bridge.uuid return bridge }) if (options.isModule) { // things like webpack rewrite the functions if there are constants so this compare does not work delete config.generators delete config.semantics config.bridges = (config.bridges || []).map((bridge) => { bridge = { ...bridge } delete bridge.where delete bridge.generatorp delete bridge.generatorr delete bridge.generatorpr delete bridge.evaluator delete bridge.evaluators delete bridge.semantic delete bridge.semantics if (false && !bridge.bridge) { bridge.bridge = '{ ...next(operator) }' } return bridge }) } else { /* done in updateQueries now config.generators = (config.generators || []).map((generator) => { generator = { ...generator } delete generator.where generator.match = generator.match.toString() generator.apply = generator.apply.toString() return generator }) config.semantics = (config.semantics || []).map((semantic) => { semantic = { ...semantic } delete semantic.where semantic.match = semantic.match.toString() semantic.apply = semantic.apply.toString() return semantic }) config.bridges = (config.bridges || []).map((bridge) => { bridge = { ...bridge } delete bridge.where if (bridge.generatorp) { bridge.generatorp = bridge.generatorp.toString() } if (bridge.generatorr) { bridge.generatorr = bridge.generatorr.toString() } if (bridge.generatorpr) { bridge.generatorpr = bridge.generatorpr.toString() } if (bridge.evaluator) { bridge.evaluator = bridge.evaluator.toString() } if (bridge.semantic) { bridge.semantic = bridge.semantic.toString() } return bridge }) */ } return config } } const toCanonicalQueries = (elements) => { return elements.map(toCanonicalQuery) } const templateQueries = toCanonicalQueries(template.configs || []).map(helpers.updateQueries) const instanceQueries = toCanonicalQueries(instance.configs || []) let sameQueries = true let startOfChanges for (let iq = 0; iq < templateQueries.length; ++iq) { if (!helpers.safeEquals(templateQueries[iq], instanceQueries[iq])) { // if the current and rest are not queries or fragments then treat as not needing rebuild if (templateQueries.length != instanceQueries.length) { sameQueries = false startOfChanges = iq } else { let hasQueryOrFragment = false for (let rest = iq; rest < templateQueries.length; ++rest) { const value = templateQueries[rest] if (typeof value === 'string' || (value.query && value.isFragment)) { hasQueryOrFragment = true break } } if (hasQueryOrFragment) { sameQueries = false startOfChanges = iq } } break } } const debug = true if (debug && startOfChanges) { console.log('templateQueries[startOfChanges]', templateQueries[startOfChanges]) console.log('instanceQueries[startOfChanges]', instanceQueries[startOfChanges]) } // things were deleted case if (templateQueries.length < instanceQueries.length) { startOfChanges = instanceQueries.length } if (debug) { if (!(instance && sameQueries && sameFragments)) { // console.log("instance", instance) console.log('sameQueries', sameQueries) console.log('sameFragments', sameFragments) // console.log("templateFragments", templateFragments) // console.log("instanceFragments", instanceFragments) } } if (startOfChanges) { return { needsRebuild: true, startOfChanges, previousResultss: instance.resultss } } else if (startOfChanges || instance.resultss) { return { needsRebuild: !(instance && sameQueries && sameFragments), startOfChanges, previousResultss: instance.resultss } } else { return { needsRebuild: !(instance && sameQueries && sameFragments) } } } validifyTemplate (template) { if (!template.configs && !template.fragments) { throw new Error(`Expected the template for ${this.name} to be an object that can have the properties: configs and fragments`) } for (const query of template.configs || []) { if (typeof query === 'string') { } else if (query instanceof Config) { throw new Error(`For the template for ${this.name}, each element in configs should be either a string or a structure with a config (not a Config object).`) } } } toData (data) { Object.assign(data, this.config) // greg99 delete data.objects data.objects = {...this.config.objects} if (!this.sendObjectsToServer) { delete data.objects.namespaced } config_toServer(data) } // loadTemplate async load (rebuildTemplate, template, instance, options = { rebuild: false, previousResultss: undefined, startOfChanges: undefined }) { this.validifyTemplate(template) instance.template = template this.logs.push(`loading template for ${this.name}`) if (options.rebuild) { // TODO fix beforeQuery template = { fragments: [], configs: [], ...template } template.fragments = template.fragments.concat(this.dynamicFragments) await rebuildTemplate({ config: this, instance, target: this.name, startOfChanges: options.startOfChanges, beforeQuery: () => {}, template, ...options, previousResultss: options.previousResultss || instance.resultss }) } else { // no change // this.initInstances.push({ ...instance, name: config.name }) const isEmpty = (instance) => { const properties = [ 'configs', 'resultss', 'fragments', 'semantics', 'associations' ] return !properties.find((property) => instance[property] && instance[property].length > 0) } if (!isEmpty(instance)) { // fix up apply functions for (let i = 0; i < instance.resultss.length; ++i) { const result = instance.resultss[i] if (result.apply) { result.apply = template.configs[i] } } instance.name = this.name this.initInstances.push(instance) this.instances.push(instance) await configHelpers.loadInstance(this, instance) } this.expect_template = false } } watchNewFragments (list) { this.addFragmentWatcher = list } addFragments (fragments) { // only run this if not loading as module write error if loading as module and different this.dynamicFragments = this.dynamicFragments.concat(fragments) if (this.addFragmentWatcher) { for (const fragment of fragments) { this.addFragmentWatcher.push(fragment) } } } objects () { return this.config.objects.namespaced[this._uuid] } addAssociations (associations) { for (const association of associations) { this.addAssociation(association) } } debugConfig () { } addAssociation (association) { if (!this.config.associations) { this.config.associations = { negative: [], positive: [] } } debugAssociation(association) this.config.associations.positive.push(association) this._delta.json.associations.push({ action: 'add', association }) } // TODO add more error checking to these like addHierarchy has // TODO change name from priorities to priority // [ context: <list of [id, level]>, choose: [<indexes of prioritized operator>], [ordered: [true|false]] ] addPriority (priority) { if (!this.config.priorities) { this.config.priorities = [] } debugPriority(priority) priority_valid(priority) this.config.priorities.push(priority) this._delta.json.priorities.push({ action: 'add', priority }) } addPriorities (priorities) { for (const priority of priorities) { this.addPriority(priority) } } addHierarchy (child, parent, instance) { if (child && parent || !child || Array.isArray(child) || (typeof child === 'string' && !parent)) { this.addHierarchyChildParent(child, parent, instance) // this.addHierarchyProperties ({ child, parent }) } else { this.addHierarchyProperties(child) } } addHierarchyInternal (edge) { debugHierarchy([edge[0], edge[1]]) // because this is called from the semantics and internally. in the semantics the config can be a pseudo config // where hierarchy and config.hierarchy do not match if (this.hierarchy) { this.hierarchy.addEdge(edge) // no dups due to sharing if (this.hierarchy._edges != this.config.hierarchy) { this.config.hierarchy.push(edge) this._delta.json.hierarchy.push(edge) } } else { this.config.hierarchy.push(edge) this._delta.json.hierarchy.push(edge) } } addHierarchyProperties (properties) { const { child, parent, instance } = properties if (typeof child !== 'string') { throw new Error(`addHierarchy expected child property to be a string. got ${JSON.stringify(child)}`) } if (typeof parent !== 'string') { throw new Error(`addHierarchy expected parent property to be a string. got ${JSON.stringify(parent)}`) } if (instance && typeof instance !== 'boolean') { throw new Error(`addHierarchy expected instance property to be a boolean or undefined. got ${JSON.stringify(instance)}`) } const edge = [child, parent, instance || false] this.addHierarchyInternal(edge) } addHierarchyChildParent (child, parent, instance) { if (typeof child !== 'string') { throw new Error(`addHierarchy expected child to be a string. got ${JSON.stringify(child)}`) } if (typeof parent !== 'string') { throw new Error(`addHierarchy expected parent to be a string. got ${JSON.stringify(parent)}`) } if (instance && typeof instance !== 'boolean') { throw new Error(`addHierarchy expected instance property to be a boolean or undefined. got ${JSON.stringify(instance)}`) } debugHierarchy([child, parent]) if (this.config.hierarchy.find((element) => { const hc = hierarchyCanonical(element) if (child == hc.child && parent == hc.parent) { return true } })) { return } const edge = [child, parent, instance || false] this.addHierarchyInternal(edge) } getBridge (id, level) { if (level) { return this.config.bridges.find((bridge) => bridge.id == id && bridge.level == level) } else { return this.config.bridges.find((bridge) => bridge.id == id) } } addBridge (bridge, uuid) { if (!this.config.bridges) { this.config.bridges = [] } const bridges = this.config.bridges const def = Object.assign({}, bridge, { uuid: uuid || this._uuid }) debugBridge(bridge) if (bridge.allowDups) { // if (bridges.find( (b) => b.id == bridge.id && b.level == bridge.level && b.bridge == bridge.bridge )) { if (bridges.find((b) => b.id == bridge.id && b.level == bridge.level)) { return } } if (global.transitoryMode) { def.transitoryMode = true } handleBridgeProps(this, def, { uuid }) bridges.push(def) this.checkBridges() this._delta.json.bridges.push({ action: 'add', bridge: def }) } addGenerator (generator, uuid, name) { if (!(typeof generator.match === 'function')) { throw new Error('addGenerator: Expected matcher to be a function') } if (!(typeof generator.apply === 'function')) { throw new Error('addGenerator: Expected action to be a function') } if (!this.config.generators) { this.config.generators = [] } if (!generator.where) { generator.where = helpers.where(3) } const generators = this.config.generators Object.assign(generator, { uuid: uuid || this._uuid, km: name || this.name, index: generators.length }) // used to be unshift generators.unshift(generator) } addSemantic (semantic, uuid, name) { if (!(typeof semantic.match === 'function')) { throw new Error('addSemantic: Expected match to be a function') } if (!(typeof semantic.apply === 'function')) { throw new Error('addSemantic: Expected apply to be a function') } if (!this.config.semantics) { this.config.semantics = [] } if (!semantic.where) { semantic.where = helpers.where(3) } const semantics = this.config.semantics Object.assign(semantic, { uuid: uuid || semantic.uuid || this._uuid, km: name || this.name, index: semantics.length, id: semantic.id || uuidv4() }) semantics.unshift(semantic) } removeSemantic (deleteSemantic) { const id = deleteSemantic.id || deleteSemantic const todo = [id] const seen = new Set() while (todo.length > 0) { const id = todo.pop() if (seen.has(id)) { continue } seen.add(id) const index = this.config.semantics.findIndex((semantic) => semantic.id === id) if (index == -1) { continue } for (const tied_id of this.config.semantics[index].tied_ids || []) { if (!seen.has(tied_id)) { todo.push(tied_id) } } if (index >= 0) { this.config.semantics.splice(index, 1) } } } addOperator (objectOrPattern, uuid) { if (!this.config.operators) { this.config.operators = [] } const operators = this.config.operators let operator if (typeof objectOrPattern === 'string') { operator = { pattern: objectOrPattern, uuid: uuid || this._uuid } } else { operator = Object.assign({}, objectOrPattern, { uuid: uuid || this._uuid }) } debugOperator(operator) if (operator.allowDups) { if (operators.find((o) => o.pattern == operator.pattern)) { return } } operators.unshift(operator) this.checkOperators() this._delta.json.operators.push({ action: 'add', operator }) } addWord (word, def, uuid) { this.addWordInternal(word, def, uuid) } addWordInternal (word, def, uuid) { if (!this.config.words) { this.config.words = { literals: {}, patterns: [], hierarchy: [] } } debugWord(word) const literals = this.config.words.literals def = Object.assign({}, def, { uuid: uuid || this._uuid }) if (literals[word]) { if (!literals[word]