aontu 0.56.0 → 0.58.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 (138) hide show
  1. package/README.md +2 -2
  2. package/dist/agentsmd.js +1 -1
  3. package/dist/alias.d.ts +3 -0
  4. package/dist/alias.js +59 -0
  5. package/dist/alias.js.map +1 -0
  6. package/dist/aontu.d.ts +5 -2
  7. package/dist/aontu.js +11 -3
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +8 -2
  10. package/dist/cli.js +564 -21
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +2 -0
  13. package/dist/ctx.js +1 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/escape.d.ts +5 -0
  16. package/dist/escape.js +455 -0
  17. package/dist/escape.js.map +1 -0
  18. package/dist/format.d.ts +9 -0
  19. package/dist/format.js +550 -55
  20. package/dist/format.js.map +1 -1
  21. package/dist/hints.js +74 -9
  22. package/dist/hints.js.map +1 -1
  23. package/dist/lang.js +374 -58
  24. package/dist/lang.js.map +1 -1
  25. package/dist/lower.d.ts +20 -0
  26. package/dist/lower.js +575 -0
  27. package/dist/lower.js.map +1 -0
  28. package/dist/lsp.d.ts +1 -1
  29. package/dist/lsp.js +4 -4
  30. package/dist/lsp.js.map +1 -1
  31. package/dist/mcp-server.js +2 -2
  32. package/dist/mcp-server.js.map +1 -1
  33. package/dist/mcp.d.ts +1 -0
  34. package/dist/mcp.js +40 -3
  35. package/dist/mcp.js.map +1 -1
  36. package/dist/mod-tool.js +8 -7
  37. package/dist/mod-tool.js.map +1 -1
  38. package/dist/mod.js +6 -6
  39. package/dist/mod.js.map +1 -1
  40. package/dist/render.d.ts +53 -0
  41. package/dist/render.js +542 -0
  42. package/dist/render.js.map +1 -0
  43. package/dist/sigdecl.js +1 -1
  44. package/dist/sigdecl.js.map +1 -1
  45. package/dist/std.d.ts +2 -0
  46. package/dist/std.js +498 -2
  47. package/dist/std.js.map +1 -1
  48. package/dist/template.d.ts +5 -0
  49. package/dist/template.js +257 -0
  50. package/dist/template.js.map +1 -0
  51. package/dist/tsconfig.tsbuildinfo +1 -1
  52. package/dist/unify.js +43 -0
  53. package/dist/unify.js.map +1 -1
  54. package/dist/val/AggFuncVal.d.ts +1 -1
  55. package/dist/val/AggFuncVal.js +10 -21
  56. package/dist/val/AggFuncVal.js.map +1 -1
  57. package/dist/val/BagVal.js +1 -1
  58. package/dist/val/BagVal.js.map +1 -1
  59. package/dist/val/ConstraintVal.js +1 -1
  60. package/dist/val/EachFuncVal.d.ts +1 -1
  61. package/dist/val/EachFuncVal.js +9 -15
  62. package/dist/val/EachFuncVal.js.map +1 -1
  63. package/dist/val/EmitFuncVal.d.ts +42 -0
  64. package/dist/val/EmitFuncVal.js +531 -0
  65. package/dist/val/EmitFuncVal.js.map +1 -0
  66. package/dist/val/FilterFuncVal.js +9 -6
  67. package/dist/val/FilterFuncVal.js.map +1 -1
  68. package/dist/val/FormFuncVal.d.ts +14 -0
  69. package/dist/val/FormFuncVal.js +55 -0
  70. package/dist/val/FormFuncVal.js.map +1 -0
  71. package/dist/val/FuncBaseVal.d.ts +1 -0
  72. package/dist/val/FuncBaseVal.js +16 -0
  73. package/dist/val/FuncBaseVal.js.map +1 -1
  74. package/dist/val/MapVal.d.ts +2 -1
  75. package/dist/val/MapVal.js +2 -1
  76. package/dist/val/MapVal.js.map +1 -1
  77. package/dist/val/PackFuncVal.d.ts +1 -1
  78. package/dist/val/PackFuncVal.js +23 -20
  79. package/dist/val/PackFuncVal.js.map +1 -1
  80. package/dist/val/PlaceVal.d.ts +3 -1
  81. package/dist/val/PlaceVal.js +9 -6
  82. package/dist/val/PlaceVal.js.map +1 -1
  83. package/dist/val/RefVal.d.ts +3 -0
  84. package/dist/val/RefVal.js +156 -17
  85. package/dist/val/RefVal.js.map +1 -1
  86. package/dist/val/StrFuncVal.d.ts +32 -0
  87. package/dist/val/StrFuncVal.js +292 -0
  88. package/dist/val/StrFuncVal.js.map +1 -0
  89. package/dist/val/Val.d.ts +7 -1
  90. package/dist/val/Val.js +12 -1
  91. package/dist/val/Val.js.map +1 -1
  92. package/dist/val/members.d.ts +9 -0
  93. package/dist/val/members.js +52 -0
  94. package/dist/val/members.js.map +1 -0
  95. package/grammar/aontu.abnf +4 -3
  96. package/grammar/aontu.gbnf +4 -3
  97. package/grammar/aontu.lark +4 -3
  98. package/grammar/aontu.tmLanguage.json +1 -1
  99. package/package.json +1 -1
  100. package/skill/SKILL.md +4 -4
  101. package/skill/error-codes.md +1 -1
  102. package/skill/examples.md +1 -1
  103. package/skill/grammar-card.md +1 -1
  104. package/src/agentsmd.ts +1 -1
  105. package/src/alias.ts +112 -0
  106. package/src/aontu.ts +20 -2
  107. package/src/cli.ts +630 -23
  108. package/src/ctx.ts +13 -0
  109. package/src/escape.ts +371 -0
  110. package/src/format.ts +648 -56
  111. package/src/hints.ts +94 -9
  112. package/src/lang.ts +430 -63
  113. package/src/lower.ts +636 -0
  114. package/src/lsp.ts +4 -4
  115. package/src/mcp-server.ts +3 -2
  116. package/src/mcp.ts +43 -4
  117. package/src/mod-tool.ts +9 -8
  118. package/src/mod.ts +6 -6
  119. package/src/render.ts +727 -0
  120. package/src/sigdecl.ts +1 -1
  121. package/src/std.ts +506 -1
  122. package/src/template.ts +291 -0
  123. package/src/unify.ts +47 -0
  124. package/src/val/AggFuncVal.ts +10 -21
  125. package/src/val/BagVal.ts +1 -1
  126. package/src/val/ConstraintVal.ts +1 -1
  127. package/src/val/EachFuncVal.ts +9 -19
  128. package/src/val/EmitFuncVal.ts +738 -0
  129. package/src/val/FilterFuncVal.ts +12 -7
  130. package/src/val/FormFuncVal.ts +119 -0
  131. package/src/val/FuncBaseVal.ts +18 -0
  132. package/src/val/MapVal.ts +3 -2
  133. package/src/val/PackFuncVal.ts +24 -22
  134. package/src/val/PlaceVal.ts +9 -6
  135. package/src/val/RefVal.ts +167 -18
  136. package/src/val/StrFuncVal.ts +334 -0
  137. package/src/val/Val.ts +42 -1
  138. package/src/val/members.ts +86 -0
