tpmkms
Version:
Reusable libraries for Entodicton, a natural language to json converter
721 lines (682 loc) • 24.6 kB
JavaScript
const { debug, knowledgeModule, where, stableId } = require('./runtime').theprogrammablemind
const meta = require('./meta.js')
const gdefaults = require('./gdefaults.js')
const sdefaults = require('./sdefaults.js')
const asking = require('./asking.js')
const conjunction = require('./conjunction.js')
const articles = require('./articles.js')
const pos = require('./pos.js')
const negation = require('./negation.js')
const punctuation = require('./punctuation.js')
const stm = require('./stm.js')
const _ = require('lodash')
const { API } = require('./helpers/dialogues')
const { isMany, propertyToArray, words } = require('./helpers')
const dialogues_tests = require('./dialogues.test.json')
const { defaultObjectCheck, defaultContextCheck, indent, focus } = require('./helpers')
const pluralize = require('pluralize')
function warningIsANotImplemented(log, context) {
const description = 'WARNING from Dialogues KM: For semantics in order to handle sentences of type "x is y?", set the response to what you like.'
const match = `({context, hierarchy}) => hierarchy.isA(context.marker, 'is') && context.query && <other conditions as you like>`
const apply = `({context}) => <do stuff...>; context.evalue = <value>`
const input = indent(JSON.stringify(context, 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(indent(message, 4))
}
function warningSameNotEvaluated(log, one) {
const description = 'WARNING from Dialogues KM: For the "X is Y" type phrase implement a same handler.'
const match = `({context}) => context.marker == '${one.marker}' && context.same && <other conditions as you like>`
const apply = '({context}) => <do stuff... context.same is the other value>; context.sameWasProcessed = true'
const input = indent(JSON.stringify(one, 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(indent(message, 4))
}
function listorama(type) {
return [
{ context: [[type, 0], ['list', 0], [type, 0]], choose: 0 },
{ context: [[type, 1], ['list', 0], [type, 0]], choose: 0 },
{ context: [[type, 1], ['list', 0], [type, 1]], choose: 0 },
]
}
// TODO implement what / what did you say ...
const config = {
name: 'dialogues',
operators: [
"(<thatVerb|that> (verb/0))",
"([makeObject] (word))",
"([setIdSuffix] (word))",
"([resetIdSuffix])",
"(([queryable]) [is|] ([queryable|]))",
"([isQuery|] ([queryable]) ([queryable]))",
// who is the car owned by
"(([queryable]) [(<isEd|> ([isEdAble|]))])",
"(([isEdee])^ <isEdAble|> ([by] ([isEder])?))",
"([isEdee|])",
"([isEder|])",
"([why])",
"([reason])",
"(<what> ([whatAble|]))",
"([what:optional])",
"([be] ([briefOrWordy|]))",
"([([canBeQuestion])])",
"(([canBeQuestion/1,2]) <questionMark|>)",
"(([what]) [(<does|> ([doesAble|]))])",
"([canBeDoQuestion])",
"(<does|> ([canBeDoQuestion/0,1]))",
// make what is it work <<<<<<<<<<<<<<<<<<<<<<<, what is greg
// joe is a person the age of joe ...
//"arm them, what, the phasers"
//greg is a first name
"([yesno|])",
{ pattern: "([debug23])" },
"([to] ([toAble|]))",
],
bridges: [
{
id: 'thatVerb',
before: ['verb'],
bridge: `
{
...after[0],
verb: after[0],
that: operator,
generate: ['that', 'verb'],
localPriorities: { actLike: ["subordinatedVerb", 0] },
bridge_override: {
operator: after[0].marker,
bridge: '{
...bridge.subject,
postModifiers: ["conditions"],
modifiers: [],
generate: concatm("thatClause.", bridge.generate),
thatClause: bridge,
conditions: append(bridge.subject.conditions, [bridge])
}'
}
}`,
},
{
id: 'queryable',
children: [ 'negatable' ],
},
{
id: 'queryable',
level: 1,
isA: ['listable'],
},
{
id: 'makeObject',
bridge: "{ ...next(operator), object: after[0] }",
generatorp: async ({context, gp}) => `${context.word} ${await gp(context.object)}`,
semantic: async ({config, context, api}) => {
await api.makeObject({ context: context.object, config, types: [] })
}
},
{
id: 'setIdSuffix',
bridge: "{ ...next(operator), suffix: after[0] }",
generatorp: async ({context, gp}) => `${context.word} ${await gp(context.suffix)}`,
semantic: ({context, api}) => {
api.setIdSuffix(context.suffix.text)
}
},
{
id: 'resetIdSuffix',
semantic: ({context, api}) => {
api.setIdSuffix('')
}
},
{
id: "by",
bridge: "{ ...next(operator), object: after[0] }",
localHierarchy: [['unknown', 'isEder']],
optional: { 1: "{ marker: 'unknown', implicit: true, concept: true }", },
},
{ id: "debug23" },
{
id: "what",
optional: "{ ...next(operator), query: ['what'], determined: true }",
bridge: "{ ...after, query: ['what'], modifiers: ['what'], what: operator }"
},
{ id: "whatAble" },
// context.instance == variables.instance (unification)
{
where: where(),
id: "to",
level: 0,
isA: ['preposition'],
bridge: "{ ...next(operator), toObject: after[0] }",
generatorp: async ({context, gp}) => {
return `to ${await gp(context.toObject)}`
},
},
{ id: "toAble" },
{
id: "be",
level: 0,
bridge: `{
...next(operator),
form: 'infinitive',
type: after[0]
}`
},
{ id: "briefOrWordy" },
{ id: "yesno" },
{ id: "canBeQuestion", level: 0, bridge: "{ ...next(operator) }" },
{ id: "canBeQuestion", level: 1, bridge: "{ ...next(operator) }" },
{ id: "questionMark", level: 0, bridge: "{ ...before[0], query: [before.marker] }" },
{
id: "isEd",
level: 0,
localHierarchy: [['unknown', 'isEder'], ['unknown', 'isEdee']],
bridge: "{ number: operator.number, ...context, [context.subject].number: operator.number }"
},
// { id: "isEd", level: 0, bridge: "{ ...context }" },
{ id: "isEdAble", level: 0, bridge: "{ ...next(operator) }" },
{ id: "isEdAble", level: 1, bridge: "{ ...next(operator) }" },
{ id: "isEdee" },
{ id: "isEder" },
{
id: "is",
localHierarchy: [['unknown', 'queryable']],
bridge: "{ ...next(operator), one: { number: operator.number, ...before[0] }, two: after[0] }",
isA: ['verb'],
},
{
id: "is",
level: 1,
localHierarchy: [['unknown', 'queryable']],
bridge: "{ ...next(operator) }"
},
{
id: "isQuery",
localHierarchy: [['unknown', 'queryable']],
bridge: "{ ...operator, marker: operator('is', 1), one: after[0], two: after[1], query: true, generate: [operator, 'one', 'two'] }" ,
isA: ['verb'],
},
{ id: "canBeDoQuestion", level: 0, bridge: "{ ...next(operator) }" },
{ id: "canBeDoQuestion", level: 1, bridge: "{ ...next(operator) }" },
{ id: "canBeDoQuestion", level: 2, bridge: "{ ...next(operator) }" },
{ id: "doesAble", level: 0, bridge: "{ ...next(operator) }" },
{ id: "doesAble", level: 1, bridge: "{ ...next(operator), before: before[0] }" },
{ id: "does", level: 0, bridge: "{ query: true, what: operator.marker, ...context, number: operator.number, object.number: operator.number }*" },
{
id: "why",
level: 0,
bridge: "{ ...next(operator), pullFromContext: true, types: ['reason'], isResponse: true }"
},
{
id: "reason",
isA: ['theAble', 'queryable'],
},
],
words: {
"literals": {
"?": [{"id": "questionMark", "initial": "{}" }],
"who": [{"id": "what", "initial": "{ modifiers: [], query: true }" }],
"yes": [{"id": "yesno", "initial": "{ value: true }" }],
"no": [{"id": "yesno", "initial": "{ value: false }" }],
"brief": [{"id": "briefOrWordy", "initial": "{ value: 'brief' }" }],
"wordy": [{"id": "briefOrWordy", "initial": "{ value: 'wordy' }" }],
"does": [{"id": "does", "initial": "{ number: 'one' }" }],
"do": [{"id": "does", "initial": "{ number: 'many' }" }],
"is": [
{"id": "be", "initial": "{ form: 'infinitive' }" },
{"id": "is", "initial": "{ number: 'one' }" },
{"id": "isQuery", "initial": "{ number: 'one' }" },
{"id": "isEd", "initial": "{ number: 'one' }" }
],
"are": [
{"id": "is", "initial": "{ number: 'many' }" },
{"id": "isQuery", "initial": "{ number: 'many' }" },
{"id": "isEd", "initial": "{ number: 'many' }" }
],
}
},
floaters: ['query', 'associations'],
priorities: [
{ "context": [['is', 0], ['means', 0], ], "choose": [0] },
{ "context": [['is', 0], ['means', 0], ], "choose": [0] },
{ "context": [["isEdAble",0], ["isEd",0],], "choose": [0] },
{ "context": [['isEd', 0], ['means', 0], ], "choose": [0] },
{ "context": [['isEdAble', 0], ['is', 0], ], "choose": [0] },
{ "context": [['isEdAble', 0], ['is', 1], ], "choose": [0] },
],
hierarchy: [
['doubleQuote', 'queryable'],
['it', 'pronoun'],
['this', 'pronoun'],
['questionMark', 'punctuation'],
['a', 'article'],
['the', 'article'],
['theAble', 'queryable'],
['it', 'queryable'],
['what', 'queryable'],
['whatAble', 'queryable'],
['negatable', 'queryable'],
['is', 'canBeQuestion'],
['it', 'toAble'],
['this', 'queryable'],
['listable', 'theAble'],
],
debug: false,
version: '3',
generators: [
{
where: where(),
notes: "handle making responses brief",
match: ({context, objects, callId}) => (context.topLevel || context.isResponse) && objects.brief && !context.briefWasRun,
apply: async ({context, g}) => {
const focussed = focus(context)
context.briefWasRun = true
const result = await g(focussed)
context.briefWasRun = false
return result
},
priority: -2,
},
{
where: where(),
notes: "unknown ",
match: ({context}) => context.marker == 'unknown' && context.implicit,
apply: ({context}) => '',
},
{
where: where(),
notes: "unknown answer default response",
match: ({context}) => context.marker == 'answerNotKnown',
apply: ({context}) => `that is not known`,
},
{
where: where(),
notes: "be brief or wordy",
match: ({context}) => context.marker == 'be',
apply: ({context}) => `be ${context.type?.word}`,
},
/*
{
notes: 'paraphrase: plural/singular',
priority: -1,
where: where(),
match: ({context}) => context.paraphrase && context.word
apply: ({context, g}) => { return { "self": "your", "other": "my" }[context.value] },
},
*/
{
where: where(),
match: ({context}) => context.marker === 'error' && context.paraphrase,
apply: ({context, gp}) => gp(context.context)
},
{
where: where(),
match: ({context}) => context.marker === 'idontknow',
apply: ({context}) => "i don't know",
},
{
where: where(),
match: ({context}) => context.marker == 'yesno',
apply: ({context}) => context.value ? 'yes' : 'no',
priority: -1,
// debug: 'call11',
},
{
where: where(),
match: ({context}) => !context.paraphrase && context.evalue && context.evalue.marker == 'yesno',
apply: ({context}) => context.evalue.value ? 'yes' : 'no',
priority: -1,
},
{
where: where(),
notes: 'paraphrase a queryable response',
// || context.evalue.paraphrase -> when the evalue acts as a paraphrase value
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'queryable') && !context.isQuery && context.evalue && (!context.paraphrase || context.evalue.paraphrase),
apply: async ({context, g, gr}) => {
if (context.isResponse) {
return await gr(context.evalue)
} else {
return await g(context.evalue)
}
}
},
{
where: where(),
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'queryable') && !context.isQuery && context.isSelf && context.subject == 'my',
apply: ({context}) => `your ${context.word}`
},
{
where: where(),
match: ({context, hierarchy}) => ['it', 'what'].includes(context.marker) && context.paraphrase,
apply: ({context}) => `${context.word}`
},
{
where: where(),
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'queryable') && !context.isQuery && context.isSelf && context.subject == 'your',
apply: ({context}) => `my ${context.word}`
},
{
where: where(),
match: ({context, hierarchy}) => ['my', 'your'].includes(context.subject) && hierarchy.isA(context.marker, 'queryable') && context.paraphrase,
apply: ({context}) => `${context.subject} ${context.marker}`
},
{
where: where(),
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'queryable') && !context.isQuery && context.subject,
apply: ({context}) => `${context.subject} ${context.word}`
},
{
where: where(),
match: ({context}) => context.marker == 'name' && !context.isQuery && context.subject,
apply: ({context}) => `${context.subject} ${context.word}`
},
{
where: where(),
match: ({context}) => context.evalue && context.evalue.verbatim && !context.paraphrase,
apply: ({context}) => context.evalue.verbatim,
},
{
where: where(),
match: ({context}) => context.isResponse && context.verbatim && !context.paraphrase,
apply: ({context}) => context.verbatim,
priority: -1,
},
{
where: where(),
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'canBeQuestion') && context.paraphrase && context.topLevel && context.query,
apply: async ({context, gp}) => {
return `${await gp({...context, topLevel: undefined})}?`
},
priority: -1,
},
{
where: where(),
notes: "x is y",
match: ({context, hierarchy}) => { return hierarchy.isA(context.marker, 'is') && context.paraphrase },
apply: async ({context, g, gp}) => {
return `${await g({ ...context.one, paraphrase: true })} ${context.word} ${await gp(context.two)}`
}
},
{
where: where(),
notes: 'is with a response defined',
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'is') && context.evalue,
apply: async ({context, g, gs}) => {
const response = context.evalue;
const concept = response.concept;
if (concept) {
concept.paraphrase = true
concept.isSelf = true
const instance = await g(response.instance)
return `${await g(concept)} ${context.word} ${instance}`
} else {
if (Array.isArray(response)) {
return `${await gs(response)}`
} else {
return `${await g(response)}`
}
}
}
},
{
where: where(),
notes: 'x is y (not a response)',
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'is') && !context.evalue,
apply: async ({context, g, gp, gr, callId}) => {
if ((context.two.evalue || {}).marker == 'answerNotKnown') {
return await g(context.two.evalue)
}
if (!context.isResponse) {
return `${await gp(context.one)} ${isMany(context.one) || isMany(context.two) || isMany(context) ? "are" : "is"} ${await g(context.two)}`
}
function hasFocus(property) {
if (context.focusableForPhrase) {
return true
}
if (context.focusable && context.focusable.includes(property) && context[property].focus) {
return true
}
}
let focus;
if (context.two.hierarchy && !isMany(context.two)) {
focus = 'one'
} else if (context.one.focusableForPhrase && !context.two.focusableForPhrase) {
focus = 'one'
} else if (!context.one.focusableForPhrase && context.two.focusableForPhrase) {
focus = 'two'
} else if (hasFocus('two')) {
focus = 'two'
} else {
focus = 'one'
}
// greg101
if (focus == 'one') {
let number = 'one'
if (context.two.evalue && isMany(context.two.evalue)) {
number = 'many'
}
return `${await g(context.two)} ${number == 'many' ? "are" : "is"} ${await gp({ ...context.one, number })}`
// return `${await g(context.two)} ${isMany(context.one) || isMany(context.two) || isMany(context) ? "are" : "is"} ${await gp(context.one)}`
} else {
// TODO fix this using the assumed and that whole mess. change isResponse to useValue
if (context.isResponse) {
return `${await gp(context.one, { assumed: { responding: true } })} ${isMany(context.one) || isMany(context.two) || isMany(context) ? "are" : "is"} ${await g(context.two)}`
} else {
return `${await gp(context.one)} ${isMany(context.one) || isMany(context.two) || isMany(context) ? "are" : "is"} ${await gr(context.two)}`
}
}
},
},
],
semantics: [
{
where: where(),
todo: 'debug23',
match: ({context}) => context.marker == 'debug23',
apply: ({context, hierarchy}) => {
debugger // eslint-disable-line no-debugger
debugger // eslint-disable-line no-debugger
},
},
{
where: where(),
todo: 'be brief or wordy',
match: ({context}) => context.marker == 'be',
apply: ({context, api}) => {
api.setBrief( context.type.value == 'brief' )
},
},
{
where: where(),
match: ({context}) => context.marker === 'error',
apply: async ({context, g, gp}) => {
context.evalue = "That is not known"
if (context.reason) {
context.evalue += ` because ${await g(context.reason)}`
}
context.isResponse = true
}
},
{
where: where(),
notes: 'what x is y?',
/*
what type is object (what type is pikachu) (the type is typeValue)
what property is object (what color are greg's eyes)
object is a type (is greg a human) // handled by queryBridge
*/
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'is') && context.query,
apply: async ({context, s, log, km, objects, e}) => {
const one = context.one;
const two = context.two;
let concept, value;
if (one.query) {
concept = one;
value = two;
} else {
concept = two;
value = one;
}
// km('dialogues').api.remember(concept)
// TODO wtf is the next line?
value = JSON.parse(JSON.stringify(value))
const instance = await e(value)
if (false && instance.evalue) {
km('stm').api.remember({ context: value })
}
if (instance.verbatim) {
context.evalue = { verbatim: instance.verbatim }
context.isResponse = true
return
}
// instance.focusable = ['one', 'two']
// concept = JSON.parse(JSON.stringify(value))
concept = _.cloneDeep(value)
concept.isQuery = undefined
// greg101
// instance.focusableForPhrase = true
instance.focus = true
if (concept.hierarchy) {
concept.focusableForPhrase = true
}
// concept.focus = true
const many = isMany(concept) || isMany(instance)
const evalue = {
"default": true,
"marker": "is",
"one": concept,
"two": instance,
"focusable": ['two', 'one'],
"word": many ? "are" : "is",
"number": many ? "many" : undefined,
}
context.evalue = evalue
context.isResponse = true
}
},
{
where: where(),
notes: 'x is y?',
match: ({context, hierarchy}) => hierarchy.isA(context.marker, 'is') && context.query,
apply: ({context, log}) => {
warningIsANotImplemented(log, context)
context.evalue = {
verbatim: "I don't know"
}
context.isResponse = true
}
},
// statement
{
/*
a car is a vehicle isA
cars are vehicles isA
the ford is a car isA
a name is car isA (reverse)
the formula is x + 5 not isA
x is 5 not isA
worth is price * quantity not isA
the name is cars not isA
*/
where: where(),
notes: 'x is y. handles x is a kind of y or x = y in the stm',
match: ({context}) => context.marker == 'is' && !context.query && context.one && context.two,
apply: async ({context, s, log, api, kms, config}) => {
// const oneZero = { ...context.one }
// const twoZero = { ...context.two }
const one = context.one;
const two = context.two;
one.same = two;
const onePrime = await s(one)
if (!onePrime.sameWasProcessed) {
warningSameNotEvaluated(log, one)
} else {
if (onePrime.evalue) {
context.evalue = onePrime.evalue
context.isResponse = true
}
}
one.same = undefined
let twoPrime;
if (!onePrime.sameWasProcessed) {
two.same = one
twoPrime = await s(two)
if (!twoPrime.sameWasProcessed) {
warningSameNotEvaluated(log, two)
} else {
if (twoPrime.evalue) {
context.evalue = twoPrime.evalue
}
}
two.same = undefined
}
// if not isA add to stm
if (!onePrime.sameWasProcessed && !twoPrime.sameWasProcessed) {
for (const child of propertyToArray(one)) {
await api.makeObject({ context: child, config, types: context.two.types || [] })
kms.stm.api.setVariable(child.value, two)
kms.stm.api.remember({ context: child, value: two })
}
}
}
},
{
where: where(),
notes: 'get variable from stm',
match: ({context, kms}) => context.evaluate && kms.stm.api.getVariable(context),
// match: ({context, kms}) => context.evaluate,
priority: -1,
apply: async ({context, kms, e}) => {
const api = kms.stm.api
context.value = api.getVariable(context)
if (context.value && context.value.marker) {
context.evalue = await e(context.value)
}
context.focusableForPhrase = true
}
},
],
};
function initializer({objects, config, isModule}) {
/* TODO add this beck in. some stuff from config needs to be here
config.addArgs((args) => ({
e: (context) => config.api.getEvaluator(args.s, args.log, context),
}))
*/
config.addArgs(({config, api, isA}) => ({
toScopedId: (context) => {
return api('dialogues').toScopedId(context)
},
addWords: (id, word, additional) => {
for (const props of words(word, { ...additional })) {
config.addWord(props.word, { id, initial: JSON.stringify(props) })
}
},
values: propertyToArray,
}))
objects.mentioned = []
objects.variables = {
}
if (isModule) {
} else {
config.addWord("canbedoquestion", { id: "canBeDoQuestion", "initial": "{}" })
config.addWord("doesable", { id: "doesAble", "initial": "{}" })
}
}
knowledgeModule( {
config,
includes: [articles, gdefaults, sdefaults, conjunction, asking, pos, negation, stm, meta, punctuation],
initializer,
api: () => new API(),
module,
description: 'framework for dialogues',
newWay: true,
test: {
name: './dialogues.test.json',
contents: dialogues_tests,
checks: {
...defaultObjectCheck(['idSuffix']),
context: [
defaultContextCheck({ extra: ['one', 'two'] }),
]
},
},
})