UNPKG

theprogrammablemind

Version:

334 lines (312 loc) • 11.8 kB
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 }