theprogrammablemind
Version:
1,650 lines (1,503 loc) • 103 kB
JavaScript
// 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]