aontu 0.48.2 → 0.48.3
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/lang.js +155 -10
- package/dist/lang.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/unify.js +1 -0
- package/dist/unify.js.map +1 -1
- package/dist/val/BagVal.js +20 -2
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/PlusOpVal.js +29 -14
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/RefVal.js +4 -1
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/VarVal.js +15 -1
- package/dist/val/VarVal.js.map +1 -1
- package/dist/val/top.js +6 -2
- package/dist/val/top.js.map +1 -1
- package/package.json +8 -7
- package/src/lang.ts +159 -10
- package/src/tsconfig.json +0 -1
- package/src/unify.ts +1 -0
- package/src/val/BagVal.ts +22 -2
- package/src/val/PlusOpVal.ts +31 -14
- package/src/val/RefVal.ts +4 -1
- package/src/val/VarVal.ts +19 -1
- package/src/val/top.ts +6 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aontu",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.3",
|
|
4
4
|
"main": "dist/aontu.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"types": "dist/aontu.d.ts",
|
|
@@ -47,12 +47,13 @@
|
|
|
47
47
|
"LICENSE"
|
|
48
48
|
],
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@tabnas/debug": "0.2.
|
|
51
|
-
"@tabnas/directive": "0.2
|
|
52
|
-
"@tabnas/expr": "0.2
|
|
53
|
-
"@tabnas/jsonic": "0.3
|
|
54
|
-
"@tabnas/multisource": "0.
|
|
55
|
-
"@tabnas/
|
|
50
|
+
"@tabnas/debug": "0.2.9",
|
|
51
|
+
"@tabnas/directive": "0.4.2",
|
|
52
|
+
"@tabnas/expr": "0.4.2",
|
|
53
|
+
"@tabnas/jsonic": "0.4.3",
|
|
54
|
+
"@tabnas/multisource": "0.4.4",
|
|
55
|
+
"@tabnas/parser": "0.6.0",
|
|
56
|
+
"@tabnas/path": "0.2.3"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@types/node": "26.1.1",
|
package/src/lang.ts
CHANGED
|
@@ -100,6 +100,20 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
|
|
|
100
100
|
|
|
101
101
|
jsonic.use(asPlugin(Path))
|
|
102
102
|
|
|
103
|
+
// Only # line comments are valid Aontu syntax (see
|
|
104
|
+
// docs/reference-language.md; go/lang.go sets the same). Clear the
|
|
105
|
+
// underlying jsonic comment markers entirely, then define # directly,
|
|
106
|
+
// so the comment set is hash-only regardless of those defaults.
|
|
107
|
+
jsonic.options({ comment: { def: null } })
|
|
108
|
+
jsonic.options({
|
|
109
|
+
comment: {
|
|
110
|
+
lex: true,
|
|
111
|
+
def: {
|
|
112
|
+
hash: { line: true, start: '#', lex: true },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
|
|
103
117
|
// TODO: refactor Val constructor
|
|
104
118
|
// let addsite = (v: Val, p: string[]) => (v.path = [...(p || [])], v)
|
|
105
119
|
let addsite = (v: Val, r: Rule, ctx: JsonicContext) => {
|
|
@@ -218,26 +232,42 @@ help isolate the syntax error.`,
|
|
|
218
232
|
}
|
|
219
233
|
|
|
220
234
|
|
|
235
|
+
// A dangling operator (`a:1|`, `a:$`, `a:*` at end of input) leaves
|
|
236
|
+
// null/undefined unfilled terms. Junction ops drop them (so `a:1&`
|
|
237
|
+
// and `a:1|` are just 1); ops missing a required operand become an
|
|
238
|
+
// incomplete_expression nil, surfaced by generate() as a "Cannot
|
|
239
|
+
// resolve value" error (the Go port mirrors this in lang.go).
|
|
240
|
+
const dropUnfilled = (terms: any) => terms.filter((t: any) => null != t)
|
|
241
|
+
|
|
242
|
+
const incompleteNil = (r: Rule, ctx: JsonicContext) =>
|
|
243
|
+
addsite(new NilVal({ why: 'incomplete_expression' }), r, ctx)
|
|
244
|
+
|
|
221
245
|
let opmap: any = {
|
|
222
246
|
'conjunct-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
|
|
223
|
-
addsite(new ConjunctVal({ peg: terms }), r, ctx),
|
|
247
|
+
addsite(new ConjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
|
|
224
248
|
|
|
225
249
|
'disjunct-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
|
|
226
|
-
addsite(new DisjunctVal({ peg: terms }), r, ctx),
|
|
250
|
+
addsite(new DisjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
|
|
227
251
|
|
|
228
252
|
'dot-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
253
|
+
terms = dropUnfilled(terms)
|
|
254
|
+
if (0 === terms.length) return incompleteNil(r, ctx)
|
|
229
255
|
return addsite(new RefVal({ peg: terms, prefix: true }), r, ctx)
|
|
230
256
|
},
|
|
231
257
|
|
|
232
258
|
'dot-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
233
|
-
|
|
259
|
+
terms = dropUnfilled(terms)
|
|
260
|
+
if (0 === terms.length) return incompleteNil(r, ctx)
|
|
234
261
|
return addsite(new RefVal({ peg: terms }), r, ctx)
|
|
235
262
|
},
|
|
236
263
|
|
|
237
|
-
'star-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
|
|
238
|
-
|
|
264
|
+
'star-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
265
|
+
if (null == terms[0]) return incompleteNil(r, ctx)
|
|
266
|
+
return addsite(new PrefVal({ peg: terms[0] }), r, ctx)
|
|
267
|
+
},
|
|
239
268
|
|
|
240
269
|
'dollar-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
270
|
+
if (null == terms[0]) return incompleteNil(r, ctx)
|
|
241
271
|
// $.a.b absolute path
|
|
242
272
|
if (terms[0] instanceof RefVal) {
|
|
243
273
|
terms[0].absolute = true
|
|
@@ -247,19 +277,34 @@ help isolate the syntax error.`,
|
|
|
247
277
|
},
|
|
248
278
|
|
|
249
279
|
'plus-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
280
|
+
if (null == terms[0] || null == terms[1]) return incompleteNil(r, ctx)
|
|
250
281
|
return addsite(new PlusOpVal({ peg: [terms[0], terms[1]] }), r, ctx)
|
|
251
282
|
},
|
|
252
283
|
|
|
253
284
|
'negative-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
254
285
|
let val = terms[0]
|
|
255
|
-
val
|
|
286
|
+
if (null == val) return incompleteNil(r, ctx)
|
|
287
|
+
// Negating a non-numeric operand (`k-x` splits into k, -x) is an
|
|
288
|
+
// error nil, not NaN (mirrors negate() in go/lang.go).
|
|
289
|
+
if (!(val instanceof IntegerVal) && !(val instanceof NumberVal)) {
|
|
290
|
+
return addsite(new NilVal({ why: 'negative' }), r, ctx)
|
|
291
|
+
}
|
|
292
|
+
// Build a fresh Val rather than mutating in place: the expr plugin
|
|
293
|
+
// can evaluate the same node twice (e.g. inside `*-1` or a
|
|
294
|
+
// disjunct member), and an in-place `peg = -peg` applied twice
|
|
295
|
+
// silently un-negates the number.
|
|
296
|
+
let peg = -1 * val.peg
|
|
256
297
|
// Normalize -0 to 0 (keeps the AST and canon free of negative zero).
|
|
257
|
-
if (0 ===
|
|
258
|
-
|
|
298
|
+
if (0 === peg) peg = 0
|
|
299
|
+
const out = val instanceof IntegerVal
|
|
300
|
+
? new IntegerVal({ peg })
|
|
301
|
+
: new NumberVal({ peg })
|
|
302
|
+
return addsite(out, r, ctx)
|
|
259
303
|
},
|
|
260
304
|
|
|
261
305
|
'positive-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
262
306
|
let val = terms[0]
|
|
307
|
+
if (null == val) return incompleteNil(r, ctx)
|
|
263
308
|
return addsite(val, r, ctx)
|
|
264
309
|
},
|
|
265
310
|
|
|
@@ -275,6 +320,8 @@ help isolate the syntax error.`,
|
|
|
275
320
|
peg: args
|
|
276
321
|
})
|
|
277
322
|
}
|
|
323
|
+
// `a:()` — grouping parens with nothing inside.
|
|
324
|
+
if (null == val) return incompleteNil(r, ctx)
|
|
278
325
|
const out = addsite(val, r, ctx)
|
|
279
326
|
return out
|
|
280
327
|
},
|
|
@@ -375,6 +422,24 @@ help isolate the syntax error.`,
|
|
|
375
422
|
const OPTKEY = [TX, ST, NR]
|
|
376
423
|
|
|
377
424
|
|
|
425
|
+
jsonic.rule('expr', (rs: RuleSpec) => {
|
|
426
|
+
rs.close([
|
|
427
|
+
// A `&` followed by `:` after an expression value belongs to the
|
|
428
|
+
// enclosing map as a spread, not to the expression as a conjunct
|
|
429
|
+
// — backtrack both tokens so the expression completes (and
|
|
430
|
+
// evaluates to a Val) and the map's spread alts take over. This
|
|
431
|
+
// is what makes `k1:$flag &:boolean` parse: without it the expr
|
|
432
|
+
// plugin consumes the `&` as an infix conjunct, chokes on the
|
|
433
|
+
// `:`, and leaves the raw unevaluated expr node in the map
|
|
434
|
+
// (mirrors the expr-rule PrependClose in go/lang.go). The
|
|
435
|
+
// `n: { expr: 0 }` reset matches the plugin's own expr-end alts —
|
|
436
|
+
// the evaluation after-close only fires when the counter is 0.
|
|
437
|
+
{ s: [CJ, CL], b: 2, n: { expr: 0 }, g: 'expr,expr-end,spread' },
|
|
438
|
+
])
|
|
439
|
+
return rs
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
|
|
378
443
|
jsonic.rule('val', (rs: RuleSpec) => {
|
|
379
444
|
|
|
380
445
|
rs
|
|
@@ -419,8 +484,13 @@ help isolate the syntax error.`,
|
|
|
419
484
|
valnode = addsite(new StringVal({ peg: r.node }), r, ctx)
|
|
420
485
|
}
|
|
421
486
|
else if ('number' === valtype) {
|
|
487
|
+
// An overflowing literal (1e999) lexes to Infinity; that is an
|
|
488
|
+
// error value, not a number (mirrors not_number in go/lang.go).
|
|
489
|
+
if (!Number.isFinite(r.node)) {
|
|
490
|
+
valnode = addsite(new NilVal({ why: 'not_number' }), r, ctx)
|
|
491
|
+
}
|
|
422
492
|
// 1.0 in source is *not* an integer
|
|
423
|
-
if (Number.isInteger(r.node) && !r.o0.src.includes('.')) {
|
|
493
|
+
else if (Number.isInteger(r.node) && !r.o0.src.includes('.')) {
|
|
424
494
|
valnode = addsite(new IntegerVal({ peg: r.node, src: r.o0.src }), r, ctx)
|
|
425
495
|
}
|
|
426
496
|
else {
|
|
@@ -466,6 +536,14 @@ help isolate the syntax error.`,
|
|
|
466
536
|
|
|
467
537
|
let mo = r.node
|
|
468
538
|
|
|
539
|
+
// An elided value (`a:`) leaves a raw null/undefined that never
|
|
540
|
+
// passed through the val rule; make it an explicit NullVal.
|
|
541
|
+
for (const k in mo) {
|
|
542
|
+
if (null == mo[k] && '___merge' !== k) {
|
|
543
|
+
mo[k] = addsite(new NullVal({ peg: null }), r, ctx)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
469
547
|
// Handle defered conjuncts, e.g. `{x:1 @"foo"}`
|
|
470
548
|
if (mo.___merge) {
|
|
471
549
|
let mop = { ...mo }
|
|
@@ -502,6 +580,14 @@ help isolate the syntax error.`,
|
|
|
502
580
|
|
|
503
581
|
let ao = r.node
|
|
504
582
|
|
|
583
|
+
// An elided element (`[,]`) is a raw null that never passed
|
|
584
|
+
// through the val rule; make it an explicit NullVal.
|
|
585
|
+
for (let i = 0; i < ao.length; i++) {
|
|
586
|
+
if (null == ao[i]) {
|
|
587
|
+
ao[i] = addsite(new NullVal({ peg: null }), r, ctx)
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
505
591
|
if (ao.___merge) {
|
|
506
592
|
let aop = [...ao]
|
|
507
593
|
delete (aop as any).___merge
|
|
@@ -593,7 +679,12 @@ help isolate the syntax error.`,
|
|
|
593
679
|
})
|
|
594
680
|
|
|
595
681
|
.close([
|
|
596
|
-
|
|
682
|
+
// A following `&:` starts a sibling spread pair in the current
|
|
683
|
+
// map: directly inside a braced map (pk<=0) at any depth, or in
|
|
684
|
+
// the implicit top-level map (dmap<=1). Inside an implicit
|
|
685
|
+
// colon-chain map (pk>0) it bubbles up instead (second alt), so
|
|
686
|
+
// `a:b:1 &:2` attaches the spread to a's map, not b's.
|
|
687
|
+
{ s: [CJ, CL], c: (r) => r.lte('pk', 0) || r.lte('dmap', 1), r: 'pair', b: 2, g: 'spread,json,pair' },
|
|
597
688
|
{ s: [CJ, CL], b: 2, g: 'spread,json,more' }
|
|
598
689
|
])
|
|
599
690
|
|
|
@@ -689,6 +780,13 @@ function makeModelResolver(options: any) {
|
|
|
689
780
|
) {
|
|
690
781
|
|
|
691
782
|
let path = 'string' === typeof spec ? spec : spec?.peg
|
|
783
|
+
|
|
784
|
+
// A bare `@` with no path (`a:@`) has nothing to resolve; report
|
|
785
|
+
// not-found instead of crashing in the underlying resolvers.
|
|
786
|
+
if (null == path || '' === path) {
|
|
787
|
+
return { found: false, path: '' + (path ?? ''), search: [] }
|
|
788
|
+
}
|
|
789
|
+
|
|
692
790
|
let search: any = []
|
|
693
791
|
let res = memResolver(path, popts, rule, ctx, jsonic)
|
|
694
792
|
res.path = path
|
|
@@ -718,6 +816,49 @@ function makeModelResolver(options: any) {
|
|
|
718
816
|
}
|
|
719
817
|
|
|
720
818
|
|
|
819
|
+
// rawToVal converts a raw parse node (or raw elements inside one) into
|
|
820
|
+
// the matching Val. Used for implicit top-level lists, whose nodes skip
|
|
821
|
+
// the aontu val rule conversions (mirrors asVal in go/lang.go; like
|
|
822
|
+
// there, source text is unavailable, so an integral number is an
|
|
823
|
+
// integer).
|
|
824
|
+
function rawToVal(n: any): Val {
|
|
825
|
+
if (null == n) {
|
|
826
|
+
return new NullVal({ peg: null })
|
|
827
|
+
}
|
|
828
|
+
if (true === n.isVal) {
|
|
829
|
+
return n
|
|
830
|
+
}
|
|
831
|
+
if (Array.isArray(n)) {
|
|
832
|
+
return new ListVal({ peg: n.map(rawToVal) })
|
|
833
|
+
}
|
|
834
|
+
const t = typeof n
|
|
835
|
+
if ('string' === t) {
|
|
836
|
+
return new StringVal({ peg: n })
|
|
837
|
+
}
|
|
838
|
+
if ('number' === t) {
|
|
839
|
+
return Number.isInteger(n) ?
|
|
840
|
+
new IntegerVal({ peg: n }) : new NumberVal({ peg: n })
|
|
841
|
+
}
|
|
842
|
+
if ('boolean' === t) {
|
|
843
|
+
return new BooleanVal({ peg: n })
|
|
844
|
+
}
|
|
845
|
+
if ('object' === t) {
|
|
846
|
+
// An expr-plugin internal (operator descriptor) leaking through a
|
|
847
|
+
// degenerate parse (`k2.b K:1`) is not data — reject it rather
|
|
848
|
+
// than emitting raw internals in generated output.
|
|
849
|
+
if (undefined !== (n as any).OP_MARK) {
|
|
850
|
+
return new NilVal({ why: 'parse_unknown' })
|
|
851
|
+
}
|
|
852
|
+
const peg: Record<string, Val> = {}
|
|
853
|
+
for (const k in n) {
|
|
854
|
+
peg[k] = rawToVal(n[k])
|
|
855
|
+
}
|
|
856
|
+
return new MapVal({ peg })
|
|
857
|
+
}
|
|
858
|
+
return new NilVal({ why: 'parse_unknown' })
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
|
|
721
862
|
class Lang {
|
|
722
863
|
jsonic: Jsonic
|
|
723
864
|
opts: AontuOptions
|
|
@@ -784,6 +925,14 @@ class Lang {
|
|
|
784
925
|
|
|
785
926
|
try {
|
|
786
927
|
val = this.jsonic(src, jm)
|
|
928
|
+
|
|
929
|
+
// An implicit top-level list (`a b`, `1,2`) is built by the core
|
|
930
|
+
// jsonic grammar without passing through the aontu val/list rules,
|
|
931
|
+
// so the root (and its elements) arrive as raw JS values. Convert
|
|
932
|
+
// them the same way the Go port's asVal post-walk does.
|
|
933
|
+
if (null != val && true !== (val as any).isVal) {
|
|
934
|
+
val = rawToVal(val)
|
|
935
|
+
}
|
|
787
936
|
}
|
|
788
937
|
catch (e: any) {
|
|
789
938
|
if (e instanceof JsonicError || 'JsonicError' === e.constructor.name) {
|
package/src/tsconfig.json
CHANGED
package/src/unify.ts
CHANGED
package/src/val/BagVal.ts
CHANGED
|
@@ -86,6 +86,18 @@ abstract class BagVal extends FeatureVal {
|
|
|
86
86
|
|
|
87
87
|
const optional = this.optionalKeys.includes('' + p)
|
|
88
88
|
|
|
89
|
+
// Lists append compactly: a skipped element (hidden, dropped
|
|
90
|
+
// optional) must not leave a hole/null at its index (matches the
|
|
91
|
+
// Go port, which also drops skipped elements).
|
|
92
|
+
const put = (v: any) => {
|
|
93
|
+
if (this.isMap) {
|
|
94
|
+
out[p] = v
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
out.push(v)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
// Optional unresolved disjuncts are not an error, just dropped.
|
|
90
102
|
if (child.isDisjunct && optional) {
|
|
91
103
|
const dctx = ctx.clone({ err: [], collect: true })
|
|
@@ -96,7 +108,7 @@ abstract class BagVal extends FeatureVal {
|
|
|
96
108
|
continue
|
|
97
109
|
}
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
put(cval)
|
|
100
112
|
}
|
|
101
113
|
|
|
102
114
|
else if (child.isScalar
|
|
@@ -122,7 +134,15 @@ abstract class BagVal extends FeatureVal {
|
|
|
122
134
|
continue
|
|
123
135
|
}
|
|
124
136
|
|
|
125
|
-
|
|
137
|
+
// A child that generates nothing contributes nothing: setting
|
|
138
|
+
// `undefined` would leave husk entries like {"q k": undefined}
|
|
139
|
+
// (the Go port also drops such children). Any real failure has
|
|
140
|
+
// already been recorded on ctx and raises below.
|
|
141
|
+
if (undefined === cval) {
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
put(cval)
|
|
126
146
|
}
|
|
127
147
|
else if (child.isNil) {
|
|
128
148
|
ctx.adderr(child)
|
package/src/val/PlusOpVal.ts
CHANGED
|
@@ -41,25 +41,42 @@ class PlusOpVal extends OpBaseVal {
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
operate(_ctx: AontuContext, args: Val[]) {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// Only concrete scalar operands are valid: anything else (kinds,
|
|
45
|
+
// maps, lists, null, top, funcs) must not coerce — the JS `+` would
|
|
46
|
+
// leak internals like "[object Object]" into output. A non-scalar
|
|
47
|
+
// operand leaves the op unresolved, which generate() reports.
|
|
48
|
+
const prim = (v: any) => {
|
|
49
|
+
// A pref operand contributes its preferred value (`pref(1)+2`).
|
|
50
|
+
while (v?.isPref) {
|
|
51
|
+
v = v.peg
|
|
52
|
+
}
|
|
53
|
+
const p = v?.isVal && v.isScalar ? v.peg : undefined
|
|
54
|
+
const t = typeof p
|
|
55
|
+
return 'string' === t || 'number' === t || 'boolean' === t ? p : undefined
|
|
56
|
+
}
|
|
57
|
+
let a: any = prim(args[0])
|
|
58
|
+
let b: any = prim(args[1])
|
|
59
|
+
|
|
60
|
+
if (undefined === a || undefined === b) {
|
|
61
|
+
return undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const at = typeof a
|
|
65
|
+
const bt = typeof b
|
|
46
66
|
|
|
47
67
|
let peg = undefined
|
|
48
|
-
if (
|
|
49
|
-
peg = b
|
|
68
|
+
if ('boolean' === at && 'boolean' === bt) {
|
|
69
|
+
peg = a || b
|
|
70
|
+
}
|
|
71
|
+
else if ('string' === at || 'string' === bt) {
|
|
72
|
+
peg = String(a) + String(b)
|
|
50
73
|
}
|
|
51
|
-
else if (
|
|
52
|
-
|
|
74
|
+
else if ('boolean' === at || 'boolean' === bt) {
|
|
75
|
+
// boolean mixed with a number does not coerce (no JS 0/1).
|
|
76
|
+
return undefined
|
|
53
77
|
}
|
|
54
78
|
else {
|
|
55
|
-
|
|
56
|
-
const bt = typeof b
|
|
57
|
-
if ('boolean' === at && 'boolean' === bt) {
|
|
58
|
-
peg = a || b
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
peg = a + b
|
|
62
|
-
}
|
|
79
|
+
peg = a + b
|
|
63
80
|
}
|
|
64
81
|
|
|
65
82
|
let pegtype = typeof peg
|
package/src/val/RefVal.ts
CHANGED
|
@@ -269,7 +269,10 @@ class RefVal extends FeatureVal {
|
|
|
269
269
|
return
|
|
270
270
|
}
|
|
271
271
|
else {
|
|
272
|
-
|
|
272
|
+
// The resolved variable IS a path segment: $seg.r with
|
|
273
|
+
// seg="x" reads ...x.r (previously the coerced value was
|
|
274
|
+
// dropped, silently reading the path without it).
|
|
275
|
+
parts.push('' + part.peg)
|
|
273
276
|
}
|
|
274
277
|
}
|
|
275
278
|
}
|
package/src/val/VarVal.ts
CHANGED
|
@@ -38,6 +38,10 @@ import {
|
|
|
38
38
|
explainClose,
|
|
39
39
|
} from '../utility'
|
|
40
40
|
|
|
41
|
+
import { unite } from '../unify'
|
|
42
|
+
|
|
43
|
+
import { top } from './top'
|
|
44
|
+
|
|
41
45
|
|
|
42
46
|
// TODO: KEY, SELF, PARENT are reserved names - error
|
|
43
47
|
|
|
@@ -67,7 +71,12 @@ class VarVal extends FeatureVal {
|
|
|
67
71
|
nameVal = this.peg
|
|
68
72
|
}
|
|
69
73
|
else {
|
|
70
|
-
|
|
74
|
+
// Resolve the NAME against TOP only — the peer applies to the
|
|
75
|
+
// variable's resolved VALUE below, not to its name (so
|
|
76
|
+
// `k1:$flag &:boolean` checks the boolean value of $flag
|
|
77
|
+
// against the spread, instead of unifying the string "flag"
|
|
78
|
+
// with it).
|
|
79
|
+
nameVal = this.peg.unify(top(), te ? ctx.clone({ explain: ec(te, 'PEG') }) : ctx)
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
82
|
else {
|
|
@@ -107,6 +116,15 @@ class VarVal extends FeatureVal {
|
|
|
107
116
|
else {
|
|
108
117
|
out = makeNilErr(ctx, 'invalid_var_kind', this, peer)
|
|
109
118
|
}
|
|
119
|
+
|
|
120
|
+
// A non-TOP peer (e.g. a spread constraint unified against the
|
|
121
|
+
// var) applies to the RESOLVED value rather than being silently
|
|
122
|
+
// dropped (mirrors VarVal.Unify in go/varval — the resolved
|
|
123
|
+
// value unites with the peer).
|
|
124
|
+
if (!out.isNil && null != peer && !peer.isTop) {
|
|
125
|
+
out = unite(te ? ctx.clone({ explain: ec(te, 'VAL') }) : ctx,
|
|
126
|
+
out, peer, 'var-val')
|
|
127
|
+
}
|
|
110
128
|
}
|
|
111
129
|
else {
|
|
112
130
|
out = makeNilErr(ctx, 'var[' + typeof nameVal + ']', this, peer)
|
package/src/val/top.ts
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
import { TopVal } from './TopVal'
|
|
5
5
|
|
|
6
|
-
const TOP = new TopVal({})
|
|
7
|
-
|
|
8
6
|
|
|
7
|
+
// A fresh instance per call: unify mutates Vals in place (paths, marks,
|
|
8
|
+
// site via addsite for a literal `top` in source), so a shared TOP
|
|
9
|
+
// singleton is silently corrupted by one parse and poisons every later
|
|
10
|
+
// parse in the same process (e.g. a hide() walk marking the shared TOP
|
|
11
|
+
// hid every subsequent unresolved top).
|
|
9
12
|
export function top(): TopVal {
|
|
10
|
-
return
|
|
13
|
+
return new TopVal({})
|
|
11
14
|
}
|
|
12
15
|
|