aontu 0.67.0 → 0.69.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.
Files changed (86) hide show
  1. package/dist/alias.d.ts +12 -1
  2. package/dist/alias.js +176 -6
  3. package/dist/alias.js.map +1 -1
  4. package/dist/aliasname.d.ts +19 -0
  5. package/dist/aliasname.js +65 -0
  6. package/dist/aliasname.js.map +1 -0
  7. package/dist/aontu.d.ts +1 -1
  8. package/dist/aontu.js +21 -8
  9. package/dist/aontu.js.map +1 -1
  10. package/dist/aontumodel.js +1 -1
  11. package/dist/aontumodel.js.map +1 -1
  12. package/dist/cli.d.ts +4 -1
  13. package/dist/cli.js +29 -13
  14. package/dist/cli.js.map +1 -1
  15. package/dist/ctx.d.ts +1 -0
  16. package/dist/ctx.js +1 -0
  17. package/dist/ctx.js.map +1 -1
  18. package/dist/err.js +18 -1
  19. package/dist/err.js.map +1 -1
  20. package/dist/format.js +14 -0
  21. package/dist/format.js.map +1 -1
  22. package/dist/helpdoc.js +1 -1
  23. package/dist/helpdoc.js.map +1 -1
  24. package/dist/hints.js +171 -0
  25. package/dist/hints.js.map +1 -1
  26. package/dist/lang.js +325 -38
  27. package/dist/lang.js.map +1 -1
  28. package/dist/lsp.d.ts +10 -4
  29. package/dist/lsp.js +116 -7
  30. package/dist/lsp.js.map +1 -1
  31. package/dist/site.d.ts +9 -0
  32. package/dist/site.js +2 -1
  33. package/dist/site.js.map +1 -1
  34. package/dist/subsume.js +6 -1
  35. package/dist/subsume.js.map +1 -1
  36. package/dist/val/BagVal.d.ts +1 -0
  37. package/dist/val/BagVal.js +3 -0
  38. package/dist/val/BagVal.js.map +1 -1
  39. package/dist/val/EmitFuncVal.js +2 -1
  40. package/dist/val/EmitFuncVal.js.map +1 -1
  41. package/dist/val/FilterFuncVal.js +1 -1
  42. package/dist/val/FilterFuncVal.js.map +1 -1
  43. package/dist/val/FuncBaseVal.d.ts +2 -1
  44. package/dist/val/FuncBaseVal.js +25 -1
  45. package/dist/val/FuncBaseVal.js.map +1 -1
  46. package/dist/val/MapVal.js +10 -4
  47. package/dist/val/MapVal.js.map +1 -1
  48. package/dist/val/MatchFuncVal.js +2 -1
  49. package/dist/val/MatchFuncVal.js.map +1 -1
  50. package/dist/val/NilVal.js +3 -0
  51. package/dist/val/NilVal.js.map +1 -1
  52. package/dist/val/RecurseVal.d.ts +1 -0
  53. package/dist/val/RecurseVal.js +7 -3
  54. package/dist/val/RecurseVal.js.map +1 -1
  55. package/dist/val/RefVal.d.ts +1 -0
  56. package/dist/val/RefVal.js +47 -18
  57. package/dist/val/RefVal.js.map +1 -1
  58. package/dist/vet.d.ts +2 -1
  59. package/dist/vet.js +51 -6
  60. package/dist/vet.js.map +1 -1
  61. package/package.json +1 -1
  62. package/skill/error-codes.md +25 -1
  63. package/src/alias.ts +214 -6
  64. package/src/aliasname.ts +95 -0
  65. package/src/aontu.ts +23 -9
  66. package/src/aontumodel.ts +1 -1
  67. package/src/cli.ts +35 -13
  68. package/src/ctx.ts +4 -1
  69. package/src/err.ts +19 -1
  70. package/src/format.ts +14 -0
  71. package/src/helpdoc.ts +1 -1
  72. package/src/hints.ts +228 -0
  73. package/src/lang.ts +370 -33
  74. package/src/lsp.ts +136 -6
  75. package/src/site.ts +15 -1
  76. package/src/subsume.ts +9 -2
  77. package/src/val/BagVal.ts +4 -0
  78. package/src/val/EmitFuncVal.ts +3 -2
  79. package/src/val/FilterFuncVal.ts +2 -2
  80. package/src/val/FuncBaseVal.ts +30 -1
  81. package/src/val/MapVal.ts +10 -4
  82. package/src/val/MatchFuncVal.ts +3 -2
  83. package/src/val/NilVal.ts +3 -0
  84. package/src/val/RecurseVal.ts +8 -3
  85. package/src/val/RefVal.ts +49 -19
  86. package/src/vet.ts +55 -6