package/src/lang.ts CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  makeJsonicProcessor,
72
72
  } from '@tabnas/multisource/processor/jsonic'
73
73
 
74
- import { STD_SOURCES } from './std'
74
+ import { STD_SOURCES, AONTU_SCHEME, AONTU_MODELS } from './std'
75
75
  import {
76
76
  parseModuleRef, resolveModule, modCacheDir, MODULE_REFUSAL_CODES,
77
77
  } from './mod'
@@ -143,8 +143,13 @@ import { ReferFuncVal, RelFuncVal } from './val/ReferFuncVal'
143
143
  import { AcyclicFuncVal, InverseFuncVal } from './val/GraphAtomVal'
144
144
  import { PackFuncVal } from './val/PackFuncVal'
145
145
  import { EachFuncVal } from './val/EachFuncVal'
146
+ import { FormFuncVal } from './val/FormFuncVal'
146
147
  import { FilterFuncVal } from './val/FilterFuncVal'
147
148
  import { MatchFuncVal } from './val/MatchFuncVal'
149
+ import { EmitFuncVal } from './val/EmitFuncVal'
150
+ import {
151
+ EscFuncVal, UscFuncVal, RepFuncVal, SplitFuncVal,
152
+ } from './val/StrFuncVal'
148
153
  import {
149
154
  AddFuncVal, SubFuncVal, MulFuncVal, DivFuncVal, ModFuncVal, RemFuncVal,
150
155
  } from './val/ArithFuncVal'
@@ -217,11 +222,123 @@ const CC_D = 68
217
222
 
218
223
  // THE ALIAS SIGIL. `%` is part of an alias's name, so the name is one
219
224
  // lexeme wherever it appears and its meaning is decided by position:
220
- // a BINDING in key position (`%uint8: …` declares), a USE in value
225
+ // a BINDING in key position (`%uint8 = …` declares), a USE in value
221
226
  // position (`listen: %uint8` refers). docs/design/ALIASES.0.md §4.
222
227
  const CC_PCT = 37
223
228
  const ALIAS_RE = /^%[A-Za-z_][A-Za-z0-9_]*/
224
229
 
