aontu 0.57.0 → 0.59.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 (132) 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 +4 -2
  7. package/dist/aontu.js +12 -3
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +3 -1
  10. package/dist/cli.js +486 -4
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +4 -0
  13. package/dist/ctx.js +3 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/err.js +6 -3
  16. package/dist/err.js.map +1 -1
  17. package/dist/format.js +8 -2
  18. package/dist/format.js.map +1 -1
  19. package/dist/hints.js +40 -9
  20. package/dist/hints.js.map +1 -1
  21. package/dist/lang.js +409 -58
  22. package/dist/lang.js.map +1 -1
  23. package/dist/lower.d.ts +20 -0
  24. package/dist/lower.js +575 -0
  25. package/dist/lower.js.map +1 -0
  26. package/dist/lsp.d.ts +1 -1
  27. package/dist/lsp.js +1 -1
  28. package/dist/lsp.js.map +1 -1
  29. package/dist/mcp-server.js +1 -1
  30. package/dist/mcp.d.ts +1 -0
  31. package/dist/mcp.js +40 -3
  32. package/dist/mcp.js.map +1 -1
  33. package/dist/render.d.ts +53 -0
  34. package/dist/render.js +542 -0
  35. package/dist/render.js.map +1 -0
  36. package/dist/sigdecl.js +1 -1
  37. package/dist/sigdecl.js.map +1 -1
  38. package/dist/std.d.ts +2 -0
  39. package/dist/std.js +498 -2
  40. package/dist/std.js.map +1 -1
  41. package/dist/template.d.ts +5 -0
  42. package/dist/template.js +257 -0
  43. package/dist/template.js.map +1 -0
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/dist/type.d.ts +2 -0
  46. package/dist/type.js.map +1 -1
  47. package/dist/unify.js +43 -0
  48. package/dist/unify.js.map +1 -1
  49. package/dist/val/AggFuncVal.d.ts +1 -1
  50. package/dist/val/AggFuncVal.js +10 -21
  51. package/dist/val/AggFuncVal.js.map +1 -1
  52. package/dist/val/BagVal.js +1 -1
  53. package/dist/val/BagVal.js.map +1 -1
  54. package/dist/val/ConstraintVal.js +1 -1
  55. package/dist/val/DisjunctVal.js +33 -2
  56. package/dist/val/DisjunctVal.js.map +1 -1
  57. package/dist/val/EachFuncVal.d.ts +1 -1
  58. package/dist/val/EachFuncVal.js +8 -13
  59. package/dist/val/EachFuncVal.js.map +1 -1
  60. package/dist/val/EmitFuncVal.d.ts +23 -4
  61. package/dist/val/EmitFuncVal.js +298 -28
  62. package/dist/val/EmitFuncVal.js.map +1 -1
  63. package/dist/val/FilterFuncVal.js +8 -4
  64. package/dist/val/FilterFuncVal.js.map +1 -1
  65. package/dist/val/FormFuncVal.d.ts +14 -0
  66. package/dist/val/FormFuncVal.js +55 -0
  67. package/dist/val/FormFuncVal.js.map +1 -0
  68. package/dist/val/FuncBaseVal.js +36 -13
  69. package/dist/val/FuncBaseVal.js.map +1 -1
  70. package/dist/val/ListVal.js +9 -1
  71. package/dist/val/ListVal.js.map +1 -1
  72. package/dist/val/MapVal.d.ts +2 -1
  73. package/dist/val/MapVal.js +2 -1
  74. package/dist/val/MapVal.js.map +1 -1
  75. package/dist/val/PackFuncVal.d.ts +1 -1
  76. package/dist/val/PackFuncVal.js +22 -18
  77. package/dist/val/PackFuncVal.js.map +1 -1
  78. package/dist/val/PlaceVal.js +7 -2
  79. package/dist/val/PlaceVal.js.map +1 -1
  80. package/dist/val/RefVal.d.ts +3 -0
  81. package/dist/val/RefVal.js +181 -18
  82. package/dist/val/RefVal.js.map +1 -1
  83. package/dist/val/Val.d.ts +8 -1
  84. package/dist/val/Val.js +39 -1
  85. package/dist/val/Val.js.map +1 -1
  86. package/dist/val/members.d.ts +9 -0
  87. package/dist/val/members.js +52 -0
  88. package/dist/val/members.js.map +1 -0
  89. package/grammar/aontu.abnf +1 -1
  90. package/grammar/aontu.gbnf +1 -1
  91. package/grammar/aontu.lark +1 -1
  92. package/grammar/aontu.tmLanguage.json +1 -1
  93. package/package.json +1 -1
  94. package/skill/SKILL.md +4 -4
  95. package/skill/error-codes.md +1 -1
  96. package/skill/examples.md +1 -1
  97. package/skill/grammar-card.md +1 -1
  98. package/src/agentsmd.ts +1 -1
  99. package/src/alias.ts +112 -0
  100. package/src/aontu.ts +20 -2
  101. package/src/cli.ts +528 -4
  102. package/src/ctx.ts +18 -0
  103. package/src/err.ts +6 -3
  104. package/src/format.ts +12 -2
  105. package/src/hints.ts +47 -9
  106. package/src/lang.ts +453 -63
  107. package/src/lower.ts +636 -0
  108. package/src/lsp.ts +1 -1
  109. package/src/mcp-server.ts +1 -1
  110. package/src/mcp.ts +43 -4
  111. package/src/render.ts +727 -0
  112. package/src/sigdecl.ts +1 -1
  113. package/src/std.ts +506 -1
  114. package/src/template.ts +291 -0
  115. package/src/type.ts +10 -1
  116. package/src/unify.ts +47 -0
  117. package/src/val/AggFuncVal.ts +10 -21
  118. package/src/val/BagVal.ts +1 -1
  119. package/src/val/ConstraintVal.ts +1 -1
  120. package/src/val/DisjunctVal.ts +33 -2
  121. package/src/val/EachFuncVal.ts +8 -16
  122. package/src/val/EmitFuncVal.ts +376 -39
  123. package/src/val/FilterFuncVal.ts +11 -4
  124. package/src/val/FormFuncVal.ts +119 -0
  125. package/src/val/FuncBaseVal.ts +36 -13
  126. package/src/val/ListVal.ts +9 -1
  127. package/src/val/MapVal.ts +3 -2
  128. package/src/val/PackFuncVal.ts +23 -19
  129. package/src/val/PlaceVal.ts +7 -2
  130. package/src/val/RefVal.ts +193 -20
  131. package/src/val/Val.ts +75 -4
  132. package/src/val/members.ts +86 -0