package/src/lang.ts CHANGED
@@ -106,6 +106,8 @@ import { DisjunctVal } from './val/DisjunctVal'
106
106
  import { IntegerVal } from './val/IntegerVal'
107
107
  import { ListVal } from './val/ListVal'
108
108
  import { MapVal } from './val/MapVal'
109
+ import { repathInstance } from './val/Val'
110
+ import { AontuContext } from './ctx'
109
111
  import { NilVal } from './val/NilVal'
110
112
  import { NullVal } from './val/NullVal'
111
113
  import { NumberVal } from './val/NumberVal'
@@ -188,11 +190,34 @@ const CC_d = 100
188
190
  const CC_D = 68
189
191
 
190
192
  const CC_PCT = 37
191
- const ALIAS_RE = /^%[A-Za-z_][A-Za-z0-9_]*/
193
+ import type { AliasBind } from './aliasname'
194
+ import {
195
+ ALIAS_RE,
196
+ ALIAS_SET,
197
+ ALIAS_SHORTHAND_RE,
198
+ EXPORT_DECL_NAME,
199
+ RESERVED_KEY_PREFIX,
200
+ exportHoldKey,
201
+ aliasBareName,
202
+ aliasScopedKey,
203
+ aliasSetItems,
204
+ } from './aliasname'
192
205
 
193
206
  const CC_EQ = 61
194
207
  const CC_SP = 32
195
208
  const CC_TAB = 9
209
+ const CC_OB = 123
210
+ const WS_RE = /\s/
211
+
212
+ let SCOPE_SEQ = 0
213
+ const MERGE_KEY = RESERVED_KEY_PREFIX + 'merge'
214
+ const ALIAS_MARK_KEY = RESERVED_KEY_PREFIX + 'alias'
215
+ const OPTIONAL_MARK_KEY = RESERVED_KEY_PREFIX + 'optional'
216
+
217
+ // `{ %a } = @"f.aon"` is the pair `<head>: <include>`, so the head is
218
+ // one token and the grammar needs nothing new.
219
+ const IMPORT_HEAD_RE = new RegExp('^(' + ALIAS_SET + ')[ \\t]*=(?!=)')
220
+ const EXPORT_RE = /^export[ \t]*\([ \t]*([^()\s][^()]*?)[ \t]*\)/
196
221
 
197
222
  const CC_9 = 57
198
223
  const CC_A = 65
@@ -326,6 +351,72 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
326
351
  },
327
352
  })
328
353
 
