aontu 0.68.0 → 0.69.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/dist/alias.d.ts +12 -1
- package/dist/alias.js +176 -6
- package/dist/alias.js.map +1 -1
- package/dist/aliasname.d.ts +19 -0
- package/dist/aliasname.js +65 -0
- package/dist/aliasname.js.map +1 -0
- package/dist/aontu.d.ts +1 -1
- package/dist/aontu.js +21 -8
- package/dist/aontu.js.map +1 -1
- package/dist/aontumodel.js +1 -1
- package/dist/aontumodel.js.map +1 -1
- package/dist/ctx.d.ts +1 -0
- package/dist/ctx.js +1 -0
- package/dist/ctx.js.map +1 -1
- package/dist/err.js +18 -1
- package/dist/err.js.map +1 -1
- package/dist/format.js +14 -0
- package/dist/format.js.map +1 -1
- package/dist/hints.js +36 -7
- package/dist/hints.js.map +1 -1
- package/dist/lang.js +289 -35
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +10 -4
- package/dist/lsp.js +116 -7
- package/dist/lsp.js.map +1 -1
- package/dist/site.d.ts +9 -0
- package/dist/site.js +2 -1
- package/dist/site.js.map +1 -1
- package/dist/subsume.js +6 -1
- package/dist/subsume.js.map +1 -1
- package/dist/val/BagVal.d.ts +1 -0
- package/dist/val/BagVal.js +3 -0
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/EmitFuncVal.js +2 -1
- package/dist/val/EmitFuncVal.js.map +1 -1
- package/dist/val/FilterFuncVal.js +1 -1
- package/dist/val/FilterFuncVal.js.map +1 -1
- package/dist/val/FuncBaseVal.d.ts +2 -1
- package/dist/val/FuncBaseVal.js +25 -1
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/MapVal.js +10 -4
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/MatchFuncVal.js +2 -1
- package/dist/val/MatchFuncVal.js.map +1 -1
- package/dist/val/NilVal.js +3 -0
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/RecurseVal.d.ts +1 -0
- package/dist/val/RecurseVal.js +7 -3
- package/dist/val/RecurseVal.js.map +1 -1
- package/dist/val/RefVal.d.ts +1 -0
- package/dist/val/RefVal.js +29 -12
- package/dist/val/RefVal.js.map +1 -1
- package/package.json +1 -1
- package/src/alias.ts +214 -6
- package/src/aliasname.ts +95 -0
- package/src/aontu.ts +23 -9
- package/src/aontumodel.ts +1 -1
- package/src/ctx.ts +4 -1
- package/src/err.ts +19 -1
- package/src/format.ts +14 -0
- package/src/hints.ts +40 -7
- package/src/lang.ts +334 -30
- package/src/lsp.ts +136 -6
- package/src/site.ts +15 -1
- package/src/subsume.ts +9 -2
- package/src/val/BagVal.ts +4 -0
- package/src/val/EmitFuncVal.ts +3 -2
- package/src/val/FilterFuncVal.ts +2 -2
- package/src/val/FuncBaseVal.ts +30 -1
- package/src/val/MapVal.ts +10 -4
- package/src/val/MatchFuncVal.ts +3 -2
- package/src/val/NilVal.ts +3 -0
- package/src/val/RecurseVal.ts +8 -3
- package/src/val/RefVal.ts +31 -12
package/src/lsp.ts
CHANGED
|
@@ -207,6 +207,7 @@ function initializeResult() {
|
|
|
207
207
|
capabilities: {
|
|
208
208
|
textDocumentSync: 1,
|
|
209
209
|
hoverProvider: true,
|
|
210
|
+
definitionProvider: true,
|
|
210
211
|
completionProvider: {},
|
|
211
212
|
signatureHelpProvider: { triggerCharacters: ['(', ','] },
|
|
212
213
|
},
|
|
@@ -301,9 +302,104 @@ export function contributionsMarkdown(conjuncts: WhyConjunct[]): string {
|
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
|
|
305
|
+
// The alias name the cursor sits on, with its span. A `%` inside a
|
|
306
|
+
// string is text (`a: "%foo"`), so quoted runs are stepped over.
|
|
307
|
+
function aliasAt(
|
|
308
|
+
src: string, position: Position,
|
|
309
|
+
): { name: string, start: number, end: number } | undefined {
|
|
310
|
+
const line = src.split('\n')[position.line]
|
|
311
|
+
if (null == line) {
|
|
312
|
+
return undefined
|
|
313
|
+
}
|
|
314
|
+
for (let i = 0; i < line.length; i++) {
|
|
315
|
+
const c = line[i]
|
|
316
|
+
if ('"' === c || "'" === c) {
|
|
317
|
+
for (i++; i < line.length && line[i] !== c; i++) { }
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
320
|
+
if ('%' !== c) {
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
const m = ALIAS_RE.exec(line.substring(i))
|
|
324
|
+
if (null == m) {
|
|
325
|
+
continue
|
|
326
|
+
}
|
|
327
|
+
if (i <= position.character && position.character < i + m[0].length) {
|
|
328
|
+
return { name: m[0], start: i, end: i + m[0].length }
|
|
329
|
+
}
|
|
330
|
+
i += m[0].length - 1
|
|
331
|
+
}
|
|
332
|
+
return undefined
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
// The binding for a name the cursor is on, or nothing where the name is
|
|
337
|
+
// used but never bound -- which the diagnostics already report.
|
|
338
|
+
function aliasBindingAt(
|
|
339
|
+
src: string, position: Position,
|
|
340
|
+
): { at: { name: string, start: number, end: number }, bind: AliasBinding }
|
|
341
|
+
| undefined {
|
|
342
|
+
const at = aliasAt(src, position)
|
|
343
|
+
if (undefined === at) {
|
|
344
|
+
return undefined
|
|
345
|
+
}
|
|
346
|
+
for (const bind of aliasScope(src)) {
|
|
347
|
+
if (bind.name === at.name) {
|
|
348
|
+
return { at, bind }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return undefined
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
function aliasMarkdown(bind: AliasBinding): string {
|
|
356
|
+
return '```aontu\n' + bind.decl + '\n```\n\n*alias' +
|
|
357
|
+
('' === bind.from ? '' : ', taken from ' + bind.from) + '*'
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
type Location = { uri: string, range: Range }
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
// Go to where the name is BOUND: its declaration, or the destructure
|
|
365
|
+
// that brought it in, which is where this file gets the name.
|
|
366
|
+
function computeDefinition(
|
|
367
|
+
src: string, position: Position, uri: string): Location | null {
|
|
368
|
+
const found = aliasBindingAt(src, position)
|
|
369
|
+
if (undefined === found) {
|
|
370
|
+
return null
|
|
371
|
+
}
|
|
372
|
+
const line = found.bind.row - 1
|
|
373
|
+
const col = found.bind.col - 1
|
|
374
|
+
return {
|
|
375
|
+
uri,
|
|
376
|
+
range: {
|
|
377
|
+
start: { line, character: col },
|
|
378
|
+
end: { line, character: col + found.bind.name.length },
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
|
|
304
384
|
function computeHover(
|
|
305
385
|
src: string, position: Position, provenance?: boolean,
|
|
306
386
|
trust?: any): Hover | null {
|
|
387
|
+
// A NAME IS NOT A VALUE: the tree holds what it resolves to, at the
|
|
388
|
+
// declaration's site, so a use has no span for the walk below to
|
|
389
|
+
// find. The unify below takes a parsed Val as well as text, and text
|
|
390
|
+
// is the only thing with a name in it to read.
|
|
391
|
+
const named = 'string' === typeof src ?
|
|
392
|
+
aliasBindingAt(src, position) : undefined
|
|
393
|
+
if (undefined !== named) {
|
|
394
|
+
return {
|
|
395
|
+
contents: { kind: 'markdown', value: aliasMarkdown(named.bind) },
|
|
396
|
+
range: {
|
|
397
|
+
start: { line: position.line, character: named.at.start },
|
|
398
|
+
end: { line: position.line, character: named.at.end },
|
|
399
|
+
},
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
307
403
|
let root: any
|
|
308
404
|
try {
|
|
309
405
|
root = new Aontu(null == trust ? {} : { trust }).unify(src, { collect: true })
|
|
@@ -409,9 +505,13 @@ type CompletionItem = {
|
|
|
409
505
|
}
|
|
410
506
|
|
|
411
507
|
import { funcSig, renderSig, renderSigArg } from './sig'
|
|
508
|
+
import { ALIAS_RE } from './aliasname'
|
|
509
|
+
import { aliasScope } from './alias'
|
|
510
|
+
import type { AliasBinding } from './alias'
|
|
412
511
|
|
|
413
512
|
// LSP CompletionItemKind subset.
|
|
414
513
|
const COMPLETION_FUNCTION = 3
|
|
514
|
+
const COMPLETION_VARIABLE = 6
|
|
415
515
|
const COMPLETION_KEYWORD = 14
|
|
416
516
|
|
|
417
517
|
const BUILTIN_FUNCS = [
|
|
@@ -441,9 +541,9 @@ const KIND_KEYWORDS = [
|
|
|
441
541
|
const LITERAL_KEYWORDS = ['_', 'true', 'false', 'null', 'top']
|
|
442
542
|
|
|
443
543
|
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
function computeCompletions(): CompletionItem[] {
|
|
544
|
+
// The built-in functions, scalar-kind keywords and literals, and the
|
|
545
|
+
// names this document binds. Clients filter by the typed prefix.
|
|
546
|
+
function computeCompletions(src: string): CompletionItem[] {
|
|
447
547
|
const out: CompletionItem[] = []
|
|
448
548
|
for (const f of BUILTIN_FUNCS) {
|
|
449
549
|
// The detail is the rendered SIGNATURE (docs/design/SIGNATURES.0.md)
|
|
@@ -457,6 +557,18 @@ function computeCompletions(): CompletionItem[] {
|
|
|
457
557
|
for (const k of LITERAL_KEYWORDS) {
|
|
458
558
|
out.push({ label: k, kind: COMPLETION_KEYWORD, detail: 'keyword' })
|
|
459
559
|
}
|
|
560
|
+
// A name that binds more than once is still one name to offer.
|
|
561
|
+
const named = new Set<string>()
|
|
562
|
+
for (const bind of aliasScope(src)) {
|
|
563
|
+
if (named.has(bind.name)) {
|
|
564
|
+
continue
|
|
565
|
+
}
|
|
566
|
+
named.add(bind.name)
|
|
567
|
+
out.push({
|
|
568
|
+
label: bind.name, kind: COMPLETION_VARIABLE,
|
|
569
|
+
detail: '' === bind.from ? 'alias' : 'alias from ' + bind.from,
|
|
570
|
+
})
|
|
571
|
+
}
|
|
460
572
|
return out
|
|
461
573
|
}
|
|
462
574
|
|
|
@@ -586,8 +698,23 @@ class LspHandler {
|
|
|
586
698
|
return [{ jsonrpc: '2.0', id: msg.id, result: hover }]
|
|
587
699
|
}
|
|
588
700
|
|
|
589
|
-
case 'textDocument/completion':
|
|
590
|
-
|
|
701
|
+
case 'textDocument/completion': {
|
|
702
|
+
const uri = msg.params?.textDocument?.uri
|
|
703
|
+
const text = null != uri ? this.docs.get(uri) : undefined
|
|
704
|
+
return [{
|
|
705
|
+
jsonrpc: '2.0', id: msg.id,
|
|
706
|
+
result: computeCompletions(text ?? ''),
|
|
707
|
+
}]
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
case 'textDocument/definition': {
|
|
711
|
+
const uri = msg.params?.textDocument?.uri
|
|
712
|
+
const pos = msg.params?.position
|
|
713
|
+
const text = null != uri ? this.docs.get(uri) : undefined
|
|
714
|
+
const at = (null != text && null != pos)
|
|
715
|
+
? computeDefinition(text, pos, uri) : null
|
|
716
|
+
return [{ jsonrpc: '2.0', id: msg.id, result: at }]
|
|
717
|
+
}
|
|
591
718
|
|
|
592
719
|
case 'textDocument/signatureHelp': {
|
|
593
720
|
const uri = msg.params?.textDocument?.uri
|
|
@@ -616,12 +743,13 @@ class LspHandler {
|
|
|
616
743
|
return publishDiagnosticsMsg(uri,
|
|
617
744
|
computeDiagnostics(this.docs.get(uri) ?? '', { trust: this.trust }))
|
|
618
745
|
}
|
|
619
|
-
} /* node:coverage ignore next
|
|
746
|
+
} /* node:coverage ignore next 31 */
|
|
620
747
|
|
|
621
748
|
|
|
622
749
|
export {
|
|
623
750
|
computeDiagnostics,
|
|
624
751
|
computeHover,
|
|
752
|
+
computeDefinition,
|
|
625
753
|
computeCompletions,
|
|
626
754
|
LspHandler,
|
|
627
755
|
LSP_VERSION,
|
|
@@ -631,6 +759,7 @@ export {
|
|
|
631
759
|
SEVERITY_INFORMATION,
|
|
632
760
|
SEVERITY_HINT,
|
|
633
761
|
COMPLETION_FUNCTION,
|
|
762
|
+
COMPLETION_VARIABLE,
|
|
634
763
|
COMPLETION_KEYWORD,
|
|
635
764
|
}
|
|
636
765
|
|
|
@@ -643,4 +772,5 @@ export type {
|
|
|
643
772
|
Hover,
|
|
644
773
|
MarkupContent,
|
|
645
774
|
CompletionItem,
|
|
775
|
+
Location,
|
|
646
776
|
}
|
package/src/site.ts
CHANGED
|
@@ -10,6 +10,13 @@ type SiteSpec = {
|
|
|
10
10
|
row?: number, col?: number, url?: string, len?: number, src?: string,
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
// WHERE A NAME WAS USED, when the value arrived through one. A finding
|
|
14
|
+
// names where a value is written; this names where it entered the path
|
|
15
|
+
// the finding is about (ALIASES.0.md A-1).
|
|
16
|
+
type ViaSite = {
|
|
17
|
+
row: number, col: number, url: string, src: string, name: string,
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
// row, col and len -1, url and src empty, mean UNKNOWN: a value the
|
|
14
21
|
// engine minted rather than read has no site, and must not be edited
|
|
15
22
|
// as though it had one.
|
|
@@ -19,6 +26,7 @@ class Site {
|
|
|
19
26
|
url: string
|
|
20
27
|
len: number
|
|
21
28
|
src: string
|
|
29
|
+
via?: ViaSite
|
|
22
30
|
|
|
23
31
|
constructor(val?: Val | SiteSpec) {
|
|
24
32
|
const site = ((val as any)?.site ?? val) as SiteSpec
|
|
@@ -28,10 +36,16 @@ class Site {
|
|
|
28
36
|
this.url = site?.url ?? ''
|
|
29
37
|
this.len = site?.len ?? -1
|
|
30
38
|
this.src = site?.src ?? ''
|
|
39
|
+
this.via = (site as any)?.via
|
|
31
40
|
}
|
|
32
|
-
} /* node:coverage ignore next
|
|
41
|
+
} /* node:coverage ignore next 10 */
|
|
33
42
|
|
|
34
43
|
|
|
35
44
|
export {
|
|
36
45
|
Site,
|
|
37
46
|
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
export type {
|
|
50
|
+
ViaSite,
|
|
51
|
+
}
|
package/src/subsume.ts
CHANGED
|
@@ -347,8 +347,10 @@ export function subsumeNode(
|
|
|
347
347
|
'the general value is a map and the specific value is not')
|
|
348
348
|
return 'no'
|
|
349
349
|
}
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
// A DECLARATION IS NOT A FIELD: an alias key is erased before the
|
|
351
|
+
// document exists, so neither side compares one.
|
|
352
|
+
return subsumeBag(state, path, g, s, fieldKeys(g), fieldKeys(s),
|
|
353
|
+
(v: any, k: string) => v.peg[k])
|
|
352
354
|
}
|
|
353
355
|
|
|
354
356
|
// Lists: element-wise by position; the same required/optional shape
|
|
@@ -378,6 +380,11 @@ export function subsumeNode(
|
|
|
378
380
|
// must be required in the specific side and subsume; optional keys
|
|
379
381
|
// compare when present; closedness bounds the specific key set; spread
|
|
380
382
|
// templates govern the specific side's surplus.
|
|
383
|
+
function fieldKeys(v: any): string[] {
|
|
384
|
+
return Object.keys(v.peg).filter((k: string) => !v.aliasKeys.includes(k))
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
381
388
|
function subsumeBag(
|
|
382
389
|
state: SubState, path: string[], g: any, s: any,
|
|
383
390
|
gKeys: string[], sKeys: string[],
|
package/src/val/BagVal.ts
CHANGED
|
@@ -32,6 +32,10 @@ abstract class BagVal extends FeatureVal {
|
|
|
32
32
|
|
|
33
33
|
aliasKeys: string[] = []
|
|
34
34
|
|
|
35
|
+
// THE NAMES THIS FILE PUBLISHES, read at parse time by a destructure,
|
|
36
|
+
// so they do not travel through unify the way aliasKeys does.
|
|
37
|
+
exportKeys: string[] = []
|
|
38
|
+
|
|
35
39
|
spread = {
|
|
36
40
|
cj: (undefined as Val | undefined),
|
|
37
41
|
}
|
package/src/val/EmitFuncVal.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { cmpCodePoint } from '../keyorder'
|
|
|
17
17
|
import { top } from './top'
|
|
18
18
|
import { ListVal } from './ListVal'
|
|
19
19
|
import { StringVal } from './StringVal'
|
|
20
|
-
import { FuncBaseVal, trialUnify } from './FuncBaseVal'
|
|
20
|
+
import { FuncBaseVal, trialUnify, sameMembers } from './FuncBaseVal'
|
|
21
21
|
import { repathInstance } from './Val'
|
|
22
22
|
import type { EmitOrigin } from './Val'
|
|
23
23
|
import { boundArgStart, fillPlace, rebuild } from './PlaceVal'
|
|
@@ -464,7 +464,8 @@ class EmitFuncVal extends FuncBaseVal {
|
|
|
464
464
|
// The trial is against CLONES: `unite` refines a bag in place
|
|
465
465
|
// against a TOP peer, and a pattern that failed must be untouched
|
|
466
466
|
// for the next node.
|
|
467
|
-
|
|
467
|
+
const met = trialUnify(ctx, node.clone(ctx), tmpl.match.clone(ctx))
|
|
468
|
+
if (undefined !== met && sameMembers(node, met, ctx)) {
|
|
468
469
|
return tmpl
|
|
469
470
|
}
|
|
470
471
|
}
|
package/src/val/FilterFuncVal.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { makeNilErr } from '../err'
|
|
14
14
|
import { MapVal } from './MapVal'
|
|
15
15
|
import { ListVal } from './ListVal'
|
|
16
|
-
import { FuncBaseVal, trialUnify } from './FuncBaseVal'
|
|
16
|
+
import { FuncBaseVal, trialUnify, sameMembers } from './FuncBaseVal'
|
|
17
17
|
import { repathInstance } from './Val'
|
|
18
18
|
import { fillPlace } from './PlaceVal'
|
|
19
19
|
import { bagMembers } from './members'
|
|
@@ -65,7 +65,7 @@ class FilterFuncVal extends FuncBaseVal {
|
|
|
65
65
|
repathInstance(inst, inst.path)
|
|
66
66
|
const test = fillPlace(inst, child, kctx)
|
|
67
67
|
const met = trialUnify(kctx, child.clone(kctx), test)
|
|
68
|
-
return undefined !== met && met
|
|
68
|
+
return undefined !== met && sameMembers(child, met, kctx)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// The candidates are the bag's MEMBERS -- what generation would
|
package/src/val/FuncBaseVal.ts
CHANGED
|
@@ -31,6 +31,34 @@ import {
|
|
|
31
31
|
import { ConjunctVal } from '../val/ConjunctVal'
|
|
32
32
|
import { FeatureVal } from '../val/FeatureVal'
|
|
33
33
|
import { hasPlace, fillPlace } from '../val/PlaceVal'
|
|
34
|
+
import { bagMembers } from './members'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// Did the meet ADD a key or narrow a leaf, or only constrain? MEMBERS,
|
|
38
|
+
// not raw keys, so an unfilled optional is not something to add.
|
|
39
|
+
function sameKids(a: any, b: any, ctx: AontuContext): boolean {
|
|
40
|
+
if (true === a?.isMap || true === a?.isList) {
|
|
41
|
+
const am = bagMembers(a, ctx)
|
|
42
|
+
const bm = bagMembers(b, ctx)
|
|
43
|
+
if (null == am || null == bm || am.length !== bm.length) {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
const peers = new Map(bm.map((m) => [m.key, m.val]))
|
|
47
|
+
return am.every((m) =>
|
|
48
|
+
peers.has(m.key) && sameKids(m.val, peers.get(m.key), ctx))
|
|
49
|
+
}
|
|
50
|
+
return a?.canon === b?.canon
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
// A pref-free disjunction is several values at once: any match counts.
|
|
55
|
+
function sameMembers(a: any, b: any, ctx: AontuContext): boolean {
|
|
56
|
+
if (true === a?.isDisjunct && Array.isArray(a.peg) &&
|
|
57
|
+
!a.peg.some((m: any) => true === m?.isPref)) {
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
return sameKids(a, b, ctx)
|
|
61
|
+
}
|
|
34
62
|
|
|
35
63
|
|
|
36
64
|
function trialUnify(ctx: AontuContext, a: Val, b: Val): Val | undefined {
|
|
@@ -326,10 +354,11 @@ class FuncBaseVal extends FeatureVal {
|
|
|
326
354
|
}
|
|
327
355
|
|
|
328
356
|
|
|
329
|
-
} /* node:coverage ignore next
|
|
357
|
+
} /* node:coverage ignore next 7 */
|
|
330
358
|
|
|
331
359
|
|
|
332
360
|
export {
|
|
333
361
|
trialUnify,
|
|
362
|
+
sameMembers,
|
|
334
363
|
FuncBaseVal,
|
|
335
364
|
}
|
package/src/val/MapVal.ts
CHANGED
|
@@ -36,11 +36,12 @@ import { NilVal } from './NilVal'
|
|
|
36
36
|
import { BagVal } from './BagVal'
|
|
37
37
|
import { repathInstance, spreadId } from './Val'
|
|
38
38
|
import { cmpCodePoint } from '../keyorder'
|
|
39
|
+
import { aliasBareName, EXPORT_DECL_NAME } from '../aliasname'
|
|
39
40
|
import { markSpread } from '../provenance'
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
function spreadSnapKey(cj: any): string {
|
|
43
|
-
return cj.spelling + '~' + cj.site.row + ':' + cj.site.col
|
|
44
|
+
return cj.spelling + '~' + cj.site.url + '~' + cj.site.row + ':' + cj.site.col
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
function snapshotRefSpread(cj: any, ctx: AontuContext): Val | undefined {
|
|
@@ -115,13 +116,17 @@ class MapVal extends BagVal {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
|
|
119
|
+
// An `export` answers to the root rule a declaration does.
|
|
118
120
|
aliasDeclarationsAreRooted(ctx: AontuContext): Val | undefined {
|
|
119
|
-
|
|
121
|
+
const named = 0 < this.aliasKeys.length ?
|
|
122
|
+
aliasBareName(this.aliasKeys[0]) :
|
|
123
|
+
0 < this.exportKeys.length ? EXPORT_DECL_NAME : undefined
|
|
124
|
+
if (undefined === named || 0 === this.path.length) {
|
|
120
125
|
return undefined
|
|
121
126
|
}
|
|
122
127
|
const nv: any = new NilVal({ why: 'alias_not_toplevel' }, ctx)
|
|
123
128
|
nv.site = this.site
|
|
124
|
-
nv.path = [...this.path,
|
|
129
|
+
nv.path = [...this.path, named]
|
|
125
130
|
return nv
|
|
126
131
|
}
|
|
127
132
|
|
|
@@ -267,7 +272,8 @@ class MapVal extends BagVal {
|
|
|
267
272
|
for (let peerkey in upeer.peg) {
|
|
268
273
|
let peerchild = upeer.peg[peerkey]
|
|
269
274
|
|
|
270
|
-
if (this.closed && !allowedKeys.includes(peerkey)
|
|
275
|
+
if (this.closed && !allowedKeys.includes(peerkey) &&
|
|
276
|
+
!upeer.aliasKeys.includes(peerkey)) {
|
|
271
277
|
bad = makeNilErr(ctx, 'closed', peerchild, undefined)
|
|
272
278
|
}
|
|
273
279
|
|
package/src/val/MatchFuncVal.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { makeNilErr } from '../err'
|
|
14
14
|
import { top } from './top'
|
|
15
15
|
import { prefInnerPeg } from './PrefVal'
|
|
16
|
-
import { FuncBaseVal, trialUnify } from './FuncBaseVal'
|
|
16
|
+
import { FuncBaseVal, trialUnify, sameMembers } from './FuncBaseVal'
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
export function effectiveScrutinee(v: Val): Val {
|
|
@@ -94,7 +94,8 @@ class MatchFuncVal extends FuncBaseVal {
|
|
|
94
94
|
// The trial is against CLONES: `unite` refines a bag in place
|
|
95
95
|
// against a TOP peer, and a pattern that failed must be
|
|
96
96
|
// untouched for the next document that reads its canon.
|
|
97
|
-
|
|
97
|
+
const met = trialUnify(ctx, scrutinee.clone(ctx), pattern.clone(ctx))
|
|
98
|
+
if (undefined !== met && sameMembers(scrutinee, met, ctx)) {
|
|
98
99
|
return args[i + 1].clone(ctx)
|
|
99
100
|
}
|
|
100
101
|
}
|
package/src/val/NilVal.ts
CHANGED
|
@@ -128,6 +128,9 @@ class NilVal extends Val {
|
|
|
128
128
|
out.secondary = this.secondary
|
|
129
129
|
|
|
130
130
|
out.msg = this.msg
|
|
131
|
+
// The hint reads the details, so a clone that dropped them left
|
|
132
|
+
// `{name}` in the message. A struct copy carries them in Go.
|
|
133
|
+
out.details = this.details
|
|
131
134
|
return out
|
|
132
135
|
}
|
|
133
136
|
|
package/src/val/RecurseVal.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from '../ctx'
|
|
16
16
|
|
|
17
17
|
import { makeNilErr } from '../err'
|
|
18
|
+
import { aliasPathSegment } from '../aliasname'
|
|
18
19
|
|
|
19
20
|
import { FeatureVal } from './FeatureVal'
|
|
20
21
|
import { ConjunctVal } from './ConjunctVal'
|
|
@@ -80,7 +81,7 @@ class RecurseVal extends FeatureVal {
|
|
|
80
81
|
if (true === p.isMap || true === p.isList || true === p.isScalar) {
|
|
81
82
|
if (ctx.budget.depth <= this.xc) {
|
|
82
83
|
return makeNilErr(ctx, 'recursion_budget', this, peer, 'recurse',
|
|
83
|
-
{ target:
|
|
84
|
+
{ target: this.targetSpelling })
|
|
84
85
|
}
|
|
85
86
|
const body = this.body(ctx)
|
|
86
87
|
if (undefined === body) {
|
|
@@ -111,13 +112,17 @@ class RecurseVal extends FeatureVal {
|
|
|
111
112
|
return out
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
get targetSpelling(): string {
|
|
116
|
+
return '$.' + this.target.map(aliasPathSegment).join('.')
|
|
117
|
+
}
|
|
118
|
+
|
|
114
119
|
get canon(): string {
|
|
115
|
-
return
|
|
120
|
+
return this.targetSpelling
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
gen(ctx: AontuContext) {
|
|
119
124
|
makeNilErr(ctx, 'recursion_unexpanded', this, undefined, 'recurse',
|
|
120
|
-
{ target:
|
|
125
|
+
{ target: this.targetSpelling })
|
|
121
126
|
return undefined
|
|
122
127
|
}
|
|
123
128
|
}
|
package/src/val/RefVal.ts
CHANGED
|
@@ -80,9 +80,7 @@ function markedChild(v: any, part: any): Val | undefined {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
// ALIAS_RE, anchored at both ends, for the canon spelling above).
|
|
85
|
-
const ALIAS_NAME_RE = /^%[A-Za-z_][A-Za-z0-9_]*$/
|
|
83
|
+
import { ALIAS_NAME_RE, aliasBareName } from '../aliasname'
|
|
86
84
|
|
|
87
85
|
|
|
88
86
|
class RefVal extends FeatureVal {
|
|
@@ -223,6 +221,15 @@ class RefVal extends FeatureVal {
|
|
|
223
221
|
}
|
|
224
222
|
}
|
|
225
223
|
else {
|
|
224
|
+
// A NAME IS WHERE THE VALUE ENTERED THIS PATH; find() answers
|
|
225
|
+
// a clone, so the use travels with it.
|
|
226
|
+
const aname = this.aliasName
|
|
227
|
+
if (undefined !== aname) {
|
|
228
|
+
resolved.site.via = {
|
|
229
|
+
row: this.site.row, col: this.site.col,
|
|
230
|
+
url: this.site.url, src: this.site.src, name: aname,
|
|
231
|
+
}
|
|
232
|
+
}
|
|
226
233
|
out = unite(te ? ctx.clone({ explain: ec(te, 'RES') }) : ctx, resolved, peer, 'ref')
|
|
227
234
|
}
|
|
228
235
|
|
|
@@ -423,7 +430,11 @@ class RefVal extends FeatureVal {
|
|
|
423
430
|
if (undefined !== ctx.reads && null != node) {
|
|
424
431
|
// The root's own address is `$`, as the coverage walk spells
|
|
425
432
|
// it: a dot with nothing after it would match no path there.
|
|
426
|
-
|
|
433
|
+
// An alias is addressed by the NAME its source spells.
|
|
434
|
+
const aname = this.aliasName
|
|
435
|
+
const addr = undefined === aname ?
|
|
436
|
+
'$' + refpath.map((seg: string) => '.' + seg).join('') :
|
|
437
|
+
'$.' + aname
|
|
427
438
|
// AN ALIAS IS NOT A PATH. `%wire` names a value the document
|
|
428
439
|
// holds unevaluated and the tree never carries, so it is an
|
|
429
440
|
// address a rule can be reported AT and never a path coverage
|
|
@@ -500,7 +511,7 @@ class RefVal extends FeatureVal {
|
|
|
500
511
|
if (null == rp) {
|
|
501
512
|
return false
|
|
502
513
|
}
|
|
503
|
-
const key = rp.join('
|
|
514
|
+
const key = rp.join('\u0000')
|
|
504
515
|
if (ancestors.has(key)) {
|
|
505
516
|
return true
|
|
506
517
|
}
|
|
@@ -588,18 +599,26 @@ class RefVal extends FeatureVal {
|
|
|
588
599
|
}
|
|
589
600
|
|
|
590
601
|
|
|
591
|
-
// THE
|
|
592
|
-
//
|
|
593
|
-
//
|
|
594
|
-
//
|
|
595
|
-
//
|
|
596
|
-
get
|
|
602
|
+
// THE KEY THIS REFERENCE RESOLVES AGAINST, or undefined for a path
|
|
603
|
+
// reference. `%u` is spelled internally as the root reference `$.%u`
|
|
604
|
+
// (ALIASES.0.md), so an alias reference is an absolute reference of
|
|
605
|
+
// one segment that is a name -- carrying the url of the file the
|
|
606
|
+
// reference was WRITTEN in (ALIAS-FILE-SCOPE.0.md).
|
|
607
|
+
get aliasKey(): string | undefined {
|
|
597
608
|
return this.absolute && 1 === this.peg.length &&
|
|
598
|
-
'string' === typeof this.peg[0] &&
|
|
609
|
+
'string' === typeof this.peg[0] &&
|
|
610
|
+
ALIAS_NAME_RE.test(aliasBareName(this.peg[0])) ?
|
|
599
611
|
this.peg[0] : undefined
|
|
600
612
|
}
|
|
601
613
|
|
|
602
614
|
|
|
615
|
+
// What the source spells: the key without its scope.
|
|
616
|
+
get aliasName(): string | undefined {
|
|
617
|
+
const key = this.aliasKey
|
|
618
|
+
return undefined === key ? undefined : aliasBareName(key)
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
|
|
603
622
|
// THE REFERENCE'S OWN SPELLING: the alias name, or the path. This is
|
|
604
623
|
// the reference's identity (the snapshot key of a ref spread, the
|
|
605
624
|
// same-path test in unify), which `canon` below is not once an
|