theprogrammablemind 9.5.1 → 9.6.0-beta.1

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,10 +12,9 @@ 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 { appendNoDups, updateQueries, safeNoDups, stableId, where, suggestAssociationsFix, suggestAssociationsFixFromSummaries, validProps } = require('./src/helpers')
15
+ const { sortJson, appendNoDups, updateQueries, safeNoDups, stableId, where, suggestAssociationsFix, suggestAssociationsFixFromSummaries, validProps } = require('./src/helpers')
16
16
  const runtime = require('./runtime')
17
- const sortJson = runtime.sortJson
18
- const debug = require('./src/debug')
17
+ const db = require('./src/debug')
19
18
 
20
19
  const getConfig_getObjectsCheck = (config, testConfig) => {
21
20
  let testConfigName = config.name
@@ -46,33 +45,33 @@ const getSuggestionMessage = (suggestion) => {
46
45
  return `Try adding this to the associations: { context: ${JSON.stringify(getSuggestion(suggestion))}, choose: <indexOfMainElement> },\n If that does not work look at the logs and check when the operators become wrong during an interation. Deduce the change based on the previous iteration and what operator was applied.`
47
46
  }
48
47
 
49
- const getConfig_getContextCheck = (testConfig) => {
50
- return (testConfig.checks && testConfig.checks.context) || []
51
- }
52
-
53
48
  const pickContext = (contextChecks) => (context) => {
54
49
  return project2(context, contextChecks)
55
50
  }
56
51
 
57
52
  const pickObjects = (config, testConfig, getObjects) => {
58
- /*
59
- let testConfigName = config.name
60
- if (testConfig.testModuleName) {
61
- objects = getObjects(config.config.objects)(config.getConfigs()[testConfig.testModuleName].uuid)
62
- testConfigName = testConfig.testModuleName
63
- }
64
- */
65
53
  const checks = getConfig_getObjectsCheck(config, testConfig)
54
+ const contextChecks = config.getContextChecks()
66
55
  const projection = {}
67
56
  for (const km in checks) {
68
57
  const objects = getObjects(km)
69
58
  if (!objects) {
70
59
  throw new Error(`In the checks for ${config.name} the KM ${km} does not exist`)
71
60
  }
72
- if (checks[km] && checks[km].find((check) => check.match && check.apply)) {
73
- projection[km] = project2(objects, checks[km])
61
+
62
+ if (false) {
63
+ if (checks[km] && checks[km].find((check) => check.match && check.apply)) {
64
+ projection[km] = project2(objects, checks[km])
65
+ } else {
66
+ projection[km] = project(objects, checks[km])
67
+ }
74
68
  } else {
75
- projection[km] = project(objects, checks[km])
69
+ const lastIndex = contextChecks.length - 1
70
+ const last = contextChecks[lastIndex]
71
+ contextChecks[lastIndex] = { match: last.match, apply: () => [...new Set([...(checks[km] || []), ...last.apply()])] }
72
+
73
+ // projection[km] = project2(objects, [...checks[km], ...contextChecks])
74
+ projection[km] = project2(objects, contextChecks)
76
75
  }
77
76
  }
78
77
  return projection
@@ -237,9 +236,10 @@ const doWithRetries = async (n, url, queryParams, data) => {
237
236
  body: JSON.stringify(data),
238
237
  timeout: 1000 * 60 * 5, // it does not respect this timeout so that's why I have the retries
239
238
  headers: {
240
- mode: 'no-cors',
239
+ // mode: 'no-cors',
241
240
  'Content-Type': 'application/json'
242
- }
241
+ },
242
+ credentials: 'same-origin',
243
243
  })
244
244
  if (result.ok) {
245
245
  return JSON.parse(JSON.stringify(await result.json()))
@@ -316,6 +316,7 @@ const _process = async (config, query, { initializer, commandLineArgs, credentia
316
316
  }
317
317
 
318
318
  let startCounter = 0
319
+ let contextIdCounter = 0
319
320
  while (true) {
320
321
  if (queries.length === 0) {
321
322
  break
@@ -356,8 +357,9 @@ const _process = async (config, query, { initializer, commandLineArgs, credentia
356
357
  }
357
358
  const summary = { summaries: json.summaries, length: json.contexts.length }
358
359
  summaries.push(summary)
359
- const { contextsPrime, generatedPrime, paraphrasesPrime, paraphrasesParenthesizedPrime, generatedParenthesizedPrime, responsesPrime } =
360
- await processContextsB({ isTest, isProcess, isModule, rebuildingTemplate, config, hierarchy, json, commandLineArgs /*, generators, semantics */ })
360
+ const { updatedContextIdCounter, contextsPrime, generatedPrime, paraphrasesPrime, paraphrasesParenthesizedPrime, generatedParenthesizedPrime, responsesPrime } =
361
+ await processContextsB({ contextIdCounter, isTest, isProcess, isModule, rebuildingTemplate, config, hierarchy, json, commandLineArgs /*, generators, semantics */ })
362
+ contextIdCounter = updatedContextIdCounter
361
363
  if (isTest) {
362
364
  const end = runtime.performance.performance.now()
363
365
  clientSideTime = end - start
@@ -475,6 +477,7 @@ const runTest = async (config, expected, { args, verbose, testConfig, debug, tim
475
477
  return
476
478
  }
477
479
  // initialize in between test so state is not preserved since the test was adding without state
480
+ config.testConfig.testModuleName = testConfig.testModuleName
478
481
  await config.rebuild()
479
482
  const errorHandler = (error) => {
480
483
  if (error.metadata) {
@@ -838,7 +841,7 @@ const defaultInnerProcess = (config, errorHandler, responses) => {
838
841
  }
839
842
  console.log(responses.trace)
840
843
 
841
- if (true) {
844
+ if (false) {
842
845
  if (global.beforeObjects) {
843
846
  console.log('objects', runtime.jsonDiff.diffString(global.beforeObjects, config.get('objects')))
844
847
  } else {
@@ -913,7 +916,7 @@ const defaultInnerProcess = (config, errorHandler, responses) => {
913
916
  console.log('')
914
917
  const screen_width = process.stdout.columns
915
918
  // || 0 for when running without a console
916
- const widths = [70, 10, Math.max(80, screen_width - 71 || 0)]
919
+ const widths = Lines.addRemainder([70, 10])
917
920
  const lines = new Lines(widths)
918
921
  lines.setElement(0, 0, '--- The paraphrases are ----------')
919
922
  lines.setElement(0, 2, '--- The response strings are ----------')
@@ -933,12 +936,15 @@ const defaultInnerProcess = (config, errorHandler, responses) => {
933
936
  }
934
937
  }
935
938
 
936
- const defaultProcess = ({ config, errorHandler }) => async (promise) => {
939
+ const defaultProcess = ({ config, errorHandler, print }) => async (promise) => {
937
940
  try {
938
941
  const responses = await promise
939
942
  defaultInnerProcess(config, errorHandler, responses)
940
943
  } catch (error) {
941
944
  error.config = config
945
+ if (print) {
946
+ print()
947
+ }
942
948
  defaultErrorHandler(error)
943
949
  }
944
950
  }
@@ -963,8 +969,11 @@ const rebuildTemplate = async ({ config, instance, target, previousResultss, reb
963
969
  }
964
970
  }
965
971
  const fragmentToTodo = (query, index) => {
966
- const pr = instance.fragments[index]
967
- return Object.assign({}, toProperties(query), { property: 'fragments', previousResults: pr, skipSemantics: false })
972
+ let pr = []
973
+ if (instance.fragments) {
974
+ pr = instance.fragments[index]
975
+ }
976
+ return Object.assign({}, toProperties(query), { property: 'fragments', previousResults: pr, skipSemantics: true })
968
977
  }
969
978
 
970
979
  const looper = async (configs) => {
@@ -973,7 +982,7 @@ const rebuildTemplate = async ({ config, instance, target, previousResultss, reb
973
982
  return
974
983
  }
975
984
  const { property, hierarchy, query: queryOrExtraConfig, previousResults, initializer, skipSemantics } = configs.shift()
976
- // queries are strings or { query: "blah", development: true/false }
985
+ // queries are strings or { query: "blah", scope: "development" | "testing" }
977
986
  if (typeof queryOrExtraConfig === 'string' || queryOrExtraConfig.query || queryOrExtraConfig.isFragment) {
978
987
  let query = queryOrExtraConfig
979
988
  const isFragment = queryOrExtraConfig.isFragment
@@ -1175,10 +1184,7 @@ const checkTemplate = (template) => {
1175
1184
  if (!template) {
1176
1185
  return
1177
1186
  }
1178
- if (template.checks) {
1179
- throw new Error("The 'checks' property should be in the 'test' property not the 'template' property")
1180
- }
1181
- validProps(['fragments', 'configs'], template.template, 'template.template')
1187
+ validProps(['fragments', 'configs'], template, 'template.template')
1182
1188
  }
1183
1189
 
1184
1190
  const checkTest = (testConfig) => {
@@ -1212,6 +1218,7 @@ const knowledgeModuleImpl = async ({
1212
1218
  demo,
1213
1219
  test,
1214
1220
  template,
1221
+ instance, // instance file
1215
1222
  errorHandler = defaultErrorHandler,
1216
1223
  process: processResults = defaultProcess,
1217
1224
  stopAtFirstFailure = true,
@@ -1253,8 +1260,8 @@ const knowledgeModuleImpl = async ({
1253
1260
  config.setTestConfig(testConfig)
1254
1261
  }
1255
1262
 
1256
- const createConfig = async () => {
1257
- const config = new Config(configStruct, moduleFromJSFile, _process, apiKMs)
1263
+ const createConfig = async (rootIsProcess, testingModuleName) => {
1264
+ const config = new Config(configStruct, moduleFromJSFile, _process, apiKMs, rootIsProcess, testingModuleName)
1258
1265
  if (sendObjectsToServer) {
1259
1266
  config.setSendObjectsToServer()
1260
1267
  }
@@ -1318,13 +1325,32 @@ const knowledgeModuleImpl = async ({
1318
1325
  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]]' })
1319
1326
  parser.add_argument('-r', '--reset', { action: 'store_true', help: 'Get the server to bypass the cache and rebuild everything' })
1320
1327
  parser.add_argument('-q', '--query', { help: 'Run the specified query' })
1328
+ parser.add_argument('-f', '--filter', { help: 'for -pd only the data for the knowledge modules that start with this string will be shown' })
1321
1329
  parser.add_argument('-ip ', '--server', { help: 'Server to run against' })
1322
1330
  parser.add_argument('-qp ', '--queryParams', { help: 'Query params for the server call' })
1323
1331
  parser.add_argument('-dt', '--deleteTest', { help: 'Delete the specified query from the tests file.' })
1324
1332
  parser.add_argument('--parenthesized', { action: 'store_true', help: 'Show the generated phrases with parenthesis.' })
1325
1333
  parser.add_argument('-c', '--clean', { help: 'Remove data from the test files. a === association' })
1326
1334
  parser.add_argument('-od', '--objectDiff', { action: 'store_true', help: 'When showing the objects use a colour diff' })
1327
- parser.add_argument('-p', '--print', { help: 'Print the specified elements c === config, w === words, b === bridges, o === operators d === objects (d for data), h === hierarchy, ha === hierarchy ancestors g === generators, s === semantics, l === load t=tests ordering p === priorities a === associations j === JSON sent to server. for example --print wb' })
1335
+ parser.add_argument('-p', '--print', { help:
1336
+ `Print the specified elements
1337
+ a === associations
1338
+ b === bridges,
1339
+ c === config,
1340
+ cc === test checks,
1341
+ d === objects (d for data),
1342
+ g === generators,
1343
+ h === hierarchy,
1344
+ ha === hierarchy ancestors,
1345
+ j === JSON sent to server,
1346
+ l === load ordering,
1347
+ o === operators,
1348
+ p === priorities,
1349
+ s === semantics,
1350
+ t === tests ordering,
1351
+ w === words,
1352
+ for example --print wb' })
1353
+ ` })
1328
1354
  parser.add_argument('-s', '--save', { action: 'store_true', help: 'When running with the --query flag this will save the current run to the test file. When running without the --query flag all tests will be run and resaved.' })
1329
1355
  parser.add_argument('-fr', '--failRebuild', { action: 'store_true', help: 'If a rebuild is required fail out.' })
1330
1356
  parser.add_argument('-sd', '--saveDeveloper', { action: 'store_true', help: 'Same as -s but the query will not show up in the info command.' })
@@ -1347,16 +1373,13 @@ const knowledgeModuleImpl = async ({
1347
1373
  if (args.rebuildTemplateFull) {
1348
1374
  args.rebuildTemplate = true
1349
1375
  }
1350
- config = await createConfig()
1376
+ config = await createConfig(true, configStruct.name)
1351
1377
 
1352
1378
  // dont debug the load of the KM's if rebuild template is on since we want to debug the template rebuild not the load
1353
1379
  if (args.rebuildTemplate) {
1354
1380
  global.pauseDebugging = true
1355
1381
  }
1356
1382
 
1357
- // setupConfig(config)
1358
- processResults = processResults({ config, errorHandler })
1359
-
1360
1383
  if (args.rebuildTemplate) {
1361
1384
  global.pauseDebugging = false
1362
1385
  }
@@ -1488,6 +1511,12 @@ const knowledgeModuleImpl = async ({
1488
1511
  counter += 1
1489
1512
  }
1490
1513
  }
1514
+ if (hasArg('cc')) {
1515
+ for (const cc of config.getContextChecks()) {
1516
+ const printable = { ...cc, match: cc.match.toString(), apply: cc.apply.toString() }
1517
+ console.log(JSON.stringify(printable, null, 2))
1518
+ }
1519
+ }
1491
1520
  if (hasArg('c')) {
1492
1521
  const { data } = setupProcessB({ config })
1493
1522
  console.log('Config as sent to server')
@@ -1549,14 +1578,29 @@ const knowledgeModuleImpl = async ({
1549
1578
  }
1550
1579
  }
1551
1580
  }
1581
+
1552
1582
  if (hasArg('d')) {
1553
- console.log(JSON.stringify(config.config.objects, null, 2))
1583
+ if (args.filter) {
1584
+ console.log(`objects (data) filtered by ${args.filter} ================`)
1585
+ const projection = { namespaced: {} }
1586
+ for (const key of Object.keys(config.config.objects.namespaced)) {
1587
+ if (key.startsWith(args.filter)) {
1588
+ projection.namespaced[key] = config.config.objects.namespaced[key]
1589
+ }
1590
+ }
1591
+ console.log(JSON.stringify(projection, null, 2))
1592
+ } else {
1593
+ console.log('objects (data) ================')
1594
+ console.log(JSON.stringify(config.config.objects, null, 2))
1595
+ }
1554
1596
  }
1597
+
1555
1598
  if (hasArg('p')) {
1556
1599
  for (const priority of config.config.priorities) {
1557
1600
  console.log(JSON.stringify(priority))
1558
1601
  }
1559
1602
  }
1603
+
1560
1604
  if (hasArg('g')) {
1561
1605
  const easyToRead = _.cloneDeep(config.config.generators)
1562
1606
  for (const semantic of easyToRead) {
@@ -1568,6 +1612,7 @@ const knowledgeModuleImpl = async ({
1568
1612
  }
1569
1613
  console.dir(easyToRead)
1570
1614
  }
1615
+
1571
1616
  if (hasArg('s')) {
1572
1617
  const easyToRead = _.cloneDeep(config.config.semantics)
1573
1618
  for (const semantic of easyToRead) {
@@ -1579,16 +1624,20 @@ const knowledgeModuleImpl = async ({
1579
1624
  }
1580
1625
  }
1581
1626
 
1627
+ // setupConfig(config)
1628
+ processResults = processResults({ config, errorHandler, print: printConfig })
1629
+
1630
+
1582
1631
  checkTemplate(template)
1583
1632
 
1584
1633
  if (template) {
1585
1634
  let needsRebuild
1586
1635
  if (args.rebuildTemplate && !args.rebuildTemplateFull) {
1587
1636
  // get the startOfChanges for the partial rebuild
1588
- needsRebuild = config.needsRebuild(template.template, template.instance, { ...options, rebuild: false })
1637
+ needsRebuild = config.needsRebuild(template, instance, { ...options, rebuild: false })
1589
1638
  } else {
1590
1639
  // do a check or full rebuild
1591
- needsRebuild = config.needsRebuild(template.template, template.instance, options)
1640
+ needsRebuild = config.needsRebuild(template, instance, options)
1592
1641
  }
1593
1642
 
1594
1643
  if (needsRebuild.needsRebuild) {
@@ -1604,7 +1653,7 @@ const knowledgeModuleImpl = async ({
1604
1653
  config.config.rebuild = true
1605
1654
  }
1606
1655
  try {
1607
- await config.load(rebuildTemplate, template.template, template.instance, { rebuild: needsRebuild.needsRebuild || options.rebuild, previousResultss: needsRebuild.previousResultss, startOfChanges: needsRebuild.startOfChanges })
1656
+ await config.load(rebuildTemplate, template, instance, { rebuild: needsRebuild.needsRebuild || options.rebuild, previousResultss: needsRebuild.previousResultss, startOfChanges: needsRebuild.startOfChanges })
1608
1657
  config.fixtures()
1609
1658
  } catch (e) {
1610
1659
  console.error(`Error loading template for ${config.name}. ${e.error ? e.error : e}${e.stack ? e.stack : ''}`)
@@ -1912,6 +1961,7 @@ const knowledgeModuleImpl = async ({
1912
1961
  try {
1913
1962
  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 }))
1914
1963
  } catch (error) {
1964
+ printConfig()
1915
1965
  console.log('Error', error)
1916
1966
  }
1917
1967
  }
@@ -1925,8 +1975,8 @@ const knowledgeModuleImpl = async ({
1925
1975
  } else {
1926
1976
  const initConfig = async (config) => {
1927
1977
  if (template) {
1928
- if (config.needsRebuild(template.template, template.instance, { isModule: !isProcess }).needsRebuild) {
1929
- config.needsRebuild(template.template, template.instance, { isModule: !isProcess })
1978
+ if (config.needsRebuild(template, instance, { isModule: !isProcess }).needsRebuild) {
1979
+ config.needsRebuild(template, instance, { isModule: !isProcess })
1930
1980
  const error = `This module "${config.name}" cannot be used because the instance file needs rebuilding. Run on the command line with no arguments or the -rt argument to rebuild.`
1931
1981
  throw new Error(error)
1932
1982
  }
@@ -1941,7 +1991,7 @@ const knowledgeModuleImpl = async ({
1941
1991
  }
1942
1992
  if (template) {
1943
1993
  try {
1944
- await config.load(rebuildTemplate, template.template, template.instance)
1994
+ await config.load(rebuildTemplate, template, instance)
1945
1995
  } catch (e) {
1946
1996
  errorHandler(e)
1947
1997
  }
@@ -1949,33 +1999,17 @@ const knowledgeModuleImpl = async ({
1949
1999
 
1950
2000
  // remove test only stuff
1951
2001
  if (!isProcess && !loadForTesting) {
1952
- config.removeDevelopmentElements(config.config)
1953
- /*
1954
- config.config.operators = config.config.operators.filter((operator) => {
1955
- if (operator.development) {
1956
- return false
1957
- } else {
1958
- return true
1959
- }
1960
- })
1961
- config.config.bridges = config.config.bridges.filter((bridge) => {
1962
- if (bridge.development) {
1963
- return false
1964
- } else {
1965
- return true
1966
- }
1967
- })
1968
- */
2002
+ config.removeDevelopmentElements(config.config, config.scope)
1969
2003
  }
1970
2004
  }
1971
2005
 
1972
2006
  // no cache 21 minutes + rebuild fails "node tester_rebuild -m colors"
1973
2007
  // cache okay
1974
- const createConfigExport = async (useCache = true) => {
2008
+ const createConfigExport = async ({ useCache=true, rootIsProcess, testingModuleName } = {}) => {
1975
2009
  if (useCache && createConfig.cached) {
1976
2010
  return createConfig.cached
1977
2011
  }
1978
- const config = await createConfig()
2012
+ const config = await createConfig(rootIsProcess, testingModuleName)
1979
2013
  await initConfig(config)
1980
2014
  // config.rebuild({ isModule: true })
1981
2015
  createConfig.cached = config
@@ -2011,9 +2045,15 @@ const ensureTestFile = (module, name, type) => {
2011
2045
  }
2012
2046
 
2013
2047
  const knowledgeModule = async (...args) => {
2014
- await knowledgeModuleImpl(...args).catch((e) => {
2048
+ await knowledgeModuleImpl(...args).catch(async (e) => {
2015
2049
  console.error(e)
2016
- process.exit(-1)
2050
+ function sleep(ms) {
2051
+ return new Promise((resolve) => {
2052
+ setTimeout(resolve, ms);
2053
+ });
2054
+ }
2055
+ await sleep(1) // get the stderr to flush
2056
+ await process.exit(-1); // tiny trick: empty write forces flush of console.error buffer
2017
2057
  })
2018
2058
  }
2019
2059
 
@@ -2038,5 +2078,6 @@ module.exports = {
2038
2078
  gs,
2039
2079
  flattens,
2040
2080
  writeTest,
2041
- getConfigForTest
2081
+ getConfigForTest,
2082
+ debug: db,
2042
2083
  }
package/lines.js CHANGED
@@ -5,6 +5,13 @@ class Lines {
5
5
  this.rows = []
6
6
  }
7
7
 
8
+ static SCREEN_WIDTH = 164
9
+
10
+ static addRemainder(widths) {
11
+ const sum = widths.reduce((a, b) => a + b)
12
+ return [...widths, Lines.SCREEN_WIDTH - sum]
13
+ }
14
+
8
15
  addLine () {
9
16
  this.lines.push(this.widths.map((width) => ''.padEnd(width)))
10
17
  }
package/package.json CHANGED
@@ -3,14 +3,14 @@
3
3
  "@eslint/js": "^9.21.0",
4
4
  "@typescript-eslint/eslint-plugin": "^4.28.4",
5
5
  "@typescript-eslint/parser": "^4.28.4",
6
+ "argparse": "^2.0.1",
6
7
  "eslint": "^7.32.0",
7
8
  "eslint-config-standard": "^16.0.3",
8
9
  "eslint-plugin-import": "^2.23.4",
9
10
  "eslint-plugin-node": "^11.1.0",
10
11
  "eslint-plugin-promise": "^5.1.0",
11
12
  "globals": "^16.0.0",
12
- "jest": "^29.7.0",
13
- "argparse": "^2.0.1"
13
+ "jest": "^30.2.0"
14
14
  },
15
15
  "scripts": {
16
16
  "to:debug": "node inspect node_modules/.bin/jest --runInBand -t NEO23",
@@ -45,6 +45,7 @@
45
45
  "runtime.js",
46
46
  "src/helpers.js",
47
47
  "src/flatten.js",
48
+ "src/fragments.js",
48
49
  "src/unflatten.js",
49
50
  "src/config.js",
50
51
  "src/configHelpers.js",
@@ -61,6 +62,7 @@
61
62
  "dependencies": {
62
63
  "base-64": "^1.0.0",
63
64
  "deep-equal": "^2.0.4",
65
+ "flatted": "^3.3.3",
64
66
  "fs": "0.0.1-security",
65
67
  "json-diff": "^1.0.3",
66
68
  "json-stable-stringify": "^1.0.1",
@@ -68,9 +70,8 @@
68
70
  "node-fetch": "^2.6.1",
69
71
  "readline": "^1.3.0",
70
72
  "scriptjs": "^2.5.9",
71
- "sort-json": "^2.0.0",
72
73
  "uuid": "^8.3.2"
73
74
  },
74
- "version": "9.5.1",
75
+ "version": "9.6.0-beta.1",
75
76
  "license": "UNLICENSED"
76
77
  }
package/runtime.js CHANGED
@@ -13,7 +13,6 @@ module.exports = {
13
13
  ArgumentParser,
14
14
  readline: require('readline'),
15
15
  jsonDiff: require('json-diff'),
16
- sortJson: require('sort-json'),
17
16
  util: require('util'),
18
17
  performance: require('perf_hooks')
19
18
  }