230
+ // THE DECLARATION OPERATOR. `%name = value` declares; the `=` is the
231
+ // pair's separator, lexed as the colon token so the declaration then
232
+ // parses as a pair whose key is the alias name (ALIASES.0.md X-1, as
233
+ // settled 2026-09-05). `=` is syntax ONLY there: anywhere else it is
234
+ // punctuation outside its syntax, and the bare-text scan below refuses
235
+ // it (`foo = 1`, `a: x=y`).
236
+ const CC_EQ = 61
237
+ const CC_SP = 32
238
+ const CC_TAB = 9
239
+
240
+ // THE BARE-TEXT RULE. A bare string holds letters, digits, `-` and `_`,
241
+ // and nothing else. Every other punctuation character is either SYNTAX,
242
+ // where the grammar gives it a meaning, or an ERROR where it does not
243
+ // -- never silently part of a string. `x=y`, `6/2`, `50%` and `>10`
244
+ // were all bare strings once, each a well-formed wrong document, and
245
+ // each is refused now, naming the character (bare_punct).
246
+ //
247
+ // scanBareRun classifies each character of a run three ways, in this
248
+ // order: TEXT continues the run; an ENDER stops it; anything else is
249
+ // BAD. The ender set is the lexer's own -- space, line, fixed tokens,
250
+ // comment starters -- read from the config its text matcher was built
251
+ // from, so it cannot drift from the grammar. A bad run is still scanned
252
+ // to its ender, so the refusal claims the whole spelling and the lexer
253
+ // never reads the tail of it as syntax.
254
+ //
255
+ // `-` is text wherever the scan sees it. A run never STARTS on one:
256
+ // `-` is the sign of a number and the negation prefix, a fixed token
257
+ // the fixed matcher claims before either scanning stage can run, so
258
+ // `a:-1` is the negation of 1 and `a:6-2` the string. The `+` of an
259
+ // exponent (`1e+2`) is admitted only by the NUMBER stage's scan, the
260
+ // one stage that can make a number of it. Mirrors scanBareRun in
261
+ // go/lang.go, decision for decision.
262
+ const CC_9 = 57
263
+ const CC_A = 65
264
+ const CC_Z = 90
265
+ const CC_a = 97
266
+ const CC_z = 122
267
+ const CC_E = 69
268
+ const CC_e = 101
269
+ const CC_US = 95
270
+ const CC_MINUS = 45
271
+ const CC_PLUS = 43
272
+
273
+ // Beyond ASCII a letter, a digit or a combining mark is text (`café`);
274
+ // a dash, a symbol or a space of any other kind is not.
275
+ const UNICODE_TEXT_RE = /^[\p{L}\p{N}\p{M}]$/u
276
+
277
+ function textChar(c: number): boolean {
278
+ return (CC_0 <= c && c <= CC_9) ||
279
+ (CC_a <= c && c <= CC_z) ||
280
+ (CC_A <= c && c <= CC_Z) ||
281
+ CC_US === c ||
282
+ CC_MINUS === c ||
283
+ (127 < c && UNICODE_TEXT_RE.test(String.fromCodePoint(c)))
284
+ }
285
+
286
+ // The lexer's text ender, at i. The regexp is the text matcher's own
287
+ // ender alternation (cfg.rePart.ender) made sticky, built once per
288
+ // config and cached on it.
289
+ function enderAt(cfg: any, src: string, i: number): boolean {
290
+ let re: RegExp = cfg.aontu_ender_re
291
+ if (null == re) {
292
+ re = cfg.aontu_ender_re = new RegExp(cfg.rePart.ender.join(''), 'y')
293
+ }
294
+ re.lastIndex = i
295
+ return re.test(src)
296
+ }
297
+
298
+ // Where the run ends, the index of its first BAD character (-1 when the
299
+ // run is clean) and that character.
300
+ type BareRun = { end: number, bad: number, ch: string }
301
+
302
+ function scanBareRun(cfg: any, src: string, start: number, expo: boolean): BareRun {
303
+ let i = start
304
+ let bad = -1
305
+ let ch = ''
306
+ while (i < src.length) {
307
+ const c = src.codePointAt(i) as number
308
+ const w = 0xffff < c ? 2 : 1
309
+ if (textChar(c)) {
310
+ i += w
311
+ continue
312
+ }
313
+ if (expo && CC_PLUS === c && start < i) {
314
+ const p = src.charCodeAt(i - 1)
315
+ const n = src.charCodeAt(i + 1)
316
+ if ((CC_e === p || CC_E === p) && CC_0 <= n && n <= CC_9) {
317
+ i += 1
318
+ continue
319
+ }
320
+ }
321
+ if (enderAt(cfg, src, i)) {
322
+ break
323
+ }
324
+ if (-1 === bad) {
325
+ bad = i
326
+ ch = String.fromCodePoint(c)
327
+ }
328
+ i += w
329
+ }
330
+ return { end: i, bad, ch }
331
+ }
332
+
333
+ // A run the number matcher may lex: its own number grammar, less the
334
+ // fraction -- `.` is a fixed token, so a run never holds one, and the
335
+ // matcher reads it past the run by itself (`1.5` is the run `1`).
336
+ const NUMBER_RUN_RE =
337
+ /^[-+]?(?:0(?:[xX][0-9a-fA-F_]+|[oO][0-7_]+|[bB][01_]+)|[0-9][0-9_]*(?:[eE][-+]?[0-9][0-9_]*)?)$/
338
+
339
+ // The number matcher's hook result where the run is not its to lex.
340
+ const NOT_A_NUMBER = { done: true, token: undefined }
341
+
225
342
  let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
226
343
 
227
344
  jsonic.use(asPlugin(Path))
@@ -302,6 +419,35 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
302
419
  // matched case-insensitively so the rule does not depend on which
303
420
  // prefix spellings the engine accepts.
304
421
  exclude: /__|^[-+]?0[xXoObB]_|_$/,
