aontu 0.55.0 → 0.57.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 (128) hide show
  1. package/README.md +3 -3
  2. package/dist/agentsmd.d.ts +1 -0
  3. package/dist/agentsmd.js +10 -3
  4. package/dist/agentsmd.js.map +1 -1
  5. package/dist/aontu.d.ts +4 -2
  6. package/dist/aontu.js +5 -2
  7. package/dist/aontu.js.map +1 -1
  8. package/dist/cli.d.ts +10 -3
  9. package/dist/cli.js +311 -53
  10. package/dist/cli.js.map +1 -1
  11. package/dist/diff.d.ts +1 -0
  12. package/dist/diff.js +3 -2
  13. package/dist/diff.js.map +1 -1
  14. package/dist/escape.d.ts +5 -0
  15. package/dist/escape.js +455 -0
  16. package/dist/escape.js.map +1 -0
  17. package/dist/format.d.ts +26 -0
  18. package/dist/format.js +1489 -0
  19. package/dist/format.js.map +1 -0
  20. package/dist/hints.js +39 -1
  21. package/dist/hints.js.map +1 -1
  22. package/dist/jsonschema.d.ts +1 -0
  23. package/dist/jsonschema.js +3 -2
  24. package/dist/jsonschema.js.map +1 -1
  25. package/dist/lang.js +84 -6
  26. package/dist/lang.js.map +1 -1
  27. package/dist/lsp.d.ts +2 -1
  28. package/dist/lsp.js +4 -4
  29. package/dist/lsp.js.map +1 -1
  30. package/dist/mcp-server.js +2 -2
  31. package/dist/mcp-server.js.map +1 -1
  32. package/dist/mcp.js +6 -4
  33. package/dist/mcp.js.map +1 -1
  34. package/dist/mod-tool.js +8 -7
  35. package/dist/mod-tool.js.map +1 -1
  36. package/dist/mod.js +6 -6
  37. package/dist/mod.js.map +1 -1
  38. package/dist/patch.d.ts +1 -0
  39. package/dist/patch.js +1 -0
  40. package/dist/patch.js.map +1 -1
  41. package/dist/query.d.ts +1 -0
  42. package/dist/query.js +4 -3
  43. package/dist/query.js.map +1 -1
  44. package/dist/reach.d.ts +1 -0
  45. package/dist/reach.js +3 -2
  46. package/dist/reach.js.map +1 -1
  47. package/dist/relation.d.ts +1 -0
  48. package/dist/relation.js +3 -2
  49. package/dist/relation.js.map +1 -1
  50. package/dist/sigdecl.js +1 -1
  51. package/dist/sigdecl.js.map +1 -1
  52. package/dist/std.js +2 -1
  53. package/dist/std.js.map +1 -1
  54. package/dist/subsume.d.ts +1 -0
  55. package/dist/subsume.js +3 -2
  56. package/dist/subsume.js.map +1 -1
  57. package/dist/trim.d.ts +1 -0
  58. package/dist/trim.js +3 -2
  59. package/dist/trim.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/dist/type.d.ts +1 -0
  62. package/dist/type.js.map +1 -1
  63. package/dist/utility.d.ts +8 -2
  64. package/dist/utility.js +9 -1
  65. package/dist/utility.js.map +1 -1
  66. package/dist/val/EachFuncVal.js +1 -2
  67. package/dist/val/EachFuncVal.js.map +1 -1
  68. package/dist/val/EmitFuncVal.d.ts +23 -0
  69. package/dist/val/EmitFuncVal.js +261 -0
  70. package/dist/val/EmitFuncVal.js.map +1 -0
  71. package/dist/val/FilterFuncVal.js +1 -2
  72. package/dist/val/FilterFuncVal.js.map +1 -1
  73. package/dist/val/FuncBaseVal.d.ts +1 -0
  74. package/dist/val/FuncBaseVal.js +16 -0
  75. package/dist/val/FuncBaseVal.js.map +1 -1
  76. package/dist/val/PackFuncVal.js +1 -2
  77. package/dist/val/PackFuncVal.js.map +1 -1
  78. package/dist/val/PlaceVal.d.ts +3 -1
  79. package/dist/val/PlaceVal.js +8 -6
  80. package/dist/val/PlaceVal.js.map +1 -1
  81. package/dist/val/StrFuncVal.d.ts +32 -0
  82. package/dist/val/StrFuncVal.js +292 -0
  83. package/dist/val/StrFuncVal.js.map +1 -0
  84. package/dist/vet.d.ts +1 -0
  85. package/dist/vet.js +1 -1
  86. package/dist/vet.js.map +1 -1
  87. package/dist/view.d.ts +2 -1
  88. package/dist/view.js +379 -8
  89. package/dist/view.js.map +1 -1
  90. package/grammar/aontu.abnf +156 -0
  91. package/grammar/aontu.gbnf +4 -3
  92. package/grammar/aontu.lark +4 -3
  93. package/grammar/aontu.tmLanguage.json +1 -1
  94. package/package.json +2 -1
  95. package/skill/grammar-card.md +5 -2
  96. package/src/agentsmd.ts +15 -3
  97. package/src/aontu.ts +8 -1
  98. package/src/cli.ts +365 -58
  99. package/src/diff.ts +7 -2
  100. package/src/escape.ts +371 -0
  101. package/src/format.ts +1728 -0
  102. package/src/hints.ts +53 -1
  103. package/src/jsonschema.ts +7 -1
  104. package/src/lang.ts +93 -6
  105. package/src/lsp.ts +8 -6
  106. package/src/mcp-server.ts +3 -2
  107. package/src/mcp.ts +6 -4
  108. package/src/mod-tool.ts +9 -8
  109. package/src/mod.ts +6 -6
  110. package/src/patch.ts +7 -0
  111. package/src/query.ts +8 -4
  112. package/src/reach.ts +7 -2
  113. package/src/relation.ts +7 -2
  114. package/src/sigdecl.ts +1 -1
  115. package/src/std.ts +2 -1
  116. package/src/subsume.ts +7 -2
  117. package/src/trim.ts +7 -2
  118. package/src/type.ts +8 -0
  119. package/src/utility.ts +27 -2
  120. package/src/val/EachFuncVal.ts +1 -3
  121. package/src/val/EmitFuncVal.ts +401 -0
  122. package/src/val/FilterFuncVal.ts +1 -3
  123. package/src/val/FuncBaseVal.ts +18 -0
  124. package/src/val/PackFuncVal.ts +1 -3
  125. package/src/val/PlaceVal.ts +8 -6
  126. package/src/val/StrFuncVal.ts +334 -0
  127. package/src/vet.ts +9 -3
  128. package/src/view.ts +442 -9