package/dist/lang.js CHANGED
@@ -72,6 +72,7 @@ const ReferFuncVal_1 = require("./val/ReferFuncVal");
72
72
  const GraphAtomVal_1 = require("./val/GraphAtomVal");
73
73
  const PackFuncVal_1 = require("./val/PackFuncVal");
74
74
  const EachFuncVal_1 = require("./val/EachFuncVal");
75
+ const FormFuncVal_1 = require("./val/FormFuncVal");
75
76
  const FilterFuncVal_1 = require("./val/FilterFuncVal");
76
77
  const MatchFuncVal_1 = require("./val/MatchFuncVal");
77
78
  const EmitFuncVal_1 = require("./val/EmitFuncVal");
@@ -125,10 +126,109 @@ const CC_d = 100;
125
126
  const CC_D = 68;
126
127
  // THE ALIAS SIGIL. `%` is part of an alias's name, so the name is one
127
128
  // lexeme wherever it appears and its meaning is decided by position:
128
- // a BINDING in key position (`%uint8: …` declares), a USE in value
129
+ // a BINDING in key position (`%uint8 = …` declares), a USE in value
129
130
  // position (`listen: %uint8` refers). docs/design/ALIASES.0.md §4.
130
131
  const CC_PCT = 37;
131
132
  const ALIAS_RE = /^%[A-Za-z_][A-Za-z0-9_]*/;