422
+
423
+ // THE NUMBER STAGE OF THE BARE-TEXT RULE. The matcher runs before
424
+ // the text matcher and reads a number up to the next ender -- and
425
+ // `-` is an ender, being the negation prefix's fixed token, so it
426
+ // would take the `2026` of `2026-09-05` and leave `-09-05` to the
427
+ // grammar. The hook scans the whole run first and declines for
428
+ // the matcher wherever the run is not its to lex: a run with a
429
+ // bad character (the text stage refuses it), a run that is not a
430
+ // number at all (`2026-09-05`, `6-2` are text). Twin of tsNumCheck
431
+ // in go/lang.go.
432
+ check: (lex: any) => {
433
+ const pnt = lex.pnt
434
+ const src = lex.src
435
+ // The hook makes the matcher a candidate at every position, so
436
+ // the common case -- a run no number can open -- declines for it
437
+ // in one char read. A DIGIT opens a number here and nothing
438
+ // else: the sign and the dot open the matcher's own grammar, but
439
+ // they are fixed tokens (the prefix operators and member
440
+ // access), claimed before this hook can run.
441
+ const c = src.charCodeAt(pnt.sI)
442
+ if (!(CC_0 <= c && c <= CC_9)) {
443
+ return NOT_A_NUMBER
444
+ }
445
+ const run = scanBareRun(lex.cfg, src, pnt.sI, true)
446
+ if (-1 !== run.bad || !NUMBER_RUN_RE.test(src.slice(pnt.sI, run.end))) {
447
+ return NOT_A_NUMBER
448
+ }
449
+ return undefined
450
+ },
305
451
  },
306
452
  })
307
453
 
@@ -356,12 +502,27 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
356
502
  // the token's source text (`0d1: 5` yields the key `0d1`), so a
357
503
  // declaration reads as the key `%uint8`, while a value position
358
504
  // calls the function below and gets the reference.
