tpmkms
Version:
Reusable libraries for Entodicton, a natural language to json converter
733 lines (709 loc) • 29.6 kB
JavaScript
const { knowledgeModule, where, debug } = require('./runtime').theprogrammablemind
const { defaultContextCheck, words } = require('./helpers')
const dialogues = require('./dialogues')
const meta = require('./meta')
const concept = require('./concept')
const instance = require('./properties.instance.json')
const properties_tests = require('./properties.test.json')
const { API } = require('./helpers/properties')
const { chooseNumber } = require('./helpers.js')
const pluralize = require('pluralize')
// TODO what is wendy's cat's name
// TODO blue is a colour my eyes are blue what color are my eyes
// TODO for my have a way to set context with current my and its changable
// TODO crew member is a type of person
// TODO captain is a type of job
// TODO do you know any captains / who are the captains
// TODO you hate brocoli do you want some brocoli
//
// TODO the photon torpedoes are armed <- have a learning mode which is more flexible?
// TODO mccoy is a crew member
// TODO status can be armed or not armed (only)
// TODO my -> have a dialog thing
// TODO pretend you are spock what is your name stop pretending what is your name
// TODO who are the crew members / who are they
// TODO the/a means put it in the context for reference
// TODO the crew members are sss abc are crew members
// TODO who are they / what are they
// TODO kirk: are you a captain
// TODO macro for verb forms -> arm x | go to y | i like x
// TODO READONLY
// TODO pokemon what is the attack/i own a pikachu/ what do i own
// TODO response == true and isResponse == true are mixed do one and not both
// own is xfx owner ownee
/*
V1
"mccoy's rank is doctor",
"mccoy is a doctor",
if class is a value of property then class is a type of property
V2
"mccoy's rank is doctor",
infer doctor is a type of rank
*/
// your name is greg -> greg is value
// you are a captain -> a captain is class
//
// duck typing:
//
// 1. Make your properties instances of 'property' add ['myProperty', 'property'] to the hierarchy
// 2. Make your objects an instance of 'object' add ['myObject', 'object'] to the hierarchy
// 3. Add semantics for getting the value
// [
// ({objects, context, args, hierarchy}) =>
// hierarchy.isA(context.marker, 'property') &&
// args({ types: ['myObjectType'], properties: ['object'] }) && context.evaluate,
// ({objects, context}) => {
// context.value = "value" // set the value here somehow
// }
// ],
//
// property value has four cases.
// Property has known value { has: true, value }
// Property exists but has unknown value { has: true }
// Property exists and the object does not have it { has: false }
// Property is not know to exist undefined
//
// value is (has, value)
const template = {
fragments: [
"the property1 of object1 is value1",
],
configs: [
],
}
const api = new API();
const config = {
name: 'properties',
associations: {
positive: [
{ context: [["the",0],["unknown",0],["propertyOf",0],["unknown",0],["is",0],["unknown",0]], choose: { index: 4, increment: true } },
{ context: [["objectPrefix",0],["unknown",0]], choose: 1 },
{ context: [["unknown",0],["propertyOf", 0],["unknown",0]], choose: 0 },
]
},
operators: [
"([hierarchyAble|])",
{ pattern: "([propertyRelation|])" }, // , scope: 'development' },
"(([property]) <([propertyOf|of] ([object]))>)",
"(<whose> ([property]))",
// "((modifier) [modifies] (concept))",
// "([concept])",
"([readonly])",
"(<objectPrefix|> ([property]))",
"(<(([object]) [possession|])> ([property|]))",
"(([object|]) [have|] ([property|]))",
"(<doesnt|> ([have/0]))",
"(([xfx]) <([between] (words))>)",
// "(([have/1]) <questionMark|>)",
// the plural of cat is cats what is the plural of cat?
// does greg have ears (yes) greg does not have ears does greg have ears (no)
],
/*
[('is', 0), ('property', 0)]-('property', 0)
got from ->
['property', 'queryable']
"(([queryable]) [is|is,are] ([queryable|]))"
<> implies output is property/1 so that should be used to put propertyOf/0 below property/1
"(([property]) <([propertyOf|of] ([object]))>)",
*/
hierarchy: [
['unknown', 'hierarchyAble'],
['what', 'object'],
],
bridges: [
{
id: 'propertyRelation',
// scope: 'development',
words: words('propertyRelation'),
evaluator: ({context, api, objects, toList}) => {
context.evalue = toList(objects.relations)
context.evalue.isResponse = true
context.evalue.paraphrase = false
}
},
{
id: 'xfx',
isA: ['queryable'],
},
{
id: 'between',
isA: ['preposition'],
bridge: "{ ...next(operator), arguments: after[0] }"
},
{ id: 'between', level: 1, bridge: "{ ...before[0], arguments: operator.arguments }" },
{
id: 'hierarchyAble',
isA: ['queryable'],
},
{
id: "readonly",
isA: ['queryable'],
},
// { id: "concept", level: 0, bridge: "{ ...next(operator) }" },
// the cars dont have wings
// greg doesnt have wings
// { id: "doesnt", level: 0, bridge: "{ ...context, number: operator.number, negation: true }*" },
// { id: "doesnt", level: 0, bridge: "{ ...context, number: 'one', negation: true }*" },
{ id: "doesnt", level: 0, bridge: "{ ...context, number: operator.number, object.number: operator.number, negation: true }*" },
{
id: "have",
level: 0,
isA: ['canBeDoQuestion', 'canBeQuestion'],
localHierarchy: [['property', 'queryable'], ['property', 'theAble'], ['property', 'unknown'], ['object', 'unknown']],
bridge: "{ ...next(operator), object: { number: operator.number, ...before }, property: after[0], do: { left: 'object', right: 'property' } }"
},
{
id: "have",
level: 1,
localHierarchy: [['property', 'queryable'], ['property', 'theAble'], ['property', 'unknown']],
bridge: "{ ...next(operator) }"
},
{
id: "property",
words: ['properties'],
isA: ['queryable', 'theAble'],
level: 0,
},
{
id: "object",
isA: ['queryable', 'theAble', 'listable'],
level: 0,
},
{
id: "possession",
level: 0,
localHierarchy: [['property', 'queryable'], ['property', 'theAble'], ['property', 'unknown'], ['object', 'unknown']],
inverted: true,
bridge: "{ ...next(operator), possession: true, object: before[0], objects: before }"
},
// greg44 { id: "possession", level: 1, inverted: true, bridge: "{ ...after[0], object: operator.object, possession: true, objects: append(default(after[0].objects, after), operator.objects), marker: operator('property', 0) }" },
{
id: "possession",
level: 1,
localHierarchy: [['property', 'queryable'], ['property', 'theAble'], ['property', 'unknown'], ['object', 'unknown']],
inverted: true,
bridge: "{ ...after[0], object: operator.object, possession: true, objects: append(default(after[0].objects, after), operator.objects), marker: after.marker, types: append(after[0].types, ['property']) }"
},
// TODO make object be after[0] that makes more sense
// { id: "possession", level: 1, inverted: true, bridge: "{ ...after[0], object: after[0], objects: append(default(after[0].objects, after), operator.objects), marker: operator('property', 0) }" },
{
id: "propertyOf",
level: 0,
isA: ['preposition'],
localHierarchy: [['property', 'queryable'], ['property', 'theAble'], ['property', 'unknown'], ['object', 'unknown']],
bridge: "{ ...next(operator), object: after[0], objects: after }"
},
{
id: "propertyOf",
level: 1,
localHierarchy: [['property', 'queryable'], ['property', 'theAble'], ['property', 'unknown']],
bridge: "{ ...before[0], propertyOf: true, object: operator.object, objects: append(default(before[0].objects, before), operator.objects) }"
},
{
id: "whose",
level: 0,
isA: ['object'],
bridge: '{ ...after[0], query: true, whose: "whose", modifiers: append(["whose"], after[0].modifiers)}'
},
{
id: "objectPrefix",
level: 0,
localHierarchy: [['property', 'queryable'], ['property', 'theAble'], ['property', 'unknown']],
bridge: '{ ...after[0], object: operator, objects: [after[0], operator] }'
},
],
words: {
literals: {
"<<possession>>": [{ id: 'possession', initial: "{ value: 'possession' }" }],
"'s": [{ id: 'possession', initial: "{ value: 'possession' }" }],
"have": [{ id: 'have', initial: "{ doesable: true, number: 'many' }" }],
"has": [{ id: 'have', initial: "{ doesable: true, number: 'one' }" }],
"dont": [{ id: 'doesnt', initial: "{ number: 'many' }" }],
"doesnt": [{ id: 'doesnt', initial: "{ number: 'one' }" }],
// "my": [{ id: 'objectPrefix', initial: "{ value: 'other' }" }],
// "your": [{ id: 'objectPrefix', initial: "{ value: 'self' }" }],
},
patterns: [
{ "pattern": ["'s"], defs: [{id: "possession", uuid: '1', initial: "{ value: 'possession' }" }]},
],
},
priorities: [
{ "context": [['list', 0], ['between', 0], ], "choose": [0] },
{ "context": [['list', 1], ['between', 0], ], "choose": [0] },
{ "context": [['between', 1], ['is', 0], ], "choose": [0] },
{ "context": [['hierarchyAble', 0], ['is', 0], ], "choose": [0] },
{ "context": [['hierarchyAble', 0], ['a', 0], ['is', 0], ], "choose": [0] },
{ "context": [['does', 0], ['have', 1], ], "choose": [0] },
{ "context": [['doesnt', 0], ['does', 0], ['have', 0], ], "choose": [0] },
{ "context": [['not', 0], ['is', 0], ['propertyOf', 0], ], "choose": [0] },
{ "context": [['objectPrefix', 0], ['is', 0], ['questionMark', 0], ], "choose": [0] },
{ "context": [['possession', 0], ['is', 0], ['questionMark', 0], ], "choose": [0] },
{ "context": [['possession', 1], ['is', 0], ['questionMark', 0], ], "choose": [0] },
{ "context": [['a', 0], ['have', 0], ], "choose": [0] },
{ "context": [['have', 0], ['does', 0], ], "choose": [0] },
{ "context": [['what', 0], ['is', 0], ['possession', 0], ['propertyOf', 0], ], "choose": [0] },
{ "context": [['possession', 1], ['is', 0], ], "choose": [0] },
{ "context": [['objectPrefix', 0], ['is', 0], ], "choose": [0] },
{ "context": [['property', 0], ['is', 0], ['what', 0], ['propertyOf', 0], ['article', 0], ], "choose": [0] },
{ "context": [['propertyOf', 1], ['is', 0], ], "choose": [0] },
{ "context": [['article', 0], ['propertyOf', 0], ], "choose": [0] },
{ "context": [['property', 0], ['article', 0], ['propertyOf', 0], ], "choose": [0] },
{ "context": [['have', 0], ['questionMark', 0], ], "choose": [0] },
{ "context": [['have', 0], ['questionMark', 0], ['have', 1], ['is', 1], ], "choose": [0] },
{ "context": [['what', 0], ['is', 0], ['objectPrefix', 0], ], "choose": [0] },
],
generators: [
{
notes: 'expression with constraints',
where: where(),
match: ({context}) => context.constraints && context.paraphrase,
apply: async ({context, g}) => {
// TODO assume one constaints deal with more in the future
const constraint = context.constraints[0]
const constrained = Object.assign({}, constraint.constraint)
const property = Object.assign({}, context)
delete property.constraints
constrained[constraint.property] = property
constrained.paraphrase = true
const paraphrase = Object.assign({}, constraint.paraphrase)
paraphrase.paraphrase = true;
paraphrase[constraint.property] = property
if (false && context.isResponse) {
return await g({...constraint.paraphrase, paraphrase: true})
} else {
return await g(constrained)
}
},
},
{
where: where(),
match: ({context}) => context.marker == 'xfx',
apply: async ({context, g}) => `${context.word} between ${await g(context.arguments)}`
},
{
notes: 'add possession ending',
priority: -1,
where: where(),
match: ({context}) => context.paraphrase && context.possessive,
apply: async ({context, g}) => {
context.possessive = false
const phrase = await g(context)
context.possessive = true
if (phrase.endsWith('s')) {
return `${phrase}'`
} else {
return `${phrase}'s`
}
}
},
{
where: where(),
match: ({context}) => context.marker == 'objectPrefix' && context.value == 'other' && context.paraphrase,
apply: ({context}) => `my`
},
{
where: where(),
match: ({context}) => context.marker == 'objectPrefix' && context.value == 'other',
apply: ({context}) => `your`
},
{
where: where(),
match: ({context}) => context.marker == 'objectPrefix' && context.value == 'self' && context.paraphrase,
apply: ({context}) => `your`
},
{
where: where(),
match: ({context}) => context.marker == 'objectPrefix' && context.value == 'self',
apply: ({context}) => `my`
},
{
notes: 'negative do questions',
where: where(),
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'canBeDoQuestion') && context.paraphrase && context.negation,
apply: async ({context, g}) => {
return `${await g(context[context.do.left])} doesnt ${pluralize.plural(context.word)} ${await g(context[context.do.right])}`
},
},
{
notes: 'do questions',
// debug: 'call9',
where: where(),
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'canBeDoQuestion') && context.paraphrase && context.query && context.do,
apply: async ({context, g}) => {
const right = context['do'].right
if (context[right].query) {
const left = context['do'].left
return `${await g(context[right])} ${chooseNumber(context[right], "does", "do")} ${await g(context[left])} ${context.word}`
} else {
// the marker is the infinite form
return `${chooseNumber(context[context.do.left], "does", "do")} ${await g(context[context.do.left])} ${context.marker} ${await g(context[context.do.right])}`
}
},
},
{
where: where(),
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'canBeDoQuestion') && context.paraphrase && !context.query && !context.interpolate,
apply: async ({context, g}) => {
return `${await g(context.object)} ${context.word} ${await g(context.property)}`
}
},
{
notes: 'the property of object',
where: where(),
// match: ({context}) => context.paraphrase && context.modifiers && context.object,
match: ({context}) => context.paraphrase && !context.possession && context.object && !context.interpolate,
apply: async ({context, g, gs}) => {
const base = { ...context }
base.object = undefined;
if (context.object.marker == 'objectPrefix') {
return `${await g(context.object)} ${await g(base)}`
} else {
if (context.objects) {
const gObjects = []
for (const object of context.objects) {
gObjects.push(await g({...object, paraphrase: true}))
}
return await gs(gObjects, ' of ')
} else {
// TODO make paraphrase be a default when paraphrasing?
return `${await g(base)} of ${await g({...context.object, paraphrase: true})}`
}
}
},
},
{
// ({context, hierarchy}) => hierarchy.isA(context.marker, 'property') && context.object && !context.value && !context.evaluate,
where: where(),
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'property') && context.object && !context.possession && !context.evaluate && !context.object.marker == 'objectPrefix',
apply: async ({context, g}) => {
const property = Object.assign({}, context, { object: undefined })
return `${await g(property)} of ${await g({ ...context.object, paraphrase: true })}`
}
},
{
notes: "object's property",
where: where(),
// match: ({context}) => context.paraphrase && !context.modifiers && context.object,
match: ({context}) => !context.modifiers && context.object && !context.interpolate,
apply: async ({context, g, gs}) => {
if (context.evalue) {
return await g(context.evalue)
} else if (context.objects) {
const objects = [ ...context.objects ]
objects.reverse()
let phrase = ''
let separator = ''
for (let i = 0; i < objects.length-1; ++i) {
phrase = phrase + separator + await g({...objects[i], paraphrase: context.paraphrase, possessive: true})
separator = ' '
}
phrase = phrase + separator + await g({...objects[objects.length-1], paraphrase: context.paraphrase})
return phrase
} else {
const base = { ...context }
base.object = undefined; // TODO make paraphrase be a default when paraphrasing?
if (context.object.marker == 'objectPrefix') {
return `${await g(context.object)} ${await g(base)}`
} else {
return `${await g({...context.object, paraphrase: context.paraphrase})}'s ${await g(base)}`
}
}
},
},
],
semantics: [
{
where: where(),
match: ({context}) => context.marker == 'concept' && context.same,
apply: async (args) => {
const {context} = args
await args.makeObject({ ...args, context: context.same })
context.sameWasProcessed = true
}
},
{
// TODO maybe use the dialogue management to get params
notes: 'wants is xfx between wanter and wantee',
where: where(),
match: ({context}) => context.same && context.same.marker == 'xfx',
// debug: 'call3',
apply: ({context, km, config}) => {
const papi = km('properties').api
const singular = pluralize.singular(context.word)
const plural = pluralize.plural(context.word)
const args = context.same.arguments.value;
papi.createBinaryRelation(config, singular, [singular, plural], args[0].word, args[1].word)
},
priority: -1,
},
{
notes: 'marking something as readonly',
where: where(),
match: ({context}) => context.marker == 'readonly' && context.same,
apply: ({context, km, objects}) => {
km('properties').api.setReadOnly([context.same.value])
context.sameWasProcessed = true
}
},
/*
"objects": {
"greg": {
"age": {
"marker": "unknown",
"types": [
"unknown"
],
"unknown": true,
"value": "23",
"word": "23",
"response": true
}
}
*/
{
notes: 'crew members. evaluate a concepts to get instances',
where: where(),
match: ({context, hierarchy, api}) =>
hierarchy.isA(context.marker, 'concept') &&
context.evaluate &&
!(context.types || []).includes('property') &&
// !context.value && // greghere
!context.ordinal &&
(!context.objects || context.objects.length !== 2 || !context.objects[1].instance) &&
(api.objects && api.objects.children && api.objects.children[context.marker]) &&
!context.evaluate.toConcept,
apply: ({context, objects, api, km}) => {
const values = api.objects.children[context.marker]
const phrases = values.map( (value) => km('concept').api.getWordForValue(value) )
// context.focusableForPhrase = true
context.hierarchy = true
context.evalue = {
marker: 'list',
listable: true,
// value: api.objects.children[context.marker]
value: phrases,
}
}
},
{
notes: 'greg has eyes',
where: where(),
match: ({context}) => context.marker == 'have' && !context.query,
apply: ({context, objects, api}) => {
if (context.negation) {
api.setProperty(pluralize.singular(context.object.value), pluralize.singular(context.property.value), null, false)
} else {
api.setProperty(pluralize.singular(context.object.value), pluralize.singular(context.property.value), null, true)
}
context.sameWasProcessed = true
}
},
{
notes: 'greg has eyes?',
where: where(),
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'have') && context.query,
apply: async ({context, g, api, objects}) => {
const object = pluralize.singular(context.object.value);
const property = pluralize.singular(context.property.value);
context.isResponse = true
if (!await api.knownObject(object)) {
context.verbatim = `There is no object named ${await g({...context.object, paraphrase: true})}`
return
}
if (!await api.hasProperty(object, property)) {
context.evalue = {
marker: 'yesno',
value: false,
}
} else {
context.evalue = {
marker: 'yesno',
value: true,
}
return
}
}
},
{
notes: 'set the property of an object',
where: where(),
// match: ({context}) => context.marker == 'property' && context.same && context.object,
match: ({context, hierarchy, uuid}) => hierarchy.isA(context.marker, 'property') && context.same && context.objects && !context[`disable${uuid}`],
apply: async (args) => {
const {context, fragments, objects, km, api, log, s, uuid} = args
// greg55
const objectContext = context.object;
const propertyContext = context;
const objectId = context.object.value
await api.makeObject({ ...args, context: objectContext })
await api.makeObject({ ...args, context: propertyContext })
// const propertyId = context.value
/*
const propertyId = context.marker
if (context.marker != context.value) {
}
*/
// const propertyId = context.marker
/*
// greg HERE
*/
propertyContext[`disable${uuid}`] = true
const propertyId = (await km("dialogues").api.evaluateToConcept(propertyContext, context, log, s)).evalue;
try{
// greg
// api.makeObject({config, context: objectContext, doPluralize: false})
// api.addWord(propertyContext)
// api.addWord(objectContext)
// propertyContext.objects = null;
api.setProperty(pluralize.singular(objectId), pluralize.singular(propertyId), context.same, true)
context.sameWasProcessed = true
} catch (e) {
log(`Error processing set property of an object: ${e}`)
const config = km('properties')
const fragment = await fragments("the property1 of object1 is value1")
const value = await api.getProperty(objectId, propertyId)
if (value.value == context.same.value) {
context.evalue = [
{ marker: 'yesno', value: true, paraphrase: true },
]
context.isResponse = true
context.sameWasProcessed = true
} else {
const mappings = [
{
where: where(),
match: ({context}) => context.value == 'property1',
apply: ({context}) => Object.assign(context, { word: propertyContext.word, value: propertyContext.value, paraphrase: true }),
},
{
where: where(),
match: ({context}) => context.value == 'object1',
apply: ({context}) => {
Object.assign(context, { word: objectContext.word, value: objectContext.value, paraphrase: true })
},
},
{
where: where(),
match: ({context}) => context.value == 'value1',
apply: ({context}) => Object.assign(context, value),
},
]
// run the query 'the property of object' then copy that here and template it
context.evalue = [
{ marker: 'yesno', value: false, paraphrase: true },
]
context.evalue = context.evalue.concat(await fragment.instantiate(mappings))
context.evalue.forEach( (r) => r.paraphrase = true )
context.isResponse = true
context.sameWasProcessed = true
}
}
}
},
{
notes: 'get/evaluate a property',
where: where(),
match: ({context, hierarchy}) =>
hierarchy.isA(context.marker, 'property') &&
context.evaluate &&
context.objects &&
!context.evaluate.toConcept, // && !context.value,
// greghere
// match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'property') && context.evaluate,
apply: async ({callId, context, api, kms, objects, g, s, log, recall}) => {
const toDo = [ ...context.objects ]
async function toValue(objectContext) {
if (!objectContext.value) {
return objectContext;
}
let objectValue = kms.stm.api.getVariable(objectContext);
if (!objectValue) {
objectValue = objectContext.value
}
if (!await api.knownObject(objectValue)) {
objectContext.verbatim = `There is no object named "${await g({...objectContext, paraphrase: true})}"`
return
}
return objectValue
}
let currentContext = toDo.pop()
let currentValue = await toValue(currentContext)
while (toDo.length > 0) {
const nextContext = toDo.pop()
const nextValue = await toValue(nextContext)
if (!nextValue) {
// TODO maybe this I aware so it can say "I don't know about blah..." and below
// if (currentContext.unknown || !currentContext.value) {
if (!api.conceptExists(currentContext.value)) {
// api.conceptExists(currentContext)
const objectPhrase = await g({...currentContext, paraphrase: true})
context.verbatim = `What "${objectPhrase}" means is unknown`
return
}
const propertyPhrase = await g({...nextContext, paraphrase: true})
const objectPhrase = await g({...currentContext, paraphrase: true})
context.verbatim = `There is no interpretation for "${propertyPhrase} of ${objectPhrase}"`
return
}
let fromMentions
if (!await api.knownProperty(currentContext, nextContext)) {
fromMentions = await recall({ context: nextContext, all: nextContext.number == 'many', frameOfReference: currentContext })
if (!fromMentions) {
context.verbatim = `There is no property ${await g({...nextContext, paraphrase: true})} of ${await g({...currentContext, paraphrase: true})}`
return
}
}
if (fromMentions) {
currentContext = fromMentions
currentValue = fromMentions // TODO not sure what is right here so just do something and fix when actually needed SOP
} else {
currentContext = await api.getProperty(currentValue, nextValue, g)
currentValue = currentContext.value
}
}
context.focusable = ['object[0]']
context.evalue = currentContext
context.object = undefined;
}
}
]
};
function initializer({objects, config, isModule}) {
// debugger
config.addArgs(({config, api, isA}) => ({
makeObject: api('properties').makeObject,
}))
}
knowledgeModule( {
config,
api: () => new API(),
includes: [concept, meta, dialogues],
initializer,
module,
description: 'properties of objects',
test: {
name: './properties.test.json',
contents: properties_tests,
checks: {
context: [
defaultContextCheck({ marker: 'property', exported: true, extra: ['object', 'objects'] }),
defaultContextCheck({ marker: 'possession', exported: true, extra: ['object', 'objects'] }),
defaultContextCheck({ marker: 'propertyOf', exported: true, extra: ['object', 'objects'] }),
defaultContextCheck({ marker: 'objectPrefix', exported: true, extra: ['object', 'objects'] }),
defaultContextCheck()
],
/*
objects: [
'relations'
]
*/
},
include: {
words: true,
operators: true,
bridges: true,
}
},
instance,
template,
})