133
+ // THE DECLARATION OPERATOR. `%name = value` declares; the `=` is the
134
+ // pair's separator, lexed as the colon token so the declaration then
135
+ // parses as a pair whose key is the alias name (ALIASES.0.md X-1, as
136
+ // settled 2026-09-05). `=` is syntax ONLY there: anywhere else it is
137
+ // punctuation outside its syntax, and the bare-text scan below refuses
138
+ // it (`foo = 1`, `a: x=y`).
139
+ const CC_EQ = 61;
140
+ const CC_SP = 32;
141
+ const CC_TAB = 9;
142
+ // THE BARE-TEXT RULE. A bare string holds letters, digits, `-` and `_`,
143
+ // and nothing else. Every other punctuation character is either SYNTAX,
144
+ // where the grammar gives it a meaning, or an ERROR where it does not
145
+ // -- never silently part of a string. `x=y`, `6/2`, `50%` and `>10`
146
+ // were all bare strings once, each a well-formed wrong document, and
147
+ // each is refused now, naming the character (bare_punct).
148
+ //
149
+ // scanBareRun classifies each character of a run three ways, in this
150
+ // order: TEXT continues the run; an ENDER stops it; anything else is
151
+ // BAD. The ender set is the lexer's own -- space, line, fixed tokens,
152
+ // comment starters -- read from the config its text matcher was built
153
+ // from, so it cannot drift from the grammar. A bad run is still scanned
154
+ // to its ender, so the refusal claims the whole spelling and the lexer
155
+ // never reads the tail of it as syntax.
156
+ //
157
+ // `-` is text wherever the scan sees it. A run never STARTS on one:
158
+ // `-` is the sign of a number and the negation prefix, a fixed token
159
+ // the fixed matcher claims before either scanning stage can run, so
160
+ // `a:-1` is the negation of 1 and `a:6-2` the string. The `+` of an
161
+ // exponent (`1e+2`) is admitted only by the NUMBER stage's scan, the
162
+ // one stage that can make a number of it. Mirrors scanBareRun in
163
+ // go/lang.go, decision for decision.
164
+ const CC_9 = 57;
165
+ const CC_A = 65;
166
+ const CC_Z = 90;
167
+ const CC_a = 97;
168
+ const CC_z = 122;
169
+ const CC_E = 69;
170
+ const CC_e = 101;
171
+ const CC_US = 95;
172
+ const CC_MINUS = 45;
173
+ const CC_PLUS = 43;
174
+ // Beyond ASCII a letter, a digit or a combining mark is text (`café`);
175
+ // a dash, a symbol or a space of any other kind is not.
176
+ const UNICODE_TEXT_RE = /^[\p{L}\p{N}\p{M}]$/u;
177
+ function textChar(c) {
178
+ return (CC_0 <= c && c <= CC_9) ||
179
+ (CC_a <= c && c <= CC_z) ||
180
+ (CC_A <= c && c <= CC_Z) ||
181
+ CC_US === c ||
182
+ CC_MINUS === c ||
183
+ (127 < c && UNICODE_TEXT_RE.test(String.fromCodePoint(c)));
184
+ }
185
+ // The lexer's text ender, at i. The regexp is the text matcher's own
186
+ // ender alternation (cfg.rePart.ender) made sticky, built once per
187
+ // config and cached on it.
188
+ function enderAt(cfg, src, i) {
189
+ let re = cfg.aontu_ender_re;
190
+ if (null == re) {
191
+ re = cfg.aontu_ender_re = new RegExp(cfg.rePart.ender.join(''), 'y');
192
+ }
193
+ re.lastIndex = i;
194
+ return re.test(src);
195
+ }
196
+ function scanBareRun(cfg, src, start, expo) {
197
+ let i = start;
198
+ let bad = -1;
199
+ let ch = '';
200
+ while (i < src.length) {
201
+ const c = src.codePointAt(i);
202
+ const w = 0xffff < c ? 2 : 1;
203
+ if (textChar(c)) {
204
+ i += w;
205
+ continue;
206
+ }
207
+ if (expo && CC_PLUS === c && start < i) {
208
+ const p = src.charCodeAt(i - 1);
209
+ const n = src.charCodeAt(i + 1);
210
+ if ((CC_e === p || CC_E === p) && CC_0 <= n && n <= CC_9) {
211
+ i += 1;
212
+ continue;
213
+ }
214
+ }
215
+ if (enderAt(cfg, src, i)) {
216
+ break;
217
+ }
218
+ if (-1 === bad) {
219
+ bad = i;
220
+ ch = String.fromCodePoint(c);
221
+ }
222
+ i += w;
223
+ }
224
+ return { end: i, bad, ch };
225
+ }
226
+ // A run the number matcher may lex: its own number grammar, less the
227
+ // fraction -- `.` is a fixed token, so a run never holds one, and the
228
+ // matcher reads it past the run by itself (`1.5` is the run `1`).
229
+ const NUMBER_RUN_RE = /^[-+]?(?:0(?:[xX][0-9a-fA-F_]+|[oO][0-7_]+|[bB][01_]+)|[0-9][0-9_]*(?:[eE][-+]?[0-9][0-9_]*)?)$/;
230
+ // The number matcher's hook result where the run is not its to lex.
231
+ const NOT_A_NUMBER = { done: true, token: undefined };
132
232
  let AontuJsonic = function AontuLang(jsonic) {
133
233
  jsonic.use(asPlugin(path_1.Path));
134
234
  // Only # line comments are valid Aontu syntax (see
@@ -203,6 +303,34 @@ let AontuJsonic = function AontuLang(jsonic) {
203
303
  // matched case-insensitively so the rule does not depend on which
204
304
  // prefix spellings the engine accepts.
205
305
  exclude: /__|^[-+]?0[xXoObB]_|_$/,
306
+ // THE NUMBER STAGE OF THE BARE-TEXT RULE. The matcher runs before
307
+ // the text matcher and reads a number up to the next ender -- and
308
+ // `-` is an ender, being the negation prefix's fixed token, so it
309
+ // would take the `2026` of `2026-09-05` and leave `-09-05` to the
310
+ // grammar. The hook scans the whole run first and declines for
311
+ // the matcher wherever the run is not its to lex: a run with a
312
+ // bad character (the text stage refuses it), a run that is not a
313
+ // number at all (`2026-09-05`, `6-2` are text). Twin of tsNumCheck
314
+ // in go/lang.go.
315
+ check: (lex) => {
316
+ const pnt = lex.pnt;
317
+ const src = lex.src;
318
+ // The hook makes the matcher a candidate at every position, so
319
+ // the common case -- a run no number can open -- declines for it
320
+ // in one char read. A DIGIT opens a number here and nothing
321
+ // else: the sign and the dot open the matcher's own grammar, but
322
+ // they are fixed tokens (the prefix operators and member
323
+ // access), claimed before this hook can run.
324
+ const c = src.charCodeAt(pnt.sI);
325
+ if (!(CC_0 <= c && c <= CC_9)) {
326
+ return NOT_A_NUMBER;
327
+ }
328
+ const run = scanBareRun(lex.cfg, src, pnt.sI, true);
329
+ if (-1 !== run.bad || !NUMBER_RUN_RE.test(src.slice(pnt.sI, run.end))) {
330
+ return NOT_A_NUMBER;
331
+ }
332
+ return undefined;
333
+ },
206
334
  },
207
335
  });
208
336
  // D3 -- the `0d` literal, the only route to the exact leaves
