tpmkms
Version:
Reusable libraries for Entodicton, a natural language to json converter
927 lines (848 loc) • 31.9 kB
JavaScript
const pluralize = require('pluralize')
const { unflatten, flattens, Digraph } = require('../runtime').theprogrammablemind
const _ = require('lodash')
const deepEqual = require('deep-equal')
const { chooseNumber } = require('../helpers.js')
const { Frankenhash } = require('./frankenhash.js')
const { compose, translationMapping, translationMappingToInstantiatorMappings } = require('./meta.js')
class API {
constructor() {
this.digraph = new Digraph()
}
initialize({ km, objects, config }) {
this._objects = objects
objects.concepts = ['properties']
// object -> property -> {has, value}
objects.properties = {}
// property -> values
objects.property = {}
objects.parents = {}
objects.children = {}
objects.relations = []
objects.valueToWords = {}
this.propertiesFH = new Frankenhash(objects.properties)
this._km = km
this.__config = config
this.digraph = new Digraph()
const toJSON = (h) => {
if (h.child && h.parent) {
return h
} else {
return { child: h[0], parent: h[1] }
}
}
// for (const tuple of [...this.config().config.hierarchy]) {
for (const tuple of [...config.getHierarchy()]) {
const h = toJSON(tuple);
// TODO should this notice development flag?
if (this.isOperator(h.child) && this.isOperator(h.parent)) {
this.rememberIsA(h.child, h.parent)
}
}
if (km('concept')) {
km('concept').api.addDefaultTypesForObjectHierarchy([
'theAble',
'queryable',
'hierarchyAble',
'object',
'isEdee',
'isEder',
'property'
])
}
}
createBinaryRelation (config, operator, words, before, after) {
this.createActionPrefix({
operator: operator,
words: words,
create: [operator],
before: [{tag: before, id: 'object'}],
after: [{tag: after, id: 'object'}],
// relation: true,
relation: false,
doAble: true,
config,
unflatten: [before, after],
})
config.addHierarchy(operator, 'canBeQuestion')
}
/*
translationMappingToInstantiatorMappings(translationMapping, from , to) {
return translationMapping.map( (tm) => {
return {
// match: ({context}) => context.value == to[tm.to].value,
match: ({context}) => context[tm.to],
apply: ({context}) => {
// Object.assign(context[tm.to], from[tm.from])
context[tm.to] = from[tm.from]
if (context[tm.to]) {
context[tm.to].instantiated = true
}
}
}
})
}
*/
setupEdAble(args) {
const { operator, word, before=[], after=[], create=[], config, relation, ordering, doAble, words = [], unflatten:unflattenArgs = [], focusable = [], edAble, localHierarchy } = args;
config.addOperator(`(([${after[0].tag}])^ <${edAble.operator}|${edAble.word}> ([by] ([${before[0].tag}])?))`)
config.addBridge({
id: edAble.operator,
level: 0,
localHierarchy,
bridge: `{
...before,
marker: operator('${after[0].tag}'),
constraints: [
{
property: '${after[0].tag}',
properties: ['${after[0].tag}', '${before[0].tag}'],
paraphrase: { marker: '${operator}', ${before[0].tag}: { marker: '${before[0].id}', types: ['${before[0].id}'], word: '${before[0].id}' }, ${after[0].tag}: { marker: '${after[0].id}', types: ['${after[0].id}'], word: '${after[0].id}' } },
constraint:
{
...next(operator),
constrained: true,
${before[0].tag}: default(after[0].object, after[0]),
${after[0].tag}: { ...before[0] }
}
}
] }`,
deferred: `{ ...next(operator), 'isEd': true, subject: 'ownee', '${after[0].tag}': { operator: operator, number: operator.number, ...before[0] }, ${before[0].tag}: after[0].object }` })
// TODO have a prepositions category and underPrep category
config.addHierarchy(edAble.operator, 'isEdAble')
config.addHierarchy(before[0].id, 'isEder')
config.addHierarchy(after[0].id, 'isEdee')
config.addHierarchy('isEdee', 'queryable')
config.addSemantic({
notes: 'semantic for setting value with constraint',
match: ({context, isA}) => isA(context.marker, after[0].tag) && context.evaluate && context.constraints,
// match: ({context, isA}) => context.marker == after[0].tag && context.evaluate,
apply: async ({km, context, e, log, isA}) => {
const constraint = context.constraints[0];
const value = constraint.constraint;
let property = constraint.property;
const properties = constraint.properties;
for (const p of properties) {
if (value[p].concept) {
property = p
constraint.property = p; // set what is used
}
}
// value.marker = 'owns'
// value.greg = true
// value.ownee.query = true
value.query = true
const instance = await e(value)
if (instance.verbatim) {
context.evalue = { verbatim: instance.verbatim }
return
}
if (instance.evalue.marker == 'answerNotKnown') {
context.evalue = instance.evalue
return
}
const selected = instance.evalue.value.map( (r) => r[property] )
context.constraints = undefined;
context.evalue = { marker: 'list', value: selected }
},
})
config.addGenerator({
notes: 'generator for constraint',
match: ({context}) => context.marker == edAble.operator && context.paraphrase && context.constrained,
apply: async ({context, g}) => {
if (context[before[0].tag].marker == 'by') {
// the cat wendy owned
return `${await g({...context[after[0].tag], paraphrase: true})} ${edAble.word} ${await g({...context[before[0].tag], paraphrase: true})}`
} else {
// the cat owned by wendy
return `${await g({...context[after[0].tag], paraphrase: true})} ${edAble.word} ${['by', await g({...context[before[0].tag], paraphrase: true})].filter((t) => t).join(' ')}`
}
},
})
config.addGenerator({
match: ({context}) => {
if (context.marker == operator && context.paraphrase) {
if (context['do']) {
const left = context['do'].left
if (context[left]) {
// who owns X should not be 'does who own x' but instead 'who owns x'
if (context[left].query) {
return true;
}
}
}
}
return false;
},
apply: async ({context, g}) => {
const chosen = chooseNumber(context, word.singular, word.plural)
return `${await g(context[before[0].tag])} ${chosen} ${await g(context[after[0].tag])}`
}
})
config.addGenerator({
match: ({context}) => context.marker == edAble.operator && context.isEd,
apply: async ({context, g}) => {
const chosen = chooseNumber(context[after[0].tag], 'is', 'are')
if (context[before[0].tag].evalue && context[before[0].tag].evalue.marker == 'answerNotKnown') {
return await g(context[before[0].tag])
}
return `${await g(context[after[0].tag])} ${chosen} ${edAble.word} by ${await g(context[before[0].tag])}`
}
})
{
const whoIsWhatVerbedBy = `${before[0].tag}var is ${after[0].tag}var ${edAble.word} by`
const thisIsVerbedByThat = `${after[0].tag}var is ${edAble.word} by ${before[0].tag}var`
// greg32 check this out
// config.addFragments([whoIsWhatVerbedBy])
config.addFragments([
whoIsWhatVerbedBy,
{
/*
hierarchy: [
['owneevar', 'ownee']
],
*/
query: thisIsVerbedByThat
},
])
// config.addHierarchy({ child: 'owneeVar', parent: 'isEdee', maybe: true})
// config.addHierarchy({ child: 'ownerVar', parent: 'isEder', maybe: true})
// config.addFragments([`${before[0].tag}Var is ${after[0].tag}Var ${edAble.word} by`, `${after[0].tag}Var is ${edAble.word} by ${before[0].tag}Var`])
// config.addFragments(["ownerVar is owneeVar owned by", "owneeVar is owned by ownerVar"])
const generator = {
notes: `generator for who/what is X owned by`,
// match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'is') && context.one && context.one.marker == 'ownee' && context.one.constraints && context.one.constraints[0] && context.one.constraints[0].constraint.marker == 'owned' && context.one.constraints[0].constraint.owner.implicit,
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'is') && context.one && context.one.marker == after[0].tag && context.one.constraints && context.one.constraints[0] && context.one.constraints[0].constraint.marker == edAble.operator && context.one.constraints[0].constraint[before[0].tag].implicit,
apply: async ({context, fragments, g, gs, callId}) => {
const isToFromM = [{"from":["one"],"to":["two"]},{"from":["two"],"to":["one"]}]
const fromF = fragments(whoIsWhatVerbedBy).contexts()[0]
const toF = fragments(thisIsVerbedByThat)
const to = toF.contexts()[0]
const tm = translationMapping(fromF, to)
/*
some kind of compose
const isToFromM = '[{"from":["one"],"to":["two"]},{"from":["two"],"to":["one"]}]'
isToFromM + tm -> '[{"from":["one"],"to":["owner"]},{"from":["two"],"to":["ownee"]},{"from":["number"],"to":["number"]}]'
output
'[{"from":["two"],"to":["owner"]},{"from":["one"],"to":["ownee"]},{"from":["number"],"to":["number"]}]'
*/
const tmPrime = compose(isToFromM, tm)
// const from = context.one.constraints[0].constraint
const from = context
const im = translationMappingToInstantiatorMappings(tmPrime, from, to)
const translation = await toF.instantiate(im)
if (Array.isArray(translation)) {
return await gs(translation)
} else {
return await g(translation)
}
}
}
config.addGenerator(generator)
}
}
// createActionPrefix({before, operator, words, after, semantic, create})
//
// tag == property name + id == operator id
// before == [ { tag, marker }, ... ]
// create == [ id, ... ] // ids to create bridges for
// doAble : true if the qustion like this work "does b[0] <marker> b[0]" for example does g2reg like bananas
// relation -> the semantics will be implements using relations
// edable: "y is owned by x" edable = { operator: 'owned' }
createActionPrefix(args, semanticApply) {
const { operator, before=[], after=[], create=[], config, localHierarchy=[], relation, ordering, doAble, words = [], unflatten:unflattenArgs = [], focusable = [], edAble } = args;
// const before = [...]
// const after = [{tag: 'weapon', id: 'weapon'}]
// const create = ['arm', 'weapon']
if (doAble) {
if (before.length != 1) {
throw "Expected exactly one before argument"
}
if (after.length != 1) {
throw "Expected exactly one after argument"
}
}
const beforeOperators = before.map( (arg) => `([${arg.id}|])` ).join('')
const beforeOperatorsEdable = before.map( (arg) => `([${arg.id}|])^` ).join('')
const afterOperators = after.map( (arg) => `([${arg.id}|])` ).join('')
// config.addOperator(`(${beforeOperators} [${operator}|] ${afterOperators})`)
if (doAble) {
config.addOperator({
pattern: `([(${beforeOperators} [${operator}|] ${afterOperators}^)])`,
allowDups: true,
})
// config.addOperator({ id: operator, level: 1, words: [operator] })
config.addBridge({
id: operator,
level: 1,
bridge: '{ ...next(operator) }',
allowDups: true,
})
config.addPriority({ "context": [['does', 0], [operator, 1], ], "choose": [0] })
config.addPriority({ "context": [['doesnt', 0], [operator, 1], ], "choose": [0] })
config.addPriority({ "context": [[operator, 0], ['does', 0], ], "choose": [0] })
config.addPriority({ "context": [[operator, 0], ['doesnt', 0], ], "choose": [0] })
} else {
config.addOperator({ pattern: `(${beforeOperators} [${operator}|] ${afterOperators})`, allowDups: true })
}
if (false) {
for (const argument of before.concat(after)) {
if (create.includes(argument.id)) {
// config.addHierarchy('unknown', argument.id)
// config.addHierarchy('what', argument.id)
// greg23 <<<<<<<<<<<< doing this
config.addHierarchy(argument.id, 'unknown')
config.addHierarchy(argument.id, 'what')
}
}
} else {
for (const argument of before.concat(after)) {
if (create.includes(argument.id)) {
// config.addHierarchy(argument.id, 'unknown')
// config.addHierarchy(argument.id, 'what')
localHierarchy.push([argument.id, 'unknown'])
localHierarchy.push([argument.id, 'what'])
}
}
}
create.map( (id) => {
if (id === operator) {
const tagsToProps = (where, args, suffix='') => {
const i = 0;
let r = ''
for (const arg of args) {
r += `, ${arg.tag}${suffix}: ${where}[${i}] `
}
return r
}
const beforeArgs = tagsToProps('before', before)
let afterArgs = tagsToProps('after', after)
let doParams = '';
if (doAble) {
doParams = `, do: { left: "${before[0].tag}", right: "${after[0].tag}" } `
afterArgs = tagsToProps('after', after, '*')
}
const unflattenArgs = [ ...before.map( (arg) => arg.tag ), ...after.map( (arg) => arg.tag ) ]
const focusable = [ ...before.map( (arg) => arg.tag ), ...after.map( (arg) => arg.tag ) ]
config.addBridge({
id: operator,
level: 0,
localHierarchy: [...localHierarchy, ['object', 'unknown']],
bridge: `{ ... next(operator) ${doParams} ${beforeArgs} ${afterArgs}, unflatten: ${JSON.stringify(unflattenArgs)}, focusable: ${JSON.stringify(focusable)} }`,
allowDups: true
})
if (words.length > 0) {
for (const word of words) {
config.addWord(word, { id: operator, initial: `{ value: "${operator}" }` })
}
} else {
// config.addWord(operator, { id: operator, initial: `{ value: "${operator}" }` })
}
} else {
config.addBridge({ id: id, allowDups: true })
}
})
if (words.length == 0) {
const operatorPlural = pluralize.singular(operator)
const operatorSingular = pluralize.plural(operator)
config.addWord(operatorSingular, { id: operator, initial: `{ value: '${operator}', number: 'one' }`})
config.addWord(operatorPlural, { id: operator, initial: `{ value: '${operator}', number: 'many' }`})
}
if (doAble) {
config.addHierarchy(operator, 'canBeDoQuestion')
}
config.addPriority({ "context": [[operator, 0], ['means', 0], ], "choose": [0] })
config.addPriority({ "context": [['article', 0], [operator, 0], ], "choose": [0] })
config.addGenerator({
notes: 'ordering generator for paraphrase',
match: ({context}) => context.marker == operator && context.paraphrase && !context.query,
apply: async ({context, gp, g}) => {
const beforeGenerator = []
for (const arg of before) {
beforeGenerator.push(await g(context[arg.tag]))
}
const afterGenerator = []
for (const arg of after) {
afterGenerator.push(await gp(context[arg.tag]))
}
const word = context.word
const sub = []
if (context.subphrase) {
sub.push(['that'])
}
return beforeGenerator.concat(sub).concat([word]).concat(afterGenerator).join(' ')
}
})
config.addGenerator({
notes: 'ordering generator for response',
match: ({context}) => context.marker == operator && context.evalue && context.isResponse,
apply: async ({context, g, km}) => {
const brief = km("dialogues").api.getBrief()
const { evalue } = context
let yesno = ''
if (!context.do.query || evalue.truthValueOnly || brief) {
if (evalue.truthValue) {
yesno = 'yes'
} else if (evalue.truthValue === false) {
yesno = 'no'
}
}
if (evalue.truthValueOnly || brief) {
return `${yesno}`
} else {
return `${yesno} ${await g(Object.assign({}, evalue, { paraphrase: true }))}`
}
}
})
if (ordering) {
config.addSemantic({
notes: 'ordering setter',
// TODO use hierarchy for operator
match: ({context}) => context.marker == operator,
apply: ({context, km}) => {
//const api = km('ordering').api
// api.setCategory(ordering.name, context[ordering.object].value, context[ordering.category].value, context)
const propertiesAPI = km('properties').api
context.ordering = ordering.name
const fcontexts = flattens(['list'], [context])
for (const fcontext of fcontexts) {
fcontext.paraphrase = true
fcontext[ordering.object].paraphrase = true
fcontext[ordering.category].paraphrase = true
}
propertiesAPI.relation_add(fcontexts)
}
})
config.addSemantic({
notes: 'ordering query',
match: ({context}) => context.marker == operator && context.query,
apply: ({context, km}) => {
const api = km('ordering').api
const propertiesAPI = km('properties').api
context.ordering = ordering.name
const matches = propertiesAPI.relation_get(context, ['ordering', ordering.object, ordering.category])
if (matches.length > 0 || (typeof context.query == 'boolean' && context.query)) {
// does greg like bananas
if (matches.length == 0) {
const response = _.clone(context)
response.isResponse = true
response.query = undefined
context.evalue = { marker: 'list', value: [response] }
} else {
context.evalue = { marker: 'list', value: unflatten(matches) }
context.evalue.isResponse = true
}
context.evalue.truthValue = matches.length > 0
context.evalue.truth = { marker: 'yesno', value: matches.length > 0, isResponse: true, focus: true }
context.evalue.focusable = ['truth']
if (!context.evalue.truthValue) {
context.evalue.truthValueOnly = true
}
// ADD this line back and remove it to check
// context.response = { marker: 'list', value: [response], isResponse: true }
// Object.assign(context, { marker: 'list', value: responses, focusable: ['value'], paraphrase: true, truthValue: matches.length > 0 })
} else {
// see if anything is preferred greg
// what does greg like
const matches = propertiesAPI.relation_get(context, ['ordering', ordering.object])
if (matches.length == 0) {
// Object.assign(context, { marker: 'idontknow', query: _.clone(context) })
context.evalue = { marker: 'idontknow', query: _.clone(context), isResponse: true }
} else {
context.evalue = { marker: 'list', value: matches, isResponse: true }
}
context.isResponse = true
context.evalue.truthValue = matches.length > 0 && matches[0].marker == ordering.marker
}
}
})
}
if (ordering || relation || doAble) {
config.addHierarchy(operator, 'canBeQuestion')
config.addHierarchy(operator, 'ifAble')
config.addHierarchy(operator, 'orAble')
}
if (relation) {
config.addSemantic({
notes: `setter for ${operator}`,
match: ({context}) => context.marker == operator,
apply: ({context, km, hierarchy, config}) => {
const api = km('properties').api
// add types for arguments
for (const argument of context.focusable || []) {
const value = api.toValue(context[argument])
if (value) {
const minimas = hierarchy.minima(context[argument].types)
for (const type of minimas) {
if (config.exists(value)) {
config.addHierarchy(value, type);
}
}
}
}
api.relation_add(context)
}
})
config.addSemantic({
notes: `getter for ${operator}`,
match: ({context}) => context.marker == operator && context.query,
apply: ({context, km}) => {
const api = km('properties').api
context.evalue = {
marker: 'list',
value: unflatten(api.relation_get(context, before.concat(after).map( (arg) => arg.tag ) ))
}
context.evalue.isResponse = true
context.isResponse = true
if (context.evalue.value.length == 0) {
context.evalue.marker = 'answerNotKnown';
context.evalue.value = [];
context.evalue.marker = 'answerNotKnown';
context.evalue.value = [];
}
}
})
}
if (semanticApply) {
config.addSemantic({
notes: `override semantic apply for ${operator}`,
match: ({context}) => context.marker == operator,
apply: semanticApply,
})
}
if (doAble && edAble) {
this.setupEdAble(args)
}
}
makeObject(args) {
const types = [ 'hierarchyAble', 'object', 'property' ];
const { config } = args;
return this._km("dialogues").api.makeObject({ ...args, types });
}
relation_add (relations) {
if (!Array.isArray(relations)) {
relations = [relations]
}
for (const relation of relations) {
this._objects.relations.push(relation)
}
}
relation_match (args, template, value) {
const matches = (t, v) => {
if (typeof t == 'string' || typeof v == 'string') {
return t == v
}
if (!t || !v) {
return null
}
if (t.query) {
return true
}
if (t.concept) {
// const api = args.km('properties').api
if (v.unknown && !t.value) {
return true;
}
return this.isA(v.value, t.value)
}
/* wtf
if (!t.value && !v.value) {
return this.isA(v.value, t.value);
}
*/
return t.value && v.value && t.value == v.value
}
for (const arg of args) {
if (!matches(template[arg], value[arg])) {
return null
}
}
return value
}
relation_get(context, args) {
const andTheAnswerIs = []
for (const relation of this._objects.relations) {
if (this.relation_match(args, context, relation)) {
const queriedArgs = args.filter( (arg) => context[arg].query )
if (queriedArgs.length == 1) {
relation[queriedArgs[0]] = { ...relation[queriedArgs[0]], focus: true }
}
andTheAnswerIs.push(Object.assign({}, relation, { paraphrase: true }))
}
}
return andTheAnswerIs
}
/*
copyShared(fromApi) {
for (let {path, handler} of fromApi.objects.initHandlers) {
this.setShared(args, handler)
}
}
*/
setShared(path, handler) {
if (!handler) {
handler = new Object({
setValue: ([object, property], value, has) => {
return this.setProperty(object, property, value, has, true)
},
getValue: async ([object, property]) => {
return await this.getPropertyDirectly(object, property)
},
})
}
this.propertiesFH.setHandler(path, handler)
this.propertiesFH.setInitHandler( { path, handler } )
return handler
}
setReadOnly(path) {
const handler = new Object({
setValue: ([object, property], value, has) => {
const error = Error(`The property '${property}' of the object '${object}' is read only`)
error.code = 'ReadOnly'
throw error
},
getValue: async ([object, property]) => {
return await this.getPropertyDirectly(object, property)
},
})
this.propertiesFH.setHandler(path, handler)
}
/*
async getObject(object) {
return this.propertiesFH.getValue([object])
}
*/
getHandler(object, property) {
return this.propertiesFH.getHandler([object, property])
}
toValue(context) {
if (typeof context == 'string') {
return context
}
if (!context) {
return;
}
return this._km("stm").api.getVariable(context.value);
// return context.value
}
async getProperty(object, property, g) {
object = this.toValue(object)
property = this.toValue(property)
const handler = this.propertiesFH.getHandler([object, property])
if (handler) {
return await handler.getValue([object, property])
}
return await this.getPropertyDirectly(object, property, g)
}
async getPropertyDirectly(object, property, g) {
if (property == 'property') {
const objectProps = await this.propertiesFH.getValue([object])
const values = []
for (const key of Object.keys(objectProps)) {
if (objectProps[key].has) {
values.push(`${await g(key)}: ${await g({ ...objectProps[key].value, paraphrase: true })}`)
}
}
return { marker: 'list', value: values }
} else {
return (await this.propertiesFH.getValue([object, property])).value
}
}
async hasProperty(object, property, has) {
return (await this.propertiesFH.getValue([object, property])).has
}
setProperty(object, property, value, has, skipHandler) {
if (!skipHandler) {
const handler = this.propertiesFH.getHandler([object, property])
if (handler) {
return handler.setValue([object, property], value, has)
}
}
this.propertiesFH.setValue([object, property], value, has)
if (has && value) {
let values = this._objects.property[property] || []
if (!values.includes(value)) {
values = values.concat(value)
}
this._objects.property[property] = values
// this._objects.property[property] = (this.objects.property[property] || []).concat(value)
// "mccoy's rank is doctor",
// infer doctor is a type of rank
this.rememberIsA(value.value, property);
}
if (!this._objects.concepts.includes(object)) {
this._objects.concepts.push(pluralize.singular(object))
}
}
async knownObject(object) {
if (object == 'property') {
return object
}
if ((object || {}).value) {
object = value
}
const path = [object]
// return this.knownPropertyNew(path)
return await this.propertiesFH.knownProperty(path)
}
async hasProperty(object, property) {
if (property == 'property') {
return true;
}
// go up the hierarchy
const todo = [object];
const seen = [object];
while (todo.length > 0) {
const next = todo.pop();
if ((await this.propertiesFH.getValue([next, property], false) || {}).has) {
return true
}
const parents = this._objects.parents[next] || [];
for (const parent of parents) {
if (!seen.includes(parent)) {
todo.push(parent)
seen.push(parent)
}
}
}
return false
}
// NOT DONE
async knownProperty(object, property) {
object = this.toValue(object)
property = this.toValue(property)
if (property == 'property') {
const objectProps = await this.propertiesFH.getValue([object])
return !_.isEmpty(objectProps)
}
// go up the hierarchy
const todo = [object];
const seen = [object];
while (todo.length > 0) {
const next = todo.pop();
if ((await this.propertiesFH.getValue([next, property], false) || {}).has) {
return true
}
const parents = this._objects.parents[next] || [];
for (const parent of parents) {
if (!seen.includes(parent)) {
todo.push(parent)
seen.push(parent)
}
}
}
return false
}
learnWords(config, context) {
}
/*
ensureDefault(map, key, default) {
if (!this._objects[map][key]) {
this._objects[map][key] = default
}
return this._objects[map][key]
}
pushListNoDups(list, value) {
if (list.includes(value)) {
return
}
list.push(value)
}
ensureConcept(concept) {
ensureDefault(this.properties, concept, {})
ensureDefault(this.concepts, concept, [])
}
canDo(object, ability) {
this.ensureConcept(object)
this.pushListNoDups(this.ensureList('abilities', object), ability)
}
*/
isA(child, ancestor) {
// return this._objects.parents[child].includes(parent);
const todo = [child];
const seen = [child];
while (todo.length > 0) {
const next = todo.pop();
if (next == ancestor) {
return true
}
const parents = this._objects.parents[next] || [];
for (const parent of parents) {
if (!seen.includes(parent)) {
todo.push(parent)
seen.push(parent)
}
}
}
return false
}
rememberIsA(child, parent) {
this.digraph.add(child, parent)
if (!this._objects.parents[child]) {
this._objects.parents[child] = []
}
if (!this._objects.parents[child].includes(parent)) {
this._objects.parents[child].push(parent)
}
if (!this._objects.children[parent]) {
this._objects.children[parent] = []
}
if (!this._objects.children[parent].includes(child)) {
this._objects.children[parent].push(child)
}
if (!this._objects.concepts.includes(child)) {
this._objects.concepts.push(child)
}
if (!this._objects.concepts.includes(parent)) {
this._objects.concepts.push(parent)
}
if (this.isOperator(child) && this.isOperator(parent)) {
this.__config.addHierarchy(child, parent)
}
this.propertiesFH.ensureValue([child], {})
this.propertiesFH.ensureValue([parent], {})
}
isOperator(id) {
for (const bridge of this.__config.getBridges()) {
if (bridge.id == id) {
return true
}
}
return false
}
children(parent) {
return this._objects.children[parent] || []
}
conceptExists(concept) {
return this._objects.concepts.includes(concept)
}
set objects(objects) {
/*
this._objects = objects
objects.concepts = ['properties']
// object -> property -> {has, value}
objects.properties = {}
// property -> values
objects.property = {}
objects.parents = {}
objects.children = {}
objects.relations = []
objects.valueToWords = {}
this.propertiesFH = new Frankenhash(objects.properties)
*/
throw "delete me"
}
get objects() {
return this._objects
}
set config(config) {
this._config = config
}
get config() {
return this._config
}
}
module.exports = {
API,
Frankenhash,
}