359
- if (CC_PCT === src.charCodeAt(pnt.sI)) {
360
- const ares = ALIAS_RE.exec(lex.refwd())
361
- if (null == ares) {
362
- return undefined
363
- }
505
+ // A `%` that opens no name (`%`, `%1`, `50%`) falls through to
506
+ // the bare-text scan below, which refuses it.
507
+ const ares = CC_PCT === src.charCodeAt(pnt.sI) ?
508
+ ALIAS_RE.exec(lex.refwd()) : null
509
+ if (null != ares) {
364
510
  const asrc = ares[0]
511
+
512
+ // A lone `=` after the name, across horizontal space only, is
513
+ // the declaration operator. Decided HERE, where the name is
514
+ // claimed, and only its position is kept: the very next text
515
+ // position is that `=`, since nothing but space sits between,
516
+ // so the mark cannot outlive its one use. `==` is not it.
517
+ let j = pnt.sI + asrc.length
518
+ while (j < src.length &&
519
+ (CC_SP === src.charCodeAt(j) || CC_TAB === src.charCodeAt(j))) {
520
+ j++
521
+ }
522
+ if (CC_EQ === src.charCodeAt(j) && CC_EQ !== src.charCodeAt(j + 1)) {
523
+ lex.aontu_eq_at = j
524
+ }
525
+
365
526
  const atkn = lex.token(
366
527
  '#VL',
367
528
  // AN ALIAS REFERENCE IS A PATH REFERENCE. `%uint8` is
@@ -380,63 +541,165 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
380
541
  return { done: true, token: atkn }
381
542
  }
382
543
 
383
- if (CC_0 !== src.charCodeAt(pnt.sI)) {
384
- return undefined
544
+ // The `=` the alias arm above marked: the separator of a
545
+ // declaration, as a colon token whose source is `=`. The pair rule
546
+ // is then the pair rule, and the formatter writes the spelling it
547
+ // read. Marked in `use` so the pair rule can tell it from a colon,
548
+ // which no longer declares.
549
+ if (CC_EQ === src.charCodeAt(pnt.sI) && lex.aontu_eq_at === pnt.sI) {
550
+ delete lex.aontu_eq_at
551
+ const eqtkn = lex.token('#CL', undefined, '=', pnt, { aontu_eq: true })
552
+ pnt.sI += 1
553
+ pnt.cI += 1
554
+ return { done: true, token: eqtkn }
385
555
  }
386
- const c1 = src.charCodeAt(pnt.sI + 1)
387
- if (CC_d !== c1 && CC_D !== c1) {
388
- return undefined
556
+
557
+ if (CC_0 === src.charCodeAt(pnt.sI)) {
558
+ const c1 = src.charCodeAt(pnt.sI + 1)
559
+ if (CC_d === c1 || CC_D === c1) {
560
+ // BIG_LITERAL_RE is `^`-anchored and read against the
561
+ // forward source (memoized per position by refwd), which is
562
+ // what lets it claim the `.` of `0d1.5`. A `0d` run it does
563
+ // not match falls through to the bare-text scan below.
564
+ const res = BIG_LITERAL_RE.exec(lex.refwd())
565
+ if (null != res) {
566
+ const msrc = res[0]
567
+ // The token value is a FUNCTION so Val construction
568
+ // happens at parse time, where the rule and context needed
569
+ // for the site exist (jsonic calls a #VL token's function
570
+ // value with them). A `0d` literal never spans a line, so
571
+ // only the source and column positions advance.
572
+ const tkn = lex.token(
573
+ '#VL',
574
+ (r: Rule, ctx: JsonicContext) => addsite(bigVal(res), r, ctx),
575
+ msrc,
576
+ pnt)
577
+ pnt.sI += msrc.length
578
+ pnt.cI += msrc.length
579
+ return { done: true, token: tkn }
580
+ }
581
+ }
389
582
  }
390
583
 
391
- // BIG_LITERAL_RE is `^`-anchored and read against the forward
392
- // source (memoized per position by refwd), which is what lets
393
- // it claim the `.` of `0d1.5`.
394
- const res = BIG_LITERAL_RE.exec(lex.refwd())
395
- if (null == res) {
396
- return undefined
584
+ // THE BARE-TEXT RULE (scanBareRun). Last, so that a name, a
585
+ // declaration operator and an exact literal are read before a
586
+ // run is judged as text.
587
+ // The run is never empty: every ender is a token an earlier
588
+ // matcher claims, so the text stage only opens on a character
589
+ // the scan classifies as text or as bad.
590
+ const run = scanBareRun(lex.cfg, src, pnt.sI, false)
591
+ const msrc = src.slice(pnt.sI, run.end)
592
+
593
+ if (-1 !== run.bad) {
594
+ // BAD: the run is refused whole, sited at the character (the
595
+ // mark in `use` is what tokenSite reads). As a VALUE the
596
+ // token's function builds the refusal at the parse, where the
597
+ // rule carries the position. As a KEY the token is read for
598
+ // its source alone, so the mark is what the pair and elem
599
+ // rules read to write the refusal where the map is built.
600
+ const ch = run.ch
601
+ const tkn = lex.token(
602
+ '#VL',
603
+ (r: Rule, ctx: JsonicContext) => {
604
+ const nv: any = addsite(new NilVal({ why: 'bare_punct' }), r, ctx)
605
+ nv.details = { char: ch, text: msrc }
606
+ return nv
607
+ },
608
+ msrc,
609
+ pnt,
610
+ { aontu_bad: ch })
611
+ pnt.sI += msrc.length
612
+ pnt.cI += msrc.length
613
+ return { done: true, token: tkn }
614
+ }
615
+
616
+ // CLEAN, with a `-` past its start (`team-payments`,
617
+ // `2026-09-05`): claimed here as one text token, because the
618
+ // default matcher's ender set would carve the run at the `-`.
619
+ // Any other clean run is the default matcher's, which also reads
620
+ // the value keywords and the `_` hole.
621
+ if (-1 !== msrc.indexOf('-')) {
622
+ const tkn = lex.token('#TX', msrc, msrc, pnt)
623
+ pnt.sI += msrc.length
624
+ pnt.cI += msrc.length
625
+ return { done: true, token: tkn }
397
626
  }
398
627
 
399
- const msrc = res[0]
400
- // The token value is a FUNCTION so Val construction happens at
401
- // parse time, where the rule and context needed for the site
402
- // exist (jsonic calls a #VL token's function value with them).
403
- // A `0d` literal never spans a line, so only the source and
404
- // column positions advance.
405
- const tkn = lex.token(
406
- '#VL',
407
- (r: Rule, ctx: JsonicContext) => addsite(bigVal(res), r, ctx),
408
- msrc,
409
- pnt)
410
- pnt.sI += msrc.length
411
- pnt.cI += msrc.length
412
- return { done: true, token: tkn }
628
+ return undefined
413
629
  },
414
630
  },
415
631
  })
416
632
 
417
633
  // TODO: refactor Val constructor
418
634
  // let addsite = (v: Val, p: string[]) => (v.path = [...(p || [])], v)
419
- let addsite = (v: Val, r: Rule, ctx: JsonicContext) => {
635
+ // WHERE A TOKEN SITES A VALUE: the token's own position and text --
636
+ // except for a token the bare-text rule marked (`aontu_bad`), which
637
+ // sites at the offending CHARACTER. Its column is the character's
638
+ // first occurrence in the run (every character before it is text,
639
+ // and it is not), and its text is the character alone. Read here and
640
+ // nowhere else, so a value sited from such a token lands on the
641
+ // character however many times the parse re-sites it.
642
+ //
643
+ // A TOKEN WITH NO TEXT IS NO SPAN, in both ports, and the extent is
644
+ // DERIVED from the text rather than read from the token's own `len`
645
+ // — so the Go twin, whose token has no len field, computes the
646
+ // identical number with utf16Len and nothing has to be kept in step.
647
+ type TokenSite = { row: number, col: number, src: string, len: number }
648
+ const NO_SITE: TokenSite = { row: -1, col: -1, src: '', len: -1 }
649
+ const tokenSite = (tkn: any): TokenSite => {
650
+ const src: string = tkn.src
651
+ const bad = tkn.use?.aontu_bad
652
+ if (null != bad) {
653
+ const ch = '' + bad
654
+ return { row: tkn.rI, col: tkn.cI + src.indexOf(ch), src: ch, len: ch.length }
655
+ }
656
+ return { row: tkn.rI, col: tkn.cI, src, len: '' === src ? -1 : src.length }
657
+ }
658
+ const siteAt = (v: Val, ts: TokenSite): Val => {
659
+ v.site.row = ts.row
660
+ v.site.col = ts.col
661
+ v.site.src = ts.src
662
+ v.site.len = ts.len
663
+ return v
664
+ }
420
665
 
421
- v.site.row = null == r.o0 ? -1 : r.o0.rI
422
- v.site.col = null == r.o0 ? -1 : r.o0.cI
423
- v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : ''
424
- // The source text, from the SAME token the row and column above
666
+ let addsite = (v: Val, r: Rule, ctx: JsonicContext) => {
667
+ // The source text comes from the SAME token the row and column
425
668
  // come from. jsonic has carried it all along; not reading it is
426
669
  // what left a site uneditable (ts/src/site.ts).
427
- //
428
- // A TOKEN WITH NO TEXT IS NO SPAN, in both ports, and the extent is
429
- // DERIVED from the text rather than read from the token's own `len`
430
- // — so the Go twin, whose token has no len field, computes the
431
- // identical number with utf16Len and nothing has to be kept in step.
432
- v.site.src = null == r.o0 ? '' : r.o0.src
433
- v.site.len = '' === v.site.src ? -1 : v.site.src.length
670
+ siteAt(v, null == r.o0 ? NO_SITE : tokenSite(r.o0))
671
+ v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : ''
434
672
  // A keyed rule always carries a path array; a keyless one has none.
435
673
  v.path = r.k ? [...r.k.path] : []
436
674
 
437
675
  return v
438
676
  }
439
677
 
678
+ // THE KEY REFUSALS a pair may carry, decided from its key TOKEN --
679
+ // never from the key text alone, since a quoted `"%a"` or `"x=y"` is
680
+ // an ordinary key: a declaration spelled with a colon (alias_colon),
681
+ // and a key the bare-text rule refuses (bare_punct). Undefined for an
682
+ // ordinary key, and for a declaration (`%a = 1`), which is a binding
683
+ // (isAliasDecl), not a refusal. Asked FIRST by the pair rule and by
684
+ // the elem rule, before the declaration is, so a pair in list
685
+ // position is held to the map's rules.
686
+ const isAliasDecl = (ktkn: any, sep: any): boolean =>
687
+ null != ktkn && VL === ktkn.tin && ALIAS_RE.test('' + ktkn.src) &&
688
+ true === sep?.use?.aontu_eq
689
+ const keyRefusalOf = (ktkn: any, sep: any):
690
+ { why: string, details?: Record<string, any> } | undefined => {
691
+ if (null == ktkn || VL !== ktkn.tin) {
692
+ return undefined
693
+ }
694
+ const kname = '' + ktkn.src
695
+ if (ALIAS_RE.test(kname)) {
696
+ return isAliasDecl(ktkn, sep) ? undefined : { why: 'alias_colon' }
697
+ }
698
+ const bad = ktkn.use?.aontu_bad
699
+ return null == bad ? undefined :
700
+ { why: 'bare_punct', details: { char: '' + bad, text: kname } }
701
+ }
702
+
440
703
 
441
704
  jsonic.options({
442
705
  hint: {
@@ -628,6 +891,14 @@ help isolate the syntax error.`,
628
891
  pack: PackFuncVal,
629
892
  each: EachFuncVal,
630
893
 
894
+ // RENDER P6: the order-preserving map. `form` makes one list
895
+ // element per child of its data, being the template with `_`
896
+ // bound to the source child -- a construction, where `each` is a
897
+ // bound (G9 §4). It exists because `pick(pack(...))` re-sorts to
898
+ // code-point order, and a struct's fields or a file's imports
899
+ // are the model's order or they are wrong.
900
+ form: FormFuncVal,
901
+
631
902
  // G8 phase 2: selection. `filter` keeps the children of a bag that
632
903
  // unify with a condition; `match` picks the first arm whose
633
904
  // pattern the scrutinee unifies with. Both select by
@@ -667,6 +938,25 @@ help isolate the syntax error.`,
667
938
  // the language does not grow a second. It is the primitive that
668
939
  // turns a bag of computed lines into a file.
669
940
  join: JoinFuncVal,
941
+
942
+ // G9 phase 6: apply-templates. One flat list of pieces from a
943
+ // selection and a RULE TABLE -- for each node, the first template
944
+ // whose `match` it unifies with, its `body` instantiated at that
945
+ // node. The dispatch is the engine's because a body referenced by
946
+ // path resolves its references at the definition site, and the
947
+ // relative resolution that does exist is a dot count that does not
948
+ // survive a second dispatch (docs/design/EMIT.0.md).
949
+ emit: EmitFuncVal,
950
+
951
+ // G9 phase 6: the string builtins the rule layer needs. `esc`
952
+ // makes a value safe inside a literal and `usc` reads it back;
953
+ // `rep` and `split` derive names from model data. All four are
954
+ // ordinary string functions -- they know nothing about generation,
955
+ // which is why they can land before the renderer does.
956
+ esc: EscFuncVal,
957
+ usc: UscFuncVal,
958
+ rep: RepFuncVal,
959
+ split: SplitFuncVal,
670
960
  }
671
961
 
672
962
 
@@ -1106,17 +1396,8 @@ help isolate the syntax error.`,
1106
1396
  }
1107
1397
 
1108
1398
  if (null != valnode && 'object' === typeof valnode && valnode.site) {
1109
- let st = r.o0
1110
- valnode.site.row = st.rI
1111
- valnode.site.col = st.cI
1399
+ siteAt(valnode, tokenSite(r.o0))
1112
1400
  valnode.site.url = ctx.meta.multisource && ctx.meta.multisource.path
1113
- // No `?? ''` and no empty-text arm here: this branch runs only
1114
- // for a rule that HAS an open token, and a token that opens a
1115
- // value always carries text — the coverage gate refuses both
1116
- // guards as dead. The unset case is the one above, where r.o0
1117
- // itself can be absent.
1118
- valnode.site.src = st.src
1119
- valnode.site.len = st.src.length
1120
1401
  }
1121
1402
  // else { ERROR? }
1122
1403
 
@@ -1200,6 +1481,22 @@ help isolate the syntax error.`,
1200
1481
  return undefined
1201
1482
  }
1202
1483
 
1484
+ // A KEY REFUSAL (the pair rule records them: a declaration
1485
+ // spelled with a colon, a key the bare-text rule refuses) becomes
1486
+ // the refusal, in place of whatever followed the colon and sited
1487
+ // at the KEY rather than at the map -- at the offending character
1488
+ // of it, where there is one -- so the frame points at the
1489
+ // spelling to change.
1490
+ for (const { key, tkn, why, details } of
1491
+ (r.u.aontu_key_refusals ?? []) as any[]) {
1492
+ const en: any = siteAt(addsite(new NilVal({ why }), r, ctx), tokenSite(tkn))
1493
+ if (null != details) {
1494
+ en.details = details
1495
+ }
1496
+ en.path = [...(r.k?.path ?? []), key]
1497
+ mo[key] = en
1498
+ }
1499
+
1203
1500
  // Handle defered conjuncts, e.g. `{x:1 @"foo"}`
1204
1501
  if (mo.___merge) {
1205
1502
  let mop = { ...mo }
@@ -1380,10 +1677,21 @@ help isolate the syntax error.`,
1380
1677
  // Being a property of the map is also what carries it through a
1381
1678
  // meet, the way optional keys are carried.
1382
1679
  const ktkn: any = rule.o0
1383
- if (null != ktkn && VL === ktkn.tin && ALIAS_RE.test('' + ktkn.src)) {
1384
- const holder: any = rule.parent
1385
- const aname = '' + ktkn.src
1386
-
1680
+ const holder: any = rule.parent
1681
+ const kr = keyRefusalOf(ktkn, rule.o1)
1682
+ if (null != kr) {
1683
+ // A KEY REFUSAL (keyRefusalOf) is written where the map is
1684
+ // built, in the value's place and sited at the key, so the
1685
+ // frame points at the spelling to change. A declaration
1686
+ // spelled with a colon is refused rather than read as the
1687
+ // ordinary key `%foo` the text would otherwise become -- a
1688
+ // document written for the old form would then generate a
1689
+ // "%foo" field and every `%foo` use would resolve to nothing,
1690
+ // and neither says why.
1691
+ holder.u.aontu_key_refusals = (holder.u.aontu_key_refusals || [])
1692
+ holder.u.aontu_key_refusals.push({ key: '' + ktkn.src, tkn: ktkn, ...kr })
1693
+ }
1694
+ else if (isAliasDecl(ktkn, rule.o1)) {
1387
1695
  // Always recorded here; whether the map is ALLOWED to carry
1388
1696
  // declarations is decided on the VALUE (MapVal.unify), not at
1389
1697
  // the parse. The parse cannot see it: an INCLUDED file's
@@ -1391,7 +1699,7 @@ help isolate the syntax error.`,
1391
1699
  // once the loaded map is placed does it become apparent that
1392
1700
  // root is not the document's.
1393
1701
  holder.u.aontu_alias_keys = (holder.u.aontu_alias_keys || [])
1394
- holder.u.aontu_alias_keys.push(aname)
1702
+ holder.u.aontu_alias_keys.push('' + ktkn.src)
1395
1703
  }
1396
1704
 
1397
1705
  if (rule.u.spread) {
@@ -1560,20 +1868,48 @@ help isolate the syntax error.`,
1560
1868
  // mistake, not an empty value.
1561
1869
  if (true === rule.u.pair) {
1562
1870
  const key = '' + rule.u.key
1871
+ // The key TOKEN: the optional spelling's sits on the elem rule
1872
+ // before this one (`[x?: 1]` is two elem rules).
1873
+ const ktkn: any = true === rule.u.aontu_optional_elem ?
1874
+ rule.prev.o0 : rule.o0
1563
1875
  let v: any = rule.child.node
1876
+ const kr = keyRefusalOf(ktkn, rule.o1)
1564
1877
  if (null == v) {
1565
1878
  v = addsite(new NilVal({ why: 'elided_value' }), rule, ctx)
1566
1879
  v.path = [...(rule.k?.path ?? []),
1567
1880
  '' + rule.node.length, key]
1568
1881
  }
1882
+ // THE KEY IS HELD TO THE MAP'S RULES: a key the map rule would
1883
+ // refuse (a colon declaration, a bare-text refusal) is refused
1884
+ // here too, in the value's place and sited at the key, rather
1885
+ // than generated as the element `[{"x=y": 1}]`.
1886
+ else if (null != kr) {
1887
+ v = siteAt(addsite(new NilVal({ why: kr.why }), rule, ctx), tokenSite(ktkn))
1888
+ if (null != kr.details) {
1889
+ v.details = kr.details
1890
+ }
1891
+ v.path = [...(rule.k?.path ?? []),
1892
+ '' + rule.node.length, key]
1893
+ }
1569
1894
  const mv: any = addsite(
1570
1895
  new MapVal({ peg: { [key]: v } }), rule, ctx)
1896
+ // The element's path is the list's plus its index, as any
1897
+ // element's is (and as the Go port paths it): the map rule's
1898
+ // "is this the top level" test reads the path, so an element
1899
+ // of a top-level list must not read as the root.
1900
+ mv.path = [...(rule.k?.path ?? []), '' + rule.node.length]
1571
1901
  // `[a?: 1]` is `[{a?: 1}]`: the key is optional IN the
1572
1902
  // element, so the two spellings stay one rule apart rather
1573
1903
  // than two behaviours apart.
1574
1904
  if (true === rule.u.aontu_optional_elem) {
1575
1905
  mv.optionalKeys = [key]
1576
1906
  }
1907
+ // ... and a declaration is a declaration IN the element, which
1908
+ // is where MapVal.unify refuses it: a list element is not the
1909
+ // top level.
1910
+ if (isAliasDecl(ktkn, rule.o1)) {
1911
+ mv.aliasKeys = [key]
1912
+ }
1577
1913
  rule.node.push(mv)
1578
1914
  }
1579
1915
 
@@ -1953,6 +2289,18 @@ function makeModelResolver(options: any) {
1953
2289
  throw err
1954
2290
  }
1955
2291
 
2292
+ // A LANGUAGE-SUPPLIED MODEL THAT DOES NOT EXIST THROWS, as a denial
2293
+ // does and for the same bare-member reason, with the not-found code
2294
+ // the include machinery already uses and a message that names the
2295
+ // set: a typo in an `aontu:` name must not go looking on disk.
2296
+ const modelNotFound = (path: string): never => {
2297
+ const err: any = new Error(
2298
+ 'source not found: ' + path +
2299
+ ' (the language-supplied models are ' + AONTU_MODELS.join(', ') + ')')
2300
+ err.code = 'multisource_not_found'
2301
+ throw err
2302
+ }
2303
+
1956
2304
  // The gate every leg that RESOLVES A NAME passes through. The std and
1957
2305
  // module legs do not: both state `kind: 'aon'` because what they
1958
2306
  // serve is Aontu source by construction, not by its spelling.
@@ -2027,6 +2375,24 @@ function makeModelResolver(options: any) {
2027
2375
  deny(path)
2028
2376
  }
2029
2377
 
2378
+ // THE LANGUAGE-SUPPLIED MODELS (docs/design/MODELS.0.md D1): an
2379
+ // `aontu:` name resolves from the engine's own table and nowhere
2380
+ // else -- the memory, module, file and package legs are never
2381
+ // asked, so nothing on disk can shadow one and a typo is refused
2382
+ // here, naming the set, rather than searched for. Available under
2383
+ // every capability but `none`, checked just above, like the std
2384
+ // names below. A path that is not a string (`a: @1`) is not a name
2385
+ // at all: it falls through to the legs below and is not found there,
2386
+ // as it always was.
2387
+ if ('string' === typeof path && path.startsWith(AONTU_SCHEME)) {
2388
+ const model = STD_SOURCES[path]
2389
+ if (null == model) {
2390
+ modelNotFound(path)
2391
+ }
2392
+ record(ctx, path, 'std')
2393
+ return { found: true, path, full: path, kind: 'aon', src: model, search: [] }
2394
+ }
2395
+
2030
2396
  // THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts): served from
2031
2397
  // the engine itself, so it needs neither the filesystem nor package
2032
2398
  // resolution and is available under every capability but `none` —
@@ -2065,7 +2431,7 @@ function makeModelResolver(options: any) {
2065
2431
  const found = resolveModule(modref, from, modFs(ctx), {
2066
2432
  // The user cache lives outside any confinement root, so it is
2067
2433
  // consulted only when nothing confines this evaluation. A
2068
- // rooted profile sees the project's own `aon_vendor/` and
2434
+ // rooted profile sees the project's own `aontu_meta/vendor/` and
2069
2435
  // nothing else, which is what `root` means.
2070
2436
  ...(null == rootDir ? { cache: modCache(options) } : {}),
2071
2437
  eval: options.mod?.eval,
@@ -2273,7 +2639,7 @@ function opCharHint(src: string): string {
2273
2639
  q = c
2274
2640
  }
2275
2641
  else if ('<' === c || '>' === c) {
2276
- return '\nThe > and < characters are not Aontu operators: write the ' +
2642
+ return '\nThe > and < characters are not aontu operators: write the ' +
2277
2643
  'bound functions min(x), max(x), above(x), below(x) instead.'
2278
2644
  }
2279
2645
  }
@@ -2423,9 +2789,10 @@ class Lang {
2423
2789
  }
2424
2790
  catch (e: any) {
2425
2791
  if ('include_denied' === e?.code || 'include_extension' === e?.code ||
2426
- MODULE_REFUSAL_CODES.has(e?.code)) {
2792
+ 'multisource_not_found' === e?.code || MODULE_REFUSAL_CODES.has(e?.code)) {
2427
2793
  // A denied include (G5), an include whose extension is not read
2428
- // as Aontu source (ADR-012, INCLUDE_KINDS), and a module that is
2794
+ // as Aontu source (ADR-012, INCLUDE_KINDS), an `aontu:` name the
2795
+ // engine does not serve (MODELS.0.md D1), and a module that is
2429
2796
  // missing, fails its pin, or names a path that escapes its store
2430
2797
  // (G6 phase 2) are refused the same way, for the same reason: the
2431
2798
  // resolver THROWS so a bare-member include cannot vanish in the