package/src/view.ts CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  // THE VIEWS (docs/design/VIEWS.0.md and VIEWS-ORDER.0.md): figures of
4
4
  // an evaluated document, drawn as deterministic text a golden diff can
5
- // check. Seven kinds:
5
+ // check. Nine kinds:
6
6
  //
7
+ // doc the shape of the document itself
8
+ // lattice the language's value lattice, with the document's own
9
+ // values placed on it
7
10
  // tree the dependency tree of one relation
8
11
  // matrix the dependency matrix over one relation, in canon or
9
12
  // partition order, with closure and the unmirrored mark
@@ -52,13 +55,15 @@ import type { WhyConjunct } from './provenance'
52
55
  import { why, pathParts } from './query'
53
56
  import { subsume } from './subsume'
54
57
  import type { SubsumeProfile } from './subsume'
58
+ import { includeOpts } from './utility'
59
+ import type { IncludeOptions } from './utility'
55
60
 
56
61
 
57
62
  export type ViewVerdict = 'rendered' | 'lossy' | 'error'
58
63
 
59
64
  export type ViewKind =
60
65
  'tree' | 'matrix' | 'graph' | 'layer' | 'sets' | 'layers' | 'ladder'
61
- | 'poset' | 'doc'
66
+ | 'poset' | 'doc' | 'lattice'
62
67
 
63
68
  // The target grammars. Each kind declares the profiles it can render
64
69
  // into, and the first is its default (PROFILES below).
