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.
- package/dist/aontu.d.ts +1 -1
- package/dist/aontu.js +50 -1
- package/dist/aontu.js.map +1 -1
- package/dist/ctx.d.ts +3 -0
- package/dist/ctx.js +1 -0
- package/dist/ctx.js.map +1 -1
- package/dist/err.js +10 -1
- package/dist/err.js.map +1 -1
- package/dist/hints.js +56 -1
- package/dist/hints.js.map +1 -1
- package/dist/lang.js +235 -15
- package/dist/lang.js.map +1 -1
- package/dist/lsp.js +4 -3
- package/dist/lsp.js.map +1 -1
- package/dist/unify.d.ts +2 -1
- package/dist/unify.js +55 -3
- package/dist/unify.js.map +1 -1
- package/dist/val/ConstraintVal.d.ts +42 -1
- package/dist/val/ConstraintVal.js +1048 -107
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/ExpectVal.d.ts +1 -0
- package/dist/val/ExpectVal.js +14 -0
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FuncBaseVal.js +7 -1
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/ListVal.js +17 -4
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/MapVal.js +9 -0
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/OpBaseVal.js +5 -1
- package/dist/val/OpBaseVal.js.map +1 -1
- package/dist/val/RefVal.js +69 -23
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/Val.d.ts +1 -1
- package/dist/val/Val.js +0 -3
- package/dist/val/Val.js.map +1 -1
- package/package.json +10 -10
- package/src/aontu.ts +58 -1
- package/src/ctx.ts +9 -0
- package/src/err.ts +10 -1
- package/src/hints.ts +63 -1
- package/src/lang.ts +258 -16
- package/src/lsp.ts +4 -3
- package/src/unify.ts +59 -2
- package/src/val/ConstraintVal.ts +1195 -111
- package/src/val/ExpectVal.ts +15 -0
- package/src/val/FuncBaseVal.ts +8 -2
- package/src/val/ListVal.ts +18 -4
- package/src/val/MapVal.ts +11 -0
- package/src/val/OpBaseVal.ts +6 -2
- package/src/val/RefVal.ts +72 -22
- package/src/val/Val.ts +8 -1
package/src/lang.ts
CHANGED
|
@@ -108,6 +108,10 @@ import {
|
|
|
108
108
|
AboveConstraintVal,
|
|
109
109
|
BelowConstraintVal,
|
|
110
110
|
NeqConstraintVal,
|
|
111
|
+
ReConstraintVal,
|
|
112
|
+
LengthConstraintVal,
|
|
113
|
+
UniqueConstraintVal,
|
|
114
|
+
MustConstraintVal,
|
|
111
115
|
} from './val/ConstraintVal'
|
|
112
116
|
|
|
113
117
|
|
|
@@ -420,6 +424,23 @@ help isolate the syntax error.`,
|
|
|
420
424
|
above: AboveConstraintVal,
|
|
421
425
|
below: BelowConstraintVal,
|
|
422
426
|
neq: NeqConstraintVal,
|
|
427
|
+
|
|
428
|
+
// G1 phase 2: pattern membership, over the portable subset both
|
|
429
|
+
// host regex engines agree on (nonPortableRe in ConstraintVal.ts).
|
|
430
|
+
re: ReConstraintVal,
|
|
431
|
+
|
|
432
|
+
// G1 phase 3: the sizing atoms. Both are properties of a CONTAINER
|
|
433
|
+
// (or, for length, of a string) rather than comparisons against a
|
|
434
|
+
// value, which is why `unique` is the one built-in taking no
|
|
435
|
+
// argument at all.
|
|
436
|
+
length: LengthConstraintVal,
|
|
437
|
+
unique: UniqueConstraintVal,
|
|
438
|
+
|
|
439
|
+
// G1 phase 5: Band B. `must` is the one atom the algebra does not
|
|
440
|
+
// reason about -- it is checked against the finished value and
|
|
441
|
+
// reported with the author's own message, never simplified and
|
|
442
|
+
// never consulted for emptiness or subsumption.
|
|
443
|
+
must: MustConstraintVal,
|
|
423
444
|
}
|
|
424
445
|
|
|
425
446
|
|
|
@@ -515,9 +536,47 @@ help isolate the syntax error.`,
|
|
|
515
536
|
'func-paren': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
516
537
|
let val = terms[1]
|
|
517
538
|
const fname = terms[0]
|
|
539
|
+
|
|
518
540
|
if ('' !== fname) {
|
|
519
541
|
const funcval = funcMap[fname]
|
|
520
|
-
|
|
542
|
+
// Arity is known for every built-in, so a surplus or missing
|
|
543
|
+
// argument is a mistake in the SOURCE, refused here where the
|
|
544
|
+
// author can see it (issue #51). It was previously left to each
|
|
545
|
+
// function to notice or not: the two ports disagreed on `upper()`
|
|
546
|
+
// and on `close()`, and `min(1,2)` noticed nothing at all -- it
|
|
547
|
+
// built a constraint that merely refused to generate later, with
|
|
548
|
+
// a message about the map rather than about the call.
|
|
549
|
+
//
|
|
550
|
+
// Counted BEFORE the rawToVal pass below, which is what makes the
|
|
551
|
+
// count possible: a comma group arrives as a RAW array and a
|
|
552
|
+
// written list literal as a ListVal, and rawToVal turns the first
|
|
553
|
+
// into the second.
|
|
554
|
+
const arity = funcArity[fname]
|
|
555
|
+
if (null != arity) {
|
|
556
|
+
const got = writtenArgCount(terms.slice(1))
|
|
557
|
+
if (got < arity[0] || (-1 !== arity[1] && got > arity[1])) {
|
|
558
|
+
// details is assigned AFTER construction: the NilVal
|
|
559
|
+
// constructor does not read it from its spec (only
|
|
560
|
+
// NilVal.make does), so passing it in the spec left the
|
|
561
|
+
// hint's {func}/{want}/{got} placeholders un-injected and
|
|
562
|
+
// printed literally.
|
|
563
|
+
const nil: any = new NilVal({ why: 'func_arity' })
|
|
564
|
+
nil.details = {
|
|
565
|
+
func: fname,
|
|
566
|
+
want: arityText(arity[0], arity[1]),
|
|
567
|
+
got: '' + got,
|
|
568
|
+
}
|
|
569
|
+
return addsite(nil, r, ctx)
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// rawToVal EVERY argument. A degenerate expression can hand this
|
|
573
|
+
// handler raw parse values rather than Vals -- `pref(1-3)` arrives
|
|
574
|
+
// as the plain numbers 1 and -3 -- and a func's peg is unified
|
|
575
|
+
// element by element, so a raw one reached `arg.unify(...)` and
|
|
576
|
+
// threw. The unifier's catch-all turned that into an `internal`
|
|
577
|
+
// verdict: a crash reported as a unification result (issue #49).
|
|
578
|
+
// The Go port has always converted here (asVal in evaluate).
|
|
579
|
+
const args = terms.slice(1).map(rawToVal)
|
|
521
580
|
val = null == funcval ?
|
|
522
581
|
new NilVal({ why: 'unknown_function' }) :
|
|
523
582
|
new funcval({
|
|
@@ -526,7 +585,11 @@ help isolate the syntax error.`,
|
|
|
526
585
|
}
|
|
527
586
|
// `a:()` — grouping parens with nothing inside.
|
|
528
587
|
if (null == val) return incompleteNil(r, ctx)
|
|
529
|
-
|
|
588
|
+
// ... and the same for a GROUPING paren, whose value is passed
|
|
589
|
+
// straight through: `(([]%))` yielded a raw array, and addsite went
|
|
590
|
+
// on to write a site onto it, throwing a TypeError that escaped the
|
|
591
|
+
// unifier entirely ("Cannot set properties of undefined").
|
|
592
|
+
const out = addsite(rawToVal(val), r, ctx)
|
|
530
593
|
return out
|
|
531
594
|
},
|
|
532
595
|
}
|
|
@@ -783,13 +846,60 @@ help isolate the syntax error.`,
|
|
|
783
846
|
let mo = r.node
|
|
784
847
|
|
|
785
848
|
// An elided value (`a:`) leaves a raw null/undefined that never
|
|
786
|
-
// passed through the val rule
|
|
849
|
+
// passed through the val rule. It is REFUSED rather than made a
|
|
850
|
+
// null (issue #48): a key with nothing after the colon is a
|
|
851
|
+
// mistake in the source, and turning it into a value made that
|
|
852
|
+
// mistake indistinguishable from a deliberate `a:null`.
|
|
853
|
+
//
|
|
854
|
+
// A colon chain (`a: b:1`) is not an elision -- the value is the
|
|
855
|
+
// nested pair, which the val rule does produce -- and neither is
|
|
856
|
+
// a trailing comma.
|
|
787
857
|
for (const k in mo) {
|
|
788
858
|
if (null == mo[k] && '___merge' !== k) {
|
|
789
|
-
|
|
859
|
+
// Pathed at the KEY, not at the enclosing map. addsite takes
|
|
860
|
+
// the rule's path, which here is the map's, so the error
|
|
861
|
+
// would otherwise name the container and leave the reader to
|
|
862
|
+
// work out which key was elided.
|
|
863
|
+
const en: any = addsite(new NilVal({ why: 'elided_value' }), r, ctx)
|
|
864
|
+
en.path = [...(r.k?.path ?? []), k]
|
|
865
|
+
mo[k] = en
|
|
866
|
+
|
|
867
|
+
// An elided value under an OPTIONAL key stops being optional.
|
|
868
|
+
// Optionality is about a value that may be absent at
|
|
869
|
+
// GENERATE; it does not excuse a source that stops after the
|
|
870
|
+
// colon. Left optional, the refusal was dropped with the key
|
|
871
|
+
// and `a?:` generated `{}` -- a silent nothing, which is
|
|
872
|
+
// worse than either the old null or the error.
|
|
873
|
+
const oi = optionalKeys.indexOf(k)
|
|
874
|
+
if (-1 !== oi) {
|
|
875
|
+
optionalKeys.splice(oi, 1)
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// ... and the OPTIONAL spelling, `a?:`, which does not leave a
|
|
881
|
+
// null behind to be found: its value never reaches the node at
|
|
882
|
+
// all, so the key is simply absent and the map generated without
|
|
883
|
+
// it. A key recorded as optional but missing from the node was
|
|
884
|
+
// written with nothing after its colon.
|
|
885
|
+
for (const k of optionalKeys) {
|
|
886
|
+
if (!(k in mo)) {
|
|
887
|
+
mo[k] = addsite(new NilVal({ why: 'elided_value' }), r, ctx)
|
|
790
888
|
}
|
|
791
889
|
}
|
|
792
890
|
|
|
891
|
+
// An elided SPREAD (`x:$obj&:` with nothing after the colon)
|
|
892
|
+
// refuses the whole map, not a key (issue #48). A spread is not a
|
|
893
|
+
// child, so a refusal stored in its place has nothing to attach
|
|
894
|
+
// to: `x:&:` has no children for the spread to apply to, and the
|
|
895
|
+
// map would generate as `{}` with the mistake silently gone.
|
|
896
|
+
// Refusing the container is what makes it visible at all.
|
|
897
|
+
const sp: any = (mo as any)[SPREAD]
|
|
898
|
+
if (sp && sp.v.some((sv: any) => null == sv)) {
|
|
899
|
+
r.node = addsite(new NilVal({ why: 'elided_value' }), r, ctx)
|
|
900
|
+
return undefined
|
|
901
|
+
}
|
|
902
|
+
|
|
793
903
|
// Handle defered conjuncts, e.g. `{x:1 @"foo"}`
|
|
794
904
|
if (mo.___merge) {
|
|
795
905
|
let mop = { ...mo }
|
|
@@ -826,11 +936,15 @@ help isolate the syntax error.`,
|
|
|
826
936
|
|
|
827
937
|
let ao = r.node
|
|
828
938
|
|
|
829
|
-
// An elided
|
|
830
|
-
//
|
|
939
|
+
// An elided ELEMENT (`[,]`, `[1,,2]`) is refused for the same
|
|
940
|
+
// reason as an elided map value (issue #48). A trailing comma
|
|
941
|
+
// (`[1,]`) is not an elision and never reaches here.
|
|
831
942
|
for (let i = 0; i < ao.length; i++) {
|
|
832
943
|
if (null == ao[i]) {
|
|
833
|
-
|
|
944
|
+
// Pathed at the INDEX, for the same reason as the map case.
|
|
945
|
+
const en: any = addsite(new NilVal({ why: 'elided_value' }), r, ctx)
|
|
946
|
+
en.path = [...(r.k?.path ?? []), '' + i]
|
|
947
|
+
ao[i] = en
|
|
834
948
|
}
|
|
835
949
|
}
|
|
836
950
|
|
|
@@ -865,6 +979,51 @@ help isolate the syntax error.`,
|
|
|
865
979
|
}
|
|
866
980
|
|
|
867
981
|
|
|
982
|
+
// A pair in LIST position writes its value at `node[key]` like any
|
|
983
|
+
// other pair, and the enclosing list's node is an ARRAY -- so a numeric
|
|
984
|
+
// key lands on an index and becomes an element, and may land on an
|
|
985
|
+
// index a real element already holds (`[5,0:1]`). Since the pair must
|
|
986
|
+
// contribute nothing (issue #40), the slot is photographed before the
|
|
987
|
+
// value is parsed and put back afterwards. Restoring beats deleting for
|
|
988
|
+
// exactly the overwrite case: deleting `[5,0:1]`'s index 0 would take
|
|
989
|
+
// the 5 with it, where restoring gives back the list the pair was
|
|
990
|
+
// never part of.
|
|
991
|
+
//
|
|
992
|
+
// `length` is saved too: writing past the end grows an array, and
|
|
993
|
+
// `[5,1?:9]` must be [5] again and not [5, <hole>].
|
|
994
|
+
// Typed as a string bag deliberately: the slot may be named by a
|
|
995
|
+
// non-numeric key (`[x:1]`), which the array type would refuse. Both
|
|
996
|
+
// helpers run only from the elem rule, whose node is the enclosing
|
|
997
|
+
// list, so neither guards against a non-array node — a guard there
|
|
998
|
+
// proved unreachable and dead code is worse than none.
|
|
999
|
+
const asSlots = (r: Rule): Record<string, any> => r.node as any
|
|
1000
|
+
|
|
1001
|
+
const snapshotPairSlot = (r: Rule, key: string) => {
|
|
1002
|
+
const node = asSlots(r)
|
|
1003
|
+
r.u.aontu_pair_slot = {
|
|
1004
|
+
key,
|
|
1005
|
+
had: Object.prototype.hasOwnProperty.call(node, key),
|
|
1006
|
+
was: node[key],
|
|
1007
|
+
len: node.length,
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const restorePairSlot = (r: Rule) => {
|
|
1012
|
+
const slot: any = r.u.aontu_pair_slot
|
|
1013
|
+
if (null == slot) {
|
|
1014
|
+
return
|
|
1015
|
+
}
|
|
1016
|
+
const node = asSlots(r)
|
|
1017
|
+
if (slot.had) {
|
|
1018
|
+
node[slot.key] = slot.was
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
delete node[slot.key]
|
|
1022
|
+
}
|
|
1023
|
+
node.length = slot.len
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
|
|
868
1027
|
jsonic.rule('pair', (rs: RuleSpec) => {
|
|
869
1028
|
rs
|
|
870
1029
|
.open([
|
|
@@ -955,11 +1114,31 @@ help isolate the syntax error.`,
|
|
|
955
1114
|
a: (r) => {
|
|
956
1115
|
pairkey(r.prev)
|
|
957
1116
|
r.u.key = r.prev.u.key
|
|
958
|
-
|
|
959
|
-
r.parent.u.aontu_optional_keys = (r.parent.u.aontu_optional_keys || [])
|
|
960
|
-
r.parent.u.aontu_optional_keys.push('' + r.u.key)
|
|
1117
|
+
snapshotPairSlot(r, '' + r.u.key)
|
|
961
1118
|
},
|
|
962
1119
|
g: 'aontu-optional-elem'
|
|
1120
|
+
},
|
|
1121
|
+
|
|
1122
|
+
// A PLAIN pair in list position, `[k:v]`. It contributes no
|
|
1123
|
+
// element either -- a key:value pair is simply not a list element,
|
|
1124
|
+
// which is the rule the optional form above already followed, and
|
|
1125
|
+
// the two spellings must not disagree (issue #40).
|
|
1126
|
+
//
|
|
1127
|
+
// It needed an alt of its own because only a NON-NUMERIC key was
|
|
1128
|
+
// already inert: jsonic writes the pair at `node[key]`, and the
|
|
1129
|
+
// node is an array, so `[x:1]` set a property that never showed up
|
|
1130
|
+
// (`length` stays 0) while `[0:1]` set an INDEX and became an
|
|
1131
|
+
// element -- `[1:2]` even filling the gap with a null. That is the
|
|
1132
|
+
// shape of a JavaScript array, not a decision about the language,
|
|
1133
|
+
// and it made the two ports disagree on generate as well as canon.
|
|
1134
|
+
{
|
|
1135
|
+
s: [OPTKEY, CL], p: 'val',
|
|
1136
|
+
u: { spread: true, done: true, list: true, pair: true },
|
|
1137
|
+
a: (r) => {
|
|
1138
|
+
pairkey(r)
|
|
1139
|
+
snapshotPairSlot(r, '' + r.u.key)
|
|
1140
|
+
},
|
|
1141
|
+
g: 'aontu-plain-pair-elem'
|
|
963
1142
|
}
|
|
964
1143
|
])
|
|
965
1144
|
|
|
@@ -973,6 +1152,8 @@ help isolate the syntax error.`,
|
|
|
973
1152
|
rule.node[SPREAD].v.push(rule.child.node)
|
|
974
1153
|
}
|
|
975
1154
|
|
|
1155
|
+
restorePairSlot(rule)
|
|
1156
|
+
|
|
976
1157
|
return undefined
|
|
977
1158
|
})
|
|
978
1159
|
|
|
@@ -1057,6 +1238,73 @@ function makeModelResolver(options: any) {
|
|
|
1057
1238
|
}
|
|
1058
1239
|
|
|
1059
1240
|
|
|
1241
|
+
// funcArity is the permitted WRITTEN argument count of each built-in, as
|
|
1242
|
+
// [min, max]; a max of -1 is unbounded. Every name in funcMap has an
|
|
1243
|
+
// entry, and the arity is a property of the language rather than of
|
|
1244
|
+
// either port -- go/func.go carries the same table.
|
|
1245
|
+
//
|
|
1246
|
+
// Nearly everything takes exactly one. The four exceptions earn their
|
|
1247
|
+
// place: key() names how many levels UP the path to read, defaulting to
|
|
1248
|
+
// the parent when omitted, neq takes a whole set of exclusions,
|
|
1249
|
+
// unique() is a property of the container rather than a comparison
|
|
1250
|
+
// against anything, so there is nothing for it to take, and must()
|
|
1251
|
+
// takes a check AND the author's message for when it fails.
|
|
1252
|
+
const funcArity: Record<string, [number, number]> = {
|
|
1253
|
+
upper: [1, 1], lower: [1, 1], copy: [1, 1], pref: [1, 1],
|
|
1254
|
+
super: [1, 1], type: [1, 1], hide: [1, 1], close: [1, 1],
|
|
1255
|
+
open: [1, 1], move: [1, 1], path: [1, 1],
|
|
1256
|
+
min: [1, 1], max: [1, 1], above: [1, 1], below: [1, 1], re: [1, 1],
|
|
1257
|
+
length: [1, 1],
|
|
1258
|
+
key: [0, 1],
|
|
1259
|
+
unique: [0, 0],
|
|
1260
|
+
neq: [1, -1],
|
|
1261
|
+
must: [2, 2],
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
// writtenArgCount counts the arguments as the AUTHOR wrote them.
|
|
1266
|
+
//
|
|
1267
|
+
// It cannot simply be terms.length: a comma group reaches the func-paren
|
|
1268
|
+
// handler as ONE term holding a raw array, so `upper("a","b")` and
|
|
1269
|
+
// `upper(["a","b"])` both arrive as a single argument. They are still
|
|
1270
|
+
// distinguishable, and that is what makes an arity check possible at
|
|
1271
|
+
// all -- the comma group is a RAW array, while a written list literal
|
|
1272
|
+
// has already been built into a ListVal by the list rule.
|
|
1273
|
+
function writtenArgCount(terms: any[]): number {
|
|
1274
|
+
if (1 === terms.length) {
|
|
1275
|
+
// `terms[0]` is re-read rather than reusing a narrowed local:
|
|
1276
|
+
// Array.isArray narrows an `any` to `any[]`, which then has no
|
|
1277
|
+
// `isVal` to test.
|
|
1278
|
+
const t: any = terms[0]
|
|
1279
|
+
if (Array.isArray(t) && true !== (terms[0] as any).isVal) {
|
|
1280
|
+
return t.length
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return terms.length
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
// arityText renders a built-in's permitted count for the error message.
|
|
1288
|
+
// The fixed-arity case says "one" outright rather than counting: every
|
|
1289
|
+
// fixed arity in the table IS one, and a phrasing for a count no entry
|
|
1290
|
+
// carries would be untested prose pretending to be tested.
|
|
1291
|
+
function arityText(lo: number, hi: number): string {
|
|
1292
|
+
if (-1 === hi) {
|
|
1293
|
+
return 'one or more arguments'
|
|
1294
|
+
}
|
|
1295
|
+
if (lo !== hi) {
|
|
1296
|
+
return 'no arguments or one'
|
|
1297
|
+
}
|
|
1298
|
+
if (0 === hi) {
|
|
1299
|
+
return 'no arguments'
|
|
1300
|
+
}
|
|
1301
|
+
if (2 === hi) {
|
|
1302
|
+
return 'exactly two arguments'
|
|
1303
|
+
}
|
|
1304
|
+
return 'exactly one argument'
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
|
|
1060
1308
|
// rawToVal converts a raw parse node (or raw elements inside one) into
|
|
1061
1309
|
// the matching Val. Used for implicit top-level lists, whose nodes skip
|
|
1062
1310
|
// the aontu val rule conversions (mirrors asVal in go/lang.go; like
|
|
@@ -1115,12 +1363,6 @@ function rawToVal(n: any): Val {
|
|
|
1115
1363
|
return new BooleanVal({ peg: n })
|
|
1116
1364
|
}
|
|
1117
1365
|
if ('object' === t) {
|
|
1118
|
-
// An expr-plugin internal (operator descriptor) leaking through a
|
|
1119
|
-
// degenerate parse (`k2.b K:1`) is not data — reject it rather
|
|
1120
|
-
// than emitting raw internals in generated output.
|
|
1121
|
-
if (undefined !== (n as any).OP_MARK) {
|
|
1122
|
-
return new NilVal({ why: 'parse_unknown' })
|
|
1123
|
-
}
|
|
1124
1366
|
const peg: Record<string, Val> = {}
|
|
1125
1367
|
for (const k in n) {
|
|
1126
1368
|
peg[k] = rawToVal(n[k])
|
package/src/lsp.ts
CHANGED
|
@@ -358,11 +358,12 @@ type CompletionItem = {
|
|
|
358
358
|
const COMPLETION_FUNCTION = 3
|
|
359
359
|
const COMPLETION_KEYWORD = 14
|
|
360
360
|
|
|
361
|
-
// The
|
|
361
|
+
// The twenty-one built-in functions. Kept in sync with the engine by
|
|
362
362
|
// `lsp.test.ts`, which asserts each is recognised and no others are.
|
|
363
363
|
const BUILTIN_FUNCS = [
|
|
364
|
-
'above', 'below', 'close', 'copy', 'hide', 'key', '
|
|
365
|
-
'
|
|
364
|
+
'above', 'below', 'close', 'copy', 'hide', 'key', 'length', 'lower',
|
|
365
|
+
'max', 'min', 'move', 'must', 'neq', 'open', 'path', 'pref', 're',
|
|
366
|
+
'super', 'type', 'unique', 'upper',
|
|
366
367
|
]
|
|
367
368
|
|
|
368
369
|
// Scalar-kind and literal keywords.
|
package/src/unify.ts
CHANGED
|
@@ -26,9 +26,52 @@ import {
|
|
|
26
26
|
} from './val/top'
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
// Per-pass revisit bound: how many times one (Val, path) pair may be
|
|
30
|
+
// re-unified within a single fixpoint pass before the evaluator calls it
|
|
31
|
+
// non-convergence (`unify_cycle`). The old false positive here -- a
|
|
32
|
+
// legal model with more than MAXCYCLE sibling conjunct terms at one
|
|
33
|
+
// path, each re-running the TOP self-unify -- is fixed by the per-pass
|
|
34
|
+
// memo below (_tcc/_tpi); test/spec/budget.tsv drives 1200 sibling
|
|
35
|
+
// terms through both engines as the regression guard.
|
|
30
36
|
const MAXCYCLE = 999
|
|
31
37
|
|
|
38
|
+
// Structural recursion budget: how deep `unite` may nest before the
|
|
39
|
+
// evaluator reports `unify_cycle`. SHARED LANGUAGE SURFACE -- Go's
|
|
40
|
+
// maxUniteDepth (go/unify.go) carries the same number, and
|
|
41
|
+
// test/spec/budget.tsv pins the boundary in both, so changing it is a
|
|
42
|
+
// spec-visible change in both ports at once.
|
|
43
|
+
//
|
|
44
|
+
// Why 1000: the whole shared suite peaks at 603 (the deliberately
|
|
45
|
+
// extreme 1200-sibling-term fixture; ordinary documents are two orders
|
|
46
|
+
// below), and V8 exhausts its call stack somewhere past depth ~1500 in
|
|
47
|
+
// this evaluator. 1000 sits above every real document and below the
|
|
48
|
+
// host limit, so the budget -- not the host -- decides the verdict.
|
|
49
|
+
const MAXDEPTH = 1000
|
|
50
|
+
|
|
51
|
+
// Charge a DIRECT `Val.unify` recursion to the same depth budget that
|
|
52
|
+
// `unite` enforces. Function and operator arguments evaluate through
|
|
53
|
+
// `arg.unify(top(), ...)` rather than through the dispatcher, so without
|
|
54
|
+
// this the counter stays flat while the JavaScript stack keeps growing:
|
|
55
|
+
// a 1500-deep `upper(upper(...))` resolved in TypeScript while Go — which
|
|
56
|
+
// routes its arguments through the counted dispatcher — reported
|
|
57
|
+
// `unify_cycle`. Returns the budget nil instead of running when the
|
|
58
|
+
// budget is spent.
|
|
59
|
+
const withDepth = (
|
|
60
|
+
ctx: AontuContext, a: any, b: any, run: () => any
|
|
61
|
+
): any => {
|
|
62
|
+
if (MAXDEPTH <= ctx._depth.n) {
|
|
63
|
+
return makeNilErr(ctx, 'unify_cycle', a, b)
|
|
64
|
+
}
|
|
65
|
+
ctx._depth.n++
|
|
66
|
+
try {
|
|
67
|
+
return run()
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
ctx._depth.n--
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
32
75
|
// Vals should only have to unify downwards (in .unify) over Vals they understand.
|
|
33
76
|
// and for complex Vals, TOP, which means self unify if not yet done
|
|
34
77
|
const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
|
|
@@ -76,12 +119,22 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
|
|
|
76
119
|
// NOTE: if this error occurs "unreasonably", attemp to avoid unnecesary unification
|
|
77
120
|
// See for example PrefVal peg.id equality inspection.
|
|
78
121
|
const sawCount = ctx.seen[saw] ?? 0
|
|
79
|
-
if (
|
|
122
|
+
if (MAXDEPTH <= ctx._depth.n) {
|
|
123
|
+
// Structural recursion budget. Without it, deep nesting exhausts the
|
|
124
|
+
// V8 call stack and the catch-all below reports a RangeError as
|
|
125
|
+
// `internal` — a verdict that depends on the host's stack size
|
|
126
|
+
// rather than on the document, which is exactly what the
|
|
127
|
+
// determinism clause forbids (docs/trust.md). Tripping here instead
|
|
128
|
+
// makes it a stated budget error, like the pass budget.
|
|
129
|
+
out = makeNilErr(ctx, 'unify_cycle', a, b)
|
|
130
|
+
}
|
|
131
|
+
else if (MAXCYCLE < sawCount) {
|
|
80
132
|
// console.log('SAW', sawCount, saw, a?.id, a?.canon, b?.id, b?.canon, ctx.cc)
|
|
81
133
|
out = makeNilErr(ctx, 'unify_cycle', a, b)
|
|
82
134
|
}
|
|
83
135
|
else {
|
|
84
136
|
ctx.seen[saw] = sawCount + 1
|
|
137
|
+
ctx._depth.n++
|
|
85
138
|
|
|
86
139
|
try {
|
|
87
140
|
let unified = false
|
|
@@ -191,6 +244,9 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
|
|
|
191
244
|
...(err instanceof RangeError ? { overflow: true } : {}),
|
|
192
245
|
})
|
|
193
246
|
}
|
|
247
|
+
finally {
|
|
248
|
+
ctx._depth.n--
|
|
249
|
+
}
|
|
194
250
|
}
|
|
195
251
|
|
|
196
252
|
ctx.explain && explainClose(te, out)
|
|
@@ -341,4 +397,5 @@ class Unify {
|
|
|
341
397
|
export {
|
|
342
398
|
Unify,
|
|
343
399
|
unite,
|
|
400
|
+
withDepth,
|
|
344
401
|
}
|