aontu 0.52.0 → 0.52.1

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 (52) hide show
  1. package/dist/aontu.d.ts +1 -1
  2. package/dist/aontu.js +50 -1
  3. package/dist/aontu.js.map +1 -1
  4. package/dist/ctx.d.ts +3 -0
  5. package/dist/ctx.js +1 -0
  6. package/dist/ctx.js.map +1 -1
  7. package/dist/err.js +10 -1
  8. package/dist/err.js.map +1 -1
  9. package/dist/hints.js +56 -1
  10. package/dist/hints.js.map +1 -1
  11. package/dist/lang.js +235 -15
  12. package/dist/lang.js.map +1 -1
  13. package/dist/lsp.js +4 -3
  14. package/dist/lsp.js.map +1 -1
  15. package/dist/unify.d.ts +2 -1
  16. package/dist/unify.js +55 -3
  17. package/dist/unify.js.map +1 -1
  18. package/dist/val/ConstraintVal.d.ts +42 -1
  19. package/dist/val/ConstraintVal.js +1048 -107
  20. package/dist/val/ConstraintVal.js.map +1 -1
  21. package/dist/val/ExpectVal.d.ts +1 -0
  22. package/dist/val/ExpectVal.js +14 -0
  23. package/dist/val/ExpectVal.js.map +1 -1
  24. package/dist/val/FuncBaseVal.js +7 -1
  25. package/dist/val/FuncBaseVal.js.map +1 -1
  26. package/dist/val/ListVal.js +17 -4
  27. package/dist/val/ListVal.js.map +1 -1
  28. package/dist/val/MapVal.js +9 -0
  29. package/dist/val/MapVal.js.map +1 -1
  30. package/dist/val/OpBaseVal.js +5 -1
  31. package/dist/val/OpBaseVal.js.map +1 -1
  32. package/dist/val/RefVal.js +69 -23
  33. package/dist/val/RefVal.js.map +1 -1
  34. package/dist/val/Val.d.ts +1 -1
  35. package/dist/val/Val.js +0 -3
  36. package/dist/val/Val.js.map +1 -1
  37. package/package.json +10 -10
  38. package/src/aontu.ts +58 -1
  39. package/src/ctx.ts +9 -0
  40. package/src/err.ts +10 -1
  41. package/src/hints.ts +63 -1
  42. package/src/lang.ts +258 -16
  43. package/src/lsp.ts +4 -3
  44. package/src/unify.ts +59 -2
  45. package/src/val/ConstraintVal.ts +1195 -111
  46. package/src/val/ExpectVal.ts +15 -0
  47. package/src/val/FuncBaseVal.ts +8 -2
  48. package/src/val/ListVal.ts +18 -4
  49. package/src/val/MapVal.ts +11 -0
  50. package/src/val/OpBaseVal.ts +6 -2
  51. package/src/val/RefVal.ts +72 -22
  52. package/src/val/Val.ts +8 -1
