aontu 0.53.0 → 0.54.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/aontu.d.ts +3 -2
- package/dist/aontu.js +36 -7
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +426 -7
- package/dist/cli.js.map +1 -1
- package/dist/ctx.d.ts +5 -0
- package/dist/ctx.js +1 -0
- package/dist/ctx.js.map +1 -1
- package/dist/diff.js.map +1 -1
- package/dist/err.js +7 -1
- package/dist/err.js.map +1 -1
- package/dist/graph.d.ts +2 -5
- package/dist/graph.js +83 -46
- package/dist/graph.js.map +1 -1
- package/dist/hcanon.js +9 -10
- package/dist/hcanon.js.map +1 -1
- package/dist/hints.js +90 -26
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.js +34 -0
- package/dist/jsonschema.js.map +1 -1
- package/dist/lang.js +593 -191
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/lsp.js +86 -6
- package/dist/lsp.js.map +1 -1
- package/dist/mcp.js +133 -6
- package/dist/mcp.js.map +1 -1
- package/dist/mod-tool.js +42 -8
- package/dist/mod-tool.js.map +1 -1
- package/dist/mod.d.ts +4 -0
- package/dist/mod.js +97 -2
- package/dist/mod.js.map +1 -1
- package/dist/patch.d.ts +5 -0
- package/dist/patch.js +25 -25
- package/dist/patch.js.map +1 -1
- package/dist/provenance.d.ts +1 -0
- package/dist/provenance.js +2 -1
- package/dist/provenance.js.map +1 -1
- package/dist/query.js.map +1 -1
- package/dist/reach.d.ts +1 -0
- package/dist/reach.js +49 -21
- package/dist/reach.js.map +1 -1
- package/dist/relation.d.ts +4 -0
- package/dist/relation.js +125 -200
- package/dist/relation.js.map +1 -1
- package/dist/sig.d.ts +25 -0
- package/dist/sig.js +277 -0
- package/dist/sig.js.map +1 -0
- package/dist/sigdecl.d.ts +2 -0
- package/dist/sigdecl.js +11 -0
- package/dist/sigdecl.js.map +1 -0
- package/dist/siggate.d.ts +4 -0
- package/dist/siggate.js +90 -0
- package/dist/siggate.js.map +1 -0
- package/dist/std.js +72 -16
- package/dist/std.js.map +1 -1
- package/dist/subsume.js +57 -10
- package/dist/subsume.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/unify.d.ts +2 -2
- package/dist/unify.js +93 -114
- package/dist/unify.js.map +1 -1
- package/dist/utility.d.ts +1 -2
- package/dist/utility.js +7 -61
- package/dist/utility.js.map +1 -1
- package/dist/val/AggFuncVal.d.ts +12 -1
- package/dist/val/AggFuncVal.js +165 -3
- package/dist/val/AggFuncVal.js.map +1 -1
- package/dist/val/BagVal.d.ts +2 -0
- package/dist/val/BagVal.js +56 -8
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/ConstraintVal.js +33 -5
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/ContainerKindVal.d.ts +35 -0
- package/dist/val/ContainerKindVal.js +99 -0
- package/dist/val/ContainerKindVal.js.map +1 -0
- package/dist/val/CopyFuncVal.js +0 -7
- package/dist/val/CopyFuncVal.js.map +1 -1
- package/dist/val/DisjunctVal.d.ts +1 -2
- package/dist/val/DisjunctVal.js +146 -39
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/ExpectVal.js +37 -2
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FuncBaseVal.d.ts +1 -0
- package/dist/val/FuncBaseVal.js +19 -8
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/GraphAtomVal.d.ts +39 -0
- package/dist/val/GraphAtomVal.js +184 -0
- package/dist/val/GraphAtomVal.js.map +1 -0
- package/dist/val/JunctionVal.js +22 -5
- package/dist/val/JunctionVal.js.map +1 -1
- package/dist/val/ListVal.js +27 -34
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/MapVal.d.ts +1 -0
- package/dist/val/MapVal.js +82 -35
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/PathFuncVal.d.ts +2 -2
- package/dist/val/PathFuncVal.js +75 -16
- package/dist/val/PathFuncVal.js.map +1 -1
- package/dist/val/PathVal.d.ts +25 -0
- package/dist/val/PathVal.js +150 -0
- package/dist/val/PathVal.js.map +1 -0
- package/dist/val/PlusOpVal.d.ts +2 -1
- package/dist/val/PlusOpVal.js +50 -35
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefVal.d.ts +2 -0
- package/dist/val/PrefVal.js +159 -32
- package/dist/val/PrefVal.js.map +1 -1
- package/dist/val/RecurseVal.d.ts +19 -0
- package/dist/val/RecurseVal.js +217 -0
- package/dist/val/RecurseVal.js.map +1 -0
- package/dist/val/RefVal.d.ts +2 -1
- package/dist/val/RefVal.js +105 -72
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ReferFuncVal.d.ts +30 -7
- package/dist/val/ReferFuncVal.js +395 -94
- package/dist/val/ReferFuncVal.js.map +1 -1
- package/dist/val/ScalarKindVal.d.ts +4 -2
- package/dist/val/ScalarKindVal.js +12 -1
- package/dist/val/ScalarKindVal.js.map +1 -1
- package/dist/val/SuperFuncVal.d.ts +4 -2
- package/dist/val/SuperFuncVal.js +118 -14
- package/dist/val/SuperFuncVal.js.map +1 -1
- package/dist/val/TopVal.d.ts +1 -1
- package/dist/val/Val.d.ts +2 -3
- package/dist/val/Val.js +39 -20
- package/dist/val/Val.js.map +1 -1
- package/dist/val/arith.js +4 -1
- package/dist/val/arith.js.map +1 -1
- package/dist/vet.js +27 -1
- package/dist/vet.js.map +1 -1
- package/dist/view.d.ts +85 -0
- package/dist/view.js +1948 -0
- package/dist/view.js.map +1 -0
- package/grammar/aontu.gbnf +18 -10
- package/grammar/aontu.lark +15 -10
- package/grammar/aontu.tmLanguage.json +184 -0
- package/package.json +10 -3
- package/skill/grammar-card.md +1 -2
- package/src/aontu.ts +37 -7
- package/src/cli.ts +472 -7
- package/src/ctx.ts +20 -0
- package/src/diff.ts +4 -2
- package/src/err.ts +8 -1
- package/src/graph.ts +125 -75
- package/src/hcanon.ts +9 -11
- package/src/hints.ts +108 -29
- package/src/jsonschema.ts +41 -0
- package/src/lang.ts +642 -203
- package/src/lsp.ts +75 -6
- package/src/mcp.ts +143 -6
- package/src/mod-tool.ts +48 -9
- package/src/mod.ts +110 -1
- package/src/patch.ts +31 -27
- package/src/provenance.ts +8 -1
- package/src/query.ts +4 -2
- package/src/reach.ts +52 -23
- package/src/relation.ts +139 -236
- package/src/sig.ts +345 -0
- package/src/sigdecl.ts +11 -0
- package/src/siggate.ts +144 -0
- package/src/std.ts +74 -16
- package/src/subsume.ts +59 -10
- package/src/unify.ts +102 -125
- package/src/utility.ts +7 -67
- package/src/val/AggFuncVal.ts +231 -4
- package/src/val/BagVal.ts +58 -9
- package/src/val/ConstraintVal.ts +34 -5
- package/src/val/ContainerKindVal.ts +158 -0
- package/src/val/CopyFuncVal.ts +0 -7
- package/src/val/DisjunctVal.ts +152 -40
- package/src/val/ExpectVal.ts +39 -4
- package/src/val/FuncBaseVal.ts +21 -8
- package/src/val/GraphAtomVal.ts +264 -0
- package/src/val/JunctionVal.ts +23 -6
- package/src/val/ListVal.ts +30 -37
- package/src/val/MapVal.ts +87 -39
- package/src/val/PathFuncVal.ts +107 -19
- package/src/val/PathVal.ts +221 -0
- package/src/val/PlusOpVal.ts +56 -37
- package/src/val/PrefVal.ts +186 -38
- package/src/val/RecurseVal.ts +285 -0
- package/src/val/RefVal.ts +105 -83
- package/src/val/ReferFuncVal.ts +445 -100
- package/src/val/ScalarKindVal.ts +12 -0
- package/src/val/SuperFuncVal.ts +137 -13
- package/src/val/TopVal.ts +1 -1
- package/src/val/Val.ts +44 -35
- package/src/val/arith.ts +4 -1
- package/src/vet.ts +36 -3
- package/src/view.ts +2563 -0
- package/dist/val/IdFuncVal.d.ts +0 -13
- package/dist/val/IdFuncVal.js +0 -54
- package/dist/val/IdFuncVal.js.map +0 -1
- package/src/val/IdFuncVal.ts +0 -91
package/src/val/AggFuncVal.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
2
|
|
|
3
|
-
// AGGREGATION AND
|
|
4
|
-
//
|
|
3
|
+
// AGGREGATION, PROJECTION AND THE STRING FOLD: `sum(d)`, `least(d)`,
|
|
4
|
+
// `greatest(d)`, `pick(d, k)` and `join(d, sep?)`.
|
|
5
5
|
//
|
|
6
6
|
// The review's finding I: "No aggregation. `length()` counts but
|
|
7
7
|
// nothing sums: an invoice total, a fleet-wide resource budget, a quota
|
|
@@ -55,9 +55,12 @@ import {
|
|
|
55
55
|
import { makeNilErr } from '../err'
|
|
56
56
|
import { IntegerVal } from './IntegerVal'
|
|
57
57
|
import { ListVal } from './ListVal'
|
|
58
|
+
import { StringVal } from './StringVal'
|
|
58
59
|
import { FuncBaseVal } from './FuncBaseVal'
|
|
59
60
|
import { arith } from './arith'
|
|
60
61
|
import { cmpNumeric } from './numcmp'
|
|
62
|
+
import { cmpCodePoint } from '../keyorder'
|
|
63
|
+
import { plusText } from './PlusOpVal'
|
|
61
64
|
|
|
62
65
|
|
|
63
66
|
type AggOp = 'sum' | 'least' | 'greatest'
|
|
@@ -72,7 +75,14 @@ function bagChildren(data: any): Val[] | undefined {
|
|
|
72
75
|
return data.peg as Val[]
|
|
73
76
|
}
|
|
74
77
|
if (true === data?.isMap) {
|
|
75
|
-
|
|
78
|
+
// THE ONE MAP-KEY ORDER (../keyorder.ts), not a bare `.sort()`:
|
|
79
|
+
// JavaScript compares by UTF-16 code unit, so an astral key's
|
|
80
|
+
// leading surrogate sorts BELOW U+E000-U+FFFF and `pick` answered
|
|
81
|
+
// in a different order from `each`, from canon, and from Go --
|
|
82
|
+
// which sorts UTF-8 bytes, i.e. code points. `pick` is the
|
|
83
|
+
// order-preserving projection, so that was one model producing two
|
|
84
|
+
// different generated files (BUGS.md 62).
|
|
85
|
+
return Object.keys(data.peg).sort(cmpCodePoint).map((k: string) => data.peg[k])
|
|
76
86
|
}
|
|
77
87
|
return undefined
|
|
78
88
|
}
|
|
@@ -294,6 +304,222 @@ class PickFuncVal extends FuncBaseVal {
|
|
|
294
304
|
}
|
|
295
305
|
|
|
296
306
|
|
|
307
|
+
|
|
308
|
+
// THE FOLD TO A STRING: `join(coll, sep?)` -- G9 phase 2.
|
|
309
|
+
//
|
|
310
|
+
// The one primitive between a model and a generated file. A spread can
|
|
311
|
+
// put a separator AFTER each element; putting one BETWEEN N elements is
|
|
312
|
+
// a reduction over strings, and the language had none: `sum` is numeric,
|
|
313
|
+
// `+` does not reduce a list, and indexed concatenation needs the arity
|
|
314
|
+
// known in advance. So a generated SQL column list carried a trailing
|
|
315
|
+
// comma and did not parse (use-cases/15-code-generation).
|
|
316
|
+
//
|
|
317
|
+
// IT FOLDS WITH `+` SEEDED WITH `""`, exactly as `sum` folds with `add`
|
|
318
|
+
// seeded with `0`. That is not a figure of speech: the members are
|
|
319
|
+
// rendered by `plusText`, which IS the function `+`'s string branch
|
|
320
|
+
// calls, so the language keeps exactly one answer to "how does a number
|
|
321
|
+
// become text" and the two cannot drift. `join(coll)` is therefore
|
|
322
|
+
// concatenation and no `concat` is needed; `join(coll, "\n")` is lines
|
|
323
|
+
// and no `lines` is needed. ONE BUILTIN, NOT A FAMILY.
|
|
324
|
+
//
|
|
325
|
+
// `split`/`words` is the inverse -- an input-PARSING operation with no
|
|
326
|
+
// generation use -- and is deliberately absent.
|
|
327
|
+
//
|
|
328
|
+
// EMPTY IS `""`, concatenation's identity, the exact parallel of
|
|
329
|
+
// `sum([]) == 0` and the opposite of `least([])`: a fold with an
|
|
330
|
+
// identity can answer the empty bag, and one without has to refuse it.
|
|
331
|
+
//
|
|
332
|
+
// MEMBERS ARE VALIDATED BEFORE THE FOLD, which is the whole reason this
|
|
333
|
+
// is not three lines. `+` with a string on the left RESIDUATES on a
|
|
334
|
+
// container or a null rather than refusing (`"" + {b:1}` and `"x" +
|
|
335
|
+
// null` both reach generation as `mapval_no_gen`), so folding blindly
|
|
336
|
+
// would turn a bad member into a useless late error pointing at the
|
|
337
|
+
// wrong thing. The three verdicts below are that check.
|
|
338
|
+
type MemberVerdict = 'text' | 'never' | 'notyet'
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
// Which of the three a value is.
|
|
342
|
+
//
|
|
343
|
+
// `text` -- `+` would take it, so the fold can.
|
|
344
|
+
//
|
|
345
|
+
// `never` -- a SETTLED value that will never become text: a map, a
|
|
346
|
+
// list, a null. This is `join_member`, class `conflict`, and it names
|
|
347
|
+
// the member rather than the call, because "one of these is not a
|
|
348
|
+
// string" is only actionable if you are told which.
|
|
349
|
+
//
|
|
350
|
+
// `notyet` -- an unresolved kind, a top, a stable residue. NOT a join
|
|
351
|
+
// failure at all: the call stays residual and generation reports
|
|
352
|
+
// `mapval_no_gen`, class `incomplete`, exactly as docs/trust.md
|
|
353
|
+
// requires ("a stable residue ... is ordinary incompleteness"). Getting
|
|
354
|
+
// this split wrong in either direction is the defect that matters here:
|
|
355
|
+
// refusing a residue makes `join` unusable inside a schema, and
|
|
356
|
+
// deferring on a map makes a real error arrive as a shrug.
|
|
357
|
+
function memberVerdict(v: any): MemberVerdict {
|
|
358
|
+
const u = unpref(v)
|
|
359
|
+
if (undefined !== plusText(u)) {
|
|
360
|
+
return 'text'
|
|
361
|
+
}
|
|
362
|
+
if (true === u?.isMap || true === u?.isList || true === u?.isNull) {
|
|
363
|
+
return 'never'
|
|
364
|
+
}
|
|
365
|
+
return 'notyet'
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
// The separator is a STRING or it is nothing.
|
|
370
|
+
//
|
|
371
|
+
// A number would render perfectly well through `+`, and is still
|
|
372
|
+
// refused: the separator is not a member of the fold, it is the
|
|
373
|
+
// parameter naming the text between members, and `join(x, 5)` is far
|
|
374
|
+
// likelier a mistake than an intent to separate with "5". `pick`'s key
|
|
375
|
+
// argument draws the same line for the same reason. This is the
|
|
376
|
+
// direction that can be loosened later without breaking a document;
|
|
377
|
+
// the other direction cannot.
|
|
378
|
+
function sepVerdict(v: any): MemberVerdict {
|
|
379
|
+
const u = unpref(v)
|
|
380
|
+
if (u?.isVal && u.isScalar) {
|
|
381
|
+
return 'string' === typeof u.peg ? 'text' : 'never'
|
|
382
|
+
}
|
|
383
|
+
if (true === u?.isMap || true === u?.isList || true === u?.isNull) {
|
|
384
|
+
return 'never'
|
|
385
|
+
}
|
|
386
|
+
return 'notyet'
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class JoinFuncVal extends FuncBaseVal {
|
|
391
|
+
isJoinFunc = true
|
|
392
|
+
|
|
393
|
+
// The bag must settle before it is folded, exactly as it must before
|
|
394
|
+
// it is summed or projected.
|
|
395
|
+
staged = true
|
|
396
|
+
|
|
397
|
+
constructor(
|
|
398
|
+
spec: ValSpec,
|
|
399
|
+
ctx?: AontuContext
|
|
400
|
+
) {
|
|
401
|
+
super(spec, ctx)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
// A `make` OVERRIDE, WHERE ITS AggFuncVal SIBLINGS HAVE NONE, and the
|
|
406
|
+
// difference is worth stating because it is not arbitrary. `sum`,
|
|
407
|
+
// `pick` and the rest are staged and nothing more: they residuate
|
|
408
|
+
// before `unify` reaches the rebuild branch, so a `make` there would
|
|
409
|
+
// be unreachable code pretending to be a contract, and the base's
|
|
410
|
+
// `func:<name>` refusal is the loud answer if that ever changes.
|
|
411
|
+
//
|
|
412
|
+
// `join` is the first builtin that is BOTH staged AND defers its
|
|
413
|
+
// resolution (see `deferResolve`). A deferred call has settled
|
|
414
|
+
// arguments, so it does reach the rebuild branch, and without this it
|
|
415
|
+
// raised `func:join` on the first document with an unresolved member
|
|
416
|
+
// — where the Go port residuated and reported `mapval_no_gen`.
|
|
417
|
+
// Opposite answers on `join($.m, ",")` with `m: [string]`, caught by
|
|
418
|
+
// running both engines rather than by either test suite. `super` and
|
|
419
|
+
// bare `id`, the two other deferring calls, each carry the same
|
|
420
|
+
// override for the same reason.
|
|
421
|
+
make(_ctx: AontuContext, spec: ValSpec): Val {
|
|
422
|
+
return new JoinFuncVal(spec)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
funcname() {
|
|
427
|
+
return 'join'
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
// The base does not drive: `unify` drives by hand, because a staged
|
|
432
|
+
// func must advance what it is waiting on every pass rather than only
|
|
433
|
+
// on the pass it fires.
|
|
434
|
+
prepare(_ctx: AontuContext, _args: Val[]) {
|
|
435
|
+
return null
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
unify(peer: Val, ctx: AontuContext): Val {
|
|
440
|
+
// BOTH arguments, unlike `pick`, which drives only its bag: `pick`'s
|
|
441
|
+
// key is a bare word the parser has already made a string, while a
|
|
442
|
+
// separator is an ordinary expression and `join($.rows, $.sep)` has
|
|
443
|
+
// to wait for it.
|
|
444
|
+
const ready = this.driveStagedArgs(ctx, 2)
|
|
445
|
+
|
|
446
|
+
if (!ready || !ctx.settle) {
|
|
447
|
+
return this.residuate(peer, ctx)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return super.unify(peer, ctx)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
// The `notyet` half of the verdict, taken before `resolve` runs: with
|
|
455
|
+
// arguments settled but a member still a kind, the call rides the
|
|
456
|
+
// ordinary args-not-done path and residuates, which is what makes an
|
|
457
|
+
// unresolved member ordinary incompleteness rather than a refusal.
|
|
458
|
+
deferResolve(_ctx: AontuContext, args?: Val[]): boolean {
|
|
459
|
+
const children = bagChildren(args?.[0])
|
|
460
|
+
if (undefined === children) {
|
|
461
|
+
// Not a bag at all: let `resolve` say so rather than waiting for
|
|
462
|
+
// a settling that has already happened.
|
|
463
|
+
return false
|
|
464
|
+
}
|
|
465
|
+
const sep = args?.[1]
|
|
466
|
+
if (undefined !== sep && 'notyet' === sepVerdict(sep)) {
|
|
467
|
+
return true
|
|
468
|
+
}
|
|
469
|
+
return children.some((c) => 'notyet' === memberVerdict(c))
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
resolve(ctx: AontuContext, args: Val[]) {
|
|
474
|
+
const children = bagChildren(args?.[0])
|
|
475
|
+
|
|
476
|
+
if (undefined === children) {
|
|
477
|
+
return this.place(makeNilErr(ctx, 'aggregate_data', this, undefined,
|
|
478
|
+
'join'))
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Arity is [1,2], so a second argument is present or the separator
|
|
482
|
+
// is the empty string -- which makes `join(coll)` concatenation.
|
|
483
|
+
// The separator's SHAPE is settled before resolve runs: the
|
|
484
|
+
// signature gate refuses every concrete non-string
|
|
485
|
+
// (docs/design/SIGNATURES.0.md; join(d: map|list, sep?: string)),
|
|
486
|
+
// and deferResolve holds the call while sepVerdict answers notyet
|
|
487
|
+
// -- so a present separator here is text.
|
|
488
|
+
let sep = ''
|
|
489
|
+
if (1 < args.length) {
|
|
490
|
+
sep = unpref(args[1]).peg
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// NO NIL-MEMBER GUARD, WHERE `sum` HAS ONE, and the difference is
|
|
494
|
+
// the fold's shape rather than an oversight. `sum` folds with
|
|
495
|
+
// `arith`, which MINTS a nil part-way through — a non-numeric child
|
|
496
|
+
// or an overflow — so it has to stop and return it. `join` folds
|
|
497
|
+
// already-unified values, and a nil among a list's elements
|
|
498
|
+
// collapses the list before this call resolves: `join([least([])],
|
|
499
|
+
// ",")` reports `aggregate_empty` at the member's own path,
|
|
500
|
+
// `$.o.0`, and never reaches here. A guard was written, the
|
|
501
|
+
// ADR-002 gate found it unexecuted, and probing confirmed no
|
|
502
|
+
// spelling reaches it, so it is removed rather than excused.
|
|
503
|
+
const parts: string[] = []
|
|
504
|
+
for (const child of children) {
|
|
505
|
+
const u: any = unpref(child)
|
|
506
|
+
const text = plusText(u)
|
|
507
|
+
if (undefined === text) {
|
|
508
|
+
return this.place(makeNilErr(ctx, 'join_member', this, undefined,
|
|
509
|
+
'join', { member: String(u?.canon) }))
|
|
510
|
+
}
|
|
511
|
+
parts.push(text)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Every part is already a string, so this IS the `+` fold seeded
|
|
515
|
+
// with `""` -- written as one concatenation because a loop of `+`
|
|
516
|
+
// over settled strings cannot differ from it, and because the Go
|
|
517
|
+
// twin's strings.Join must produce the same bytes.
|
|
518
|
+
return this.place(new StringVal({ peg: parts.join(sep) }))
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
|
|
297
523
|
// The three the registry names. Each is its operation and nothing else.
|
|
298
524
|
class SumFuncVal extends AggFuncVal {
|
|
299
525
|
constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'sum') }
|
|
@@ -307,11 +533,12 @@ class GreatestFuncVal extends AggFuncVal {
|
|
|
307
533
|
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
308
534
|
super(spec, ctx, 'greatest')
|
|
309
535
|
}
|
|
310
|
-
} /* node:coverage ignore next
|
|
536
|
+
} /* node:coverage ignore next 10 */
|
|
311
537
|
|
|
312
538
|
|
|
313
539
|
export {
|
|
314
540
|
AggFuncVal,
|
|
541
|
+
JoinFuncVal,
|
|
315
542
|
PickFuncVal,
|
|
316
543
|
SumFuncVal,
|
|
317
544
|
LeastFuncVal,
|
package/src/val/BagVal.ts
CHANGED
|
@@ -30,6 +30,17 @@ abstract class BagVal extends FeatureVal {
|
|
|
30
30
|
closed: boolean = false
|
|
31
31
|
optionalKeys: string[] = []
|
|
32
32
|
|
|
33
|
+
// ALIAS DECLARATIONS, by key. `%uint8: …` binds a name for this file
|
|
34
|
+
// and is not a field of the document: it does not generate and does
|
|
35
|
+
// not appear in canon, so a document using aliases and its expanded
|
|
36
|
+
// twin are the SAME document and hash identically
|
|
37
|
+
// (docs/design/ALIASES.0.md §4).
|
|
38
|
+
//
|
|
39
|
+
// Keyed on the map rather than marked on the value, because a
|
|
40
|
+
// reference copies the value it resolves to -- a mark riding the
|
|
41
|
+
// value would erase the referring field along with the declaration.
|
|
42
|
+
aliasKeys: string[] = []
|
|
43
|
+
|
|
33
44
|
spread = {
|
|
34
45
|
cj: (undefined as Val | undefined),
|
|
35
46
|
}
|
|
@@ -107,6 +118,17 @@ abstract class BagVal extends FeatureVal {
|
|
|
107
118
|
this.mark.hide !== peer.mark.hide) {
|
|
108
119
|
return false
|
|
109
120
|
}
|
|
121
|
+
// The SPREAD is part of the shape (BUGS.md §52 regime 4): `[]`
|
|
122
|
+
// and `[&: T]` share an empty key set, and calling them the same
|
|
123
|
+
// value collapsed the disjunction to its first arm before any
|
|
124
|
+
// data could pick -- the vacuous-schema hole. Two spreads are the
|
|
125
|
+
// same when their canons are.
|
|
126
|
+
const scj: any = (this as any).spread?.cj
|
|
127
|
+
const pcj: any = peer.spread?.cj
|
|
128
|
+
if ((null == scj) !== (null == pcj) ||
|
|
129
|
+
(null != scj && null != pcj && scj.canon !== pcj.canon)) {
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
110
132
|
|
|
111
133
|
const keys = Object.keys(this.peg)
|
|
112
134
|
if (keys.length !== Object.keys(peer.peg).length) {
|
|
@@ -157,6 +179,14 @@ abstract class BagVal extends FeatureVal {
|
|
|
157
179
|
continue
|
|
158
180
|
}
|
|
159
181
|
|
|
182
|
+
// An alias declaration contributes no field, and unlike a marked
|
|
183
|
+
// one it is skipped even under `probe`: the probe descends
|
|
184
|
+
// through output marks to check what a `--at` anchor really
|
|
185
|
+
// holds, and an alias is not part of the document at all.
|
|
186
|
+
if (this.aliasKeys.includes('' + p)) {
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
|
|
160
190
|
const optional = this.optionalKeys.includes('' + p)
|
|
161
191
|
|
|
162
192
|
// Lists append compactly: a skipped element (hidden, dropped
|
|
@@ -192,15 +222,7 @@ abstract class BagVal extends FeatureVal {
|
|
|
192
222
|
// atom decides, in ConjunctVal.gen. Any OTHER conjunct is
|
|
193
223
|
// unresolved and falls through to the residue error below,
|
|
194
224
|
// exactly as before.
|
|
195
|
-
else if (child
|
|
196
|
-
|| child.isMap
|
|
197
|
-
|| child.isList
|
|
198
|
-
|| child.isPref
|
|
199
|
-
|| child.isRef
|
|
200
|
-
|| child.isDisjunct
|
|
201
|
-
|| child.isNil
|
|
202
|
-
|| undefined !== sizingResidue(child)
|
|
203
|
-
) {
|
|
225
|
+
else if (bagGenable(child)) {
|
|
204
226
|
// An optional child is generated in an isolated collect context so an
|
|
205
227
|
// unresolved inner value (a bare type that survived unification, e.g.
|
|
206
228
|
// an absent optional sub-map's `field: string`) is dropped rather than
|
|
@@ -281,3 +303,30 @@ export function sizingResidue(v: any): { con: any, bag: any } | undefined {
|
|
|
281
303
|
return undefined !== con && (true === bag?.isMap || true === bag?.isList) ?
|
|
282
304
|
{ con, bag } : undefined
|
|
283
305
|
}
|
|
306
|
+
|
|
307
|
+
// bagGenable is the bag's generability whitelist. A graph atom
|
|
308
|
+
// (RELATIONS P2) is exactly as generable as the value it carries: the
|
|
309
|
+
// atom is transparent at generation, so wrapping a field's value in
|
|
310
|
+
// acyclic() must not change whether the bag accepts it -- an unmet
|
|
311
|
+
// rel() refuses required generation with or without the atom, and a
|
|
312
|
+
// BARE atom generates nothing and is dropped, exactly as an unmet
|
|
313
|
+
// rel() under an optional key is.
|
|
314
|
+
export function bagGenable(child: any): boolean {
|
|
315
|
+
if (true === child.isGraphAtom) {
|
|
316
|
+
return undefined === child.held || bagGenable(child.held)
|
|
317
|
+
}
|
|
318
|
+
// The recursive residual carries its own generation refusal
|
|
319
|
+
// (recursion_unexpanded), which names the schema and the site --
|
|
320
|
+
// the bag's generic residue error would bury both.
|
|
321
|
+
if (true === child.isRecurse) {
|
|
322
|
+
return true
|
|
323
|
+
}
|
|
324
|
+
return true === child.isScalar
|
|
325
|
+
|| true === child.isMap
|
|
326
|
+
|| true === child.isList
|
|
327
|
+
|| true === child.isPref
|
|
328
|
+
|| true === child.isRef
|
|
329
|
+
|| true === child.isDisjunct
|
|
330
|
+
|| true === child.isNil
|
|
331
|
+
|| undefined !== sizingResidue(child)
|
|
332
|
+
}
|
package/src/val/ConstraintVal.ts
CHANGED
|
@@ -544,6 +544,16 @@ function stringLeaf(v: any): boolean {
|
|
|
544
544
|
return true === v?.isScalar && 'string' === typeof v.peg && v.isString
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
+
// The VALUE-admission widening of stringLeaf: a path value is a string
|
|
548
|
+
// with more structure (path <: string, ADR-016), so the string-domain
|
|
549
|
+
// constraints admit one -- `refer() & re("auth$")` must still check a
|
|
550
|
+
// link whose value is now a path. The ARGUMENT sites keep the strict
|
|
551
|
+
// test: a pattern, a bound or an exclusion is spelled with a plain
|
|
552
|
+
// string, never a path.
|
|
553
|
+
function stringishLeaf(v: any): boolean {
|
|
554
|
+
return stringLeaf(v) || (true === v?.isScalar && true === v.isPath)
|
|
555
|
+
}
|
|
556
|
+
|
|
547
557
|
|
|
548
558
|
// Scalar identity: leaf AND value, the lattice's own rule (1 and 1.0
|
|
549
559
|
// are different scalars). Exact numeric comparison decides the value
|
|
@@ -552,6 +562,12 @@ function sameScalar(a: any, b: any): boolean {
|
|
|
552
562
|
if (numericLeaf(a) && numericLeaf(b)) {
|
|
553
563
|
return towerRank(a) === towerRank(b) && 0 === cmpNumeric(a, b)
|
|
554
564
|
}
|
|
565
|
+
if (true === a?.isPath || true === b?.isPath) {
|
|
566
|
+
// Path identity is kind AND spelling (ADR-016): `neq(path($.x))`
|
|
567
|
+
// excludes exactly that address, and never a plain string that
|
|
568
|
+
// happens to spell it.
|
|
569
|
+
return true === a?.isPath && true === b?.isPath && a.peg === b.peg
|
|
570
|
+
}
|
|
555
571
|
if (stringLeaf(a) && stringLeaf(b)) {
|
|
556
572
|
return a.peg === b.peg
|
|
557
573
|
}
|
|
@@ -745,7 +761,7 @@ class ConstraintVal extends FeatureVal {
|
|
|
745
761
|
}
|
|
746
762
|
let domain: 'number' | 'string' | undefined = undefined
|
|
747
763
|
for (const a of args) {
|
|
748
|
-
const d = numericLeaf(a) ? 'number' :
|
|
764
|
+
const d = numericLeaf(a) ? 'number' : stringishLeaf(a) ? 'string' : undefined
|
|
749
765
|
if (null == d || (null != domain && d !== domain)) {
|
|
750
766
|
return bad('invalid-arg')
|
|
751
767
|
}
|
|
@@ -828,7 +844,7 @@ class ConstraintVal extends FeatureVal {
|
|
|
828
844
|
return
|
|
829
845
|
}
|
|
830
846
|
|
|
831
|
-
const domain = numericLeaf(a) ? 'number' :
|
|
847
|
+
const domain = numericLeaf(a) ? 'number' : stringishLeaf(a) ? 'string' : undefined
|
|
832
848
|
if (null == domain) {
|
|
833
849
|
return bad('invalid-arg')
|
|
834
850
|
}
|
|
@@ -852,7 +868,20 @@ class ConstraintVal extends FeatureVal {
|
|
|
852
868
|
// residual is stable and the ladder is total.
|
|
853
869
|
let out: Val
|
|
854
870
|
|
|
855
|
-
|
|
871
|
+
// A rel residual or a graph atom HOLDS constraints for the value
|
|
872
|
+
// they stand for (RELATIONS P1/P2): hand the drive over before
|
|
873
|
+
// any settling, exactly as a container does, so `rel(t) & re(x)`
|
|
874
|
+
// reads the same in either order. Reached through the INCLUDE
|
|
875
|
+
// flow (a loaded schema's conjunct re-drives with the constraint
|
|
876
|
+
// on the left) -- inline documents route rel/atom peers through
|
|
877
|
+
// unite's b-drives first, which is why use-cases/12-relations
|
|
878
|
+
// refused without this arm while every inline row stayed green;
|
|
879
|
+
// pinned by constraint-hands-drive-to-rel-and-atom.
|
|
880
|
+
if (true === (peer as any)?.isRel
|
|
881
|
+
|| true === (peer as any)?.isGraphAtom) {
|
|
882
|
+
out = (peer as any).unify(this, ctx)
|
|
883
|
+
}
|
|
884
|
+
else if (null != this.pending) {
|
|
856
885
|
out = this.settle(peer, ctx)
|
|
857
886
|
}
|
|
858
887
|
else if (null != this.invalid) {
|
|
@@ -975,7 +1004,7 @@ class ConstraintVal extends FeatureVal {
|
|
|
975
1004
|
// Only a string among the scalars has a length, and it is counted
|
|
976
1005
|
// in CODE POINTS -- not UTF-16 units (this host's native count)
|
|
977
1006
|
// and not bytes (Go's). Iterating a string yields code points.
|
|
978
|
-
if (!
|
|
1007
|
+
if (!stringishLeaf(peer)) {
|
|
979
1008
|
return this.fail(ctx, peer)
|
|
980
1009
|
}
|
|
981
1010
|
if (!stateAdmits(this.count, countVal([...peer.peg].length))) {
|
|
@@ -1710,7 +1739,7 @@ function constraintAdmitsScalar(
|
|
|
1710
1739
|
// the count.
|
|
1711
1740
|
function stateAdmits(s: ConstraintState, peer: any): boolean {
|
|
1712
1741
|
const domainOf = numericLeaf(peer) ? 'number' :
|
|
1713
|
-
|
|
1742
|
+
stringishLeaf(peer) ? 'string' : undefined
|
|
1714
1743
|
|
|
1715
1744
|
if (null == s.domain) {
|
|
1716
1745
|
// A sizing residual has no domain, and admits any scalar the sizing
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// CONTAINER KINDS (docs/design/PATHS.0.md). `{}` and `[]` are the
|
|
4
|
+
// container UNITS: they admit any map (or list) AND generate empty
|
|
5
|
+
// when nothing arrives. `map()` and `list()` are the container KINDS
|
|
6
|
+
// proper: they admit exactly the same values and default to NOTHING,
|
|
7
|
+
// as `string` does -- the spelling of "this must be a map, and it
|
|
8
|
+
// must be supplied", which the unit cannot say because an unmet unit
|
|
9
|
+
// silently manufactures its empty value.
|
|
10
|
+
//
|
|
11
|
+
// The vacuous call is the kind; the literal is the unit. That is the
|
|
12
|
+
// whole convention, and it is why neither function takes arguments:
|
|
13
|
+
// element constraints already belong to the spreads (`{&: V}`,
|
|
14
|
+
// `[&: V]`), and a second spelling of them here would drift.
|
|
15
|
+
//
|
|
16
|
+
// No null/top/nil arms, as RelVal records: unite's dispatch ladder
|
|
17
|
+
// absorbs those peers before any Val's own unify is consulted, and
|
|
18
|
+
// FuncBaseVal returns a done resolution directly against a top peer.
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
Val,
|
|
22
|
+
ValSpec,
|
|
23
|
+
} from '../type'
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
DONE,
|
|
27
|
+
} from '../type'
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
AontuContext,
|
|
31
|
+
} from '../ctx'
|
|
32
|
+
|
|
33
|
+
import { makeNilErr } from '../err'
|
|
34
|
+
|
|
35
|
+
import { FeatureVal } from './FeatureVal'
|
|
36
|
+
import { FuncBaseVal } from './FuncBaseVal'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MapKindVal extends FeatureVal {
|
|
40
|
+
isContainerKind = true
|
|
41
|
+
isMapKind = true
|
|
42
|
+
|
|
43
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
44
|
+
super(spec, ctx)
|
|
45
|
+
this.dc = DONE
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
unify(peer: Val, ctx: AontuContext): Val {
|
|
49
|
+
const p: any = peer
|
|
50
|
+
if (true === p.isMap) {
|
|
51
|
+
return peer
|
|
52
|
+
}
|
|
53
|
+
if (true === p.isMapKind) {
|
|
54
|
+
return this
|
|
55
|
+
}
|
|
56
|
+
// The unit's own refusal code: a kind mismatch here is the same
|
|
57
|
+
// fact `{} & 1` reports, and two codes for one fact would drift.
|
|
58
|
+
return makeNilErr(ctx, 'map', this, peer)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get canon() {
|
|
62
|
+
return 'map()'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
same(peer: any): boolean {
|
|
66
|
+
return true === peer?.isMapKind
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
} /* node:coverage ignore next 4 */
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ListKindVal extends FeatureVal {
|
|
73
|
+
isContainerKind = true
|
|
74
|
+
isListKind = true
|
|
75
|
+
|
|
76
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
77
|
+
super(spec, ctx)
|
|
78
|
+
this.dc = DONE
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
unify(peer: Val, ctx: AontuContext): Val {
|
|
82
|
+
const p: any = peer
|
|
83
|
+
if (true === p.isList) {
|
|
84
|
+
return peer
|
|
85
|
+
}
|
|
86
|
+
if (true === p.isListKind) {
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
return makeNilErr(ctx, 'list', this, peer)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get canon() {
|
|
93
|
+
return 'list()'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
same(peer: any): boolean {
|
|
97
|
+
return true === peer?.isListKind
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
} /* node:coverage ignore next 4 */
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class MapFuncVal extends FuncBaseVal {
|
|
104
|
+
isMapFunc = true
|
|
105
|
+
|
|
106
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
107
|
+
super(spec, ctx)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
make(_ctx: AontuContext, spec: ValSpec): Val {
|
|
111
|
+
return new MapFuncVal(spec)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
funcname() {
|
|
115
|
+
return 'map'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
resolve(ctx: AontuContext, _args: Val[]) {
|
|
119
|
+
const out = new MapKindVal({}, ctx)
|
|
120
|
+
out.site = this.site
|
|
121
|
+
out.path = this.path
|
|
122
|
+
return out
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
} /* node:coverage ignore next 4 */
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ListFuncVal extends FuncBaseVal {
|
|
129
|
+
isListFunc = true
|
|
130
|
+
|
|
131
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
132
|
+
super(spec, ctx)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
make(_ctx: AontuContext, spec: ValSpec): Val {
|
|
136
|
+
return new ListFuncVal(spec)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
funcname() {
|
|
140
|
+
return 'list'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
resolve(ctx: AontuContext, _args: Val[]) {
|
|
144
|
+
const out = new ListKindVal({}, ctx)
|
|
145
|
+
out.site = this.site
|
|
146
|
+
out.path = this.path
|
|
147
|
+
return out
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
} /* node:coverage ignore next 8 */
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
MapKindVal,
|
|
155
|
+
ListKindVal,
|
|
156
|
+
MapFuncVal,
|
|
157
|
+
ListFuncVal,
|
|
158
|
+
}
|
package/src/val/CopyFuncVal.ts
CHANGED
|
@@ -65,13 +65,6 @@ class CopyFuncVal extends FuncBaseVal {
|
|
|
65
65
|
// console.log('WALK', val)
|
|
66
66
|
val.mark.type = false
|
|
67
67
|
val.mark.hide = false
|
|
68
|
-
// `copy()` CLEARS IDENTITY (G4 phase 1, clearing rule 2),
|
|
69
|
-
// consistent with its clearing of the marks: a copy of an
|
|
70
|
-
// entity is a second value shaped like it, and leaving the id
|
|
71
|
-
// on would merge the copy straight back into the original —
|
|
72
|
-
// making `copy()` a no-op for exactly the values it exists to
|
|
73
|
-
// detach.
|
|
74
|
-
val.entity = undefined
|
|
75
68
|
return val
|
|
76
69
|
})
|
|
77
70
|
}
|