theprogrammablemind
Version:
334 lines (312 loc) • 11.8 kB
JavaScript
const { args: contextArgs, normalizeGenerator, normalizeSemantic } = require('./helpers')
const Lines = require('../lines')
const helpers = require('./helpers')
class Semantic {
// constructor ({match, apply, uuid, index, km, notes}) {
constructor (semantic) {
semantic = normalizeSemantic(semantic)
const { match, apply, uuid, index, km, notes, priority, debug, where, source, applyWrapped, property, oneShot, id, tied_ids, isQuestion } = semantic
this.matcher = match
this._apply = apply
this._applyWrapped = applyWrapped
this.property = property
this.uuid = uuid
this.index = index
this.km = km
this.priority = priority || 0
this.notes = notes
this.callId = debug
this.where = where
this.source = source
this.oneShot = oneShot
this.isQuestion = isQuestion
this.id = id
this.tied_ids = tied_ids || []
}
toLabel () {
const where = (this.where) ? `where: "${this.where}"` : ''
if (!this.km) {
return where
}
return `KM '${this.km}' ordinal: ${this.index} ${where}`
}
toString () {
return `Semantic(${this.matcher}, ${this._applyWrapped || this._apply})${this.property ? `\nsee the ${this.property} property` : ''}`
}
getAPI (config) {
if (config && config.getAPI) {
return config.getAPI(this.uuid)
}
}
getAPIs (config) {
if (config && config._api && config._api.multiApi) {
return config._api.apis
}
}
fixUpArgs (args, context) {
args.uuid = this.uuid
args.callId = args.calls.current()
const objects = args.getObjects(this.uuid)
args.objects = objects
args.global = objects
const config = args.config
args.api = this.getAPI(config)
args.apis = this.getAPIs(config)
args.args = contextArgs(context, args.hierarchy)
args.context = context
let n = (id) => id
if (config && 'nsToString' in config) {
n = (id) => config.nsToString(id)
}
args.n = n
args.uuid = this.uuid
Object.assign(args, (args.getUUIDScoped || (() => { return {} }))(this.uuid))
}
async matches (args, context, options = {}) {
this.fixUpArgs(args, context)
const matches = await this.matcher(args)
if (matches && (options.debug || {}).match || args.callId == this.callId) {
// next line is the matcher
debugger // eslint-disable-line no-debugger
await this.matcher(args)
}
return matches
}
async apply (args, context, s, options = {}) {
const { config } = args
if (config && config.debugLoops) {
console.log('apply', this.toLabel())
}
if (args.calls && config && args.calls.stack.length > config.maxDepth) {
throw new Error(`Max depth of ${config.maxDepth} for calls has been exceeded. maxDepth can be set on the config object. To see the calls run with the --dl or set the debugLoops property on the config`)
}
const contextPrime = Object.assign({}, context)
this.fixUpArgs(args, contextPrime)
if ((options.debug || {}).apply || args.callId == this.callId) {
debugger // eslint-disable-line no-debugger
}
if (args.breakOnSemantics) {
debugger // eslint-disable-line no-debugger
}
await this._apply(args)
return contextPrime
}
}
class Semantics {
constructor (semantics, logs = [], options = {}) {
let index = -1
semantics = semantics.map((semantic) => {
if (semantic instanceof Semantic) {
return semantic
} else {
index += 1
return new Semantic({ km: options.km, index, ...normalizeSemantic(semantic) })
}
})
const priorityToSemantics = {}
const add = (priority, value) => {
if (!priorityToSemantics[priority]) {
priorityToSemantics[priority] = []
}
priorityToSemantics[priority].push(value)
}
for (const semantic of semantics) {
const priority = semantic.priority
add(priority, semantic)
}
this.semantics = []
for (const key of Object.keys(priorityToSemantics).sort()) {
this.semantics = this.semantics.concat(priorityToSemantics[key])
}
this.logs = logs
// map ordinal to number of calls
this.calls = {}
};
getMostCalled () {
let maxOrdinal = 0
let maxCounter = 0
for (const ordinal in this.calls) {
const counter = this.calls[ordinal]
if (counter > maxCounter) {
maxOrdinal = ordinal
maxCounter = counter
}
}
return this.semantics[maxOrdinal]
}
async applyToContext (args, context, options) {
// let context_prime = {}
if (!(context instanceof Array || context instanceof Object)) {
return context
}
args = { ...args }
const config = args.config
let contextPrime = Object.assign({}, context)
const s = (context, options) => this.apply(args, context, options)
let applied = false
const stack = args.calls.push()
let counter = 0
let seenQuestion = false
const deferred = []
args.log = (message) => { this.logs.push(message) }
for (const isemantic in this.semantics) {
const semantic = this.semantics[isemantic]
// only one question at a time
if (semantic.isQuestion && seenQuestion) {
continue
}
if (await semantic.matches(args, context, options)) {
if (!this.calls[counter]) {
this.calls[counter] = 0
}
this.calls[counter] += 1
try {
let deferWasCalled = false
const defer = (listener) => {
deferred.push({ semantic, listener })
deferWasCalled = true
}
args.defer = defer
let continueWasCalled = false
const _continue = () => {
continueWasCalled = true
}
args._continue = _continue
contextPrime = await semantic.apply(args, context, s, options)
if (continueWasCalled) {
continue
}
if (deferWasCalled) {
continue
}
if (!contextPrime.controlKeepMotivation && semantic.oneShot) {
// semantic.tied_ids.forEach((tied_id) => args.config.removeSemantic(tied_id))
args.config.removeSemantic(semantic)
}
for (const { listener } of deferred) {
listener(args)
}
} catch (e) {
contextPrime = null
let errorMessage
e.retryCall = () => semantic.apply(args, context, s, options)
const help = 'The error has a retryCall property that will recall the function that failed.'
if (e.stack && e.message) {
const info = `${semantic.notes ? semantic.notes : ''}${semantic.where ? semantic.where : ''}`
errorMessage = `Error applying semantics '${info}'. Error is ${e.toString()} stack is ${e.stack}. Semantic is ${semantic.toString()}. ${help}`
} else if (e.error) {
const info = `${semantic.notes ? semantic.notes : ''}${semantic.where ? semantic.where : ''}`
errorMessage = `Error applying semantics '${info}'. Error is ${e.error.join()}. Semantic is ${semantic.toString()}. ${help}`
} else {
errorMessage = e.toString()
}
const widths = [10, 10, 90]
const lines = new Lines(widths)
lines.setElement(0, 0, 'Semantic')
const source = `${semantic.km}/#${semantic.index}`
lines.setElement(0, 2, `ERROR while applying (${source}) ${semantic.toLabel()}`)
lines.newRow()
lines.setElement(0, 2, semantic.toString())
lines.newRow()
lines.setElement(0, 1, 'TO')
lines.setElement(0, 2, `context_id: ${context.context_id}`)
lines.setElement(1, 2, JSON.stringify(helpers.sortJson(context, { depth: 25 }), null, 2))
lines.newRow()
lines.setElement(0, 1, 'STACK')
lines.setElement(0, 2, stack)
lines.newRow()
lines.setElement(0, 1, 'DEBUG')
lines.setElement(0, 2, `To debug this use args.callId == '${args.calls.current()}'`)
lines.newRow()
lines.setElement(0, 1, 'ERROR')
lines.setElement(0, 2, errorMessage)
this.logs.push(lines.toString())
const message = `ERROR while applying (${source}) ${semantic.toLabel()}\n to\n ${JSON.stringify(context, null, 2)}.\n${errorMessage}'`
// this.logs.push(message)
// return [message]
args.calls.pop()
throw { error: [message], logs: this.logs, reason: e.reason }
}
args.calls.touch(contextPrime)
// this.logs.push(`Semantics: applied ${semantic.toString()}\n to\n ${JSON.stringify(context)}\n the result was ${JSON.stringify(contextPrime)}\n`)
if (((config || {}).config || {}).debug) {
const widths = [10, 10, 90]
const lines = new Lines(widths)
lines.setElement(0, 0, 'Semantic')
if (semantic.index > -1 && semantic.km) {
// lines.setElement(0, 2, `KM '${semantic.km}' ordinal: ${semantic.index} ${ semantic.notes ? semantic.nodes : '' }`)
lines.setElement(0, 2, semantic.toLabel())
}
lines.newRow()
lines.setElement(0, 1, 'APPLIED')
lines.setElement(0, 2, semantic.toLabel())
lines.newRow()
lines.setElement(0, 2, semantic.toString())
lines.newRow()
lines.setElement(0, 1, 'TO')
lines.setElement(0, 2, `context_id: ${context.context_id}`)
lines.setElement(1, 2, JSON.stringify(helpers.sortJson(context, { depth: 25 }), null, 2))
lines.newRow()
lines.setElement(0, 1, 'STACK')
lines.setElement(0, 2, stack)
lines.newRow()
lines.setElement(0, 1, 'DEBUG')
lines.setElement(0, 2, `To debug this use args.callId == '${args.calls.current()}'`)
lines.newRow()
lines.setElement(0, 1, 'RESULT')
lines.setElement(0, 2, `context_id: ${context.context_id}`)
lines.setElement(1, 2, JSON.stringify(contextPrime, null, 2))
for (const { semantic } of deferred) {
lines.setElement(0, 1, 'DEFERRED')
lines.setElement(0, 2, semantic.toLabel())
lines.newRow()
lines.setElement(0, 2, semantic.toString())
lines.newRow()
}
this.logs.push(lines.toString())
}
applied = true
seenQuestion = seenQuestion || semantic.isQuestion
if (contextPrime.cascade) {
contextPrime.cascade = false
} else {
break
}
}
counter += 1
}
args.calls.pop()
if (!applied && ((config || {}).config || {}).debug) {
const widths = [10, 10, 90]
const lines = new Lines(widths)
lines.setElement(0, 0, 'Semantic')
lines.setElement(0, 2, 'No semantic applied')
lines.newRow()
lines.setElement(0, 1, 'STACK')
lines.setElement(0, 2, stack)
lines.newRow()
lines.setElement(0, 1, 'TO')
lines.setElement(0, 2, `context_id: ${context.context_id}`)
lines.setElement(1, 2, JSON.stringify(helpers.sortJson(context, { depth: 25 }), null, 2))
this.logs.push(lines.toString())
}
return contextPrime
}
async applyToContexts (args, contexts, options) {
const contextsPrime = []
for (const context of contexts) {
contextsPrime.push(await this.applyToContext(args, context, options))
}
return contextsPrime
}
async apply (args, context, options) {
if (Array.isArray(context)) {
return await this.applyToContexts(args, context, options)
} else if (context instanceof Object) {
return await this.applyToContext(args, context, options)
} else {
return context
}
}
}
module.exports = { Semantic, Semantics, normalizeGenerator }