aontu 0.63.0 → 0.64.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 +6 -6
- package/dist/aontu.d.ts +3 -3
- package/dist/aontu.js +4 -6
- package/dist/aontu.js.map +1 -1
- package/dist/aontumodel.js +6 -12
- package/dist/aontumodel.js.map +1 -1
- package/dist/casing.d.ts +5 -0
- package/dist/casing.js +90 -0
- package/dist/casing.js.map +1 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +147 -335
- package/dist/cli.js.map +1 -1
- package/dist/helpdoc.js +1 -1
- package/dist/helpdoc.js.map +1 -1
- package/dist/hints.js +3 -0
- package/dist/hints.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/mcp.js +0 -36
- package/dist/mcp.js.map +1 -1
- package/dist/mod-tool.js +16 -1
- package/dist/mod-tool.js.map +1 -1
- package/dist/profile.d.ts +9 -0
- package/dist/profile.js +28 -0
- package/dist/profile.js.map +1 -0
- package/dist/trace.d.ts +21 -0
- package/dist/trace.js +107 -0
- package/dist/trace.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/unify.js +1 -6
- package/dist/unify.js.map +1 -1
- package/dist/val/CmpFuncVal.d.ts +2 -0
- package/dist/val/CmpFuncVal.js +47 -13
- package/dist/val/CmpFuncVal.js.map +1 -1
- package/dist/val/NomFuncVal.js +11 -13
- package/dist/val/NomFuncVal.js.map +1 -1
- package/dist/val/ReferFuncVal.js +3 -0
- package/dist/val/ReferFuncVal.js.map +1 -1
- package/package.json +1 -1
- package/skill/tasks.md +9 -7
- package/src/aontu.ts +3 -8
- package/src/aontumodel.ts +6 -12
- package/src/casing.ts +95 -0
- package/src/cli.ts +162 -359
- package/src/helpdoc.ts +1 -1
- package/src/hints.ts +3 -0
- package/src/mcp.ts +0 -39
- package/src/mod-tool.ts +18 -1
- package/src/profile.ts +42 -0
- package/src/trace.ts +157 -0
- package/src/unify.ts +1 -6
- package/src/val/CmpFuncVal.ts +66 -20
- package/src/val/NomFuncVal.ts +3 -5
- package/src/val/ReferFuncVal.ts +4 -0
- package/dist/lower.d.ts +0 -23
- package/dist/lower.js +0 -528
- package/dist/lower.js.map +0 -1
- package/dist/render.d.ts +0 -53
- package/dist/render.js +0 -432
- package/dist/render.js.map +0 -1
- package/src/lower.ts +0 -586
- package/src/render.ts +0 -580
package/src/render.ts
DELETED
|
@@ -1,580 +0,0 @@
|
|
|
1
|
-
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { Aontu } from './aontu'
|
|
5
|
-
import { vet, failureFinding, anchorAt } from './vet'
|
|
6
|
-
import type { VetFinding } from './vet'
|
|
7
|
-
import { hcanon } from './hcanon'
|
|
8
|
-
import { makeNilErr } from './err'
|
|
9
|
-
import { cmpCodePoint } from './keyorder'
|
|
10
|
-
import { includeOpts } from './utility'
|
|
11
|
-
import type { IncludeOptions } from './utility'
|
|
12
|
-
import { lowerDecl, lowerHeader, ident } from './lower'
|
|
13
|
-
import type { LowerCtx } from './lower'
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export type RenderVerdict = 'ok' | 'lossy' | 'error'
|
|
17
|
-
|
|
18
|
-
// One rendered unit: the path the instance gave it, its language, and
|
|
19
|
-
// its bytes, as one string.
|
|
20
|
-
export type RenderUnit = {
|
|
21
|
-
path: string
|
|
22
|
-
lang: string
|
|
23
|
-
text: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type RenderLoss = {
|
|
27
|
-
unit: string
|
|
28
|
-
path: string
|
|
29
|
-
tier: 1 | 2 | 3
|
|
30
|
-
construct: string
|
|
31
|
-
reason: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type RenderTrace = {
|
|
35
|
-
unit: string
|
|
36
|
-
piece: string
|
|
37
|
-
node: string
|
|
38
|
-
rule: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type RenderCoverage = {
|
|
42
|
-
// Every model path a reference resolved to, in walk order: what the
|
|
43
|
-
// render READ.
|
|
44
|
-
read: string[]
|
|
45
|
-
// Dead model: the SHALLOWEST model paths no read reached. A path
|
|
46
|
-
// whose subtree holds a read is not named; its unread children are.
|
|
47
|
-
dead: string[]
|
|
48
|
-
unruled: { unit: string, path: string }[]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export type RenderReport = {
|
|
52
|
-
verdict: RenderVerdict
|
|
53
|
-
units: RenderUnit[]
|
|
54
|
-
// Per unit and path. Empty on `error`.
|
|
55
|
-
lossy: RenderLoss[]
|
|
56
|
-
errors?: VetFinding[]
|
|
57
|
-
trace?: RenderTrace[]
|
|
58
|
-
// The coverage report, under `coverage` (P7).
|
|
59
|
-
coverage?: RenderCoverage
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export type RenderOptions = IncludeOptions & {
|
|
63
|
-
// The value to render, a path into the document. Absent means the
|
|
64
|
-
// root.
|
|
65
|
-
at?: string
|
|
66
|
-
// Where the document CAME FROM, so a relative `@"file"` load inside
|
|
67
|
-
// it resolves from its own directory.
|
|
68
|
-
path?: string
|
|
69
|
-
profiles?: any[]
|
|
70
|
-
// Render only the unit at this path.
|
|
71
|
-
unit?: string
|
|
72
|
-
strict?: boolean
|
|
73
|
-
trace?: boolean
|
|
74
|
-
// Compute the coverage report, which needs the trace and turns it on.
|
|
75
|
-
coverage?: boolean
|
|
76
|
-
coverageAt?: string
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const VOCABULARY = '@"aontu:code"'
|
|
81
|
-
// The bundled profiles, by lang: aontu:render/lang/<lang>.
|
|
82
|
-
const BUNDLED_LANGS = ['go', 'markdown', 'text', 'typescript']
|
|
83
|
-
const PROFILE_VOCABULARY = '@"aontu:render"'
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
function finding(
|
|
87
|
-
code: string, cls: string, path: string, message: string
|
|
88
|
-
): VetFinding {
|
|
89
|
-
return { code, class: cls as any, severity: 'error', path, message, sites: [] }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
function errorReport(errors: VetFinding[]): RenderReport {
|
|
94
|
-
return { verdict: 'error', units: [], lossy: [], errors }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
export function render(src: string, options?: RenderOptions): RenderReport {
|
|
99
|
-
const opts = options ?? {}
|
|
100
|
-
const aontu = new Aontu(includeOpts(opts))
|
|
101
|
-
|
|
102
|
-
const rec = true === opts.trace || true === opts.coverage
|
|
103
|
-
const reads = rec ? new Set<string>() : undefined
|
|
104
|
-
|
|
105
|
-
// COLLECT MODE, so a syntax error arrives on the context rather than
|
|
106
|
-
// as a throw (the jsonschema and vet precedent; no try/catch, for
|
|
107
|
-
// the reason those verbs have none).
|
|
108
|
-
const actx = aontu.ctx({ collect: true, reads })
|
|
109
|
-
const root: any = aontu.unify(src, { path: opts.path, collect: true }, actx)
|
|
110
|
-
if (0 < actx.err.length || true === root?.isNil) {
|
|
111
|
-
return errorReport([failureFinding(actx, opts.path, root)])
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let node: any = root
|
|
115
|
-
if (null != opts.at && '' !== opts.at) {
|
|
116
|
-
const found: any = anchorAt(root, opts.at)
|
|
117
|
-
if (null == found) {
|
|
118
|
-
// The anchor names nothing: a `no_path` nil through the finding
|
|
119
|
-
// shape every other refusal here uses.
|
|
120
|
-
const nil: any = makeNilErr(actx, 'no_path', root, undefined, 'at')
|
|
121
|
-
actx.err.push(nil)
|
|
122
|
-
return errorReport([failureFinding(actx, opts.path, root)])
|
|
123
|
-
}
|
|
124
|
-
node = found
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const value = hcanon(node)
|
|
128
|
-
const report = vet(VOCABULARY, value)
|
|
129
|
-
if ('valid' !== report.verdict) {
|
|
130
|
-
return errorReport(report.findings)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const codeVal: any = node.peg.aontu?.peg?.Code
|
|
134
|
-
const instance = new Aontu().generate(
|
|
135
|
-
VOCABULARY + (undefined === codeVal ? '' : '\naontu: Code: ' + hcanon(codeVal)))
|
|
136
|
-
const folded = renderValue(instance, opts)
|
|
137
|
-
|
|
138
|
-
if (rec && 'error' !== folded.verdict) {
|
|
139
|
-
const marks = emitted(node)
|
|
140
|
-
const trace = traceOf(marks, instance, folded.units)
|
|
141
|
-
if (0 < trace.length) {
|
|
142
|
-
folded.trace = trace
|
|
143
|
-
}
|
|
144
|
-
if (true === opts.coverage) {
|
|
145
|
-
const cov = coverOf(root, node, reads as Set<string>, opts, marks,
|
|
146
|
-
instance, folded.units)
|
|
147
|
-
if (undefined === cov) {
|
|
148
|
-
const nil: any = makeNilErr(actx, 'no_path', root, undefined,
|
|
149
|
-
'coverageAt')
|
|
150
|
-
actx.err.push(nil)
|
|
151
|
-
return errorReport([failureFinding(actx, opts.path, root)])
|
|
152
|
-
}
|
|
153
|
-
folded.coverage = cov
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return folded
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// ---------------------------------------------------------------------
|
|
161
|
-
// THE TRACE AND THE COVERAGE REPORT (RENDER.0.md D11, P7).
|
|
162
|
-
|
|
163
|
-
// The one walk order both ports walk in: a list by index, a map by key
|
|
164
|
-
// in code-point order. `fn` answers whether to descend.
|
|
165
|
-
function walkVals(root: any, fn: (v: any, path: string[]) => boolean): void {
|
|
166
|
-
const walk = (v: any, path: string[]): void => {
|
|
167
|
-
if (null == v || true !== v.isVal || !fn(v, path)) {
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
if (true === v.isList && null != v.peg) {
|
|
171
|
-
for (let i = 0; i < v.peg.length; i++) {
|
|
172
|
-
walk(v.peg[i], [...path, String(i)])
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
else if (true === v.isMap && null != v.peg) {
|
|
176
|
-
for (const k of Object.keys(v.peg).sort(cmpCodePoint)) {
|
|
177
|
-
if (!v.aliasKeys.includes(k)) {
|
|
178
|
-
walk(v.peg[k], [...path, k])
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
walk(root, [])
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// A path as an address: `$`, then a dot before every segment.
|
|
188
|
-
function addr(path: string[]): string {
|
|
189
|
-
return '$' + path.map((seg) => '.' + seg).join('')
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// Every piece a dispatch stamped, under the anchored value, in
|
|
194
|
-
// document order. The path is relative to the anchor, which is where
|
|
195
|
-
// the instance the fold reads is rooted too.
|
|
196
|
-
function emitted(node: any): { path: string, mark: any }[] {
|
|
197
|
-
const out: { path: string, mark: any }[] = []
|
|
198
|
-
walkVals(node, (v: any, path: string[]) => {
|
|
199
|
-
if (null != v.emitted) {
|
|
200
|
-
out.push({ path: addr(path), mark: v.emitted })
|
|
201
|
-
}
|
|
202
|
-
return true
|
|
203
|
-
})
|
|
204
|
-
return out
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
function traceOf(marks: { path: string, mark: any }[], instance: any,
|
|
209
|
-
units: RenderUnit[]): RenderTrace[] {
|
|
210
|
-
const pre: { at: string, path: string }[] = []
|
|
211
|
-
unitList(instance).forEach((u: any, i: number) => {
|
|
212
|
-
if (units.some((r) => r.path === u.path)) {
|
|
213
|
-
pre.push({ at: '$.aontu.Code.units.' + i, path: u.path })
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
const out: RenderTrace[] = []
|
|
217
|
-
for (const m of marks) {
|
|
218
|
-
const hit = pre.find((p) => m.path === p.at || m.path.startsWith(p.at + '.'))
|
|
219
|
-
if (undefined === hit) {
|
|
220
|
-
continue
|
|
221
|
-
}
|
|
222
|
-
out.push({ unit: hit.path, piece: m.path, node: m.mark.node, rule: m.mark.rule })
|
|
223
|
-
}
|
|
224
|
-
return out
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// Every proper ancestor of an address, `$` included.
|
|
229
|
-
function ancestors(a: string): string[] {
|
|
230
|
-
const parts = a.split('.')
|
|
231
|
-
const out: string[] = []
|
|
232
|
-
for (let i = 1; i < parts.length; i++) {
|
|
233
|
-
out.push(parts.slice(0, i).join('.'))
|
|
234
|
-
}
|
|
235
|
-
return out
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
function covered(set: Set<string>, a: string): boolean {
|
|
240
|
-
if (set.has(a)) {
|
|
241
|
-
return true
|
|
242
|
-
}
|
|
243
|
-
for (const up of ancestors(a)) {
|
|
244
|
-
if (set.has(up)) {
|
|
245
|
-
return true
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return false
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
function coverOf(root: any, node: any, reads: Set<string>,
|
|
253
|
-
opts: RenderOptions, marks: { path: string, mark: any }[], instance: any,
|
|
254
|
-
units: RenderUnit[]): RenderCoverage | undefined {
|
|
255
|
-
// THE ANCHOR IS THE ONE render() ALREADY FOUND, so the namespace
|
|
256
|
-
// under it is named without asking a second time: `--at` is resolved
|
|
257
|
-
// before the vet, and an anchor that named nothing never reached here.
|
|
258
|
-
const codeAddr = addr(node.path.concat('aontu'))
|
|
259
|
-
|
|
260
|
-
let from: any = root
|
|
261
|
-
let base: string[] = []
|
|
262
|
-
if (null != opts.coverageAt && '' !== opts.coverageAt) {
|
|
263
|
-
const found: any = anchorAt(root, opts.coverageAt)
|
|
264
|
-
if (null == found) {
|
|
265
|
-
return undefined
|
|
266
|
-
}
|
|
267
|
-
from = found
|
|
268
|
-
base = found.path
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const above = new Set<string>(reads)
|
|
272
|
-
const below = new Set<string>()
|
|
273
|
-
for (const r of reads) {
|
|
274
|
-
for (const up of ancestors(r)) {
|
|
275
|
-
below.add(up)
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const dead: string[] = []
|
|
280
|
-
walkVals(from, (_v: any, path: string[]) => {
|
|
281
|
-
const a = addr(base.concat(path))
|
|
282
|
-
if (a === codeAddr || covered(above, a)) {
|
|
283
|
-
return false
|
|
284
|
-
}
|
|
285
|
-
if (below.has(a) || 0 === path.length) {
|
|
286
|
-
return true
|
|
287
|
-
}
|
|
288
|
-
dead.push(a)
|
|
289
|
-
return false
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
// A SILENT HOLE: a declaration of a rendered unit that no stamp
|
|
293
|
-
// touches -- neither its own, nor one on the unit above it, nor one
|
|
294
|
-
// on a piece inside it.
|
|
295
|
-
const stamped = new Set<string>(marks.map((m) => m.path))
|
|
296
|
-
const inside = new Set<string>()
|
|
297
|
-
for (const m of marks) {
|
|
298
|
-
for (const up of ancestors(m.path)) {
|
|
299
|
-
inside.add(up)
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const unruled: { unit: string, path: string }[] = []
|
|
303
|
-
unitList(instance).forEach((unit: any, i: number) => {
|
|
304
|
-
if (!units.some((u) => u.path === unit.path)) {
|
|
305
|
-
return
|
|
306
|
-
}
|
|
307
|
-
const decls: any[] = unit.decls
|
|
308
|
-
decls.forEach((_d: any, j: number) => {
|
|
309
|
-
const a = '$.aontu.Code.units.' + i + '.decls.' + j
|
|
310
|
-
if (!covered(stamped, a) && !inside.has(a)) {
|
|
311
|
-
unruled.push({ unit: unit.path, path: a })
|
|
312
|
-
}
|
|
313
|
-
})
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
return { read: [...reads].sort(cmpCodePoint), dead, unruled }
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// The bundled text profile, evaluated once: the profile of a unit
|
|
321
|
-
// whose declarations are fragments and text escapes only.
|
|
322
|
-
const bundled: Record<string, any> = {}
|
|
323
|
-
|
|
324
|
-
// A bundled profile, evaluated once: the meet of aontu:render/lang/<lang>
|
|
325
|
-
// with the vocabulary, so its defaults are in it.
|
|
326
|
-
function bundledProfile(lang: string): any {
|
|
327
|
-
if (!BUNDLED_LANGS.includes(lang)) {
|
|
328
|
-
return undefined
|
|
329
|
-
}
|
|
330
|
-
if (undefined === bundled[lang]) {
|
|
331
|
-
bundled[lang] = new Aontu().generate('@"aontu:render/lang/' + lang + '"').aontu.render.Lang
|
|
332
|
-
}
|
|
333
|
-
return bundled[lang]
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
function profileFor(lang: string, given: any[] | undefined, fragOnly: boolean): any {
|
|
338
|
-
const supplied = (given ?? []).find((p: any) => p?.lang === lang)
|
|
339
|
-
if (undefined !== supplied) {
|
|
340
|
-
return supplied
|
|
341
|
-
}
|
|
342
|
-
const own = bundledProfile(lang)
|
|
343
|
-
if (undefined !== own) {
|
|
344
|
-
return own
|
|
345
|
-
}
|
|
346
|
-
return fragOnly ? bundledProfile('text') : undefined
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
function isMap(v: any): boolean {
|
|
351
|
-
return null != v && 'object' === typeof v && !Array.isArray(v)
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
// An inline profile merged over its base, map by map, the inline
|
|
356
|
-
// value winning at a leaf; keys in code-point order, so the merge is
|
|
357
|
-
// the same in both ports.
|
|
358
|
-
function mergeProfile(base: any, over: any): any {
|
|
359
|
-
const out: any = { ...base }
|
|
360
|
-
for (const k of Object.keys(over).sort(cmpCodePoint)) {
|
|
361
|
-
out[k] = isMap(base[k]) && isMap(over[k]) ?
|
|
362
|
-
mergeProfile(base[k], over[k]) : over[k]
|
|
363
|
-
}
|
|
364
|
-
return out
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
function pad(profile: any, at: number): string {
|
|
369
|
-
const indent = profile.indent ?? { unit: ' ', width: 2 }
|
|
370
|
-
return (indent.unit ?? ' ').repeat((indent.width ?? 2) * at)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function line(profile: any, at: number, text: string): string {
|
|
374
|
-
return ('' === text ? '' : pad(profile, at)) + text + '\n'
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// A reference inline is its name: through the profile's identifier
|
|
378
|
-
// rules under a lowering, verbatim under text.
|
|
379
|
-
function inline(piece: any, ctx: LowerCtx | undefined): string {
|
|
380
|
-
if ('string' === typeof piece) {
|
|
381
|
-
return piece
|
|
382
|
-
}
|
|
383
|
-
return undefined === ctx ? piece.name : ident(piece.name, 'record', ctx, '', false)
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function foldPiece(
|
|
387
|
-
piece: any, profile: any, unit: string, path: string, lossy: RenderLoss[],
|
|
388
|
-
ctx?: LowerCtx
|
|
389
|
-
): string {
|
|
390
|
-
if ('string' === typeof piece) {
|
|
391
|
-
return line(profile, 0, piece)
|
|
392
|
-
}
|
|
393
|
-
if ('line' === piece.k) {
|
|
394
|
-
return line(profile, piece.at ?? 0, piece.n.map((p: any) => inline(p, ctx)).join(''))
|
|
395
|
-
}
|
|
396
|
-
if ('blank' === piece.k) {
|
|
397
|
-
return '\n'.repeat(piece.n ?? 1)
|
|
398
|
-
}
|
|
399
|
-
lossy.push({
|
|
400
|
-
unit, path, tier: 3, construct: 'raw',
|
|
401
|
-
reason: 'verbatim text: the renderer re-indents it and checks nothing else',
|
|
402
|
-
})
|
|
403
|
-
const at: number = piece.at ?? 0
|
|
404
|
-
const reindent: boolean = piece.reindent ?? true
|
|
405
|
-
const lines: string[] = piece.text.split('\n')
|
|
406
|
-
if ('' === lines[lines.length - 1]) {
|
|
407
|
-
lines.pop()
|
|
408
|
-
}
|
|
409
|
-
return lines.map((l: string) => reindent ? line(profile, at, l) : l + '\n').join('')
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
export function renderProfile(src: string, options?: RenderOptions):
|
|
414
|
-
{ profile?: any, errors?: VetFinding[] } {
|
|
415
|
-
const opts = options ?? {}
|
|
416
|
-
const aontu = new Aontu(includeOpts(opts))
|
|
417
|
-
const actx = aontu.ctx({ collect: true })
|
|
418
|
-
const root: any = aontu.unify(src, { path: opts.path, collect: true }, actx)
|
|
419
|
-
if (0 < actx.err.length || true === root?.isNil) {
|
|
420
|
-
return { errors: [failureFinding(actx, opts.path, root)] }
|
|
421
|
-
}
|
|
422
|
-
const report = vet(PROFILE_VOCABULARY, hcanon(root))
|
|
423
|
-
if ('valid' !== report.verdict) {
|
|
424
|
-
return { errors: report.findings }
|
|
425
|
-
}
|
|
426
|
-
// The meet, keyed as render's is: the vocabulary requires `profile`,
|
|
427
|
-
// so a value the vet admitted has one.
|
|
428
|
-
const instance = new Aontu().generate(
|
|
429
|
-
PROFILE_VOCABULARY + '\naontu: render: Lang: ' +
|
|
430
|
-
hcanon(root.peg.aontu.peg.render.peg.Lang))
|
|
431
|
-
return { profile: instance.aontu.render.Lang }
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
// The instance's unit list, or none: `code` is the vocabulary's own
|
|
436
|
-
// key and is always there, `units` is not. One reader, so the fold,
|
|
437
|
-
// the trace and the coverage report all see the same list.
|
|
438
|
-
function unitList(instance: any): any[] {
|
|
439
|
-
return Array.isArray(instance?.aontu?.Code?.units) ?
|
|
440
|
-
instance.aontu.Code.units : []
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
export function renderValue(instance: any, options?: RenderOptions): RenderReport {
|
|
445
|
-
const opts = options ?? {}
|
|
446
|
-
const errors: VetFinding[] = []
|
|
447
|
-
const lossy: RenderLoss[] = []
|
|
448
|
-
const units: RenderUnit[] = []
|
|
449
|
-
const list: any[] = unitList(instance)
|
|
450
|
-
|
|
451
|
-
const seen: string[] = []
|
|
452
|
-
let selected = 0
|
|
453
|
-
list.forEach((unit: any, i: number) => {
|
|
454
|
-
const upath = '$.aontu.Code.units.' + i
|
|
455
|
-
const path: string = unit.path
|
|
456
|
-
const lang: string = unit.lang
|
|
457
|
-
|
|
458
|
-
// A UNIT PATH IS RELATIVE, DESCENDS, AND IS ITS OWN (RENDER.0.md
|
|
459
|
-
// D8): an absolute path, a `..` segment or a repeat of another
|
|
460
|
-
// unit's path is refused before anything is written.
|
|
461
|
-
if (path.startsWith('/')) {
|
|
462
|
-
errors.push(finding('render_path', 'parse', upath + '.path',
|
|
463
|
-
'the unit path ' + path + ' is absolute.'))
|
|
464
|
-
return
|
|
465
|
-
}
|
|
466
|
-
if (path.split('/').includes('..')) {
|
|
467
|
-
errors.push(finding('render_path', 'parse', upath + '.path',
|
|
468
|
-
'the unit path ' + path + ' climbs out of the output directory.'))
|
|
469
|
-
return
|
|
470
|
-
}
|
|
471
|
-
if (seen.includes(path)) {
|
|
472
|
-
errors.push(finding('render_path', 'parse', upath + '.path',
|
|
473
|
-
'the unit path ' + path + ' repeats another unit\'s.'))
|
|
474
|
-
return
|
|
475
|
-
}
|
|
476
|
-
seen.push(path)
|
|
477
|
-
|
|
478
|
-
if (undefined !== opts.unit && opts.unit !== path) {
|
|
479
|
-
return
|
|
480
|
-
}
|
|
481
|
-
selected++
|
|
482
|
-
|
|
483
|
-
const decls: any[] = unit.decls
|
|
484
|
-
const fragOnly = decls.every((d: any) => 'frag' === d.k || 'text' === d.k)
|
|
485
|
-
const base = profileFor(lang, opts.profiles, fragOnly)
|
|
486
|
-
if (undefined === base) {
|
|
487
|
-
errors.push(finding('render_profile', 'parse', upath + '.lang',
|
|
488
|
-
'no profile renders ' + lang + ': a declaration needs a lowering, and ' +
|
|
489
|
-
'only fragments and text escapes render under aontu:render/lang/text.'))
|
|
490
|
-
return
|
|
491
|
-
}
|
|
492
|
-
const profile = null == unit.profile ? base : mergeProfile(base, unit.profile)
|
|
493
|
-
|
|
494
|
-
const family: string | undefined = profile.lowering
|
|
495
|
-
const ctx: LowerCtx | undefined = undefined === family ? undefined
|
|
496
|
-
: { profile, family, unit: path, lossy }
|
|
497
|
-
|
|
498
|
-
let text = ''
|
|
499
|
-
if (undefined !== ctx) {
|
|
500
|
-
const header = lowerHeader(unit, instance?.aontu?.Code?.source, ctx)
|
|
501
|
-
for (const piece of header) {
|
|
502
|
-
text += foldPiece(piece, profile, path, upath, lossy, ctx)
|
|
503
|
-
}
|
|
504
|
-
if (0 < header.length && 0 < decls.length) {
|
|
505
|
-
text += '\n'
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
let lowered = false
|
|
509
|
-
decls.forEach((decl: any, j: number) => {
|
|
510
|
-
const dpath = upath + '.decls.' + j
|
|
511
|
-
if ('frag' === decl.k) {
|
|
512
|
-
lowered = false
|
|
513
|
-
// Lossy only against a language whose DECLARATIONS could have
|
|
514
|
-
// been lowered instead: a fragment is all the others have.
|
|
515
|
-
if (null != profile.lowering) {
|
|
516
|
-
lossy.push({
|
|
517
|
-
unit: path, path: dpath, tier: 2, construct: 'frag',
|
|
518
|
-
reason: 'a fragment says nothing about ' + lang + ' syntax',
|
|
519
|
-
})
|
|
520
|
-
}
|
|
521
|
-
decl.n.forEach((piece: any, n: number) => {
|
|
522
|
-
text += foldPiece(piece, profile, path, dpath + '.n.' + n, lossy, ctx)
|
|
523
|
-
})
|
|
524
|
-
}
|
|
525
|
-
else if ('text' === decl.k) {
|
|
526
|
-
lowered = false
|
|
527
|
-
if (decl.lang !== lang) {
|
|
528
|
-
errors.push(finding('render_lang', 'conflict', dpath + '.lang',
|
|
529
|
-
'the text escape is ' + decl.lang + ' in a ' + lang + ' unit.'))
|
|
530
|
-
return
|
|
531
|
-
}
|
|
532
|
-
lossy.push({
|
|
533
|
-
unit: path, path: dpath, tier: 3, construct: 'text',
|
|
534
|
-
reason: 'verbatim ' + lang + ': the renderer checks nothing in it',
|
|
535
|
-
})
|
|
536
|
-
text += decl.text
|
|
537
|
-
}
|
|
538
|
-
else if (undefined !== ctx) {
|
|
539
|
-
if (lowered) {
|
|
540
|
-
text += '\n'
|
|
541
|
-
}
|
|
542
|
-
for (const piece of lowerDecl(decl, dpath, ctx)) {
|
|
543
|
-
text += foldPiece(piece, profile, path, dpath, lossy, ctx)
|
|
544
|
-
}
|
|
545
|
-
lowered = true
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
548
|
-
errors.push(finding('render_profile', 'parse', dpath + '.k',
|
|
549
|
-
'a ' + decl.k + ' declaration has no lowering under the ' +
|
|
550
|
-
profile.lang + ' profile.'))
|
|
551
|
-
}
|
|
552
|
-
})
|
|
553
|
-
|
|
554
|
-
units.push({ path, lang, text })
|
|
555
|
-
})
|
|
556
|
-
|
|
557
|
-
if (undefined !== opts.unit && 0 === selected) {
|
|
558
|
-
errors.push(finding('render_unit', 'reference', '$.aontu.Code.units',
|
|
559
|
-
'no unit has the path ' + opts.unit + '.'))
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (true === opts.strict) {
|
|
563
|
-
for (const loss of lossy) {
|
|
564
|
-
if (3 === loss.tier) {
|
|
565
|
-
errors.push(finding('render_strict', 'conflict', loss.path,
|
|
566
|
-
'the ' + loss.construct + ' in ' + loss.unit +
|
|
567
|
-
' is an opaque escape, refused under strict.'))
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (0 < errors.length) {
|
|
573
|
-
return errorReport(errors)
|
|
574
|
-
}
|
|
575
|
-
return {
|
|
576
|
-
verdict: 0 < lossy.length ? 'lossy' : 'ok',
|
|
577
|
-
units,
|
|
578
|
-
lossy,
|
|
579
|
-
}
|
|
580
|
-
}
|