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.
Files changed (74) hide show
  1. package/dist/alias.d.ts +12 -1
  2. package/dist/alias.js +176 -6
  3. package/dist/alias.js.map +1 -1
  4. package/dist/aliasname.d.ts +19 -0
  5. package/dist/aliasname.js +65 -0
  6. package/dist/aliasname.js.map +1 -0
  7. package/dist/aontu.d.ts +1 -1
  8. package/dist/aontu.js +21 -8
  9. package/dist/aontu.js.map +1 -1
  10. package/dist/aontumodel.js +1 -1
  11. package/dist/aontumodel.js.map +1 -1
  12. package/dist/ctx.d.ts +1 -0
  13. package/dist/ctx.js +1 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/err.js +18 -1
  16. package/dist/err.js.map +1 -1
  17. package/dist/format.js +14 -0
  18. package/dist/format.js.map +1 -1
  19. package/dist/hints.js +36 -7
  20. package/dist/hints.js.map +1 -1
  21. package/dist/lang.js +289 -35
  22. package/dist/lang.js.map +1 -1
  23. package/dist/lsp.d.ts +10 -4
  24. package/dist/lsp.js +116 -7
  25. package/dist/lsp.js.map +1 -1
  26. package/dist/site.d.ts +9 -0
  27. package/dist/site.js +2 -1
  28. package/dist/site.js.map +1 -1
  29. package/dist/subsume.js +6 -1
  30. package/dist/subsume.js.map +1 -1
  31. package/dist/val/BagVal.d.ts +1 -0
  32. package/dist/val/BagVal.js +3 -0
  33. package/dist/val/BagVal.js.map +1 -1
  34. package/dist/val/EmitFuncVal.js +2 -1
  35. package/dist/val/EmitFuncVal.js.map +1 -1
  36. package/dist/val/FilterFuncVal.js +1 -1
  37. package/dist/val/FilterFuncVal.js.map +1 -1
  38. package/dist/val/FuncBaseVal.d.ts +2 -1
  39. package/dist/val/FuncBaseVal.js +25 -1
  40. package/dist/val/FuncBaseVal.js.map +1 -1
  41. package/dist/val/MapVal.js +10 -4
  42. package/dist/val/MapVal.js.map +1 -1
  43. package/dist/val/MatchFuncVal.js +2 -1
  44. package/dist/val/MatchFuncVal.js.map +1 -1
  45. package/dist/val/NilVal.js +3 -0
  46. package/dist/val/NilVal.js.map +1 -1
  47. package/dist/val/RecurseVal.d.ts +1 -0
  48. package/dist/val/RecurseVal.js +7 -3
  49. package/dist/val/RecurseVal.js.map +1 -1
  50. package/dist/val/RefVal.d.ts +1 -0
  51. package/dist/val/RefVal.js +29 -12
  52. package/dist/val/RefVal.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/alias.ts +214 -6
  55. package/src/aliasname.ts +95 -0
  56. package/src/aontu.ts +23 -9
  57. package/src/aontumodel.ts +1 -1
  58. package/src/ctx.ts +4 -1
  59. package/src/err.ts +19 -1
  60. package/src/format.ts +14 -0
  61. package/src/hints.ts +40 -7
  62. package/src/lang.ts +334 -30
  63. package/src/lsp.ts +136 -6
  64. package/src/site.ts +15 -1
  65. package/src/subsume.ts +9 -2
  66. package/src/val/BagVal.ts +4 -0
  67. package/src/val/EmitFuncVal.ts +3 -2
  68. package/src/val/FilterFuncVal.ts +2 -2
  69. package/src/val/FuncBaseVal.ts +30 -1
  70. package/src/val/MapVal.ts +10 -4
  71. package/src/val/MatchFuncVal.ts +3 -2
  72. package/src/val/NilVal.ts +3 -0
  73. package/src/val/RecurseVal.ts +8 -3
  74. 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
- // Context-free completion: the built-in functions, scalar-kind keywords
445
- // and literals. Clients filter by the typed prefix.
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
- return [{ jsonrpc: '2.0', id: msg.id, result: computeCompletions() }]
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 28 */
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 6 */
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
- return subsumeBag(state, path, g, s, Object.keys(g.peg),
351
- Object.keys(s.peg), (v: any, k: string) => v.peg[k])
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
  }
@@ -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
- if (undefined !== trialUnify(ctx, node.clone(ctx), tmpl.match.clone(ctx))) {
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
  }
@@ -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.canon === child.canon
68
+ return undefined !== met && sameMembers(child, met, kctx)
69
69
  }
70
70
 
71
71
  // The candidates are the bag's MEMBERS -- what generation would
@@ -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 6 */
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
- if (0 === this.aliasKeys.length || 0 === this.path.length) {
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, this.aliasKeys[0]]
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
 
@@ -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
- if (undefined !== trialUnify(ctx, scrutinee.clone(ctx), pattern.clone(ctx))) {
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
 
@@ -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: '$.' + this.target.join('.') })
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 '$.' + this.target.join('.')
120
+ return this.targetSpelling
116
121
  }
117
122
 
118
123
  gen(ctx: AontuContext) {
119
124
  makeNilErr(ctx, 'recursion_unexpanded', this, undefined, 'recurse',
120
- { target: '$.' + this.target.join('.') })
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
- // An alias name, whole: the sigil and an identifier (the lexer's
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
- const addr = '$' + refpath.map((seg: string) => '.' + seg).join('')
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 NAME OF THE ALIAS THIS REFERENCE NAMES, or undefined for a
592
- // path reference. `%u` is spelled internally as the root reference
593
- // `$.%u` (docs/design/ALIASES.0.md: the name is a path into the
594
- // declaration), so an alias reference is an absolute reference of
595
- // one segment that is an alias name.
596
- get aliasName(): string | undefined {
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] && ALIAS_NAME_RE.test(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