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.
- package/README.md +3 -3
- package/dist/agentsmd.d.ts +1 -0
- package/dist/agentsmd.js +10 -3
- package/dist/agentsmd.js.map +1 -1
- package/dist/aontu.d.ts +4 -2
- package/dist/aontu.js +5 -2
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +10 -3
- package/dist/cli.js +311 -53
- package/dist/cli.js.map +1 -1
- package/dist/diff.d.ts +1 -0
- package/dist/diff.js +3 -2
- package/dist/diff.js.map +1 -1
- package/dist/escape.d.ts +5 -0
- package/dist/escape.js +455 -0
- package/dist/escape.js.map +1 -0
- package/dist/format.d.ts +26 -0
- package/dist/format.js +1489 -0
- package/dist/format.js.map +1 -0
- package/dist/hints.js +39 -1
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.d.ts +1 -0
- package/dist/jsonschema.js +3 -2
- package/dist/jsonschema.js.map +1 -1
- package/dist/lang.js +84 -6
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +2 -1
- package/dist/lsp.js +4 -4
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.js +2 -2
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp.js +6 -4
- package/dist/mcp.js.map +1 -1
- package/dist/mod-tool.js +8 -7
- package/dist/mod-tool.js.map +1 -1
- package/dist/mod.js +6 -6
- package/dist/mod.js.map +1 -1
- package/dist/patch.d.ts +1 -0
- package/dist/patch.js +1 -0
- package/dist/patch.js.map +1 -1
- package/dist/query.d.ts +1 -0
- package/dist/query.js +4 -3
- package/dist/query.js.map +1 -1
- package/dist/reach.d.ts +1 -0
- package/dist/reach.js +3 -2
- package/dist/reach.js.map +1 -1
- package/dist/relation.d.ts +1 -0
- package/dist/relation.js +3 -2
- package/dist/relation.js.map +1 -1
- package/dist/sigdecl.js +1 -1
- package/dist/sigdecl.js.map +1 -1
- package/dist/std.js +2 -1
- package/dist/std.js.map +1 -1
- package/dist/subsume.d.ts +1 -0
- package/dist/subsume.js +3 -2
- package/dist/subsume.js.map +1 -1
- package/dist/trim.d.ts +1 -0
- package/dist/trim.js +3 -2
- package/dist/trim.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.d.ts +1 -0
- package/dist/type.js.map +1 -1
- package/dist/utility.d.ts +8 -2
- package/dist/utility.js +9 -1
- package/dist/utility.js.map +1 -1
- package/dist/val/EachFuncVal.js +1 -2
- package/dist/val/EachFuncVal.js.map +1 -1
- package/dist/val/EmitFuncVal.d.ts +23 -0
- package/dist/val/EmitFuncVal.js +261 -0
- package/dist/val/EmitFuncVal.js.map +1 -0
- package/dist/val/FilterFuncVal.js +1 -2
- package/dist/val/FilterFuncVal.js.map +1 -1
- package/dist/val/FuncBaseVal.d.ts +1 -0
- package/dist/val/FuncBaseVal.js +16 -0
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/PackFuncVal.js +1 -2
- package/dist/val/PackFuncVal.js.map +1 -1
- package/dist/val/PlaceVal.d.ts +3 -1
- package/dist/val/PlaceVal.js +8 -6
- package/dist/val/PlaceVal.js.map +1 -1
- package/dist/val/StrFuncVal.d.ts +32 -0
- package/dist/val/StrFuncVal.js +292 -0
- package/dist/val/StrFuncVal.js.map +1 -0
- package/dist/vet.d.ts +1 -0
- package/dist/vet.js +1 -1
- package/dist/vet.js.map +1 -1
- package/dist/view.d.ts +2 -1
- package/dist/view.js +379 -8
- package/dist/view.js.map +1 -1
- package/grammar/aontu.abnf +156 -0
- package/grammar/aontu.gbnf +4 -3
- package/grammar/aontu.lark +4 -3
- package/grammar/aontu.tmLanguage.json +1 -1
- package/package.json +2 -1
- package/skill/grammar-card.md +5 -2
- package/src/agentsmd.ts +15 -3
- package/src/aontu.ts +8 -1
- package/src/cli.ts +365 -58
- package/src/diff.ts +7 -2
- package/src/escape.ts +371 -0
- package/src/format.ts +1728 -0
- package/src/hints.ts +53 -1
- package/src/jsonschema.ts +7 -1
- package/src/lang.ts +93 -6
- package/src/lsp.ts +8 -6
- package/src/mcp-server.ts +3 -2
- package/src/mcp.ts +6 -4
- package/src/mod-tool.ts +9 -8
- package/src/mod.ts +6 -6
- package/src/patch.ts +7 -0
- package/src/query.ts +8 -4
- package/src/reach.ts +7 -2
- package/src/relation.ts +7 -2
- package/src/sigdecl.ts +1 -1
- package/src/std.ts +2 -1
- package/src/subsume.ts +7 -2
- package/src/trim.ts +7 -2
- package/src/type.ts +8 -0
- package/src/utility.ts +27 -2
- package/src/val/EachFuncVal.ts +1 -3
- package/src/val/EmitFuncVal.ts +401 -0
- package/src/val/FilterFuncVal.ts +1 -3
- package/src/val/FuncBaseVal.ts +18 -0
- package/src/val/PackFuncVal.ts +1 -3
- package/src/val/PlaceVal.ts +8 -6
- package/src/val/StrFuncVal.ts +334 -0
- package/src/vet.ts +9 -3
- 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.
|
|
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,
|
|
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
|
|
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,
|
|
2868
|
+
src: string, path: string | undefined,
|
|
2869
|
+
include: IncludeOptions,
|
|
2441
2870
|
prov: Provenance | undefined
|
|
2442
2871
|
): Loaded {
|
|
2443
|
-
const aontu = new Aontu(
|
|
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
|
|
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
|
|
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)
|