@@ -210,6 +215,12 @@ export type ViewOptions = {
210
215
  // The include capability this document evaluates under
211
216
  // (docs/trust.md).
212
217
  trust?: TrustOptions
218
+ // The extensions an include additionally reads as text (the CLI's
219
+ // --text-ext). It rides WITH the capability everywhere, never beside
220
+ // it: both answer "what may an include read", and a verb that
221
+ // threads one and not the other refuses under a flag the bare
222
+ // command honours -- which is exactly what this verb did.
223
+ textExt?: string[]
213
224
  // Restrict the figure to nodes (or paths) under this path. For the
214
225
  // ladder it is the path drawn, and required; for the poset it is
215
226
  // where the documents are compared.
@@ -295,6 +306,7 @@ export type ViewOptions = {
295
306
  // node-link drawing and no sensible Mermaid form of a matrix.
296
307
  const PROFILES: Record<ViewKind, ViewProfile[]> = {
297
308
  doc: ['text', 'svg'],
309
+ lattice: ['text', 'svg'],
298
310
  tree: ['text', 'svg'],
299
311
  matrix: ['text', 'svg'],
300
312
  graph: ['mermaid', 'dot', 'er'],
@@ -972,6 +984,421 @@ function treeSvg(
972
984
  // that stops without saying so is the one thing a structural drawing
973
985
  // must not be.
974
986
 
987
+ // ---------------------------------------------------------------------
988
+ // THE VALUE LATTICE, and where this document's values sit on it.
989
+ //
990
+ // THE SCAFFOLD IS THE LANGUAGE'S, NOT THE DOCUMENT'S: `top` at the
991
+ // join, the four kind families under it, `path()` under `string`, the
992
+ // four numeric leaves under `number`, and `nil` at the meet. Every
993
+ // Aontu document is drawn against the SAME shape, which is what makes
994
+ // two of these figures comparable -- and what makes this a view of the
995
+ // language that a document annotates, rather than a picture assembled
996
+ // out of whatever the document happened to contain.
997
+ //
998
+ // See docs/unification.md for what the ordering means.
999
+
1000
+ // The scaffold: each kind and the one above it. The ENGINE decides
1001
+ // which kind sits under which -- kindParent in ts/src/val/ScalarKindVal.ts,
1002
+ // and its twin in go/scalar.go -- and a test in each port holds this
1003
+ // table to it, so adding a kind to the engine makes the figure grow a
1004
+ // node rather than quietly leave one out.
1005
+ const LATTICE_PARENT: [string, string][] = [
1006
+ ['string', 'top'],
1007
+ ['path()', 'string'],
1008
+ ['number', 'top'],
1009
+ ['integer', 'number'],
1010
+ ['float', 'number'],
1011
+ ['biginteger', 'number'],
1012
+ ['bigdecimal', 'number'],
1013
+ ['boolean', 'top'],
1014
+ ['null', 'top'],
1015
+ ]
1016
+
1017
+ // The columns, left to right: the MINIMAL kinds, the ones with nothing
1018
+ // under them. Everything else is drawn centred over the columns it
1019
+ // covers, so this list alone fixes the figure's horizontal order -- and
1020
+ // it puts the kinds that reach the bottom from higher up (`boolean`,
1021
+ // `null`) on the outside, where their lines pass the numeric fan
1022
+ // rather than crossing it.
1023
+ const LATTICE_COLS =
1024
+ ['path()', 'integer', 'float', 'biginteger', 'bigdecimal', 'boolean',
1025
+ 'null']
1026
+
1027
+ // The rows, top to bottom. `top` and `nil` are the endpoints and are
1028
+ // not kinds: no `superior()` answers either, and no entry above names
1029
+ // them as a parent.
1030
+ const LATTICE_ROWS: string[][] = [
1031
+ ['top'],
1032
+ ['string', 'number', 'boolean', 'null'],
1033
+ ['path()', 'integer', 'float', 'biginteger', 'bigdecimal'],
1034
+ ['nil'],
1035
+ ]
1036
+
1037
+ const LATTICE_NODES: string[] =
1038
+ ['top', ...LATTICE_PARENT.map(([name]) => name), 'nil']
1039
+
1040
+ // Every node at or above one, itself included.
1041
+ function latticeAncestors(name: string): string[] {
1042
+ const out: string[] = [name]
1043
+ for (let at = name; '' !== at;) {
1044
+ const row = LATTICE_PARENT.find(([child]) => child === at)
1045
+ at = undefined === row ? '' : row[1]
1046
+ if ('' !== at) {
1047
+ out.push(at)
1048
+ }
1049
+ }
1050
+ return out
1051
+ }
1052
+
1053
+ // The columns one node covers: its own if it is minimal, otherwise
1054
+ // every column beneath it. `nil` is beneath everything and above
1055
+ // nothing, so the walk finds no column under it and the whole width is
1056
+ // its span -- which is where it belongs.
1057
+ function latticeSpan(name: string): number[] {
1058
+ const own = LATTICE_COLS.indexOf(name)
1059
+ if (-1 !== own) {
1060
+ return [own]
1061
+ }
1062
+ const under = LATTICE_COLS
1063
+ .map((col, i) => latticeAncestors(col).includes(name) ? i : -1)
1064
+ .filter((i) => -1 !== i)
1065
+ return 0 === under.length ? LATTICE_COLS.map((_, i) => i) : under
1066
+ }
1067
+
1068
+ // True when `parent` is immediately above `child`. NIL IS COVERED BY
1069
+ // EVERY MINIMAL KIND: it is the meet of all of them, and the only node
1070
+ // the parent table does not name, because nothing in the engine ever
1071
+ // answers `nil` as a superior.
1072
+ function latticeCovers(parent: string, child: string): boolean {
1073
+ return 'nil' === child
1074
+ ? -1 !== LATTICE_COLS.indexOf(parent)
1075
+ : LATTICE_PARENT.some(([c, p]) => c === child && p === parent)
1076
+ }
1077
+
1078
+
1079
+ // WHERE ONE VALUE SITS, or undefined for a value that is not at a
1080
+ // single point. The answers are the kinds of thing a document holds:
1081
+ //
1082
+ // a CONCRETE scalar sits at its kind -- `8080` is an `integer`, and
1083
+ // `superior()` is the lattice's own answer to which;
1084
+ // a KIND MARKER sits AT that kind -- `integer` written as a schema
1085
+ // is the node itself, not a value under it;
1086
+ // everything else -- a constraint, an unresolved disjunction, a
1087
+ // reference -- is not one point. `integer & min(1)` is a REGION of
1088
+ // the lattice and `*8080 | integer` is two places at once, so
1089
+ // drawing either at a node would be a claim the figure cannot
1090
+ // support. Both are counted into the loss report instead.
1091
+ function latticePoint(v: any): string | undefined {
1092
+ const node: any = throughDoc(v)
1093
+ if (true === node?.isNil) {
1094
+ return 'nil'
1095
+ }
1096
+ if (true === node?.isTop) {
1097
+ return 'top'
1098
+ }
1099
+ // A kind marker names its own node; a concrete scalar names the node
1100
+ // above it. Either way the name has to BE one of the figure's: a
1101
+ // kind the scaffold does not draw has nowhere to go, and saying so
1102
+ // through the loss report is the only honest answer.
1103
+ const name: string = true === node?.isScalarKind ? String(node.canon)
1104
+ : true === node?.isScalar ? String(node.superior?.().canon) : ''
1105
+ return LATTICE_NODES.includes(name) ? name : undefined
1106
+ }
1107
+
1108
+
1109
+ // The document's own values, gathered by lattice node. Containers are
1110
+ // walked but not placed: a map is not a scalar lattice citizen, and
1111
+ // counting one at `top` would put every document's root there.
1112
+ function latticeCensus(root: any, at: string):
1113
+ { counts: Map<string, string[]>, unplaced: string[] } {
1114
+ const counts = new Map<string, string[]>()
1115
+ const unplaced: string[] = []
1116
+ const stack: { node: any, path: string }[] = [{ node: root, path: at }]
1117
+ while (0 < stack.length) {
1118
+ const { node, path } = stack.pop() as { node: any, path: string }
1119
+ const kids = docKids(node)
1120
+ if (0 < kids.length) {
1121
+ // A container is a shape, not a point: walk into it and place
1122
+ // what it holds.
1123
+ for (const key of kids) {
1124
+ stack.push({
1125
+ node: throughDoc(throughDoc(node).peg[key]),
1126
+ path: path + '.' + key,
1127
+ })
1128
+ }
1129
+ continue
1130
+ }
1131
+ const point = latticePoint(node)
1132
+ if (undefined === point) {
1133
+ // AN EMPTY CONTAINER IS NEITHER A POINT NOR A SHAPE with
1134
+ // anything in it, and is no more unplaced than `{}` is a value:
1135
+ // skip it rather than report a loss a reader cannot act on.
1136
+ const inner: any = throughDoc(node)
1137
+ if (true !== inner?.isMap && true !== inner?.isList) {
1138
+ unplaced.push(path)
1139
+ }
1140
+ continue
1141
+ }
1142
+ const there = counts.get(point) ?? []
1143
+ there.push(path)
1144
+ counts.set(point, there)
1145
+ }
1146
+ for (const paths of counts.values()) {
1147
+ paths.sort(cmpCodePoint)
1148
+ }
1149
+ unplaced.sort(cmpCodePoint)
1150
+ return { counts, unplaced }
1151
+ }
1152
+
1153
+
1154
+ // What one node is written as: its name, and the count of the
1155
+ // document's values that landed on it. A node with nothing at it is
1156
+ // still drawn -- the shape is the language's, and a figure that left
1157
+ // the empty nodes out would be a different lattice for every document.
1158
+ function latticeCell(counts: Map<string, string[]>, name: string): string {
1159
+ const n = (counts.get(name) ?? []).length
1160
+ return 0 === n ? name : `${name} (${n})`
1161
+ }
1162
+
1163
+ // The horizontal layout, in characters: one column per minimal kind,
1164
+ // each as wide as the widest cell drawn over it plus a gutter, and the
1165
+ // centre of each. The spanning nodes are narrower than the span they
1166
+ // cover, so none of them needs a width of its own. The gutter is THREE
1167
+ // because the SVG draws a box a character wider than its text: two of
1168
+ // those characters are the box's own padding and the third is the gap
1169
+ // between one box and the next.
1170
+ const LATTICE_GUTTER = 3
1171
+
1172
+ function latticeCols(counts: Map<string, string[]>):
1173
+ { cx: number[], width: number } {
1174
+ const w = LATTICE_COLS.map((col) => LATTICE_GUTTER + Math.max(
1175
+ ...LATTICE_ROWS.flat()
1176
+ .filter((name) => {
1177
+ const span = latticeSpan(name)
1178
+ return 1 === span.length && col === LATTICE_COLS[span[0]]
1179
+ })
1180
+ .map((name) => latticeCell(counts, name).length)))
1181
+ let x = 0
1182
+ const cx = w.map((n) => {
1183
+ const c = x + Math.floor(n / 2)
1184
+ x += n
1185
+ return c
1186
+ })
1187
+ return { cx, width: x }
1188
+ }
1189
+
1190
+ // The centre of a node, from the columns it covers.
1191
+ function latticeAt(name: string, cx: number[]): number {
1192
+ const span = latticeSpan(name)
1193
+ return Math.round((cx[span[0]] + cx[span[span.length - 1]]) / 2)
1194
+ }
1195
+
1196
+
1197
+ // The box-drawing glyph for one column of a rule, from the four facts
1198
+ // that meet there: whether the rule continues left and right, and
1199
+ // whether a stem leaves upward and downward. Deciding it this way is
1200
+ // what lets `number` -- which is BOTH one of the many under `top` and
1201
+ // the one above the numeric leaves -- come out as the join it is,
1202
+ // without a case written for it. The table is total, so no column has
1203
+ // to be asked whether it has a glyph.
1204
+ const LATTICE_GLYPH: Record<string, string> = {
1205
+ '....': '─', '...d': '│', '..u.': '│', '..ud': '│',
1206
+ '.r..': '─', '.r.d': '┌', '.ru.': '└', '.rud': '├',
1207
+ 'l...': '─', 'l..d': '┐', 'l.u.': '┘', 'l.ud': '┤',
1208
+ 'lr..': '─', 'lr.d': '┬', 'lru.': '┴', 'lrud': '┼',
1209
+ }
1210
+
1211
+ // The figure is PAINTED rather than assembled from padded strings: the
1212
+ // nodes have to line up with the rules that join them, and a count
1213
+ // changes a cell's width -- so the geometry is settled first, in
1214
+ // columns, and every glyph is then written at a place already known.
1215
+ function latticeText(counts: Map<string, string[]>, style: ViewStyle): string {
1216
+ const paint = painter(style)
1217
+ const { cx, width } = latticeCols(counts)
1218
+ const canvas: string[][] = []
1219
+ const roles: ViewRole[][] = []
1220
+ const put = (y: number, x: number, text: string, role: ViewRole) => {
1221
+ while (canvas.length <= y) {
1222
+ canvas.push(new Array(width).fill(' '))
1223
+ roles.push(new Array(width).fill('label'))
1224
+ }
1225
+ for (let i = 0; i < text.length; i++) {
1226
+ canvas[y][x + i] = text[i]
1227
+ roles[y][x + i] = role
1228
+ }
1229
+ }
1230
+ // A cell is its name and, where the document reached it, the count:
1231
+ // two roles, so a terminal can mute the second without touching the
1232
+ // first.
1233
+ const cell = (y: number, name: string) => {
1234
+ const text = latticeCell(counts, name)
1235
+ const left = latticeAt(name, cx) - Math.floor(text.length / 2)
1236
+ put(y, left, name, 'label')
1237
+ put(y, left + name.length, text.slice(name.length), 'muted')
1238
+ }
1239
+ const stems = (y: number, at: string[]) => {
1240
+ for (const name of at) {
1241
+ put(y, latticeAt(name, cx), '│', 'rule')
1242
+ }
1243
+ }
1244
+ // The rule that joins one row to the next, plus the lines that pass
1245
+ // it by: a kind with nothing under it runs on down the OUTSIDE of the
1246
+ // fan, which the column order guarantees is clear of it.
1247
+ const rule = (y: number, up: string[], down: string[], by: string[]) => {
1248
+ const at = (names: string[]) => names.map((n) => latticeAt(n, cx))
1249
+ const [u, d] = [at(up), at(down)]
1250
+ const lo = Math.min(...u, ...d), hi = Math.max(...u, ...d)
1251
+ for (let x = lo; x <= hi; x++) {
1252
+ put(y, x, LATTICE_GLYPH[
1253
+ (x > lo ? 'l' : '.') + (x < hi ? 'r' : '.') +
1254
+ (u.includes(x) ? 'u' : '.') + (d.includes(x) ? 'd' : '.')], 'rule')
1255
+ }
1256
+ stems(y, by)
1257
+ }
1258
+
1259
+ // Four node rows and three joins. `open` is every node whose line
1260
+ // downward has not been drawn yet, which is what carries `boolean`
1261
+ // and `null` past the numeric row to the bottom rule.
1262
+ let open: string[] = []
1263
+ let y = 0
1264
+ for (let r = 0; r < LATTICE_ROWS.length; r++) {
1265
+ stems(y, open)
1266
+ for (const name of LATTICE_ROWS[r]) {
1267
+ cell(y, name)
1268
+ }
1269
+ open = [...open, ...LATTICE_ROWS[r]]
1270
+ if (LATTICE_ROWS.length - 1 === r) {
1271
+ break
1272
+ }
1273
+ const next = LATTICE_ROWS[r + 1]
1274
+ const parents =
1275
+ open.filter((n) => next.some((k) => latticeCovers(n, k)))
1276
+ const by = open.filter((n) => !parents.includes(n))
1277
+ stems(y + 1, open)
1278
+ rule(y + 2, parents, next, by)
1279
+ open = by
1280
+ y += 3
1281
+ }
1282
+
1283
+ return canvas.map((line, i) => {
1284
+ const bare = line.join('').replace(/\s+$/, '')
1285
+ let out = '', at = 0
1286
+ while (at < bare.length) {
1287
+ let end = at
1288
+ while (end < bare.length && roles[i][end] === roles[i][at]) {
1289
+ end++
1290
+ }
1291
+ out += paint(roles[i][at], bare.slice(at, end))
1292
+ at = end
1293
+ }
1294
+ return out
1295
+ }).join('\n')
1296
+ }
1297
+
1298
+
1299
+ // The same figure as SVG, off the same column layout, so the two
1300
+ // profiles are one drawing in two grammars rather than two drawings.
1301
+ // A node the document REACHES is drawn with the ordinary rule stroke
1302
+ // (`av-box`) and one it does not with the faint one (`av-cell`),
1303
+ // because every node is drawn whether this document reaches it or not
1304
+ // and a reader has to see which is which without counting. NO NEW
1305
+ // CLASS: those two already mean a box and a faint box, so a host page
1306
+ // that themed the other figures gets this one for nothing.
1307
+ function latticeSvg(
1308
+ counts: Map<string, string[]>, at: string, style: ViewStyle
1309
+ ): string {
1310
+ const ROWH = 3 * LH
1311
+ const BOXH = 26
1312
+ const { cx, width } = latticeCols(counts)
1313
+ const parts: string[] = []
1314
+ const rowOf = new Map<string, number>()
1315
+ LATTICE_ROWS.forEach((row, r) => row.forEach((name) => rowOf.set(name, r)))
1316
+ const x = (name: string): number => PAD + latticeAt(name, cx) * CH
1317
+ const y = (name: string): number =>
1318
+ PAD + BOXH / 2 + (rowOf.get(name) as number) * ROWH
1319
+
1320
+ // Edges first, so a box always sits over the lines that reach it.
1321
+ // The horizontal jog is placed just above the CHILD rather than
1322
+ // halfway down, which is what keeps `boolean` and `null` -- three
1323
+ // rows from `top` to `nil` with nothing between -- clear of the
1324
+ // numeric row they pass.
1325
+ const edges: [string, string][] = [...LATTICE_PARENT,
1326
+ ...LATTICE_COLS.map((col): [string, string] => ['nil', col])]
1327
+ for (const [child, parent] of edges) {
1328
+ const y2 = y(child) - BOXH / 2
1329
+ parts.push(svgPath(`M${x(parent)} ${y(parent) + BOXH / 2}` +
1330
+ `V${y2 - (ROWH - BOXH) / 2}H${x(child)}V${y2}`, 'av-line'))
1331
+ }
1332
+
1333
+ for (const name of LATTICE_ROWS.flat()) {
1334
+ const text = latticeCell(counts, name)
1335
+ const w = (text.length + 2) * CH
1336
+ parts.push(svgRect(x(name) - w / 2, y(name) - BOXH / 2, w, BOXH,
1337
+ name === text ? 'av-cell' : 'av-box'))
1338
+ // The name and the count in ONE text element, as the tree does it:
1339
+ // two runs on one baseline, so the count is muted without the
1340
+ // figure having to place it.
1341
+ parts.push(`<text x="${x(name)}" y="${y(name) + 5}" text-anchor="middle">` +
1342
+ `<tspan class="av-t">${svgEsc(name)}</tspan>` +
1343
+ `<tspan class="av-m">${svgEsc(text.slice(name.length))}</tspan></text>`)
1344
+ }
1345
+
1346
+ const placed = [...counts.values()].reduce((n, p) => n + p.length, 0)
1347
+ return svgDoc(width * CH + 2 * PAD,
1348
+ 2 * PAD + BOXH + (LATTICE_ROWS.length - 1) * ROWH,
1349
+ `Value lattice at ${at}: ${placed} value(s) placed`, parts, style)
1350
+ }
1351
+
1352
+
1353
+ // The figure. The row count is fixed -- the lattice is the language's,
1354
+ // and no option makes it smaller -- so `--max-rows` below it is still a
1355
+ // refusal, because a figure that quietly overran a stated bound is the
1356
+ // thing every other kind here refuses to be; the message says raise
1357
+ // rather than narrow.
1358
+ const LATTICE_LINES = 3 * LATTICE_ROWS.length - 2
1359
+
1360
+ function drawLattice(
1361
+ root: any,
1362
+ o: { at?: string, as: ViewProfile, style: ViewStyle },
1363
+ max: number, loss: ViewLoss[]
1364
+ ): Figure {
1365
+ const at = o.at ?? '$'
1366
+ const anchor = anchorAt(root, at)
1367
+ if (null == anchor) {
1368
+ // The same code and the same sentence `get` answers with, for the
1369
+ // same question.
1370
+ return {
1371
+ errors: [finding('no_path', 'reference', at,
1372
+ `The path ${at} names nothing in this document.`)],
1373
+ }
1374
+ }
1375
+ if (max < LATTICE_LINES) {
1376
+ return {
1377
+ errors: [finding('view_rows_exceeded', 'budget', '$',
1378
+ `The figure has ${LATTICE_LINES} rows, above --max-rows ${max}; ` +
1379
+ 'the value lattice is fixed, so raise the limit.',
1380
+ `rows: ${LATTICE_LINES}, max: ${max}`)],
1381
+ }
1382
+ }
1383
+ const { counts, unplaced } = latticeCensus(anchor, at)
1384
+
1385
+ if (0 < unplaced.length) {
1386
+ // NOT A LOSS OF DETAIL BUT A LOSS OF PLACE: these values are real,
1387
+ // and the figure cannot say where they are because they are not
1388
+ // anywhere single. Named, not merely counted -- a reader who sees
1389
+ // `2` wants to know which two.
1390
+ loss.push({
1391
+ code: 'lattice_unplaced', count: unplaced.length, detail: unplaced,
1392
+ })
1393
+ }
1394
+
1395
+ return {
1396
+ text: 'svg' === o.as
1397
+ ? latticeSvg(counts, at, o.style) : latticeText(counts, o.style),
1398
+ }
1399
+ }
1400
+
1401
+
975
1402
  const DEFAULT_DOC_DEPTH = 3
976
1403
 
977
1404
  // A node's own children, as the anchor walk sees them: map keys sorted
@@ -2167,7 +2594,8 @@ function drawLadder(
2167
2594
  'The ladder needs the path to draw; name it with --at.')],
2168
2595
  }
2169
2596
  }
2170
- const rep = why(src, options.at, { path: options.path, trust: options.trust })
2597
+ const rep = why(src, options.at,
2598
+ { path: options.path, trust: options.trust, textExt: options.textExt })
2171
2599
  if (undefined === rep.record) {
2172
2600
  return { errors: rep.findings }
2173
2601
  }
@@ -2244,7 +2672,7 @@ const compareBySubsume: ViewCompare = (general, specific, options) => {
2244
2672
  const r = subsume(general.src, specific.src, {
2245
2673
  at: options.at, profile: options.profile,
2246
2674
  generalPath: general.path, specificPath: specific.path,
2247
- trust: options.trust,
2675
+ trust: options.trust, textExt: options.textExt,
2248
2676
  })
2249
2677
  return { verdict: r.verdict, code: r.findings[0]?.code ?? 'undecided' }
2250
2678
  }
@@ -2415,7 +2843,7 @@ function drawPoset(
2415
2843
  // on their own, each with its own finding, or the anchor a document
2416
2844
  // lacks.
2417
2845
  function docFailure(d: Doc, options: ViewOptions): VetFinding[] {
2418
- const loaded = load(d.src, d.path, options.trust, undefined)
2846
+ const loaded = load(d.src, d.path, options, undefined)
2419
2847
  if (undefined !== loaded.errors) {
2420
2848
  return loaded.errors
2421
2849
  }
@@ -2437,10 +2865,11 @@ type Loaded = { root?: any, ctx?: any, errors?: VetFinding[] }
2437
2865
  // recorder can stamp the parsed tree before the fixpoint runs (`why`'s
2438
2866
  // precedent).
2439
2867
  function load(
2440
- src: string, path: string | undefined, trust: TrustOptions | undefined,
2868
+ src: string, path: string | undefined,
2869
+ include: IncludeOptions,
2441
2870
  prov: Provenance | undefined
2442
2871
  ): Loaded {
2443
- const aontu = new Aontu(null == trust ? undefined : { trust })
2872
+ const aontu = new Aontu(includeOpts(include))
2444
2873
  const ctx = aontu.ctx({ collect: true, prov })
2445
2874
  const parseOpts = null == path ? undefined : { path }
2446
2875
  const parsed: any = aontu.parse(src, parseOpts, ctx)
@@ -2542,7 +2971,7 @@ export function view(
2542
2971
 
2543
2972
  const prov = 'layers' === kind
2544
2973
  ? (hooks?.provenance ?? (() => new Provenance()))() : undefined
2545
- const loaded = load(src, options.path, options.trust, prov)
2974
+ const loaded = load(src, options.path, options, prov)
2546
2975
  if (undefined !== loaded.errors) {
2547
2976
  return done({ errors: loaded.errors })
2548
2977
  }
@@ -2568,6 +2997,9 @@ function drawLoaded(
2568
2997
  if ('doc' === kind) {
2569
2998
  return drawDoc(root, { ...options, as, style }, max, loss)
2570
2999
  }
3000
+ if ('lattice' === kind) {
3001
+ return drawLattice(root, { ...options, as, style }, max, loss)
3002
+ }
2571
3003
  if ('layers' === kind) {
2572
3004
  return drawLayers(prov as Provenance, root, options.path,
2573
3005
  { ...options, as, style }, max, loss)
@@ -2812,7 +3244,7 @@ export function viewSet(
2812
3244
  // always costs a little and makes the one-evaluation claim true for
2813
3245
  // every kind but the ladder, which re-runs `why` by construction.
2814
3246
  const prov = (hooks?.provenance ?? (() => new Provenance()))()
2815
- const loaded = load(src, options.path, options.trust, prov)
3247
+ const loaded = load(src, options.path, options, prov)
2816
3248
  if (undefined !== loaded.errors) {
2817
3249
  return { verdict: 'error', views: [], errors: loaded.errors }
2818
3250
  }
@@ -2857,6 +3289,7 @@ export function viewSet(
2857
3289
  const loss: ViewLoss[] = []
2858
3290
  const each: ViewOptions = {
2859
3291
  ...plan.opts, path: options.path, trust: options.trust,
3292
+ textExt: options.textExt,
2860
3293
  }
2861
3294
  const fig: Figure = 'ladder' === plan.kind
2862
3295
  ? drawLadder(src, each, plan.as, plan.max)