theprogrammablemind 9.7.1 → 9.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js CHANGED
@@ -12,7 +12,7 @@ const _ = require('lodash')
12
12
  const stringify = require('json-stable-stringify')
13
13
  const Lines = require('./lines')
14
14
  const flattens = require('./src/flatten')
15
- const { sortJson, appendNoDups, InitCalls, updateQueries, safeNoDups, stableId, where, suggestAssociationsFix, suggestAssociationsFixFromSummaries, validProps } = require('./src/helpers')
15
+ const { helpDebugHierarchy, sortJson, appendNoDups, InitCalls, updateQueries, safeNoDups, stableId, where, suggestAssociationsFix, suggestAssociationsFixFromSummaries, validProps, OverrideCheck, } = require('./src/helpers')
16
16
  const runtime = require('./runtime')
17
17
  const db = require('./src/debug')
18
18
 
@@ -167,10 +167,12 @@ const writeTest = (fn, query, objects, generated, paraphrases, responses, contex
167
167
  if (runtime.fs.existsSync(fn)) {
168
168
  tests = JSON.parse(runtime.fs.readFileSync(fn))
169
169
  }
170
+ /*
170
171
  for (const association of associations) {
171
172
  association.sort()
172
173
  }
173
174
  associations.sort()
175
+ */
174
176
  // tests[query] = sortJson({ paraphrases, responses, contexts, objects: convertToStable(objects), associations, metadata, config, developerTest: saveDeveloper }, { depth: 25 })
175
177
  const results = sortJson({
176
178
  query,
@@ -265,7 +267,7 @@ const throwErrorHandler = (error) => {
265
267
  throw error
266
268
  }
267
269
 
268
- const _process = async (config, query, { initializer, commandLineArgs, credentials, writeTests, isProcess, isModule, isTest, saveDeveloper, rebuildingTemplate, testConfig, testsFN, errorHandler = throwErrorHandler } = {}) => {
270
+ const _process = async (config, query, { queryNumber, initializer, commandLineArgs, credentials, writeTests, logs, isProcess, isModule, isTest, saveDeveloper, rebuildingTemplate, testConfig, testsFN, errorHandler = throwErrorHandler } = {}) => {
269
271
  if (credentials) {
270
272
  config.server(credentials.server, credentials.key)
271
273
  }
@@ -296,11 +298,12 @@ const _process = async (config, query, { initializer, commandLineArgs, credentia
296
298
  // '\\n' from tests of '\n' from users. the former is because newline is not a valid json character so the testfile has it encoded
297
299
  let queries = query.split(/\\n|\n/)
298
300
  const summaries = [] // for error
301
+ logs = logs || []
299
302
  try {
300
303
  const response = {
301
304
  hierarchy: [],
302
305
  load_cache_time: 0.0,
303
- logs: [],
306
+ logs: logs,
304
307
  metadata: {
305
308
  opChoices: []
306
309
  },
@@ -334,6 +337,9 @@ const _process = async (config, query, { initializer, commandLineArgs, credentia
334
337
  data.utterance = queries[0]
335
338
  data.start_counter = startCounter
336
339
  let json = await doWithRetries(retries, url, queryParams, data)
340
+ if (json.logs) {
341
+ logs.push(...json.logs)
342
+ }
337
343
  let resetData = false
338
344
  if (json.code === 'NOT_IN_CACHE') {
339
345
  resetData = true
@@ -367,28 +373,26 @@ const _process = async (config, query, { initializer, commandLineArgs, credentia
367
373
  const summary = { summaries: json.summaries, length: json.contexts.length }
368
374
  summaries.push(summary)
369
375
  const { updatedContextIdCounter, contextsPrime, generatedPrime, paraphrasesPrime, paraphrasesParenthesizedPrime, generatedParenthesizedPrime, responsesPrime } =
370
- await processContextsB({ contextIdCounter, calls, isTest, isProcess, isModule, rebuildingTemplate, config, hierarchy, json, commandLineArgs /*, generators, semantics */ })
376
+ await processContextsB({ contextIdCounter, logs, calls, isTest, isProcess, isModule, rebuildingTemplate, config, hierarchy, json, commandLineArgs /*, generators, semantics */ })
371
377
  contextIdCounter = updatedContextIdCounter
372
378
  if (isTest) {
373
379
  const end = runtime.performance.performance.now()
374
380
  clientSideTime = end - start
375
381
  }
376
- response.associations = json.associations
382
+ // response.associations = json.associations
377
383
  response.learned_contextual_priorities = json.learned_contextual_priorities
378
384
  response.hierarchy = json.hierarchy
379
385
  response.load_cache_time += json.load_cache_time
380
- appendNoDups(response.logs, json.logs)
381
386
  response.memory_free_percent = json.memory_free_percent
382
- // appendNoDups(response.metadata.associations, json.metadata.associations)
383
- // appendNoDups(response.metadata.priorities, json.metadata.priorities)
384
387
  appendNoDups(response.metadata.opChoices, json.metadata.opChoices)
385
388
  response.times += json.times
386
389
  response.clientSideTimes += clientSideTime
387
390
  response.trace = response.trace.concat(json.trace)
388
391
  response.version = json.version
389
392
  response.explain_priorities = json.explain_priorities
393
+ response.explain_associations = json.explain_associations
390
394
  response.contextual_priorities_ambiguities = json.contextual_priorities_ambiguities
391
- response.rtf_associations = json.rtf_associations
395
+ // response.rtf_associations = json.rtf_associations
392
396
 
393
397
  response.contexts = response.contexts.concat(contextsPrime)
394
398
  response.generated = response.generated.concat(generatedPrime)
@@ -414,6 +418,7 @@ const _process = async (config, query, { initializer, commandLineArgs, credentia
414
418
  return response
415
419
  } catch (error) {
416
420
  error.summaries = summaries
421
+ error.queryNumber = queryNumber
417
422
  error.query = query
418
423
  errorHandler(error)
419
424
  }
@@ -479,7 +484,7 @@ const getConfigForTest = (config, testConfig) => {
479
484
  return configForTest
480
485
  }
481
486
 
482
- const runTest = async (config, expected, { args, verbose, testConfig, debug, timings = { server: 0, client: 0 } }) => {
487
+ const runTest = async (config, expected, { args, verbose, testConfig, debug, timings = { server: 0, client: 0 }, index = 0 }) => {
483
488
  const test = expected.query
484
489
  if (args.query && args.query != test) {
485
490
  // no run this
@@ -506,16 +511,17 @@ const runTest = async (config, expected, { args, verbose, testConfig, debug, tim
506
511
  setupArgs(args, config)
507
512
  await testConfig.initializer(args)
508
513
  }
509
- const result = await _process(config, test, { errorHandler, isTest: true, isProcess: true, isModule: false })
514
+ const result = await _process(config, test, { queryNumber: args.queryNumber, errorHandler, isTest: true, isProcess: true, isModule: false })
510
515
  result.query = test
511
516
  if (debug) {
512
517
  defaultInnerProcess(config, errorHandler, result)
513
518
  }
514
519
  if (verbose) {
515
- const widths = [100, 60]
520
+ const widths = [4, 100, 60]
516
521
  const lines = new Lines(widths)
517
- lines.setElement(0, 0, test)
518
- lines.setElement(0, 1, `time on server: ${result.times.toFixed(2)} client: ${(result.clientSideTimes / 1000).toFixed(2)}`)
522
+ lines.setElement(0, 0, index)
523
+ lines.setElement(0, 1, test)
524
+ lines.setElement(0, 2, `time on server: ${result.times.toFixed(2)} client: ${(result.clientSideTimes / 1000).toFixed(2)}`)
519
525
  timings.server += result.times
520
526
  timings.client += result.clientSideTimes / 1000
521
527
  lines.log()
@@ -618,18 +624,23 @@ const runTest = async (config, expected, { args, verbose, testConfig, debug, tim
618
624
 
619
625
  const runTestsHelper = async (config, tests, failed, juicyBits) => {
620
626
  const { stopAtFirstError } = juicyBits
627
+ let index = 0
621
628
  while (true) {
622
629
  if (tests.length === 0) {
623
630
  return Promise.resolve(failed)
624
631
  }
625
632
  const test = tests.shift()
633
+ juicyBits.index = index
634
+ juicyBits.args.queryNumber = index
626
635
  const result = await runTest(config, test, juicyBits)
627
636
  if (result != null) {
637
+ result.index = index
628
638
  failed.push(result)
629
639
  if (stopAtFirstError) {
630
640
  return failed
631
641
  }
632
642
  }
643
+ index += 1
633
644
  }
634
645
  }
635
646
 
@@ -678,13 +689,6 @@ const saveTests = (config, testFile, testConfig) => {
678
689
  return saveTestsHelper(testFile, config, tests, tests.map((test) => test.query), testConfig)
679
690
  }
680
691
 
681
- /*
682
- const showExamples = (testFile) => {
683
- const tests = JSON.parse(fs.readFileSync(testFile))
684
- Object.keys(tests).forEach((test) => console.log(test))
685
- }
686
- */
687
-
688
692
  const showInfo = (description, section, config) => {
689
693
  console.log(JSON.stringify(config.getInfo(), null, 2))
690
694
  }
@@ -798,7 +802,7 @@ const defaultErrorHandler = async (error) => {
798
802
  }
799
803
 
800
804
  if (error.query) {
801
- console.log('query: ', error.query)
805
+ console.log(`query: #${error.queryNumber} ${error.query}`)
802
806
  doErrorExit = true
803
807
  }
804
808
 
@@ -890,6 +894,11 @@ const defaultInnerProcess = (config, errorHandler, responses) => {
890
894
  console.log(` inputs: ${JSON.stringify(inputs)} output: ${JSON.stringify(output)} reason: ${reason}`)
891
895
  }
892
896
  }
897
+
898
+ if (responses.explain_associations) {
899
+ console.log(responses.explain_associations)
900
+ }
901
+
893
902
  // const objects = config.get('objects').namespaced[config.uuid]
894
903
  const actualGetObjects = (name) => {
895
904
  if (!name) {
@@ -1066,7 +1075,7 @@ const rebuildTemplate = async ({ config, instance, target, previousResultss, reb
1066
1075
  config.fragmentsBeingBuilt.push({ query: query.query, contexts: results.contexts })
1067
1076
  }
1068
1077
  accumulators.summaries = accumulators.summaries.concat(results.summaries)
1069
- accumulators.associations = accumulators.associations.concat(results.associations)
1078
+ // accumulators.associations = accumulators.associations.concat(results.associations)
1070
1079
  accumulators.learned_contextual_priorities = accumulators.learned_contextual_priorities.concat(results.learned_contextual_priorities)
1071
1080
  await looper(configs)
1072
1081
  } catch (e) {
@@ -1102,7 +1111,7 @@ const rebuildTemplate = async ({ config, instance, target, previousResultss, reb
1102
1111
  await looper([])
1103
1112
  } else {
1104
1113
  try {
1105
- config.addInternal(_.cloneDeep(extraConfig), { handleCalculatedProps: true })
1114
+ config.addInternal(_.cloneDeep(extraConfig), { handleCalculatedProps: true, addFirst: true })
1106
1115
  } catch (e) {
1107
1116
  const where = extraConfig.where ? ` ${extraConfig.where}` : ''
1108
1117
  throw new Error(`Error processing extra config${where}: ${e.stack}}`)
@@ -1124,7 +1133,7 @@ const rebuildTemplate = async ({ config, instance, target, previousResultss, reb
1124
1133
  associations.sort()
1125
1134
  }
1126
1135
  const stabilizeOutput = (template) => {
1127
- stabilizeAssociations(template.associations)
1136
+ // stabilizeAssociations(template.associations)
1128
1137
  const stabilize = (results) => {
1129
1138
  for (let i = 0; i < results.length; ++i) {
1130
1139
  const result = results[i]
@@ -1138,8 +1147,7 @@ const rebuildTemplate = async ({ config, instance, target, previousResultss, reb
1138
1147
  delete result.memory_free_percent
1139
1148
  delete result.logs
1140
1149
  delete result.version
1141
- result.hierarchy.sort()
1142
- stabilizeAssociations(result.associations)
1150
+ // stabilizeAssociations(result.associations)
1143
1151
  result.learned_contextual_priorities = safeNoDups(result.learned_contextual_priorities)
1144
1152
  }
1145
1153
  }
@@ -1314,7 +1322,6 @@ const knowledgeModuleImpl = async ({
1314
1322
 
1315
1323
  const helpDebugWord = 'In order to get a debug break when a specific word is created set the DEBUG_WORD environment variable to the JSON of the association to break on. For example DEBUG_WORD=\'"the"\''
1316
1324
  const helpDebugAssociation = 'In order to get a debug break when a specific association is created set the DEBUG_ASSOCIATION environment variable to the JSON of the association to break on. For example DEBUG_ASSOCIATION=\'[["the", 0], ["mammal", 1]]\''
1317
- const helpDebugHierarchy = 'In order to get a debug break when a specific hierarchy is created set the DEBUG_HIERARCHY environment variable to the JSON of the child-parent pair to break on. For example DEBUG_HIERARCHY=\'[["cat", 1], ["mammel", 1]]\''
1318
1325
  const helpDebugPriority = 'In order to get a debug break when a specific set of priorities is created set set DEBUG_PRIORITY environment variable to the JSON of the priorities that you want to break on. For example DEBUG_PRIORITY=\'[["verb", 0], ["article", 0]]\''
1319
1326
  const helpDebugContextualPriority = 'In order to get a debug break when a specific set of contextual priorities is created set set DEBUG_CONTEXTUAL_PRIORITY environment variable to the JSON of the priorities that you want to break on. For example DEBUG_CONTEXTUAL_PRIORITY=\'{ context: [["verb", 0], ["article", 0], select: 1}\''
1320
1327
  const helpDebugBridge = 'In order to get a debug break when a specific bridge is created set the DEBUG_BRIDGE environment variable to id to break on. For example DEBUG_BRIDGE=\'car\''
@@ -1337,6 +1344,7 @@ const knowledgeModuleImpl = async ({
1337
1344
  parser.add_argument('-cl', '--checkForLoop', { nargs: '?', help: 'Check for loops in the priorities, Optional argument is list of operator keys to consider. For example [["banana", 0], ["food", 1]]' })
1338
1345
  parser.add_argument('-r', '--reset', { action: 'store_true', help: 'Get the server to bypass the cache and rebuild everything' })
1339
1346
  parser.add_argument('-q', '--query', { help: 'Run the specified query' })
1347
+ parser.add_argument('-qn', '--queryNumber', { help: 'Run the query specified by the given index' })
1340
1348
  parser.add_argument('-f', '--filter', { help: 'for -pd only the data for the knowledge modules that start with this string will be shown' })
1341
1349
  parser.add_argument('-ip ', '--server', { help: 'Server to run against' })
1342
1350
  parser.add_argument('--trace', { action: 'store_true', help: 'Trace the semantics and generator calls.' })
@@ -1376,6 +1384,7 @@ const knowledgeModuleImpl = async ({
1376
1384
  parser.add_argument('-dcp', '--debugContextualPriority', { action: 'store_true', help: helpDebugContextualPriority })
1377
1385
  parser.add_argument('-db', '--debugBridge', { action: 'store_true', help: helpDebugBridge })
1378
1386
  parser.add_argument('-do', '--debugOperator', { action: 'store_true', help: helpDebugOperator })
1387
+ parser.add_argument('-ea', '--explainAssociations', { action: 'store_true', help: 'The server will return information about the associations used to decide on the interpretation.' })
1379
1388
  parser.add_argument('-ep', '--explainPriorities', { action: 'store_true', help: 'The server will return all priorities including the generated one along with an explanation of there they came from' })
1380
1389
  parser.add_argument('-dic', '--debugIncludeConvolutions', { nargs: '?', help: 'When running with the --debugIncludeConvolutions flag the logs will include convolutions which are somewhat annoyingly verbose. Default is false' })
1381
1390
  parser.add_argument('-bc', '--bypassCache', { action: 'store_true', help: 'Bypass the cache on the server side and rebuild' })
@@ -1383,6 +1392,10 @@ const knowledgeModuleImpl = async ({
1383
1392
  const args = parser.parse_args()
1384
1393
  args.count = args.count || 1
1385
1394
 
1395
+ if (args.queryNumber) {
1396
+ args.query = test.contents[args.queryNumber].query
1397
+ }
1398
+
1386
1399
  if (args.rebuildTemplateFull) {
1387
1400
  args.rebuildTemplate = true
1388
1401
  }
@@ -1476,9 +1489,10 @@ const knowledgeModuleImpl = async ({
1476
1489
 
1477
1490
  if (args.deleteTest) {
1478
1491
  let tests = JSON.parse(runtime.fs.readFileSync(testConfig.name))
1492
+ const nTests = tests.length
1479
1493
  tests = tests.filter((test) => test.query !== args.deleteTest)
1480
1494
  writeTestFile(testConfig.name, tests)
1481
- console.log(`Delete the test for "${args.deleteTest}"`)
1495
+ console.log(`Deleted ${nTests - tests.length} tests for "${args.deleteTest}"`)
1482
1496
  return
1483
1497
  }
1484
1498
 
@@ -1509,6 +1523,10 @@ const knowledgeModuleImpl = async ({
1509
1523
  config.config.explain_priorities = true
1510
1524
  }
1511
1525
 
1526
+ if (args.explainAssociations) {
1527
+ config.config.explain_associations = true
1528
+ }
1529
+
1512
1530
  config.config.debugIncludeConvolutions = args.debugIncludeConvolutions || process.argv.includes('--debugIncludeConvolutions') || process.argv.includes('-dic')
1513
1531
 
1514
1532
  let configPrinted = false
@@ -1528,7 +1546,7 @@ const knowledgeModuleImpl = async ({
1528
1546
  console.log('Test queries')
1529
1547
  let counter = 0
1530
1548
  for (const test of config.tests) {
1531
- console.log(`${counter} - ${test.query}`)
1549
+ console.log(`#${counter} - ${test.query}`)
1532
1550
  counter += 1
1533
1551
  }
1534
1552
  }
@@ -1541,7 +1559,10 @@ const knowledgeModuleImpl = async ({
1541
1559
  if (hasArg('c')) {
1542
1560
  const { data } = setupProcessB({ config })
1543
1561
  console.log('Config as sent to server')
1544
- console.log(JSON.stringify(data, null, 2))
1562
+ const toPrint = { ...data }
1563
+ toPrint['priorities'] = toPrint['priorities'].map((value) => JSON.stringify(value).replace(/\\"/g, '"'))
1564
+ toPrint['associations']['positive'] = toPrint['associations']['positive'].map((value) => JSON.stringify(value).replace(/\\"/g, '"'))
1565
+ console.log(JSON.stringify(toPrint, null, 2).replace(/\\"/g, '"'))
1545
1566
  }
1546
1567
  if (hasArg('l')) {
1547
1568
  console.log('Module load ordering')
@@ -1635,12 +1656,7 @@ const knowledgeModuleImpl = async ({
1635
1656
  }
1636
1657
 
1637
1658
  if (hasArg('s')) {
1638
- const easyToRead = _.cloneDeep(config.config.semantics)
1639
- for (const semantic of easyToRead) {
1640
- semantic.match = semantic.match.toString()
1641
- semantic.apply = semantic.apply.toString()
1642
- }
1643
- console.dir(easyToRead)
1659
+ config.printSemantics()
1644
1660
  }
1645
1661
  }
1646
1662
  }
@@ -1757,7 +1773,7 @@ const knowledgeModuleImpl = async ({
1757
1773
  if (hasError) {
1758
1774
  console.log('**************************** ERRORS ************************')
1759
1775
  for (const result of results) {
1760
- console.log('Utterance: ', result.utterance)
1776
+ console.log(`Utterance: #${result.index} ${result.utterance}`)
1761
1777
  if (!result.hasError) {
1762
1778
  continue
1763
1779
  }
@@ -1773,7 +1789,7 @@ const knowledgeModuleImpl = async ({
1773
1789
  newError = true
1774
1790
  headerShown = true
1775
1791
  if (args.vimdiff) {
1776
- vimdiff(actual, expected, `"${label} - ${result.utterance}"`)
1792
+ vimdiff(actual, expected, `"${label} - #${result.index} ${result.utterance}"`)
1777
1793
  }
1778
1794
  result.hasError = true
1779
1795
  }
@@ -1916,12 +1932,17 @@ const knowledgeModuleImpl = async ({
1916
1932
  console.log('use -v arg to write files expected.json and actual.json in the current directory for detailed comparison. Or do -s and then git diff the changes.')
1917
1933
  // console.log(JSON.stringify(contexts))
1918
1934
  let errorCount = 0
1935
+ const failedTests = []
1919
1936
  for (const result of results) {
1920
1937
  if (result.hasError) {
1921
- console.log(`FAILED ${result.utterance}`)
1938
+ console.log(`FAILED #${result.index} ${result.utterance}`)
1939
+ failedTests.push({ index: result.index, utterance: result.utterance })
1922
1940
  errorCount += 1
1923
1941
  }
1924
1942
  }
1943
+ const failedFile = './FAILED.json'
1944
+ console.log(`Failed tests are written to ${failedFile}`)
1945
+ runtime.fs.writeFileSync(failedFile, JSON.stringify(failedTests, 0, 2))
1925
1946
  console.log(`**************************** THERE WERE ${errorCount} TEST FAILURES ************************`)
1926
1947
  }
1927
1948
  }
@@ -1980,7 +2001,7 @@ const knowledgeModuleImpl = async ({
1980
2001
  global.beforeObjects = _.cloneDeep(objects)
1981
2002
  }
1982
2003
  try {
1983
- await processResults(_process(config, args.query, { commandLineArgs: args, isProcess, isModule: !isProcess, dontAddAssociations: args.dontAddAssociations, writeTests: args.save || args.saveDeveloper, saveDeveloper: args.saveDeveloper, testConfig, testsFN: test }))
2004
+ await processResults(_process(config, args.query, { commandLineArgs: args, isProcess, logs: [], isModule: !isProcess, dontAddAssociations: args.dontAddAssociations, writeTests: args.save || args.saveDeveloper, saveDeveloper: args.saveDeveloper, testConfig, testsFN: test }))
1984
2005
  } catch (error) {
1985
2006
  printConfig()
1986
2007
  console.log('Error', error)
@@ -2104,4 +2125,5 @@ module.exports = {
2104
2125
  writeTest,
2105
2126
  getConfigForTest,
2106
2127
  debug: db,
2128
+ OverrideCheck,
2107
2129
  }
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const { Semantics, Semantic } = require('./src/semantics')
2
2
  const { Generators, Generator } = require('./src/generators')
3
3
  const { Config } = require('./src/config')
4
- const { w, where } = require('./src/helpers')
4
+ const { w, where, OverrideCheck, setByPath, getByPath } = require('./src/helpers')
5
5
  const Digraph = require('./src/digraph')
6
6
  const client = require('./client')
7
7
  const flattens = require('./src/flatten')
@@ -16,6 +16,8 @@ module.exports = {
16
16
  runTests: client.runTests,
17
17
  knowledgeModule: client.knowledgeModule,
18
18
  ensureTestFile: client.ensureTestFile,
19
+ setByPath,
20
+ getByPath,
19
21
  where,
20
22
  stableId: client.stableId,
21
23
  w,
@@ -28,5 +30,6 @@ module.exports = {
28
30
  flattens: flattens.flattens,
29
31
  flatten: flattens.flatten,
30
32
  unflatten: unflatten.unflatten,
31
- debug
33
+ debug,
34
+ OverrideCheck,
32
35
  }
package/package.json CHANGED
@@ -73,6 +73,6 @@
73
73
  "scriptjs": "^2.5.9",
74
74
  "uuid": "^8.3.2"
75
75
  },
76
- "version": "9.7.1",
76
+ "version": "9.8.0",
77
77
  "license": "UNLICENSED"
78
78
  }
package/src/config.js CHANGED
@@ -454,7 +454,7 @@ const handleBridgeProps = (config, bridge, { addFirst, uuid } = {}) => {
454
454
  // match: async (args) => bridge.id === args.context.marker && args.context.level === level && !args.context.paraphrase && (args.context.response || args.context.isResponse) && await match(args),
455
455
  match: async (args) => {
456
456
  if (bridge.callId == args.callId) {
457
- debugger
457
+ debugger // eslint-disable-line no-debugger
458
458
  }
459
459
  return args.isA(args.context.marker, bridge.id) && args.context.level === level && !args.context.paraphrase && (args.context.response != null || args.context.isResponse) && await match(args)
460
460
  },
@@ -563,7 +563,7 @@ const handleCalculatedProps = (baseConfig, moreConfig, { addFirst, uuid } = {})
563
563
  'generatorr',
564
564
  'generators',
565
565
  'id',
566
- 'init',
566
+ 'initial',
567
567
  'inverted',
568
568
  'isA',
569
569
  'level',
@@ -572,6 +572,7 @@ const handleCalculatedProps = (baseConfig, moreConfig, { addFirst, uuid } = {})
572
572
  'operator',
573
573
  'optional',
574
574
  'parents',
575
+ 'preferOver',
575
576
  'return_type_selector',
576
577
  'scope',
577
578
  'selector',
@@ -601,7 +602,12 @@ const handleCalculatedProps = (baseConfig, moreConfig, { addFirst, uuid } = {})
601
602
  }
602
603
 
603
604
  if (runtime.process.env.DEBUG_HIERARCHY) {
604
- global.entodictonDebugHierarchy = JSON.parse(runtime.process.env.DEBUG_HIERARCHY)
605
+ try {
606
+ global.entodictonDebugHierarchy = JSON.parse(runtime.process.env.DEBUG_HIERARCHY)
607
+ } catch( e ) {
608
+ console.error("Invalid valid for DEBUG_HIERARCHY")
609
+ console.error(helpers.helpDebugHierarchy)
610
+ }
605
611
  }
606
612
 
607
613
  // i keep randomly doing one of the other so I will just make both work the same way
@@ -975,6 +981,15 @@ class Config {
975
981
  return config_toServer(config)
976
982
  }
977
983
 
984
+ printSemantics() {
985
+ const easyToRead = _.cloneDeep(this.config.semantics)
986
+ for (const semantic of easyToRead) {
987
+ semantic.match = semantic.match.toString()
988
+ semantic.apply = semantic.apply.toString()
989
+ }
990
+ console.dir(easyToRead)
991
+ }
992
+
978
993
  async run(handler) {
979
994
  return configHelpers.run(this, handler)
980
995
  }
@@ -1285,6 +1300,8 @@ class Config {
1285
1300
  } else {
1286
1301
  return fi
1287
1302
  }
1303
+ } else {
1304
+ throw new Error(`The fragment for '${query} was not found.`)
1288
1305
  }
1289
1306
  }
1290
1307
 
@@ -1655,6 +1672,7 @@ class Config {
1655
1672
  }
1656
1673
  }
1657
1674
 
1675
+ // handler/listener
1658
1676
  updateBridge(id, updater) {
1659
1677
  const bridge = this.config.bridges.find((b) => b.id === id)
1660
1678
  updater({ config: this, bridge })
@@ -1674,6 +1692,7 @@ class Config {
1674
1692
  return
1675
1693
  }
1676
1694
  }
1695
+
1677
1696
  if (global.transitoryMode) {
1678
1697
  def.transitoryMode = true
1679
1698
  }
@@ -2313,6 +2332,7 @@ class Config {
2313
2332
  }
2314
2333
  }
2315
2334
 
2335
+ // TODO Api or API -> pick one and do that
2316
2336
  async setMultiApi (initializer) {
2317
2337
  await this.setApi(() => multiApiImpl(initializer))
2318
2338
  }
@@ -2420,11 +2440,17 @@ class Config {
2420
2440
  if (!api.initialize) {
2421
2441
  throw new Error(`Expected the API for ${this.name} to have an initialize function.`)
2422
2442
  }
2443
+ if (api.baseAPI) {
2444
+ new helpers.OverrideCheck(api.baseAPI).check(api)
2445
+ }
2423
2446
  }
2424
2447
  } else {
2425
2448
  if (!value.initialize) {
2426
2449
  throw new Error(`Expected the API to have an initialize function for ${this.name}. If you are trying to pass in multiple API configs at once use setApiKMs to set the names of the configs that you want set.`)
2427
2450
  }
2451
+ if (value.baseAPI) {
2452
+ new helpers.OverrideCheck(value.baseAPI).check(value)
2453
+ }
2428
2454
  }
2429
2455
 
2430
2456
  if (this._api && this._api.multiApi) {
@@ -3368,6 +3394,10 @@ class Config {
3368
3394
  let duplicated = new Set()
3369
3395
  const seen = new Set()
3370
3396
  for (const bridge of this.config.bridges) {
3397
+ if (typeof bridge.id !== 'string') {
3398
+ throw new Error('Config.addBridge: expected the bridge id to be a non-empty string.')
3399
+ }
3400
+
3371
3401
  const id = `${bridge.id}/${bridge.level} (namespace: ${bridge.uuid || this.uuid})`
3372
3402
  if (seen.has(id)) {
3373
3403
  duplicated.add(id)
@@ -145,6 +145,7 @@ const setupArgs = (args, config, logs, hierarchy, uuidForScoping) => {
145
145
  args.objects = config.get('objects')
146
146
  args.getObjects = getObjects(args.objects)
147
147
  }
148
+ args.debug = debug
148
149
  args.enable = (key) => {
149
150
  config.addEnable(key)
150
151
  }
@@ -163,7 +164,23 @@ const setupArgs = (args, config, logs, hierarchy, uuidForScoping) => {
163
164
  }
164
165
  }
165
166
  args.contextHierarchy = new ContextHierarchy()
166
- args.handlerStack = new HandlerStack()
167
+ args.namespaced = {
168
+ get: (km, context, property, _default) => {
169
+ context.namespaced ??= {}
170
+ context.namespaced[km] ??= {}
171
+ const value = context.namespaced[km][property]
172
+ if (value == undefined) {
173
+ return _default
174
+ }
175
+ return value
176
+ },
177
+ set: (km, context, property, value) => {
178
+ context.namespaced ??= {}
179
+ context.namespaced[km] ??= {}
180
+ return context.namespaced[km][property] = value
181
+ }
182
+ }
183
+ args.stack = new HandlerStack()
167
184
  args.cleanAssign = cleanAssign
168
185
  args.km = (name) => config.getConfig(name)
169
186
  args.api = (name) => config.getConfig(name).api
@@ -332,9 +349,11 @@ const processContext = async (context, { objects = {}, config, logs = [] }) => {
332
349
  const setupProcessB = ({ config, initializer, allowDelta = false, rebuildingTemplate = false } = {}) => {
333
350
  const key = config._key
334
351
  const data = Object.assign({ key, version: '3' }, { uuid: config._uuid })
352
+ /*
335
353
  if (rebuildingTemplate) {
336
354
  data.return_rtf_associations = true
337
355
  }
356
+ */
338
357
  if (allowDelta && config.allowDelta && config.hasDelta()) {
339
358
  // console.log('config', config)
340
359
  data.delta = config.delta()
@@ -392,7 +411,7 @@ const setupContexts = (rawContexts) => {
392
411
  return contexts
393
412
  }
394
413
 
395
- const processContextsB = async ({ config, calls, hierarchy, semantics, generators, json, isTest, isProcess, isModule, rebuildingTemplate, isInstance, instance, query, data, retries, url, commandLineArgs, forTemplate, contextIdCounter }) => {
414
+ const processContextsB = async ({ config, calls, hierarchy, logs, semantics, generators, json, isTest, isProcess, isModule, rebuildingTemplate, isInstance, instance, query, data, retries, url, commandLineArgs, forTemplate, contextIdCounter }) => {
396
415
  // TODO fix this name to contextsPrime
397
416
  const contextsPrime = []
398
417
  const generatedPrime = []
@@ -404,10 +423,7 @@ const processContextsB = async ({ config, calls, hierarchy, semantics, generator
404
423
 
405
424
  const objects = config.get('objects')
406
425
  const args = { objects, isResponse: true, response: json, isTest, isInstance, getObjects: getObjects(objects), instance, contexts, isProcess, isModule, calls }
407
- if (!json.logs) {
408
- json.logs = []
409
- }
410
- setupArgs(args, config, json.logs, hierarchy)
426
+ setupArgs(args, config, logs, hierarchy)
411
427
  const toDo = [...contexts]
412
428
  args.insert = (context) => toDo.unshift(context)
413
429
  let overlap, lastRange
@@ -425,7 +441,7 @@ const processContextsB = async ({ config, calls, hierarchy, semantics, generator
425
441
  }
426
442
  const generateParenthesized = isTest || (commandLineArgs && commandLineArgs.save)
427
443
  if (!config.get('skipSemantics')) {
428
- const semantics = config.getSemantics(json.logs)
444
+ const semantics = config.getSemantics(logs)
429
445
  try {
430
446
  contextPrime = await semantics.apply(args, context)
431
447
  // contextPrime.greg = 'yes'
@@ -468,11 +484,11 @@ const processContextsB = async ({ config, calls, hierarchy, semantics, generator
468
484
  // noop
469
485
  } else {
470
486
  let assumed = { isResponse: true }
471
- const generated = contextPrime.isResponse ? await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed) : ''
487
+ const generated = contextPrime.isResponse ? await config.getGenerators(logs).apply({ ...args, assumed }, contextPrime, assumed) : ''
472
488
  let generatedParenthesized = []
473
489
  if (generateParenthesized) {
474
490
  config.setParenthesized(true)
475
- generatedParenthesized = contextPrime.isResponse ? await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed) : ''
491
+ generatedParenthesized = contextPrime.isResponse ? await config.getGenerators(logs).apply({ ...args, assumed }, contextPrime, assumed) : ''
476
492
  config.setParenthesized(false)
477
493
  }
478
494
  // assumed = { paraphrase: true, response: false };
@@ -480,11 +496,11 @@ const processContextsB = async ({ config, calls, hierarchy, semantics, generator
480
496
  if (generateParenthesized) {
481
497
  config.setParenthesized(false)
482
498
  }
483
- const paraphrases = await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed)
499
+ const paraphrases = await config.getGenerators(logs).apply({ ...args, assumed }, contextPrime, assumed)
484
500
  let paraphrasesParenthesized = []
485
501
  if (generateParenthesized) {
486
502
  config.setParenthesized(true)
487
- paraphrasesParenthesized = await config.getGenerators(json.logs).apply({ ...args, assumed }, contextPrime, assumed)
503
+ paraphrasesParenthesized = await config.getGenerators(logs).apply({ ...args, assumed }, contextPrime, assumed)
488
504
  config.setParenthesized(false)
489
505
  }
490
506
  contextsPrime.push(contextPrime)
@@ -513,9 +529,9 @@ const processContextsB = async ({ config, calls, hierarchy, semantics, generator
513
529
  }
514
530
  e.context = contextPrime
515
531
  if (e.logs) {
516
- e.logs = e.logs.concat(json.logs)
532
+ e.logs = e.logs.concat(logs)
517
533
  } else {
518
- e.logs = json.logs
534
+ e.logs = logs
519
535
  }
520
536
  e.metadata = json.metadata
521
537
  if (json.trace) {
@@ -532,10 +548,12 @@ const loadInstance = async (config, instance) => {
532
548
  const transitoryMode = global.transitoryMode
533
549
  global.transitoryMode = false
534
550
 
551
+ /*
535
552
  const rl = instance.resultss.length
536
553
  if (rl > 0) {
537
554
  config.addAssociations(instance.resultss[instance.resultss.length - 1].rtf_associations || [])
538
555
  }
556
+ */
539
557
  /*
540
558
  TODO needs updating if still wanted
541
559
  if (instance && (instance.associations || instance.learned_contextual_priorities)) {
package/src/debug.js CHANGED
@@ -27,6 +27,18 @@ const unhit = (name) => {
27
27
  hits[name] = false
28
28
  }
29
29
 
30
+ const hitScoped = async (name, call) => {
31
+ hit(name)
32
+ try {
33
+ const result = await call()
34
+ unhit(name)
35
+ return result
36
+ } catch( e ) {
37
+ unhit(name)
38
+ throw e
39
+ }
40
+ }
41
+
30
42
  const wasHit = (name) => {
31
43
  return hits[name]
32
44
  }
@@ -64,16 +76,19 @@ const _break = (name) => {
64
76
  }
65
77
  }
66
78
 
79
+ const breakAt = _break;
80
+
67
81
  module.exports = {
68
82
  counter,
69
83
  get,
70
84
  _break, // break is a keyword
71
-
85
+ breakAt,
72
86
  // used for tests
73
87
  reset,
74
88
  hit,
75
89
  unhit,
76
90
  wasHit,
77
91
  getDb,
78
- setDb
92
+ setDb,
93
+ hitScoped,
79
94
  }
package/src/digraph.js CHANGED
@@ -73,11 +73,25 @@ class Digraph {
73
73
  this._edges = edges
74
74
  }
75
75
 
76
- /*
77
- set edges(edges) {
78
- this._edges = edges.map( toA )
76
+ tos(from) {
77
+ const tos = []
78
+ for (const edge of this._edges) {
79
+ if (edge[0] == from) {
80
+ tos.push(edge[1])
81
+ }
82
+ }
83
+ return tos
84
+ }
85
+
86
+ froms(to) {
87
+ const froms = []
88
+ for (const edge of this._edges) {
89
+ if (edge[1] == to) {
90
+ froms.push(edge[0])
91
+ }
92
+ }
93
+ return froms
79
94
  }
80
- */
81
95
 
82
96
  acdcs (s, from, to) {
83
97
  if (!s) {
@@ -170,18 +184,6 @@ class Digraph {
170
184
  return this.minima(common)
171
185
  }
172
186
 
173
- /*
174
- maxima (nodes) {
175
- const maxima = new Set(nodes)
176
- const descendants = new Set([])
177
- nodes.forEach((node) => {
178
- this.descendants(node).forEach((n) => descendants.add(n))
179
- })
180
- descendants.forEach((n) => maxima.delete(n))
181
- return maxima
182
- }
183
- */
184
-
185
187
  add (child, parent) {
186
188
  this._edges.push([child, parent])
187
189
  }
@@ -39,6 +39,26 @@ class DigraphInternal {
39
39
  this._edges = edges
40
40
  }
41
41
 
42
+ tos(from) {
43
+ const tos = []
44
+ for (const edge of this._edges) {
45
+ if (edge[0] == from) {
46
+ tos.push(edge[1])
47
+ }
48
+ }
49
+ return tos
50
+ }
51
+
52
+ froms(to) {
53
+ const froms = []
54
+ for (const edge of this._edges) {
55
+ if (edge[1] == to) {
56
+ froms.push(edge[0])
57
+ }
58
+ }
59
+ return froms
60
+ }
61
+
42
62
  get length () {
43
63
  return this._edges.length
44
64
  }
@@ -137,18 +157,6 @@ class DigraphInternal {
137
157
  return this.minima(common)
138
158
  }
139
159
 
140
- /*
141
- maxima (nodes) {
142
- const maxima = new Set(nodes)
143
- const descendants = new Set([])
144
- nodes.forEach((node) => {
145
- this.descendants(node).forEach((n) => descendants.add(n))
146
- })
147
- descendants.forEach((n) => maxima.delete(n))
148
- return maxima
149
- }
150
- */
151
-
152
160
  add (child, parent) {
153
161
  this._edges.push([child, parent])
154
162
  }
package/src/flatten.js CHANGED
@@ -1,7 +1,8 @@
1
- const { isArray, isObject, isCompound } = require('./helpers')
1
+ const { isArray, isObject, isCompound, } = require('./helpers')
2
+ const debug = require('./debug')
2
3
  const _ = require('lodash')
3
4
 
4
- const DEBUG = (...args) => {
5
+ const DEBUG = (label, details) => {
5
6
  // DEBUG(...args)
6
7
  }
7
8
 
@@ -46,33 +47,24 @@ const zip = (a1, a2) => {
46
47
  }
47
48
 
48
49
  const flattenListHelper = (markers, values) => {
49
- const flats = []
50
50
  let wasFlattened = false
51
- let singles = []
51
+ const all = []
52
52
  for (const value of values) {
53
53
  if (!isCompound(value)) {
54
- singles.push(value)
54
+ all.push([value])
55
55
  } else {
56
56
  const [more, wf] = flatten(markers, value)
57
57
  if (wf) {
58
58
  wasFlattened = wasFlattened || wf
59
- flats.push(more)
59
+ all.push(more)
60
60
  } else {
61
- singles = singles.concat(more)
61
+ all.push(...more.map((m) => [m]))
62
62
  }
63
63
  }
64
64
  }
65
65
 
66
- // product does weirdness with only choice
67
- if (!wasFlattened) {
68
- return [[singles], wasFlattened]
69
- } else {
70
- const results = []
71
- for (const flat of cartesian(...flats)) {
72
- results.push(singles.concat(flat))
73
- }
74
- return [results, wasFlattened]
75
- }
66
+ const result = cartesian(...all)
67
+ return [result, wasFlattened]
76
68
  }
77
69
 
78
70
  // returns (list, wasFlattened)
@@ -84,6 +76,9 @@ const flatten = (markers, value) => {
84
76
  if (value.flatten === false) {
85
77
  return [[value], false]
86
78
  }
79
+
80
+ const flatten_ignore = value.flatten_ignore || []
81
+
87
82
  const marker = value.marker
88
83
  let properties = value
89
84
 
@@ -113,8 +108,9 @@ const flatten = (markers, value) => {
113
108
  for (const key in properties) {
114
109
  let wf = false
115
110
  let values
116
- // if isinstance(properties[key], Context) {
117
- if (isObject(properties[key])) {
111
+ if (flatten_ignore.includes(key)) {
112
+ values = [properties[key]]
113
+ } else if (isObject(properties[key])) {
118
114
  const context = properties[key];
119
115
  [values, wf] = flatten(markers, context)
120
116
  // } else if (isinstance(properties[key], list)) {
package/src/fragments.js CHANGED
@@ -1,6 +1,29 @@
1
1
  const _ = require('lodash')
2
2
  const helpers = require('./helpers')
3
3
 
4
+ function createChangeTracker(obj) {
5
+ const changes = new Set();
6
+
7
+ const handler = {
8
+ set(target, prop, value) {
9
+ changes.add(prop);
10
+ target[prop] = value;
11
+ return true;
12
+ },
13
+ deleteProperty(target, prop) {
14
+ changes.add(prop);
15
+ delete target[prop];
16
+ return true;
17
+ }
18
+ };
19
+
20
+ return {
21
+ proxy: new Proxy(obj, handler),
22
+ getChanges: () => Array.from(changes)
23
+ };
24
+ }
25
+
26
+
4
27
  function fragmentInstantiator (args, contexts) {
5
28
  return new Object({
6
29
  contexts: () => {
@@ -13,13 +36,15 @@ function fragmentInstantiator (args, contexts) {
13
36
  args.pathEquals = helpers.pathEquals
14
37
  while (todo.length > 0) {
15
38
  const { context, path } = todo.pop()
16
- args.context = context
39
+ const { proxy, getChanges } = createChangeTracker(context);
40
+ args.context = proxy
17
41
  args.path = path
18
42
  for (const mapping of mappings) {
19
43
  if (await mapping.match(args)) {
20
44
  await mapping.apply(args)
21
45
  }
22
46
  }
47
+ const ignore = getChanges()
23
48
  for (const key of Object.keys(context)) {
24
49
  // if (['number', 'string', 'boolean'].includes(typeof (context[key]))) {
25
50
  if (!helpers.isCompound(context[key])) {
@@ -28,7 +53,9 @@ function fragmentInstantiator (args, contexts) {
28
53
  if (context[key].instantiated) {
29
54
  continue
30
55
  }
31
- todo.push({ context: context[key], path: [...path, key] })
56
+ if (!ignore.includes(key)) {
57
+ todo.push({ context: context[key], path: [...path, key] })
58
+ }
32
59
  }
33
60
  }
34
61
  if (contexts.length == 1 && instantiated.length == 1) {
package/src/generators.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { args: contextArgs, normalizeGenerator } = require('./helpers')
2
2
  const Lines = require('../lines')
3
3
  const helpers = require('./helpers')
4
+ const debug = require('./debug')
4
5
 
5
6
  class Generator {
6
7
  // constructor ({ match, apply, uuid, index, km, priority, notes }) {
@@ -65,14 +66,16 @@ class Generator {
65
66
  }
66
67
  const args = Object.assign({}, baseArgs, moreArgs, (baseArgs.getUUIDScoped || (() => { return {} }))(this.uuid))
67
68
  // return this.match(args)
68
- const matches = await this.match(args)
69
- if ((matches && (options.debug || {}).match) ||
70
- callId === this.callId) {
71
- // next line is the matcher
72
- debugger // eslint-disable-line no-debugger
73
- await this.match(args)
74
- }
75
- return matches
69
+ return await debug.hitScoped(args.callId, async () => {
70
+ const matches = await this.match(args)
71
+ if ((matches && (options.debug || {}).match) ||
72
+ callId === this.callId) {
73
+ // next line is the matcher
74
+ debugger // eslint-disable-line no-debugger
75
+ await this.match(args)
76
+ }
77
+ return matches
78
+ })
76
79
  }
77
80
 
78
81
  // apply (baseArgs, objects, g, gs, context, hierarchy, config, response, log, options = {}) {
@@ -121,11 +124,12 @@ class Generator {
121
124
  if (this.property === 'generatorp') {
122
125
  args.g = args.gp
123
126
  }
124
- if ((options.debug || {}).apply ||
125
- callId === this.callId) {
126
- debugger // eslint-disable-line no-debugger
127
- }
128
- return await this._apply(args)
127
+ return await debug.hitScoped(args.callId, async () => {
128
+ if ((options.debug || {}).apply || process.env.DEBUG_CALLID == args.callId || callId === this.callId) {
129
+ debugger // eslint-disable-line no-debugger
130
+ }
131
+ return await this._apply(args) // DEBUG: This is the line you want to step into
132
+ })
129
133
  }
130
134
  }
131
135
 
@@ -186,6 +190,13 @@ class Generators {
186
190
  const log = (message) => { this.logs.push(message) }
187
191
  // this.logs.push(`Generators: applied ${generator.toString()}\n to\n ${stringify(context)}`)
188
192
  let errorMessage = 'The apply function did not return a value'
193
+ if (process.env.TPMKMS_TRACE) {
194
+ console.error("TPMKMS_TRACE", debug.counter("TPMKMS_TRACE"))
195
+ console.error(generator.toLabel())
196
+ console.error(generator.toString())
197
+ console.log(`To debug this use debug.breakAt('${args.calls.current()}') or DEBUG_CALLID="${args.calls.current()}"`)
198
+ }
199
+
189
200
  try {
190
201
  generated = await generator.apply(args, objects, context, hierarchy, config, response, log, options)
191
202
  } catch (e) {
@@ -220,7 +231,7 @@ class Generators {
220
231
  lines.setElement(0, 2, stack)
221
232
  lines.newRow()
222
233
  lines.setElement(0, 1, 'DEBUG')
223
- lines.setElement(0, 2, `To debug this use args.callId === '${args.calls.current()}'`)
234
+ lines.setElement(0, 2, `To debug this use debug.breakAt('${args.calls.current()}') or DEBUG_CALLID="${args.calls.current()}"`)
224
235
  lines.newRow()
225
236
  lines.setElement(0, 1, 'ERROR')
226
237
  lines.setElement(0, 2, errorMessage)
@@ -252,7 +263,7 @@ class Generators {
252
263
  lines.setElement(0, 2, stack)
253
264
  lines.newRow()
254
265
  lines.setElement(0, 1, 'DEBUG')
255
- lines.setElement(0, 2, `To debug this use args.callId === '${args.calls.current()}'`)
266
+ lines.setElement(0, 2, `To debug this use debug.breakAt('${args.calls.current()}')`)
256
267
  lines.newRow()
257
268
  lines.setElement(0, 1, 'TO')
258
269
  lines.setElement(0, 2, `context_id: ${context.context_id}`)
package/src/helpers.js CHANGED
@@ -1,6 +1,27 @@
1
1
  const deepEqual = require('deep-equal')
2
2
  const stringify = require('json-stable-stringify')
3
3
 
4
+
5
+ // check if member function of a class have been overriden by an subclass
6
+ class OverrideCheck {
7
+ constructor(base, suffix='API') {
8
+ this.base = base
9
+ this.checks = Object.getOwnPropertyNames(base.prototype).filter(key => typeof base.prototype[key] === 'function' && key.endsWith(suffix));
10
+ }
11
+
12
+ check(obj) {
13
+ const overriden = obj.constructor !== obj.baseAPI
14
+ if (!overriden) {
15
+ return
16
+ }
17
+ for (const check of this.checks) {
18
+ if (obj[check] == this.base.prototype[check]) {
19
+ throw new Error(`For ${obj.constructor.name} you need to override ${check}`)
20
+ }
21
+ }
22
+ }
23
+ }
24
+
4
25
  function pathEquals (p1, p2) {
5
26
  if (p1.length !== p2.length) {
6
27
  return false
@@ -523,52 +544,77 @@ const stableId = (tag) => {
523
544
  }
524
545
 
525
546
  function getByPath(obj, path, defaultValue) {
547
+ const segments = Array.isArray(path) ? path : [path];
548
+ const keys = segments.flatMap((segment) => {
549
+ if (typeof segment !== "string") return [segment];
550
+ return segment
551
+ .replace(/\[(\d+)\]/g, ".$1")
552
+ .split(".")
553
+ .filter((key) => key !== "");
554
+ });
555
+
526
556
  let current = obj;
527
- for (const key of path) {
557
+ for (const key of keys) {
528
558
  if (current === null || current === undefined) return defaultValue;
529
- if (typeof current !== 'object') return defaultValue;
559
+ if (typeof current !== "object") return defaultValue;
530
560
  current = current[key];
531
561
  }
532
562
  return current === undefined ? defaultValue : current;
533
563
  }
534
564
 
565
+ function normalizePath(path) {
566
+ const segments = Array.isArray(path) ? path : [path];
567
+ return segments.flatMap((segment) => {
568
+ if (typeof segment !== "string") return [segment];
569
+ return segment
570
+ .replace(/\[(\d+)\]/g, ".$1")
571
+ .split(".")
572
+ .filter((key) => key !== "");
573
+ });
574
+ }
575
+
576
+ function isArrayIndex(key) {
577
+ if (typeof key === "number") return Number.isInteger(key) && key >= 0;
578
+ return key !== "" && String(key >>> 0) === String(key);
579
+ }
580
+
535
581
  /**
536
- * Set a value in an object by path array.
582
+ * Set a value in an object by path (string or array).
537
583
  * Automatically creates missing objects {} or arrays [] as needed.
538
584
  *
539
585
  * @param {Object} obj - The root object to modify
540
- * @param {Array<string|number>} path - Array of keys/indices
586
+ * @param {string|Array<string|number>} path - Path keys/indices
541
587
  * @param {*} value - Value to set
542
588
  * @returns {*} The set value (for chaining)
543
589
  */
544
590
  function setByPath(obj, path, value) {
545
- if (!Array.isArray(path) || path.length === 0) {
546
- throw new Error('Path must be a non-empty array');
591
+ const keys = normalizePath(path);
592
+
593
+ if (keys.length === 0) {
594
+ throw new Error("Path must be a non-empty string or array");
595
+ }
596
+
597
+ if (obj == null || typeof obj !== "object") {
598
+ throw new Error("Target must be an object or array");
547
599
  }
548
600
 
549
601
  let current = obj;
550
602
 
551
- for (let i = 0; i < path.length; i++) {
552
- const key = path[i];
553
- const isLast = i === path.length - 1;
603
+ for (let i = 0; i < keys.length; i++) {
604
+ const key = keys[i];
605
+ const isLast = i === keys.length - 1;
554
606
 
555
607
  if (isLast) {
556
- // Final step — just assign
557
608
  current[key] = value;
558
609
  } else {
559
- // Not last ensure next level exists
560
- const nextKey = path[i + 1];
561
-
562
- if (current[key] == null) {
563
- // Auto-create: array if next key is number, otherwise object
564
- current[key] = typeof nextKey === 'number' || String(nextKey >>> 0) === nextKey
565
- ? []
566
- : {};
567
- } else if (Array.isArray(current[key]) && typeof nextKey !== 'number') {
568
- // Safety: if current is array but next key isn't a valid index → convert to object
610
+ const nextKey = keys[i + 1];
611
+ const nextIsIndex = isArrayIndex(nextKey);
612
+
613
+ if (current[key] == null || typeof current[key] !== "object") {
614
+ current[key] = nextIsIndex ? [] : {};
615
+ } else if (Array.isArray(current[key]) && !nextIsIndex) {
569
616
  current[key] = { ...current[key] };
570
- } else if (!Array.isArray(current[key]) && typeof nextKey === 'number') {
571
- // If next expects array but current is object → convert
617
+ } else if (!Array.isArray(current[key]) && nextIsIndex) {
572
618
  current[key] = Object.values(current[key]);
573
619
  }
574
620
 
@@ -579,7 +625,11 @@ function setByPath(obj, path, value) {
579
625
  return value;
580
626
  }
581
627
 
628
+ const helpDebugHierarchy = 'In order to get a debug break when a specific hierarchy is created set the DEBUG_HIERARCHY environment variable to the JSON of the child-parent pair to break on. For example DEBUG_HIERARCHY=\'["cat", "mammel"]\''
629
+
582
630
  module.exports = {
631
+ helpDebugHierarchy,
632
+
583
633
  stableId,
584
634
  ecatch,
585
635
  functionsToStrings,
@@ -611,4 +661,5 @@ module.exports = {
611
661
  assignAssumed,
612
662
  watchProperty,
613
663
  pathEquals,
664
+ OverrideCheck,
614
665
  }
package/src/project2.js CHANGED
@@ -101,6 +101,10 @@ function project(source, filters, path=[]) {
101
101
  } else if (prop.property && source.hasOwnProperty(prop.property)) {
102
102
  // If the property is an object and not null, recursively project it
103
103
  if (typeof source[prop.property] === 'object' && source[prop.property] !== null) {
104
+ if (prop.all) {
105
+ result[prop.property] = source[prop.property]
106
+ return
107
+ }
104
108
  result[prop.property] = {}
105
109
  const instantiatedCheck = []
106
110
  for (const check of prop.check) {
package/src/semantics.js CHANGED
@@ -75,13 +75,16 @@ class Semantic {
75
75
  async matches (args, context, options = {}) {
76
76
  args = {...args}
77
77
  this.fixUpArgs(args, context)
78
- const matches = await this.matcher(args)
79
- if (matches && (options.debug || {}).match || args.callId === this.callId) {
80
- // next line is the matcher
81
- debugger // eslint-disable-line no-debugger
82
- await this.matcher(args)
83
- }
84
- return matches
78
+ return await debug.hitScoped(args.callId, async () => {
79
+ const matches = await this.matcher(args)
80
+ if (matches) {
81
+ if ((options.debug || {}).match || args.callId === this.callId || process.env.DEBUG_CALLID == args.callId) {
82
+ debugger // eslint-disable-line no-debugger
83
+ await this.matcher(args) // DEBUG: This is the line you step into
84
+ }
85
+ }
86
+ return matches
87
+ })
85
88
  }
86
89
 
87
90
  async apply (args, context, s, options = {}) {
@@ -97,21 +100,24 @@ class Semantic {
97
100
  const contextPrime = Object.assign({}, context)
98
101
  this.fixUpArgs(args, contextPrime)
99
102
 
100
- if ((options.debug || {}).apply || args.callId === this.callId) {
101
- debugger // eslint-disable-line no-debugger
102
- }
103
- if (args.breakOnSemantics) {
104
- debugger // eslint-disable-line no-debugger
105
- }
106
- try {
107
- args.handlerStack.push(this)
108
- await this._apply(args)
109
- args.handlerStack.pop()
110
- } catch( e ) {
111
- args.handlerStack.pop()
112
- throw e
113
- }
114
- return contextPrime
103
+ return await debug.hitScoped(args.callId, async () => {
104
+ if ((options.debug || {}).apply || args.callId === this.callId || process.env.DEBUG_CALLID == args.callId) {
105
+ debugger // eslint-disable-line no-debugger // STEP ABOUT THREE TIMES TO THE DEBUG LINE
106
+ }
107
+ if (args.breakOnSemantics) {
108
+ debugger // eslint-disable-line no-debugger
109
+ }
110
+
111
+ try {
112
+ args.stack.push(this)
113
+ await this._apply(args) // DEBUG: This is the line you want to step into
114
+ args.stack.pop()
115
+ } catch( e ) {
116
+ args.stack.pop()
117
+ throw e
118
+ }
119
+ return contextPrime
120
+ })
115
121
  }
116
122
  }
117
123
 
@@ -187,6 +193,8 @@ class Semantics {
187
193
  let seenQuestion = false
188
194
  const deferred = []
189
195
  args.log = (message) => { this.logs.push(message) }
196
+ const finallyList = []; // called after the semantic is processed
197
+ args._finally = (call) => finallyList.unshift(call)
190
198
  for (const isemantic in this.semantics) {
191
199
  const semantic = this.semantics[isemantic]
192
200
  // only one question at a time
@@ -253,7 +261,7 @@ class Semantics {
253
261
  lines.setElement(0, 2, semantic.toString())
254
262
  lines.newRow()
255
263
  lines.setElement(0, 1, 'DEBUG')
256
- lines.setElement(0, 2, `To debug this use args.callId === '${args.calls.current()}'`)
264
+ lines.setElement(0, 2, `To debug this use debug.breakAt('${args.calls.current()}') in the code or DEBUG_CALLID="${args.calls.current()}"`)
257
265
  lines.newRow()
258
266
  lines.setElement(0, 1, 'TO')
259
267
  lines.setElement(0, 2, `context_id: ${context.context_id}`)
@@ -288,7 +296,7 @@ class Semantics {
288
296
  lines.setElement(0, 2, semantic.toString())
289
297
  lines.newRow()
290
298
  lines.setElement(0, 1, 'DEBUG')
291
- lines.setElement(0, 2, `To debug this use args.callId === '${args.calls.current()}'`)
299
+ lines.setElement(0, 2, `To debug this use debug.breakAt('${args.calls.current()}') in the code or DEBUG_CALLID="${args.calls.current()}"`)
292
300
  lines.newRow()
293
301
  lines.setElement(0, 1, 'TO')
294
302
  lines.setElement(0, 2, `context_id: ${context.context_id}`)
@@ -319,6 +327,9 @@ class Semantics {
319
327
  }
320
328
  counter += 1
321
329
  }
330
+ for (const f of finallyList) {
331
+ await f()
332
+ }
322
333
  args.calls.pop()
323
334
  if (!applied && debug) {
324
335
  const widths = Lines.addRemainder([10, 10])