354
+ // THE FIXED MATCHER'S CHECK RUNS AT EVERY POSITION: `{` opens a fixed
355
+ // token the text check never sees.
356
+ jsonic.options({
357
+ fixed: {
358
+ check: (lex: any) => {
359
+ const pnt = lex.pnt
360
+ const ex = lex.aontu_export
361
+
362
+ // The `(` is the pair's separator, the argument its value.
363
+ if (null != ex && pnt.sI === ex.cl) {
364
+ const cltkn = lex.token('#CL', undefined, '(', pnt)
365
+ pnt.cI += ex.arg - pnt.sI
366
+ pnt.sI = ex.arg
367
+ return { done: true, token: cltkn }
368
+ }
369
+
370
+ if (null != ex && pnt.sI === ex.arg) {
371
+ delete lex.aontu_export
372
+ const atkn = lex.token('#TX', ex.src, ex.src, pnt)
373
+ pnt.cI += ex.end - pnt.sI
374
+ pnt.sI = ex.end
375
+ return { done: true, token: atkn }
376
+ }
377
+
378
+ // THE SHORTHAND IS READ OFF THE SOURCE: a name lexes as its pair.
379
+ const sh = lex.aontu_shorthand
380
+ if (null != sh && pnt.sI === sh.at) {
381
+ if (1 === sh.stage) {
382
+ sh.stage = 2
383
+ return { done: true, token: lex.token('#CL', undefined, ':', pnt) }
384
+ }
385
+ delete lex.aontu_shorthand
386
+ return {
387
+ done: true, token: lex.token(
388
+ '#VL',
389
+ (r: Rule, ctx: JsonicContext) =>
390
+ addsite(new RefVal(
391
+ { peg: [aliasScopedKey(sh.name, srcUrl(ctx))], absolute: true }),
392
+ r, ctx),
393
+ sh.name, pnt)
394
+ }
395
+ }
396
+
397
+ if (CC_OB === lex.src.charCodeAt(pnt.sI)) {
398
+ const sres = ALIAS_SHORTHAND_RE.exec(lex.refwd())
399
+ if (null != sres) {
400
+ lex.aontu_shorthand_end = pnt.sI + sres[0].length
401
+ }
402
+ const hres = IMPORT_HEAD_RE.exec(lex.refwd())
403
+ if (null != hres) {
404
+ const hsrc = hres[1]
405
+ lex.aontu_eq_at = pnt.sI + hres[0].length - 1
406
+ const htkn = lex.token('#TX', hsrc, hsrc, pnt, {
407
+ aontu_import: aliasSetItems(hsrc) as AliasBind[],
408
+ })
409
+ pnt.sI += hsrc.length
410
+ pnt.cI += hsrc.length
411
+ return { done: true, token: htkn }
412
+ }
413
+ }
414
+
415
+ return undefined
416
+ },
417
+ },
418
+ })
419
+
329
420
  jsonic.options({
330
421
  text: {
331
422
  check: (lex: any) => {
@@ -346,10 +437,29 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
346
437
  lex.aontu_eq_at = j
347
438
  }
348
439
 
440
+ // The name is the KEY; separator and reference follow it.
441
+ const shend = lex.aontu_shorthand_end
442
+ if (null != shend && pnt.sI < shend) {
443
+ let k = pnt.sI + asrc.length
444
+ while (k < src.length && WS_RE.test(src[k])) {
445
+ k++
446
+ }
447
+ const ktkn = lex.token('#TX', asrc.substring(1), asrc, pnt)
448
+ pnt.cI += k - pnt.sI
449
+ pnt.sI = k
450
+ lex.aontu_shorthand = { at: k, name: asrc, stage: 1 }
451
+ if (shend - 1 <= k) {
452
+ delete lex.aontu_shorthand_end
453
+ }
454
+ return { done: true, token: ktkn }
455
+ }
456
+
349
457
  const atkn = lex.token(
350
458
  '#VL',
351
459
  (r: Rule, ctx: JsonicContext) =>
352
- addsite(new RefVal({ peg: [asrc], absolute: true }), r, ctx),
460
+ addsite(new RefVal(
461
+ { peg: [aliasScopedKey(asrc, srcUrl(ctx))], absolute: true }),
462
+ r, ctx),
353
463
  asrc,
354
464
  pnt)
355
465
  pnt.sI += asrc.length
@@ -357,6 +467,27 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
357
467
  return { done: true, token: atkn }
358
468
  }
359
469
 
470
+ // `export(...)`: the word is the key, spelled unwritably.
471
+ if (CC_e === src.charCodeAt(pnt.sI)) {
472
+ const eres = EXPORT_RE.exec(lex.refwd())
473
+ if (null != eres) {
474
+ const open = eres[0].indexOf('(')
475
+ const arg = eres[0].indexOf(eres[1], open)
476
+ lex.aontu_export = {
477
+ cl: pnt.sI + open,
478
+ arg: pnt.sI + arg,
479
+ end: pnt.sI + eres[0].length,
480
+ src: eres[1],
481
+ }
482
+ const etkn = lex.token(
483
+ '#TX', exportHoldKey(), EXPORT_DECL_NAME, pnt,
484
+ { aontu_export: true, aontu_export_items: aliasSetItems(eres[1]) })
485
+ pnt.sI += EXPORT_DECL_NAME.length
486
+ pnt.cI += EXPORT_DECL_NAME.length
487
+ return { done: true, token: etkn }
488
+ }
489
+ }
490
+
360
491
  if (CC_EQ === src.charCodeAt(pnt.sI) && lex.aontu_eq_at === pnt.sI) {
361
492
  delete lex.aontu_eq_at
362
493
  const eqtkn = lex.token('#CL', undefined, '=', pnt, { aontu_eq: true })
@@ -434,6 +565,28 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
434
565
  return v
435
566
  }
436
567
 
568
+ // `export` publishes a name; it renames none, and `{%}` is theirs.
569
+ const publishedNames = (items?: AliasBind[]): string[] | undefined =>
570
+ undefined === items || 0 === items.length ? undefined :
571
+ items.every((i) => i.local === i.remote) ?
572
+ items.map((i) => i.local) : undefined
573
+
574
+ // A ROOT MAY BE WRAPPED: a file's names are its MAP'S, not open()'s.
575
+ const declaringMap = (val: any): any => {
576
+ let cur = val
577
+ while (true === cur?.isFunc && 1 === cur.peg?.length) {
578
+ cur = cur.peg[0]
579
+ }
580
+ return cur
581
+ }
582
+
583
+ // The file a value was written in, and so any alias name's scope. A
584
+ // DOCUMENT IS ITS OWN SCOPE too, so a fileless parse is tagged.
585
+ const srcUrl = (ctx: JsonicContext): string => {
586
+ const url = ctx.meta.multisource && ctx.meta.multisource.path
587
+ return url ? url : ((ctx.meta as any).aontu_scope ??= '#' + (++SCOPE_SEQ))
588
+ }
589
+
437
590
  let addsite = (v: Val, r: Rule, ctx: JsonicContext) => {
438
591
  // The source text comes from the SAME token the row and column
439
592
  // come from. jsonic has carried it all along; not reading it is
@@ -449,8 +602,16 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
449
602
  const isAliasDecl = (ktkn: any, sep: any): boolean =>
450
603
  null != ktkn && VL === ktkn.tin && ALIAS_RE.test('' + ktkn.src) &&
451
604
  true === sep?.use?.aontu_eq
605
+ const keyName = (ktkn: any): string =>
606
+ 'string' === typeof ktkn?.val ? ktkn.val : '' + ktkn?.src
607
+
452
608
  const keyRefusalOf = (ktkn: any, sep: any):
453
609
  { why: string, details?: Record<string, any> } | undefined => {
610
+ // THE SENTINEL NAMESPACE IS THE ENGINE'S; it writes `export` here.
611
+ if (keyName(ktkn).startsWith(RESERVED_KEY_PREFIX) &&
612
+ true !== ktkn.use?.aontu_export) {
613
+ return { why: 'reserved_key' }
614
+ }
454
615
  if (null == ktkn || VL !== ktkn.tin) {
455
616
  return undefined
456
617
  }
@@ -576,14 +737,14 @@ help isolate the syntax error.`,
576
737
  ((prev as any)[SPREAD] || { o: '&', v: [] })
577
738
  ;(prev as any)[SPREAD].v.push(lm.spread.cj)
578
739
  }
579
- prev.___optional = (prev.___optional || [])
580
- for (const k of lm.optionalKeys) { prev.___optional.push(k) }
581
- prev.___alias = (prev.___alias || [])
582
- for (const k of lm.aliasKeys) { prev.___alias.push(k) }
740
+ prev[OPTIONAL_MARK_KEY] = (prev[OPTIONAL_MARK_KEY] || [])
741
+ for (const k of lm.optionalKeys) { prev[OPTIONAL_MARK_KEY].push(k) }
742
+ prev[ALIAS_MARK_KEY] = (prev[ALIAS_MARK_KEY] || [])
743
+ for (const k of lm.aliasKeys) { prev[ALIAS_MARK_KEY].push(k) }
583
744
  return prev
584
745
  }
585
- prev.___merge = (prev.___merge || [])
586
- prev.___merge.push(curr)
746
+ prev[MERGE_KEY] = (prev[MERGE_KEY] || [])
747
+ prev[MERGE_KEY].push(curr)
587
748
  return prev
588
749
  }
589
750
  }
@@ -940,6 +1101,16 @@ help isolate the syntax error.`,
940
1101
  jsonic.rule('val', (rs: RuleSpec) => {
941
1102
 
942
1103
  rs
1104
+ // Before the core implicit-map alts: a value prefix, not a key.
1105
+ .open([
1106
+ {
1107
+ s: [VL, CL],
1108
+ c: (r: Rule) => 0 !== r.d && isAliasDecl(r.o0, r.o1),
1109
+ p: 'val',
1110
+ a: (r: Rule) => { r.u.aontu_alias_val = '' + r.o0.src },
1111
+ g: 'aontu-alias-val',
1112
+ },
1113
+ ], { append: false })
943
1114
  .open([
944
1115
  {
945
1116
  s: [CJ, CL], p: 'map', b: 2, n: { pk: 1 },
@@ -1017,6 +1188,13 @@ help isolate the syntax error.`,
1017
1188
 
1018
1189
  r.node = valnode
1019
1190
 
1191
+ const aname = r.u.aontu_alias_val
1192
+ if (null != aname && null != valnode) {
1193
+ const hoist = ((ctx as any).aontu_alias_hoist ||= [])
1194
+ hoist.push(
1195
+ { name: aliasScopedKey('' + aname, srcUrl(ctx)), val: valnode })
1196
+ }
1197
+
1020
1198
  return undefined
1021
1199
  })
1022
1200
 
@@ -1036,13 +1214,27 @@ help isolate the syntax error.`,
1036
1214
 
1037
1215
  .bc((r: Rule, ctx: JsonicContext) => {
1038
1216
  const optionalKeys = r.u.aontu_optional_keys ?? []
1039
- const aliasKeys = r.u.aontu_alias_keys ?? []
1217
+ const aliasDecls = r.u.aontu_alias_keys ?? []
1218
+ const aliasKeys: string[] = []
1219
+ const exportKeys: string[] = []
1040
1220
 
1041
1221
  let mo = r.node
1042
1222
 
1223
+ // A REFUSED KEY TAKES NO KEY and is not OPTIONAL: its name is a
1224
+ // mark's, and the elision below would put it back.
1225
+ for (const kr of (r.u.aontu_key_refusals ?? []) as any[]) {
1226
+ if ('reserved_key' === kr.why) {
1227
+ delete mo[kr.key]
1228
+ const oi = optionalKeys.indexOf(kr.key)
1229
+ if (-1 !== oi) {
1230
+ optionalKeys.splice(oi, 1)
1231
+ }
1232
+ }
1233
+ }
1234
+
1043
1235
  for (const k in mo) {
1044
- if (null == mo[k] && '___merge' !== k &&
1045
- '___optional' !== k && '___alias' !== k) {
1236
+ if (null == mo[k] && MERGE_KEY !== k &&
1237
+ OPTIONAL_MARK_KEY !== k && ALIAS_MARK_KEY !== k) {
1046
1238
  // Pathed at the KEY, not at the enclosing map. addsite takes
1047
1239
  // the rule's path, which here is the map's, so the error
1048
1240
  // would otherwise name the container and leave the reader to
@@ -1070,45 +1262,167 @@ help isolate the syntax error.`,
1070
1262
  return undefined
1071
1263
  }
1072
1264
 
1073
- for (const { key, tkn, why, details } of
1265
+ const reservedRefusals: Val[] = []
1266
+ for (const { key, tkn, why, details, url } of
1074
1267
  (r.u.aontu_key_refusals ?? []) as any[]) {
1075
1268
  const en: any = siteAt(addsite(new NilVal({ why }), r, ctx), tokenSite(tkn))
1076
1269
  if (null != details) {
1077
1270
  en.details = details
1078
1271
  }
1079
1272
  en.path = [...(r.k?.path ?? []), key]
1080
- mo[key] = en
1273
+ // A COLON DECLARATION IS REFUSED AND STILL NAMES SOMETHING, so
1274
+ // the refusal is held under the key that name would have had
1275
+ // and a use of it reports why; not an alias key, so it
1276
+ // generates unused.
1277
+ if ('alias_colon' === why) {
1278
+ delete mo[key]
1279
+ mo[aliasScopedKey(key, url)] = en
1280
+ }
1281
+ // A RESERVED KEY WAITS: its name is where the marks are kept.
1282
+ else if ('reserved_key' === why) {
1283
+ reservedRefusals.push(en)
1284
+ }
1285
+ else {
1286
+ mo[key] = en
1287
+ }
1288
+ }
1289
+
1290
+ // A NAME BELONGS TO THE FILE THAT DECLARED IT. Renamed here and
1291
+ // not at the key, so an elided value is still reported by the
1292
+ // name the source spells; rebuilt, as key order is resolution.
1293
+ const renamed = new Map<string, string>()
1294
+ for (const { name, key } of aliasDecls as any[]) {
1295
+ renamed.set(name, key)
1296
+ if (!aliasKeys.includes(key)) {
1297
+ aliasKeys.push(key)
1298
+ }
1299
+ }
1300
+ if (0 < renamed.size) {
1301
+ const entries = Object.entries(mo)
1302
+ for (const [k] of entries) {
1303
+ delete mo[k]
1304
+ }
1305
+ for (const [k, v] of entries) {
1306
+ const key = renamed.get(k)
1307
+ if (undefined === key) {
1308
+ mo[k] = v
1309
+ }
1310
+ else {
1311
+ mo[key] = v
1312
+ repathInstance(v, [...(r.k?.path ?? []), key])
1313
+ }
1314
+ }
1315
+ }
1316
+
1317
+ // `export` PUBLISHES NAMES AND NOTHING ELSE; a bare name, a key
1318
+ // and the wildcard are refused, and the argument is erased.
1319
+ for (const { items, tkn } of (r.u.aontu_export_decls ?? []) as any[]) {
1320
+ delete mo[tkn.val]
1321
+ const names = publishedNames(items)
1322
+ if (undefined === names) {
1323
+ const en: any =
1324
+ siteAt(addsite(new NilVal({ why: 'export_arg' }), r, ctx), tokenSite(tkn))
1325
+ en.path = [...(r.k?.path ?? []), EXPORT_DECL_NAME]
1326
+ mo[EXPORT_DECL_NAME] = en
1327
+ }
1328
+ else {
1329
+ exportKeys.push(...names.map((n) => aliasScopedKey(n, srcUrl(ctx))))
1330
+ }
1331
+ }
1332
+
1333
+ // THE DESTRUCTURE IS ADDITIVE: the values land where the head
1334
+ // stands, each name binds in THIS file's scope, and a file
1335
+ // publishes only what IT declares. A DECLARATION IS THE
1336
+ // DOCUMENT'S, so the names go to the root and the values may
1337
+ // sit under a key.
1338
+ for (const im of (r.u.aontu_import_decls ?? []) as any[]) {
1339
+ const hoist = ((ctx as any).aontu_alias_hoist ||= [])
1340
+ const iv: any = mo[im.key]
1341
+ delete mo[im.key]
1342
+ const dv: any = declaringMap(iv)
1343
+ const ex: string[] = dv.exportKeys ?? []
1344
+ const ak: string[] = dv.aliasKeys ?? []
1345
+ for (const k of ak) {
1346
+ hoist.push({ name: k, val: dv.peg[k] })
1347
+ delete dv.peg[k]
1348
+ }
1349
+ if (0 < ak.length + ex.length) {
1350
+ dv.aliasKeys = []
1351
+ dv.exportKeys = []
1352
+ }
1353
+ repathInstance(iv, [...(r.k?.path ?? [])])
1354
+ ; (mo[MERGE_KEY] = mo[MERGE_KEY] || []).push(iv)
1355
+ const binds: AliasBind[] = 0 === im.names.length ?
1356
+ ex.map((k: string) =>
1357
+ ({ local: aliasBareName(k), remote: aliasBareName(k) })) : im.names
1358
+ for (const { local, remote } of binds) {
1359
+ const from = ex.find((k: string) => aliasBareName(k) === remote)
1360
+ let bind: Val
1361
+ if (undefined === from || !ak.includes(from)) {
1362
+ bind = siteAt(addsite(
1363
+ new NilVal({ why: 'import_not_exported' }), r, ctx), tokenSite(im.tkn))
1364
+ ; (bind as any).details = { name: remote }
1365
+ const en: any = siteAt(addsite(
1366
+ new NilVal({ why: 'import_not_exported' }), r, ctx), tokenSite(im.tkn))
1367
+ en.details = { name: remote }
1368
+ mo[MERGE_KEY].push(en)
1369
+ }
1370
+ else {
1371
+ bind = addsite(new RefVal({ peg: [from], absolute: true }), r, ctx)
1372
+ }
1373
+ hoist.push({ name: aliasScopedKey(local, im.url), val: bind })
1374
+ }
1081
1375
  }
1082
1376
 
1083
1377
  // Marks carried over from a map include folded in the merge
1084
1378
  // hook above, applied here where the MapVal is built.
1085
- if (mo.___optional || mo.___alias) {
1086
- for (const k of (mo.___optional || [])) {
1379
+ if (mo[OPTIONAL_MARK_KEY] || mo[ALIAS_MARK_KEY]) {
1380
+ for (const k of (mo[OPTIONAL_MARK_KEY] || [])) {
1087
1381
  if (!optionalKeys.includes(k)) { optionalKeys.push(k) }
1088
1382
  }
1089
- for (const k of (mo.___alias || [])) {
1383
+ for (const k of (mo[ALIAS_MARK_KEY] || [])) {
1090
1384
  if (!aliasKeys.includes(k)) { aliasKeys.push(k) }
1091
1385
  }
1092
- delete mo.___optional
1093
- delete mo.___alias
1386
+ delete mo[OPTIONAL_MARK_KEY]
1387
+ delete mo[ALIAS_MARK_KEY]
1388
+ }
1389
+
1390
+ // A value-prefix declaration lands here, at the document root
1391
+ // (ALIASES.0.md), as a copy pathed at its name.
1392
+ if (1 === r.d) {
1393
+ for (const { name, val } of ((ctx as any).aontu_alias_hoist ?? [])) {
1394
+ const acx = new AontuContext({ path: [name] })
1395
+ const copy: any = (val as any).clone(acx, { path: [name] })
1396
+ repathInstance(copy, [name])
1397
+ mo[name] = null == mo[name] ? copy :
1398
+ addsite(new ConjunctVal({ peg: [mo[name], copy] }), r, ctx)
1399
+ if (!aliasKeys.includes(name)) { aliasKeys.push(name) }
1400
+ }
1094
1401
  }
1095
1402
 
1096
1403
  // Handle defered conjuncts, e.g. `{x:1 @"foo"}`
1097
- if (mo.___merge) {
1404
+ const deferred = mo[MERGE_KEY]
1405
+ delete mo[MERGE_KEY]
1406
+
1407
+ const merge = 0 < reservedRefusals.length ?
1408
+ [...(deferred ?? []), ...reservedRefusals] : deferred
1409
+
1410
+ if (merge) {
1098
1411
  let mop = { ...mo }
1099
- delete mop.___merge
1100
1412
 
1101
1413
  let mopv = new MapVal({ peg: mop })
1102
1414
  mopv.optionalKeys = optionalKeys
1103
1415
  mopv.aliasKeys = aliasKeys
1416
+ mopv.exportKeys = exportKeys
1104
1417
 
1105
1418
  r.node =
1106
- addsite(new ConjunctVal({ peg: [mopv, ...mo.___merge] }), r, ctx)
1419
+ addsite(new ConjunctVal({ peg: [mopv, ...merge] }), r, ctx)
1107
1420
  }
1108
1421
  else {
1109
1422
  r.node = addsite(new MapVal({ peg: mo }), r, ctx)
1110
1423
  r.node.optionalKeys = optionalKeys
1111
1424
  r.node.aliasKeys = aliasKeys
1425
+ r.node.exportKeys = exportKeys
1112
1426
  }
1113
1427
 
1114
1428
  return undefined
@@ -1137,7 +1451,7 @@ help isolate the syntax error.`,
1137
1451
  }
1138
1452
  }
1139
1453
 
1140
- // No ___merge arm here: the deferred map.merge that writes it
1454
+ // No merge-key arm here: the deferred map.merge that writes it
1141
1455
  // only ever fires for a `pair` rule, whose parent is always a
1142
1456
  // `map` rule with a plain-object node — never a list.
1143
1457
  {
@@ -1231,19 +1545,39 @@ help isolate the syntax error.`,
1231
1545
  }
1232
1546
  })
1233
1547
 
1234
- .bc((rule: Rule) => {
1548
+ .bc((rule: Rule, ctx: JsonicContext) => {
1235
1549
  // TRAVERSE PARENTS TO GET PATH
1236
1550
 
1237
- const ktkn: any = rule.o0
1551
+ // AN OPTIONAL PAIR OPENS ON ITS `?`: the rule before read the key.
1552
+ const ktkn: any = true === rule.prev?.u?.aontu_optional ?
1553
+ rule.prev.o0 : rule.o0
1238
1554
  const holder: any = rule.parent
1239
1555
  const kr = keyRefusalOf(ktkn, rule.o1)
1240
1556
  if (null != kr) {
1241
1557
  holder.u.aontu_key_refusals = (holder.u.aontu_key_refusals || [])
1242
- holder.u.aontu_key_refusals.push({ key: '' + ktkn.src, tkn: ktkn, ...kr })
1558
+ holder.u.aontu_key_refusals.push(
1559
+ { key: keyName(ktkn), tkn: ktkn, url: srcUrl(ctx), ...kr })
1243
1560
  }
1244
1561
  else if (isAliasDecl(ktkn, rule.o1)) {
1245
1562
  holder.u.aontu_alias_keys = (holder.u.aontu_alias_keys || [])
1246
- holder.u.aontu_alias_keys.push('' + ktkn.src)
1563
+ holder.u.aontu_alias_keys.push({
1564
+ name: '' + ktkn.src,
1565
+ key: aliasScopedKey('' + ktkn.src, srcUrl(ctx)),
1566
+ })
1567
+ }
1568
+ else if (true === ktkn?.use?.aontu_export) {
1569
+ holder.u.aontu_export_decls = (holder.u.aontu_export_decls || [])
1570
+ holder.u.aontu_export_decls.push(
1571
+ { items: ktkn.use.aontu_export_items, tkn: ktkn })
1572
+ }
1573
+ else if (null != ktkn?.use?.aontu_import) {
1574
+ holder.u.aontu_import_decls = (holder.u.aontu_import_decls || [])
1575
+ holder.u.aontu_import_decls.push({
1576
+ key: '' + ktkn.src,
1577
+ names: ktkn.use.aontu_import,
1578
+ url: srcUrl(ctx),
1579
+ tkn: ktkn,
1580
+ })
1247
1581
  }
1248
1582
 
1249
1583
  if (rule.u.spread) {
@@ -1360,6 +1694,14 @@ help isolate the syntax error.`,
1360
1694
  v.path = [...(rule.k?.path ?? []),
1361
1695
  '' + rule.node.length, key]
1362
1696
  }
1697
+ // An element is a value position, so the prefix form applies.
1698
+ if (null == kr && isAliasDecl(ktkn, rule.o1)) {
1699
+ ; ((ctx as any).aontu_alias_hoist ||= [])
1700
+ .push({ name: aliasScopedKey(key, srcUrl(ctx)), val: v })
1701
+ rule.node.push(v)
1702
+ return undefined
1703
+ }
1704
+
1363
1705
  const mv: any = addsite(
1364
1706
  new MapVal({ peg: { [key]: v } }), rule, ctx)
1365
1707
  // The element's path is the list's plus its index, as any
@@ -1367,15 +1709,10 @@ help isolate the syntax error.`,
1367
1709
  // "is this the top level" test reads the path, so an element
1368
1710
  // of a top-level list must not read as the root.
1369
1711
  mv.path = [...(rule.k?.path ?? []), '' + rule.node.length]
1370
- if (true === rule.u.aontu_optional_elem) {
1712
+ // A REFUSED KEY IS NOT OPTIONAL: the refusal would be dropped.
1713
+ if (true === rule.u.aontu_optional_elem && null == kr) {
1371
1714
  mv.optionalKeys = [key]
1372
1715
  }
1373
- // ... and a declaration is a declaration IN the element, which
1374
- // is where MapVal.unify refuses it: a list element is not the
1375
- // top level.
1376
- if (isAliasDecl(ktkn, rule.o1)) {
1377
- mv.aliasKeys = [key]
1378
- }
1379
1716
  rule.node.push(mv)
1380
1717
  }
1381
1718