@@ -35,6 +35,21 @@ class ExpectVal extends FeatureVal {
35
35
  parent?: Val
36
36
  key?: string
37
37
 
38
+ // An expectation canons as THE EXPECTATION ITSELF -- the peg the peer
39
+ // must satisfy. Val.canon's default was the empty string, which
40
+ // rendered a key with no value (`{"r":}`): text that is not a document
41
+ // and could not be reparsed, breaking canon's round-trip contract in
42
+ // both engines (issue #43).
43
+ //
44
+ // Not `top`, which was the first fix here and was wrong. An ExpectVal
45
+ // is created for EVERY peer-introduced non-generable key, not just for
46
+ // `&:` spread children -- `m:{x:1} m:{y:string}` makes one at `y` with
47
+ // no spread in sight -- so rendering `top` erased the `string` and the
48
+ // canon reparsed into a document that accepts values the original
49
+ // rejects. A canon that silently drops a constraint is worse than one
50
+ // that fails to parse. Go's ExpectVal.Canon renders the same peg.
51
+ get canon() { return this.peg.canon }
52
+
38
53
  constructor(
39
54
  spec: ValSpec,
40
55
  ctx?: AontuContext
@@ -11,7 +11,7 @@ import {
11
11
  } from '../type'
12
12
 
13
13
  import { AontuContext } from '../ctx'
14
- import { unite } from '../unify'
14
+ import { unite, withDepth } from '../unify'
15
15
 
16
16
 
17
17
  import {
@@ -100,7 +100,13 @@ class FuncBaseVal extends FeatureVal {
100
100
 
101
101
  let newarg = arg
102
102
  if (!arg.done) {
103
- newarg = arg.unify(TOP, te ? ctx.clone({ explain: ec(te, 'ARG') }) : ctx)
103
+ // Charged to the depth budget: this recurses without going
104
+ // through `unite`, so the counter would otherwise stay flat
105
+ // while the stack grows (see withDepth in unify.ts). The
106
+ // arg context is built OUTSIDE the closure so its explain
107
+ // ternary stays one branch rather than one per call.
108
+ const argctx = te ? ctx.clone({ explain: ec(te, 'ARG') }) : ctx
109
+ newarg = withDepth(ctx, arg, TOP, () => arg.unify(TOP, argctx))
104
110
  newtype = newtype || newarg.mark.type
105
111
  newhide = newhide || newarg.mark.hide
106
112
  // console.log('FUNCBASE-UNIFY-PEG-B', arg.canon, arg.done, '->', newarg.canon, newarg.done)
@@ -76,6 +76,16 @@ class ListVal extends BagVal {
76
76
  const TOP = top()
77
77
  peer = peer ?? TOP
78
78
 
79
+ // A sizing residual (`length`, `unique`) sorts AFTER containers in a
80
+ // conjunct so that it counts the MERGED list rather than the first
81
+ // fragment (SIZING_CJO in ConstraintVal.ts). That makes the list the
82
+ // accumulator and the constraint its peer, the reverse of the usual
83
+ // order — and the reading belongs to the constraint either way, so
84
+ // hand it straight back.
85
+ if (true === (peer as any).isConstraint) {
86
+ return peer.unify(this, ctx)
87
+ }
88
+
79
89
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'List', this, peer)
80
90
  let done: boolean = true
81
91
  let exit = false
@@ -263,10 +273,14 @@ class ListVal extends BagVal {
263
273
  '[' +
264
274
  (this.spread.cj ? '&:' + this.spread.cj.canon +
265
275
  (0 < keys.length ? ',' : '') : '') +
266
- keys
267
- .map(k => this.optionalKeys.includes(k) ?
268
- k + '?:' + this.peg[k].canon :
269
- this.peg[k].canon).join(',') +
276
+ // No optional-element rendering. A list HAS no optional elements to
277
+ // render: a key:value pair in list position contributes no element
278
+ // at all, in either spelling and whatever its key (issue #40), so
279
+ // nothing a source can write reaches this method with an optional
280
+ // key. The Go port's ListVal.Canon has no such arm either, and the
281
+ // two canons must agree -- a canon is round-trippable, and a marker
282
+ // on an element the grammar cannot produce would not reparse.
283
+ keys.map(k => this.peg[k].canon).join(',') +
270
284
  ']'
271
285
  }
272
286
  } /* node:coverage ignore next 8 */
package/src/val/MapVal.ts CHANGED
@@ -125,6 +125,17 @@ class MapVal extends BagVal {
125
125
 
126
126
  const TOP = top()
127
127
  peer = peer ?? TOP
128
+
129
+ // A sizing residual (`length`, `unique`) sorts AFTER containers in a
130
+ // conjunct so that it counts the MERGED map rather than the first
131
+ // fragment (SIZING_CJO in ConstraintVal.ts). That makes the map the
132
+ // accumulator and the constraint its peer, the reverse of the usual
133
+ // order — and the reading belongs to the constraint either way, so
134
+ // hand it straight back.
135
+ if (true === (peer as any).isConstraint) {
136
+ return peer.unify(this, ctx)
137
+ }
138
+
128
139
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Map', this, peer)
129
140
 
130
141
  let done: boolean = true
@@ -17,7 +17,7 @@ import {
17
17
  } from '../err'
18
18
 
19
19
  import { AontuContext } from '../ctx'
20
- import { unite } from '../unify'
20
+ import { unite, withDepth } from '../unify'
21
21
 
22
22
  import {
23
23
  explainOpen,
@@ -83,7 +83,11 @@ class OpBaseVal extends FeatureVal {
83
83
 
84
84
  for (let arg of this.peg) {
85
85
  if (!arg.done) {
86
- arg = arg.unify(top(), ctx, ec(te, 'ARG'))
86
+ // Charged to the depth budget: this recurses without going
87
+ // through `unite`, so the counter would otherwise stay flat
88
+ // while the stack grows (see withDepth in unify.ts).
89
+ const a = arg
90
+ arg = withDepth(ctx, a, top(), () => a.unify(top(), ctx, ec(te, 'ARG')))
87
91
  }
88
92
  pegdone &&= arg.done
89
93
  newpeg.push(arg)
package/src/val/RefVal.ts CHANGED
@@ -413,12 +413,15 @@ class RefVal extends FeatureVal {
413
413
  else if (pI === refpath.length) {
414
414
  out = node
415
415
 
416
- // A reference landing on another reference may be a PROVEN
417
- // mutual cycle (a: $.b, b: $.a) -- follow the plain-ref chain
418
- // and, if it revisits a node, report path_cycle now instead of
419
- // deferring every pass and dying later as a generic ref error.
420
- // No proof (chain leaves plain refs, or ends) defers as before.
421
- if (null != out && (out as any).isRef && this.detectRefCycle(ctx)) {
416
+ // A reference landing on another reference -- or on a FUNCTION,
417
+ // whose arguments the chase now follows (issue #35) -- may be a
418
+ // PROVEN mutual cycle (a: $.b, b: $.a; a: $.b, b: upper($.a)).
419
+ // Follow the chain and, if it returns to a node still open above
420
+ // it, report path_cycle now instead of deferring every pass and
421
+ // dying later as a spent budget. No proof (the chain leaves plain
422
+ // refs and calls, or ends) defers as before.
423
+ if (null != out && ((out as any).isRef || (out as any).isFunc) &&
424
+ this.detectRefCycle(ctx)) {
422
425
  out = makeNilErr(ctx, 'path_cycle', this)
423
426
  }
424
427
  // Types and hidden values are cloned and made concrete
@@ -468,33 +471,80 @@ class RefVal extends FeatureVal {
468
471
  // chain revisits a node -- a PROVEN reference cycle, distinct from a
469
472
  // merely unresolved reference. Detection is only on the resolution
470
473
  // chain revisiting a node, never on syntactic shape: a chain that
471
- // passes through a variable segment, a conjunct, a function, or any
472
- // non-ref value yields no proof and the ref defers as before.
474
+ // passes through a variable segment, a conjunct or any other non-ref
475
+ // value yields no proof and the ref defers as before.
476
+ //
477
+ // A FUNCTION is followed, through its arguments (issue #35). A
478
+ // function resolves only once every argument does, so a chain that
479
+ // reaches `b:upper($.a)` and out through `$.a` has proved the same
480
+ // dependency a bare `b:$.a` proves -- `a:$.b b:upper($.a)` is a cycle
481
+ // whichever link wears the call. Without this the shape exhausted the
482
+ // depth budget instead: a `unify_cycle`, which under the G5 taxonomy
483
+ // means "retry with more may help", where a proven structural cycle is
484
+ // FIX THE MODEL. A conjunct and a disjunct stay unfollowed for reasons
485
+ // that are not the same: a disjunct member may simply not be taken, so
486
+ // reaching one proves nothing; a conjunct would be sound to follow, and
487
+ // is left out only because nothing needs it yet.
488
+ //
489
+ // The Go port reaches the same verdict on this shape by a DIFFERENT
490
+ // arm, and that difference outlives this method: its clonePath re-paths
491
+ // a resolved clone to the referring site, so the inner `$.a` lands at
492
+ // path [a] and the plain isprefixpath test proves the cycle before any
493
+ // chase is needed. TypeScript's clone keeps the source paths. ADR-001
494
+ // asks for arm-for-arm correspondence, so the clone-path difference is
495
+ // worth closing on its own; it is wider than this issue and both ports
496
+ // now agree on the verdict and the code either way.
473
497
  detectRefCycle(ctx: AontuContext): boolean {
474
- const seen = new Set<number>()
475
- let cur: RefVal = this
476
- // No hop cap: every iteration either returns or adds a NEW ref to
477
- // `seen`, and the tree holds finitely many refs, so the chase
478
- // terminates at the first repetition or the first non-ref — a cap
479
- // would just make cycles longer than it invisible.
480
- for (; ;) {
481
- if (seen.has(cur.id)) {
482
- return true
483
- }
484
- seen.add(cur.id)
485
- const rp = cur.plainRefPath()
498
+ // Depth-first with an explicit ANCESTOR set, because a function may
499
+ // carry several reference arguments and the cycle can run through
500
+ // any one of them. The set holds the chain currently being walked,
501
+ // not every node ever walked: revisiting a node reached down a
502
+ // DIFFERENT branch is an ordinary shared reference (two keys reading
503
+ // one third key), and only revisiting one that is still open above
504
+ // us is a cycle.
505
+ //
506
+ // Identity is the RESOLVED PATH, not the RefVal instance: the same
507
+ // target can be reached through distinct ref instances, and it is
508
+ // returning to the same place that closes a loop.
509
+ const chase = (ref: RefVal, ancestors: Set<string>): boolean => {
510
+ const rp = ref.plainRefPath()
486
511
  if (null == rp) {
487
512
  return false
488
513
  }
514
+ const key = rp.join('')
515
+ if (ancestors.has(key)) {
516
+ return true
517
+ }
518
+
489
519
  let node: any = ctx.root
490
520
  for (let i = 0; i < rp.length && null != node; i++) {
491
521
  node = (node.isMap || node.isList) ? node.peg[rp[i]] : undefined
492
522
  }
493
- if (null == node || !node.isRef) {
523
+ if (null == node) {
494
524
  return false
495
525
  }
496
- cur = node
526
+
527
+ // Terminates: each level adds a path to `ancestors` and refuses a
528
+ // repeat, and the tree holds finitely many distinct paths.
529
+ ancestors.add(key)
530
+ let found = false
531
+ if (node.isRef) {
532
+ found = chase(node, ancestors)
533
+ }
534
+ else if (node.isFunc && Array.isArray(node.peg)) {
535
+ for (const arg of node.peg) {
536
+ if (null != arg && arg.isRef && chase(arg, ancestors)) {
537
+ found = true
538
+ break
539
+ }
540
+ }
541
+ }
542
+ ancestors.delete(key)
543
+
544
+ return found
497
545
  }
546
+
547
+ return chase(this, new Set<string>())
498
548
  }
499
549
 
500
550
 
package/src/val/Val.ts CHANGED
@@ -297,7 +297,14 @@ abstract class Val {
297
297
 
298
298
  // TODO: indicate marks in some way that is ignored by reparse.
299
299
  // Need an annotation/taggins syntax? a:{}/type ?
300
- get canon(): string { return '' }
300
+ // ABSTRACT: every concrete Val renders its own canonical form. There
301
+ // used to be an empty-string default here, and the one class relying
302
+ // on it (ExpectVal) was rendering a key with no value -- `{"r":}`,
303
+ // text that is not a document (issue #43). With that fixed, nothing
304
+ // reached the default, so it is declared rather than defaulted: a new
305
+ // Val that forgets `canon` is now a compile error instead of silently
306
+ // canoning as nothing.
307
+ abstract get canon(): string
301
308
 
302
309
 
303
310
  errcanon(): string {