@@ -255,12 +383,25 @@ let AontuJsonic = function AontuLang(jsonic) {
255
383
  // the token's source text (`0d1: 5` yields the key `0d1`), so a
256
384
  // declaration reads as the key `%uint8`, while a value position
257
385
  // calls the function below and gets the reference.
258
- if (CC_PCT === src.charCodeAt(pnt.sI)) {
259
- const ares = ALIAS_RE.exec(lex.refwd());
260
- if (null == ares) {
261
- return undefined;
262
- }
386
+ // A `%` that opens no name (`%`, `%1`, `50%`) falls through to
387
+ // the bare-text scan below, which refuses it.
388
+ const ares = CC_PCT === src.charCodeAt(pnt.sI) ?
389
+ ALIAS_RE.exec(lex.refwd()) : null;
390
+ if (null != ares) {
263
391
  const asrc = ares[0];
392
+ // A lone `=` after the name, across horizontal space only, is
393
+ // the declaration operator. Decided HERE, where the name is
394
+ // claimed, and only its position is kept: the very next text
395
+ // position is that `=`, since nothing but space sits between,
396
+ // so the mark cannot outlive its one use. `==` is not it.
397
+ let j = pnt.sI + asrc.length;
398
+ while (j < src.length &&
399
+ (CC_SP === src.charCodeAt(j) || CC_TAB === src.charCodeAt(j))) {
400
+ j++;
401
+ }
402
+ if (CC_EQ === src.charCodeAt(j) && CC_EQ !== src.charCodeAt(j + 1)) {
403
+ lex.aontu_eq_at = j;
404
+ }
264
405
  const atkn = lex.token('#VL',
265
406
  // AN ALIAS REFERENCE IS A PATH REFERENCE. `%uint8` is
266
407
  // `$.%uint8`: root-absolute, one segment, spelled with the
@@ -274,53 +415,129 @@ let AontuJsonic = function AontuLang(jsonic) {
274
415
  pnt.cI += asrc.length;
275
416
  return { done: true, token: atkn };
276
417
  }
277
- if (CC_0 !== src.charCodeAt(pnt.sI)) {
278
- return undefined;
418
+ // The `=` the alias arm above marked: the separator of a
419
+ // declaration, as a colon token whose source is `=`. The pair rule
420
+ // is then the pair rule, and the formatter writes the spelling it
421
+ // read. Marked in `use` so the pair rule can tell it from a colon,
422
+ // which no longer declares.
423
+ if (CC_EQ === src.charCodeAt(pnt.sI) && lex.aontu_eq_at === pnt.sI) {
424
+ delete lex.aontu_eq_at;
425
+ const eqtkn = lex.token('#CL', undefined, '=', pnt, { aontu_eq: true });
426
+ pnt.sI += 1;
427
+ pnt.cI += 1;
428
+ return { done: true, token: eqtkn };
429
+ }
430
+ if (CC_0 === src.charCodeAt(pnt.sI)) {
431
+ const c1 = src.charCodeAt(pnt.sI + 1);
432
+ if (CC_d === c1 || CC_D === c1) {
433
+ // BIG_LITERAL_RE is `^`-anchored and read against the
434
+ // forward source (memoized per position by refwd), which is
435
+ // what lets it claim the `.` of `0d1.5`. A `0d` run it does
436
+ // not match falls through to the bare-text scan below.
437
+ const res = Decimal_1.BIG_LITERAL_RE.exec(lex.refwd());
438
+ if (null != res) {
439
+ const msrc = res[0];
440
+ // The token value is a FUNCTION so Val construction
441
+ // happens at parse time, where the rule and context needed
442
+ // for the site exist (jsonic calls a #VL token's function
443
+ // value with them). A `0d` literal never spans a line, so
444
+ // only the source and column positions advance.
445
+ const tkn = lex.token('#VL', (r, ctx) => addsite(bigVal(res), r, ctx), msrc, pnt);
446
+ pnt.sI += msrc.length;
447
+ pnt.cI += msrc.length;
448
+ return { done: true, token: tkn };
449
+ }
450
+ }
279
451
  }
280
- const c1 = src.charCodeAt(pnt.sI + 1);
281
- if (CC_d !== c1 && CC_D !== c1) {
282
- return undefined;
452
+ // THE BARE-TEXT RULE (scanBareRun). Last, so that a name, a
453
+ // declaration operator and an exact literal are read before a
454
+ // run is judged as text.
455
+ // The run is never empty: every ender is a token an earlier
456
+ // matcher claims, so the text stage only opens on a character
457
+ // the scan classifies as text or as bad.
458
+ const run = scanBareRun(lex.cfg, src, pnt.sI, false);
459
+ const msrc = src.slice(pnt.sI, run.end);
460
+ if (-1 !== run.bad) {
461
+ // BAD: the run is refused whole, sited at the character (the
462
+ // mark in `use` is what tokenSite reads). As a VALUE the
463
+ // token's function builds the refusal at the parse, where the
464
+ // rule carries the position. As a KEY the token is read for
465
+ // its source alone, so the mark is what the pair and elem
466
+ // rules read to write the refusal where the map is built.
467
+ const ch = run.ch;
468
+ const tkn = lex.token('#VL', (r, ctx) => {
469
+ const nv = addsite(new NilVal_1.NilVal({ why: 'bare_punct' }), r, ctx);
470
+ nv.details = { char: ch, text: msrc };
471
+ return nv;
472
+ }, msrc, pnt, { aontu_bad: ch });
473
+ pnt.sI += msrc.length;
474
+ pnt.cI += msrc.length;
475
+ return { done: true, token: tkn };
283
476
  }
284
- // BIG_LITERAL_RE is `^`-anchored and read against the forward
285
- // source (memoized per position by refwd), which is what lets
286
- // it claim the `.` of `0d1.5`.
287
- const res = Decimal_1.BIG_LITERAL_RE.exec(lex.refwd());
288
- if (null == res) {
289
- return undefined;
477
+ // CLEAN, with a `-` past its start (`team-payments`,
478
+ // `2026-09-05`): claimed here as one text token, because the
479
+ // default matcher's ender set would carve the run at the `-`.
480
+ // Any other clean run is the default matcher's, which also reads
481
+ // the value keywords and the `_` hole.
482
+ if (-1 !== msrc.indexOf('-')) {
483
+ const tkn = lex.token('#TX', msrc, msrc, pnt);
484
+ pnt.sI += msrc.length;
485
+ pnt.cI += msrc.length;
486
+ return { done: true, token: tkn };
290
487
  }
291
- const msrc = res[0];
292
- // The token value is a FUNCTION so Val construction happens at
293
- // parse time, where the rule and context needed for the site
294
- // exist (jsonic calls a #VL token's function value with them).
295
- // A `0d` literal never spans a line, so only the source and
296
- // column positions advance.
297
- const tkn = lex.token('#VL', (r, ctx) => addsite(bigVal(res), r, ctx), msrc, pnt);
298
- pnt.sI += msrc.length;
299
- pnt.cI += msrc.length;
300
- return { done: true, token: tkn };
488
+ return undefined;
301
489
  },
302
490
  },
303
491
  });
304
- // TODO: refactor Val constructor
305
- // let addsite = (v: Val, p: string[]) => (v.path = [...(p || [])], v)
492
+ const NO_SITE = { row: -1, col: -1, src: '', len: -1 };
493
+ const tokenSite = (tkn) => {
494
+ const src = tkn.src;
495
+ const bad = tkn.use?.aontu_bad;
496
+ if (null != bad) {
497
+ const ch = '' + bad;
498
+ return { row: tkn.rI, col: tkn.cI + src.indexOf(ch), src: ch, len: ch.length };
499
+ }
500
+ return { row: tkn.rI, col: tkn.cI, src, len: '' === src ? -1 : src.length };
501
+ };
502
+ const siteAt = (v, ts) => {
503
+ v.site.row = ts.row;
504
+ v.site.col = ts.col;
505
+ v.site.src = ts.src;
506
+ v.site.len = ts.len;
507
+ return v;
508
+ };
306
509
  let addsite = (v, r, ctx) => {
307
- v.site.row = null == r.o0 ? -1 : r.o0.rI;
308
- v.site.col = null == r.o0 ? -1 : r.o0.cI;
309
- v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : '';
310
- // The source text, from the SAME token the row and column above
510
+ // The source text comes from the SAME token the row and column
311
511
  // come from. jsonic has carried it all along; not reading it is
312
512
  // what left a site uneditable (ts/src/site.ts).
313
- //
314
- // A TOKEN WITH NO TEXT IS NO SPAN, in both ports, and the extent is
315
- // DERIVED from the text rather than read from the token's own `len`
316
- // — so the Go twin, whose token has no len field, computes the
317
- // identical number with utf16Len and nothing has to be kept in step.
318
- v.site.src = null == r.o0 ? '' : r.o0.src;
319
- v.site.len = '' === v.site.src ? -1 : v.site.src.length;
513
+ siteAt(v, null == r.o0 ? NO_SITE : tokenSite(r.o0));
514
+ v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : '';
320
515
  // A keyed rule always carries a path array; a keyless one has none.
321
516
  v.path = r.k ? [...r.k.path] : [];
322
517
  return v;
323
518
  };
519
+ // THE KEY REFUSALS a pair may carry, decided from its key TOKEN --
520
+ // never from the key text alone, since a quoted `"%a"` or `"x=y"` is
521
+ // an ordinary key: a declaration spelled with a colon (alias_colon),
522
+ // and a key the bare-text rule refuses (bare_punct). Undefined for an
523
+ // ordinary key, and for a declaration (`%a = 1`), which is a binding
524
+ // (isAliasDecl), not a refusal. Asked FIRST by the pair rule and by
525
+ // the elem rule, before the declaration is, so a pair in list
526
+ // position is held to the map's rules.
527
+ const isAliasDecl = (ktkn, sep) => null != ktkn && VL === ktkn.tin && ALIAS_RE.test('' + ktkn.src) &&
528
+ true === sep?.use?.aontu_eq;
529
+ const keyRefusalOf = (ktkn, sep) => {
530
+ if (null == ktkn || VL !== ktkn.tin) {
531
+ return undefined;
532
+ }
533
+ const kname = '' + ktkn.src;
534
+ if (ALIAS_RE.test(kname)) {
535
+ return isAliasDecl(ktkn, sep) ? undefined : { why: 'alias_colon' };
536
+ }
537
+ const bad = ktkn.use?.aontu_bad;
538
+ return null == bad ? undefined :
539
+ { why: 'bare_punct', details: { char: '' + bad, text: kname } };
540
+ };
324
541
  jsonic.options({
325
542
  hint: {
326
543
  unknown: `
@@ -409,6 +626,43 @@ help isolate the syntax error.`,
409
626
  // Handle defered conjuncts, where MapVal does not yet
410
627
  // exist, by creating ConjunctVal later.
411
628
  else {
629
+ // AN INCLUDE UNIFIES IN PLACE. multisource calls this hook at
630
+ // the `@`'s own source position, so `prev` holds exactly the
631
+ // pairs written BEFORE it. Folding the loaded map's keys in
632
+ // here -- host-so-far first, arriving value second -- is what
633
+ // inlining the loaded bytes at the `@` does, and mirrors
634
+ // go/lang.go's Map.Merge, which multisource-go drives one key
635
+ // at a time. A non-map load has no keys to fold and stays a
636
+ // deferred conjunct arm.
637
+ if (true === cval?.isMap) {
638
+ const lm = cval;
639
+ for (const k of Object.keys(lm.peg)) {
640
+ const own = prev[k];
641
+ prev[k] = (null == own) ? lm.peg[k] :
642
+ (own?.isVal
643
+ ? new ConjunctVal_1.ConjunctVal({ peg: [own, lm.peg[k]] })
644
+ : lm.peg[k]);
645
+ }
646
+ // The loaded map's spread joins THIS map's spread list at
647
+ // the `@`'s position: the parse pushes each `&:` onto the
648
+ // node as it is read, so `prev[SPREAD].v` already holds the
649
+ // spreads written before the `@` and nothing after it.
650
+ if (null != lm.spread?.cj) {
651
+ ;
652
+ prev[type_1.SPREAD] =
653
+ (prev[type_1.SPREAD] || { o: '&', v: [] });
654
+ prev[type_1.SPREAD].v.push(lm.spread.cj);
655
+ }
656
+ prev.___optional = (prev.___optional || []);
657
+ for (const k of lm.optionalKeys) {
658
+ prev.___optional.push(k);
659
+ }
660
+ prev.___alias = (prev.___alias || []);
661
+ for (const k of lm.aliasKeys) {
662
+ prev.___alias.push(k);
663
+ }
664
+ return prev;
665
+ }
412
666
  prev.___merge = (prev.___merge || []);
413
667
  prev.___merge.push(curr);
414
668
  return prev;
@@ -481,6 +735,13 @@ help isolate the syntax error.`,
481
735
  // G8 phase 0).
482
736
  pack: PackFuncVal_1.PackFuncVal,
483
737
  each: EachFuncVal_1.EachFuncVal,
738
+ // RENDER P6: the order-preserving map. `form` makes one list
739
+ // element per child of its data, being the template with `_`
740
+ // bound to the source child -- a construction, where `each` is a
741
+ // bound (G9 §4). It exists because `pick(pack(...))` re-sorts to
742
+ // code-point order, and a struct's fields or a file's imports
743
+ // are the model's order or they are wrong.
744
+ form: FormFuncVal_1.FormFuncVal,
484
745
  // G8 phase 2: selection. `filter` keeps the children of a bag that
485
746
  // unify with a condition; `match` picks the first arm whose
486
747
  // pattern the scrutinee unifies with. Both select by
@@ -913,17 +1174,8 @@ help isolate the syntax error.`,
913
1174
  valnode = addsite(new NullVal_1.NullVal({ peg: r.node }), r, ctx);
914
1175
  }
915
1176
  if (null != valnode && 'object' === typeof valnode && valnode.site) {
916
- let st = r.o0;
917
- valnode.site.row = st.rI;
918
- valnode.site.col = st.cI;
1177
+ siteAt(valnode, tokenSite(r.o0));
919
1178
  valnode.site.url = ctx.meta.multisource && ctx.meta.multisource.path;
920
- // No `?? ''` and no empty-text arm here: this branch runs only
921
- // for a rule that HAS an open token, and a token that opens a
922
- // value always carries text — the coverage gate refuses both
923
- // guards as dead. The unset case is the one above, where r.o0
924
- // itself can be absent.
925
- valnode.site.src = st.src;
926
- valnode.site.len = st.src.length;
927
1179
  }
928
1180
  // else { ERROR? }
929
1181
  r.node = valnode;
@@ -952,7 +1204,8 @@ help isolate the syntax error.`,
952
1204
  // nested pair, which the val rule does produce -- and neither is
953
1205
  // a trailing comma.
954
1206
  for (const k in mo) {
955
- if (null == mo[k] && '___merge' !== k) {
1207
+ if (null == mo[k] && '___merge' !== k &&
1208
+ '___optional' !== k && '___alias' !== k) {
956
1209
  // Pathed at the KEY, not at the enclosing map. addsite takes
957
1210
  // the rule's path, which here is the map's, so the error
958
1211
  // would otherwise name the container and leave the reader to
@@ -993,6 +1246,36 @@ help isolate the syntax error.`,
993
1246
  r.node = addsite(new NilVal_1.NilVal({ why: 'elided_value' }), r, ctx);
994
1247
  return undefined;
995
1248
  }
1249
+ // A KEY REFUSAL (the pair rule records them: a declaration
1250
+ // spelled with a colon, a key the bare-text rule refuses) becomes
1251
+ // the refusal, in place of whatever followed the colon and sited
1252
+ // at the KEY rather than at the map -- at the offending character
1253
+ // of it, where there is one -- so the frame points at the
1254
+ // spelling to change.
1255
+ for (const { key, tkn, why, details } of (r.u.aontu_key_refusals ?? [])) {
1256
+ const en = siteAt(addsite(new NilVal_1.NilVal({ why }), r, ctx), tokenSite(tkn));
1257
+ if (null != details) {
1258
+ en.details = details;
1259
+ }
1260
+ en.path = [...(r.k?.path ?? []), key];
1261
+ mo[key] = en;
1262
+ }
1263
+ // Marks carried over from a map include folded in the merge
1264
+ // hook above, applied here where the MapVal is built.
1265
+ if (mo.___optional || mo.___alias) {
1266
+ for (const k of (mo.___optional || [])) {
1267
+ if (!optionalKeys.includes(k)) {
1268
+ optionalKeys.push(k);
1269
+ }
1270
+ }
1271
+ for (const k of (mo.___alias || [])) {
1272
+ if (!aliasKeys.includes(k)) {
1273
+ aliasKeys.push(k);
1274
+ }
1275
+ }
1276
+ delete mo.___optional;
1277
+ delete mo.___alias;
1278
+ }
996
1279
  // Handle defered conjuncts, e.g. `{x:1 @"foo"}`
997
1280
  if (mo.___merge) {
998
1281
  let mop = { ...mo };
@@ -1142,9 +1425,21 @@ help isolate the syntax error.`,
1142
1425
  // Being a property of the map is also what carries it through a
1143
1426
  // meet, the way optional keys are carried.
1144
1427
  const ktkn = rule.o0;
1145
- if (null != ktkn && VL === ktkn.tin && ALIAS_RE.test('' + ktkn.src)) {
1146
- const holder = rule.parent;
1147
- const aname = '' + ktkn.src;
1428
+ const holder = rule.parent;
1429
+ const kr = keyRefusalOf(ktkn, rule.o1);
1430
+ if (null != kr) {
1431
+ // A KEY REFUSAL (keyRefusalOf) is written where the map is
1432
+ // built, in the value's place and sited at the key, so the
1433
+ // frame points at the spelling to change. A declaration
1434
+ // spelled with a colon is refused rather than read as the
1435
+ // ordinary key `%foo` the text would otherwise become -- a
1436
+ // document written for the old form would then generate a
1437
+ // "%foo" field and every `%foo` use would resolve to nothing,
1438
+ // and neither says why.
1439
+ holder.u.aontu_key_refusals = (holder.u.aontu_key_refusals || []);
1440
+ holder.u.aontu_key_refusals.push({ key: '' + ktkn.src, tkn: ktkn, ...kr });
1441
+ }
1442
+ else if (isAliasDecl(ktkn, rule.o1)) {
1148
1443
  // Always recorded here; whether the map is ALLOWED to carry
1149
1444
  // declarations is decided on the VALUE (MapVal.unify), not at
1150
1445
  // the parse. The parse cannot see it: an INCLUDED file's
@@ -1152,7 +1447,7 @@ help isolate the syntax error.`,
1152
1447
  // once the loaded map is placed does it become apparent that
1153
1448
  // root is not the document's.
1154
1449
  holder.u.aontu_alias_keys = (holder.u.aontu_alias_keys || []);
1155
- holder.u.aontu_alias_keys.push(aname);
1450
+ holder.u.aontu_alias_keys.push('' + ktkn.src);
1156
1451
  }
1157
1452
  if (rule.u.spread) {
1158
1453
  rule.node[type_1.SPREAD] =
@@ -1303,19 +1598,47 @@ help isolate the syntax error.`,
1303
1598
  // mistake, not an empty value.
1304
1599
  if (true === rule.u.pair) {
1305
1600
  const key = '' + rule.u.key;
1601
+ // The key TOKEN: the optional spelling's sits on the elem rule
1602
+ // before this one (`[x?: 1]` is two elem rules).
1603
+ const ktkn = true === rule.u.aontu_optional_elem ?
1604
+ rule.prev.o0 : rule.o0;
1306
1605
  let v = rule.child.node;
1606
+ const kr = keyRefusalOf(ktkn, rule.o1);
1307
1607
  if (null == v) {
1308
1608
  v = addsite(new NilVal_1.NilVal({ why: 'elided_value' }), rule, ctx);
1309
1609
  v.path = [...(rule.k?.path ?? []),
1310
1610
  '' + rule.node.length, key];
1311
1611
  }
1612
+ // THE KEY IS HELD TO THE MAP'S RULES: a key the map rule would
1613
+ // refuse (a colon declaration, a bare-text refusal) is refused
1614
+ // here too, in the value's place and sited at the key, rather
1615
+ // than generated as the element `[{"x=y": 1}]`.
1616
+ else if (null != kr) {
1617
+ v = siteAt(addsite(new NilVal_1.NilVal({ why: kr.why }), rule, ctx), tokenSite(ktkn));
1618
+ if (null != kr.details) {
1619
+ v.details = kr.details;
1620
+ }
1621
+ v.path = [...(rule.k?.path ?? []),
1622
+ '' + rule.node.length, key];
1623
+ }
1312
1624
  const mv = addsite(new MapVal_1.MapVal({ peg: { [key]: v } }), rule, ctx);
1625
+ // The element's path is the list's plus its index, as any
1626
+ // element's is (and as the Go port paths it): the map rule's
1627
+ // "is this the top level" test reads the path, so an element
1628
+ // of a top-level list must not read as the root.
1629
+ mv.path = [...(rule.k?.path ?? []), '' + rule.node.length];
1313
1630
  // `[a?: 1]` is `[{a?: 1}]`: the key is optional IN the
1314
1631
  // element, so the two spellings stay one rule apart rather
1315
1632
  // than two behaviours apart.
1316
1633
  if (true === rule.u.aontu_optional_elem) {
1317
1634
  mv.optionalKeys = [key];
1318
1635
  }
1636
+ // ... and a declaration is a declaration IN the element, which
1637
+ // is where MapVal.unify refuses it: a list element is not the
1638
+ // top level.
1639
+ if (isAliasDecl(ktkn, rule.o1)) {
1640
+ mv.aliasKeys = [key];
1641
+ }
1319
1642
  rule.node.push(mv);
1320
1643
  }
1321
1644
  return undefined;
@@ -1658,6 +1981,16 @@ function makeModelResolver(options) {
1658
1981
  err.code = 'include_extension';
1659
1982
  throw err;
1660
1983
  };
1984
+ // A LANGUAGE-SUPPLIED MODEL THAT DOES NOT EXIST THROWS, as a denial
1985
+ // does and for the same bare-member reason, with the not-found code
1986
+ // the include machinery already uses and a message that names the
1987
+ // set: a typo in an `aontu:` name must not go looking on disk.
1988
+ const modelNotFound = (path) => {
1989
+ const err = new Error('source not found: ' + path +
1990
+ ' (the language-supplied models are ' + std_1.AONTU_MODELS.join(', ') + ')');
1991
+ err.code = 'multisource_not_found';
1992
+ throw err;
1993
+ };
1661
1994
  // The gate every leg that RESOLVES A NAME passes through. The std and
1662
1995
  // module legs do not: both state `kind: 'aon'` because what they
1663
1996
  // serve is Aontu source by construction, not by its spelling.
@@ -1716,6 +2049,23 @@ function makeModelResolver(options) {
1716
2049
  if ('none' === capability) {
1717
2050
  deny(path);
1718
2051
  }
2052
+ // THE LANGUAGE-SUPPLIED MODELS (docs/design/MODELS.0.md D1): an
2053
+ // `aontu:` name resolves from the engine's own table and nowhere
2054
+ // else -- the memory, module, file and package legs are never
2055
+ // asked, so nothing on disk can shadow one and a typo is refused
2056
+ // here, naming the set, rather than searched for. Available under
2057
+ // every capability but `none`, checked just above, like the std
2058
+ // names below. A path that is not a string (`a: @1`) is not a name
2059
+ // at all: it falls through to the legs below and is not found there,
2060
+ // as it always was.
2061
+ if ('string' === typeof path && path.startsWith(std_1.AONTU_SCHEME)) {
2062
+ const model = std_1.STD_SOURCES[path];
2063
+ if (null == model) {
2064
+ modelNotFound(path);
2065
+ }
2066
+ record(ctx, path, 'std');
2067
+ return { found: true, path, full: path, kind: 'aon', src: model, search: [] };
2068
+ }
1719
2069
  // THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts): served from
1720
2070
  // the engine itself, so it needs neither the filesystem nor package
1721
2071
  // resolution and is available under every capability but `none` —
@@ -1942,7 +2292,7 @@ function opCharHint(src) {
1942
2292
  q = c;
1943
2293
  }
1944
2294
  else if ('<' === c || '>' === c) {
1945
- return '\nThe > and < characters are not Aontu operators: write the ' +
2295
+ return '\nThe > and < characters are not aontu operators: write the ' +
1946
2296
  'bound functions min(x), max(x), above(x), below(x) instead.';
1947
2297
  }
1948
2298
  }
@@ -2068,9 +2418,10 @@ class Lang {
2068
2418
  }
2069
2419
  catch (e) {
2070
2420
  if ('include_denied' === e?.code || 'include_extension' === e?.code ||
2071
- mod_1.MODULE_REFUSAL_CODES.has(e?.code)) {
2421
+ 'multisource_not_found' === e?.code || mod_1.MODULE_REFUSAL_CODES.has(e?.code)) {
2072
2422
  // A denied include (G5), an include whose extension is not read
2073
- // as Aontu source (ADR-012, INCLUDE_KINDS), and a module that is
2423
+ // as Aontu source (ADR-012, INCLUDE_KINDS), an `aontu:` name the
2424
+ // engine does not serve (MODELS.0.md D1), and a module that is
2074
2425
  // missing, fails its pin, or names a path that escapes its store
2075
2426
  // (G6 phase 2) are refused the same way, for the same reason: the
2076
2427
  // resolver THROWS so a bare-member include cannot vanish in the