aontu 0.52.1 → 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/README.md +88 -0
- package/bin/aontu-mcp.js +4 -0
- package/dist/agentsmd.d.ts +16 -0
- package/dist/agentsmd.js +107 -0
- package/dist/agentsmd.js.map +1 -0
- package/dist/aontu.d.ts +15 -3
- package/dist/aontu.js +126 -5
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +45 -1
- package/dist/cli.js +2845 -69
- package/dist/cli.js.map +1 -1
- package/dist/ctx.d.ts +18 -0
- package/dist/ctx.js +44 -0
- package/dist/ctx.js.map +1 -1
- package/dist/diff.d.ts +22 -0
- package/dist/diff.js +141 -0
- package/dist/diff.js.map +1 -0
- package/dist/err.d.ts +3 -1
- package/dist/err.js +45 -8
- package/dist/err.js.map +1 -1
- package/dist/graph.d.ts +13 -0
- package/dist/graph.js +110 -0
- package/dist/graph.js.map +1 -0
- package/dist/hcanon.d.ts +3 -0
- package/dist/hcanon.js +145 -0
- package/dist/hcanon.js.map +1 -0
- package/dist/hints.js +235 -8
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.d.ts +20 -0
- package/dist/jsonschema.js +425 -0
- package/dist/jsonschema.js.map +1 -0
- package/dist/lang.js +955 -110
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +9 -2
- package/dist/lsp.js +343 -48
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.d.ts +20 -0
- package/dist/mcp-server.js +147 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp.d.ts +42 -0
- package/dist/mcp.js +941 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mod-tool.d.ts +58 -0
- package/dist/mod-tool.js +532 -0
- package/dist/mod-tool.js.map +1 -0
- package/dist/mod.d.ts +35 -0
- package/dist/mod.js +345 -0
- package/dist/mod.js.map +1 -0
- package/dist/patch.d.ts +49 -0
- package/dist/patch.js +506 -0
- package/dist/patch.js.map +1 -0
- package/dist/provenance.d.ts +41 -0
- package/dist/provenance.js +336 -0
- package/dist/provenance.js.map +1 -0
- package/dist/query.d.ts +27 -0
- package/dist/query.js +294 -0
- package/dist/query.js.map +1 -0
- package/dist/reach.d.ts +15 -0
- package/dist/reach.js +168 -0
- package/dist/reach.js.map +1 -0
- package/dist/relation.d.ts +23 -0
- package/dist/relation.js +230 -0
- package/dist/relation.js.map +1 -0
- package/dist/report-sarif.d.ts +14 -0
- package/dist/report-sarif.js +102 -0
- package/dist/report-sarif.js.map +1 -0
- 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/site.d.ts +4 -0
- package/dist/site.js +31 -0
- package/dist/site.js.map +1 -1
- package/dist/std.d.ts +1 -0
- package/dist/std.js +129 -0
- package/dist/std.js.map +1 -0
- package/dist/subsume.d.ts +39 -0
- package/dist/subsume.js +573 -0
- package/dist/subsume.js.map +1 -0
- package/dist/trim.d.ts +19 -0
- package/dist/trim.js +155 -0
- package/dist/trim.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.d.ts +17 -1
- package/dist/type.js.map +1 -1
- package/dist/unify.d.ts +2 -1
- package/dist/unify.js +234 -38
- package/dist/unify.js.map +1 -1
- package/dist/utility.d.ts +8 -1
- package/dist/utility.js +68 -1
- package/dist/utility.js.map +1 -1
- package/dist/val/AggFuncVal.d.ts +44 -0
- package/dist/val/AggFuncVal.js +364 -0
- package/dist/val/AggFuncVal.js.map +1 -0
- package/dist/val/ArithFuncVal.d.ts +31 -0
- package/dist/val/ArithFuncVal.js +62 -0
- package/dist/val/ArithFuncVal.js.map +1 -0
- package/dist/val/BagVal.d.ts +7 -0
- package/dist/val/BagVal.js +150 -11
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/CloseFuncVal.js +9 -1
- package/dist/val/CloseFuncVal.js.map +1 -1
- package/dist/val/ConjunctVal.d.ts +1 -1
- package/dist/val/ConjunctVal.js +19 -0
- package/dist/val/ConjunctVal.js.map +1 -1
- package/dist/val/ConstraintVal.d.ts +7 -1
- package/dist/val/ConstraintVal.js +523 -45
- 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.d.ts +1 -2
- package/dist/val/CopyFuncVal.js.map +1 -1
- package/dist/val/Decimal.d.ts +1 -0
- package/dist/val/Decimal.js +13 -0
- package/dist/val/Decimal.js.map +1 -1
- package/dist/val/DeprecateFuncVal.d.ts +11 -0
- package/dist/val/DeprecateFuncVal.js +47 -0
- package/dist/val/DeprecateFuncVal.js.map +1 -0
- package/dist/val/DisjunctVal.d.ts +1 -2
- package/dist/val/DisjunctVal.js +269 -53
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/EachFuncVal.d.ts +15 -0
- package/dist/val/EachFuncVal.js +75 -0
- package/dist/val/EachFuncVal.js.map +1 -0
- package/dist/val/ExpectVal.js +62 -4
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FeatureVal.js +1 -1
- package/dist/val/FeatureVal.js.map +1 -1
- package/dist/val/FilterFuncVal.d.ts +15 -0
- package/dist/val/FilterFuncVal.js +91 -0
- package/dist/val/FilterFuncVal.js.map +1 -0
- package/dist/val/FuncBaseVal.d.ts +7 -1
- package/dist/val/FuncBaseVal.js +183 -2
- 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/HideFuncVal.js.map +1 -1
- package/dist/val/JunctionVal.js +24 -1
- package/dist/val/JunctionVal.js.map +1 -1
- package/dist/val/KeyFuncVal.d.ts +1 -1
- package/dist/val/KeyFuncVal.js +38 -30
- package/dist/val/KeyFuncVal.js.map +1 -1
- package/dist/val/ListVal.js +94 -14
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/LowerFuncVal.js.map +1 -1
- package/dist/val/MapVal.d.ts +1 -0
- package/dist/val/MapVal.js +146 -14
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/MatchFuncVal.d.ts +15 -0
- package/dist/val/MatchFuncVal.js +107 -0
- package/dist/val/MatchFuncVal.js.map +1 -0
- package/dist/val/MoveFuncVal.js.map +1 -1
- package/dist/val/NilVal.js +24 -0
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/OpBaseVal.d.ts +1 -1
- package/dist/val/OpBaseVal.js +19 -1
- package/dist/val/OpBaseVal.js.map +1 -1
- package/dist/val/OpenFuncVal.js +4 -1
- package/dist/val/OpenFuncVal.js.map +1 -1
- package/dist/val/PackFuncVal.d.ts +15 -0
- package/dist/val/PackFuncVal.js +108 -0
- package/dist/val/PackFuncVal.js.map +1 -0
- 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/PlaceVal.d.ts +13 -0
- package/dist/val/PlaceVal.js +131 -0
- package/dist/val/PlaceVal.js.map +1 -0
- package/dist/val/PlusOpVal.d.ts +2 -1
- package/dist/val/PlusOpVal.js +61 -37
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefFuncVal.js.map +1 -1
- package/dist/val/PrefVal.d.ts +4 -2
- package/dist/val/PrefVal.js +223 -41
- 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 +3 -2
- package/dist/val/RefVal.js +181 -66
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ReferFuncVal.d.ts +59 -0
- package/dist/val/ReferFuncVal.js +604 -0
- package/dist/val/ReferFuncVal.js.map +1 -0
- package/dist/val/ScalarKindVal.d.ts +4 -3
- package/dist/val/ScalarKindVal.js +12 -12
- 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/TopVal.js.map +1 -1
- package/dist/val/TypeFuncVal.js.map +1 -1
- package/dist/val/UpperFuncVal.js.map +1 -1
- package/dist/val/Val.d.ts +7 -1
- package/dist/val/Val.js +170 -2
- package/dist/val/Val.js.map +1 -1
- package/dist/val/VarVal.js.map +1 -1
- package/dist/val/arith.d.ts +6 -0
- package/dist/val/arith.js +173 -0
- package/dist/val/arith.js.map +1 -0
- package/dist/vet.d.ts +45 -0
- package/dist/vet.js +802 -0
- package/dist/vet.js.map +1 -0
- package/dist/view.d.ts +85 -0
- package/dist/view.js +1948 -0
- package/dist/view.js.map +1 -0
- package/dist/walk.d.ts +2 -0
- package/dist/walk.js +91 -0
- package/dist/walk.js.map +1 -0
- package/grammar/aontu.gbnf +138 -0
- package/grammar/aontu.lark +118 -0
- package/grammar/aontu.tmLanguage.json +184 -0
- package/package.json +30 -8
- package/skill/SKILL.md +37 -0
- package/skill/error-codes.md +62 -0
- package/skill/examples.md +99 -0
- package/skill/grammar-card.md +56 -0
- package/src/agentsmd.ts +135 -0
- package/src/aontu.ts +166 -5
- package/src/cli.ts +3324 -72
- package/src/ctx.ts +93 -1
- package/src/diff.ts +198 -0
- package/src/err.ts +50 -8
- package/src/graph.ts +185 -0
- package/src/hcanon.ts +167 -0
- package/src/hints.ts +291 -8
- package/src/jsonschema.ts +552 -0
- package/src/lang.ts +1049 -110
- package/src/lsp.ts +351 -49
- package/src/mcp-server.ts +187 -0
- package/src/mcp.ts +1130 -0
- package/src/mod-tool.ts +718 -0
- package/src/mod.ts +453 -0
- package/src/patch.ts +628 -0
- package/src/provenance.ts +437 -0
- package/src/query.ts +381 -0
- package/src/reach.ts +213 -0
- package/src/relation.ts +298 -0
- package/src/report-sarif.ts +137 -0
- package/src/sig.ts +345 -0
- package/src/sigdecl.ts +11 -0
- package/src/siggate.ts +144 -0
- package/src/site.ts +36 -1
- package/src/std.ts +131 -0
- package/src/subsume.ts +739 -0
- package/src/trim.ts +195 -0
- package/src/tsconfig.json +10 -4
- package/src/type.ts +51 -2
- package/src/unify.ts +253 -38
- package/src/utility.ts +79 -1
- package/src/val/AggFuncVal.ts +546 -0
- package/src/val/ArithFuncVal.ts +108 -0
- package/src/val/BagVal.ts +158 -12
- package/src/val/CloseFuncVal.ts +9 -1
- package/src/val/ConjunctVal.ts +20 -0
- package/src/val/ConstraintVal.ts +576 -48
- package/src/val/ContainerKindVal.ts +158 -0
- package/src/val/CopyFuncVal.ts +0 -1
- package/src/val/Decimal.ts +15 -0
- package/src/val/DeprecateFuncVal.ts +84 -0
- package/src/val/DisjunctVal.ts +282 -59
- package/src/val/EachFuncVal.ts +133 -0
- package/src/val/ExpectVal.ts +65 -8
- package/src/val/FeatureVal.ts +1 -1
- package/src/val/FilterFuncVal.ts +154 -0
- package/src/val/FuncBaseVal.ts +206 -2
- package/src/val/GraphAtomVal.ts +264 -0
- package/src/val/HideFuncVal.ts +0 -2
- package/src/val/JunctionVal.ts +24 -1
- package/src/val/KeyFuncVal.ts +39 -35
- package/src/val/ListVal.ts +101 -15
- package/src/val/LowerFuncVal.ts +0 -1
- package/src/val/MapVal.ts +153 -14
- package/src/val/MatchFuncVal.ts +176 -0
- package/src/val/MoveFuncVal.ts +0 -2
- package/src/val/NilVal.ts +25 -0
- package/src/val/OpBaseVal.ts +20 -1
- package/src/val/OpenFuncVal.ts +4 -2
- package/src/val/PackFuncVal.ts +175 -0
- package/src/val/PathFuncVal.ts +107 -20
- package/src/val/PathVal.ts +221 -0
- package/src/val/PlaceVal.ts +193 -0
- package/src/val/PlusOpVal.ts +67 -39
- package/src/val/PrefFuncVal.ts +0 -1
- package/src/val/PrefVal.ts +251 -60
- package/src/val/RecurseVal.ts +285 -0
- package/src/val/RefVal.ts +183 -78
- package/src/val/ReferFuncVal.ts +732 -0
- package/src/val/ScalarKindVal.ts +12 -13
- package/src/val/SuperFuncVal.ts +137 -13
- package/src/val/TopVal.ts +1 -2
- package/src/val/TypeFuncVal.ts +0 -2
- package/src/val/UpperFuncVal.ts +0 -1
- package/src/val/Val.ts +216 -4
- package/src/val/VarVal.ts +0 -1
- package/src/val/arith.ts +319 -0
- package/src/vet.ts +1025 -0
- package/src/view.ts +2563 -0
- package/src/walk.ts +99 -0
package/src/lang.ts
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
|
|
4
4
|
// import { performance } from 'node:perf_hooks'
|
|
5
5
|
|
|
6
|
+
// Named imports, not `import * as`: the namespace form makes tsc emit
|
|
7
|
+
// the __importStar downlevel helper, whose branches no supported Node
|
|
8
|
+
// takes (the same rule cli.ts records). Aliased because `Path` is
|
|
9
|
+
// already the @tabnas/path plugin below.
|
|
10
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
11
|
+
import {
|
|
12
|
+
basename as pathBasename,
|
|
13
|
+
dirname as pathDirname,
|
|
14
|
+
join as pathJoin,
|
|
15
|
+
resolve as pathResolve,
|
|
16
|
+
sep as pathSep,
|
|
17
|
+
} from 'node:path'
|
|
18
|
+
|
|
6
19
|
import {
|
|
7
20
|
Jsonic,
|
|
8
21
|
Tabnas,
|
|
@@ -14,6 +27,20 @@ import {
|
|
|
14
27
|
} from '@tabnas/jsonic'
|
|
15
28
|
|
|
16
29
|
|
|
30
|
+
// THE CONFIG-FORMAT READERS (ADR-012). Each is a jsonic plugin for one
|
|
31
|
+
// format, so an included `.toml` or `.yaml` is parsed by a real parser
|
|
32
|
+
// for that format rather than guessed at by this one. `@tabnas/json` is
|
|
33
|
+
// the strict RFC 8259 reader, used for `.json` and `.jsonld`.
|
|
34
|
+
import { funcSig } from './sig'
|
|
35
|
+
import type { FuncSig } from './sig'
|
|
36
|
+
|
|
37
|
+
import { make as makeJsonParser } from '@tabnas/json'
|
|
38
|
+
import { Toml } from '@tabnas/toml'
|
|
39
|
+
import { Jsonc } from '@tabnas/jsonc'
|
|
40
|
+
import { Json5 } from '@tabnas/json5'
|
|
41
|
+
import { Yaml } from '@tabnas/yaml'
|
|
42
|
+
import { Ini } from '@tabnas/ini'
|
|
43
|
+
|
|
17
44
|
import { Debug } from '@tabnas/debug'
|
|
18
45
|
|
|
19
46
|
import {
|
|
@@ -34,6 +61,21 @@ import {
|
|
|
34
61
|
makeMemResolver
|
|
35
62
|
} from '@tabnas/multisource/resolver/mem'
|
|
36
63
|
|
|
64
|
+
// The Aontu-source processor, TAKEN RATHER THAN ALIASED. The obvious
|
|
65
|
+
// spelling is the alias `aon: 'jsonic'`, which multisource resolves
|
|
66
|
+
// through its own processor map -- but `jsonic` is a FORMAT NAME in
|
|
67
|
+
// the include table now, so that alias resolved to the plain-jsonic
|
|
68
|
+
// DATA reader and every `.aon` include was suddenly parsed without the
|
|
69
|
+
// language in it. Naming the function leaves nothing to collide with.
|
|
70
|
+
import {
|
|
71
|
+
makeJsonicProcessor,
|
|
72
|
+
} from '@tabnas/multisource/processor/jsonic'
|
|
73
|
+
|
|
74
|
+
import { STD_SOURCES } from './std'
|
|
75
|
+
import {
|
|
76
|
+
parseModuleRef, resolveModule, modCacheDir, MODULE_REFUSAL_CODES,
|
|
77
|
+
} from './mod'
|
|
78
|
+
|
|
37
79
|
import {
|
|
38
80
|
Expr,
|
|
39
81
|
Op,
|
|
@@ -96,8 +138,23 @@ import { CopyFuncVal } from './val/CopyFuncVal'
|
|
|
96
138
|
import { KeyFuncVal } from './val/KeyFuncVal'
|
|
97
139
|
import { TypeFuncVal } from './val/TypeFuncVal'
|
|
98
140
|
import { HideFuncVal } from './val/HideFuncVal'
|
|
141
|
+
import { DeprecateFuncVal } from './val/DeprecateFuncVal'
|
|
142
|
+
import { ReferFuncVal, RelFuncVal } from './val/ReferFuncVal'
|
|
143
|
+
import { AcyclicFuncVal, InverseFuncVal } from './val/GraphAtomVal'
|
|
144
|
+
import { PackFuncVal } from './val/PackFuncVal'
|
|
145
|
+
import { EachFuncVal } from './val/EachFuncVal'
|
|
146
|
+
import { FilterFuncVal } from './val/FilterFuncVal'
|
|
147
|
+
import { MatchFuncVal } from './val/MatchFuncVal'
|
|
148
|
+
import {
|
|
149
|
+
AddFuncVal, SubFuncVal, MulFuncVal, DivFuncVal, ModFuncVal, RemFuncVal,
|
|
150
|
+
} from './val/ArithFuncVal'
|
|
151
|
+
import {
|
|
152
|
+
SumFuncVal, LeastFuncVal, GreatestFuncVal, PickFuncVal, JoinFuncVal,
|
|
153
|
+
} from './val/AggFuncVal'
|
|
154
|
+
import { PlaceVal } from './val/PlaceVal'
|
|
99
155
|
import { MoveFuncVal } from './val/MoveFuncVal'
|
|
100
156
|
import { PathFuncVal } from './val/PathFuncVal'
|
|
157
|
+
import { MapFuncVal, ListFuncVal } from './val/ContainerKindVal'
|
|
101
158
|
import { PrefFuncVal } from './val/PrefFuncVal'
|
|
102
159
|
import { CloseFuncVal } from './val/CloseFuncVal'
|
|
103
160
|
import { OpenFuncVal } from './val/OpenFuncVal'
|
|
@@ -158,6 +215,13 @@ const CC_0 = 48
|
|
|
158
215
|
const CC_d = 100
|
|
159
216
|
const CC_D = 68
|
|
160
217
|
|
|
218
|
+
// THE ALIAS SIGIL. `%` is part of an alias's name, so the name is one
|
|
219
|
+
// lexeme wherever it appears and its meaning is decided by position:
|
|
220
|
+
// a BINDING in key position (`%uint8: …` declares), a USE in value
|
|
221
|
+
// position (`listen: %uint8` refers). docs/design/ALIASES.0.md §4.
|
|
222
|
+
const CC_PCT = 37
|
|
223
|
+
const ALIAS_RE = /^%[A-Za-z_][A-Za-z0-9_]*/
|
|
224
|
+
|
|
161
225
|
let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
|
|
162
226
|
|
|
163
227
|
jsonic.use(asPlugin(Path))
|
|
@@ -169,6 +233,38 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
|
|
|
169
233
|
let dotRef = (r: Rule, ctx: JsonicContext, terms: any, prefix: boolean) => {
|
|
170
234
|
terms = dropUnfilled(terms)
|
|
171
235
|
if (0 === terms.length) return incompleteNil(r, ctx)
|
|
236
|
+
|
|
237
|
+
// AN ALIAS IS NOT A PATH SEGMENT. `$.%foo` is refused: the alias
|
|
238
|
+
// namespace and the path namespace are disjoint, and an alias is
|
|
239
|
+
// reached by writing `%foo` and only that.
|
|
240
|
+
//
|
|
241
|
+
// The engine spells an alias reference AS a root reference to the
|
|
242
|
+
// declaration -- which is what gives it order independence and a
|
|
243
|
+
// cycle check shared with paths -- but that is an implementation of
|
|
244
|
+
// the name, not a second way to write it. Left writable, the two
|
|
245
|
+
// spellings would drift apart the moment aliases stop being
|
|
246
|
+
// file-shaped, and `$.%b` inside an included file would reach the
|
|
247
|
+
// INCLUDER's `%b` rather than its own, which is exactly the
|
|
248
|
+
// cross-file capture the sigil exists to prevent.
|
|
249
|
+
// `%foo` lexes to the reference itself, so in `$.%foo` it arrives
|
|
250
|
+
// as a TERM rather than as a string segment -- both shapes are
|
|
251
|
+
// checked, since a quoted `$."%foo"` would arrive as the string.
|
|
252
|
+
// Terms here are always Vals -- dropUnfilled has removed the
|
|
253
|
+
// nulls, and the dot rules never hand over a raw string -- so the
|
|
254
|
+
// shapes are exactly three: a RefVal (peg is the segment array), a
|
|
255
|
+
// StringVal (peg is the segment), and anything else (a numeric or
|
|
256
|
+
// exact segment, which cannot be an alias name).
|
|
257
|
+
for (const t of terms) {
|
|
258
|
+
const segs: any[] =
|
|
259
|
+
Array.isArray(t.peg) ? t.peg :
|
|
260
|
+
('string' === typeof t.peg ? [t.peg] : [])
|
|
261
|
+
for (const seg of segs) {
|
|
262
|
+
if ('string' === typeof seg && ALIAS_RE.test(seg)) {
|
|
263
|
+
return addsite(new NilVal({ why: 'alias_in_path' }), r, ctx)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
172
268
|
return addsite(new RefVal({ peg: terms, prefix }), r, ctx)
|
|
173
269
|
}
|
|
174
270
|
|
|
@@ -243,9 +339,47 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
|
|
|
243
339
|
check: (lex: any) => {
|
|
244
340
|
// Guard first, on char codes: this hook runs at every text
|
|
245
341
|
// position, and the common case (any run that cannot be a `0d`
|
|
246
|
-
// literal) must cost two char reads and no
|
|
342
|
+
// literal or an alias) must cost two char reads and no
|
|
343
|
+
// allocation.
|
|
247
344
|
const pnt = lex.pnt
|
|
248
345
|
const src = lex.src
|
|
346
|
+
|
|
347
|
+
// AN ALIAS NAME IS CLAIMED WHOLE, for the same reason the `0d`
|
|
348
|
+
// run below is: the text matcher's ender regexp would otherwise
|
|
349
|
+
// carve `%uint8` at the `%` and emit the sigil as its own token,
|
|
350
|
+
// leaving a bare `uint8` behind -- which is exactly the capture
|
|
351
|
+
// the sigil exists to prevent. Claiming it here, before that
|
|
352
|
+
// ender runs, keeps the name one lexeme.
|
|
353
|
+
//
|
|
354
|
+
// The token's SOURCE is the whole `%name`, which is what makes
|
|
355
|
+
// the same lexeme work in both positions: jsonic keys a pair by
|
|
356
|
+
// the token's source text (`0d1: 5` yields the key `0d1`), so a
|
|
357
|
+
// declaration reads as the key `%uint8`, while a value position
|
|
358
|
+
// calls the function below and gets the reference.
|
|
359
|
+
if (CC_PCT === src.charCodeAt(pnt.sI)) {
|
|
360
|
+
const ares = ALIAS_RE.exec(lex.refwd())
|
|
361
|
+
if (null == ares) {
|
|
362
|
+
return undefined
|
|
363
|
+
}
|
|
364
|
+
const asrc = ares[0]
|
|
365
|
+
const atkn = lex.token(
|
|
366
|
+
'#VL',
|
|
367
|
+
// AN ALIAS REFERENCE IS A PATH REFERENCE. `%uint8` is
|
|
368
|
+
// `$.%uint8`: root-absolute, one segment, spelled with the
|
|
369
|
+
// sigil the declaration is spelled with. Everything the
|
|
370
|
+
// design asks of it -- order independence, alias-of-alias,
|
|
371
|
+
// redeclaration unifying, cycle refusal spanning both
|
|
372
|
+
// namespaces -- is then the reference machinery already in
|
|
373
|
+
// the language, not a second resolver beside it.
|
|
374
|
+
(r: Rule, ctx: JsonicContext) =>
|
|
375
|
+
addsite(new RefVal({ peg: [asrc], absolute: true }), r, ctx),
|
|
376
|
+
asrc,
|
|
377
|
+
pnt)
|
|
378
|
+
pnt.sI += asrc.length
|
|
379
|
+
pnt.cI += asrc.length
|
|
380
|
+
return { done: true, token: atkn }
|
|
381
|
+
}
|
|
382
|
+
|
|
249
383
|
if (CC_0 !== src.charCodeAt(pnt.sI)) {
|
|
250
384
|
return undefined
|
|
251
385
|
}
|
|
@@ -287,6 +421,16 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
|
|
|
287
421
|
v.site.row = null == r.o0 ? -1 : r.o0.rI
|
|
288
422
|
v.site.col = null == r.o0 ? -1 : r.o0.cI
|
|
289
423
|
v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : ''
|
|
424
|
+
// The source text, from the SAME token the row and column above
|
|
425
|
+
// come from. jsonic has carried it all along; not reading it is
|
|
426
|
+
// what left a site uneditable (ts/src/site.ts).
|
|
427
|
+
//
|
|
428
|
+
// A TOKEN WITH NO TEXT IS NO SPAN, in both ports, and the extent is
|
|
429
|
+
// DERIVED from the text rather than read from the token's own `len`
|
|
430
|
+
// — so the Go twin, whose token has no len field, computes the
|
|
431
|
+
// identical number with utf16Len and nothing has to be kept in step.
|
|
432
|
+
v.site.src = null == r.o0 ? '' : r.o0.src
|
|
433
|
+
v.site.len = '' === v.site.src ? -1 : v.site.src.length
|
|
290
434
|
// A keyed rule always carries a path array; a keyless one has none.
|
|
291
435
|
v.path = r.k ? [...r.k.path] : []
|
|
292
436
|
|
|
@@ -365,6 +509,14 @@ help isolate the syntax error.`,
|
|
|
365
509
|
|
|
366
510
|
// TODO: FIX: need a TOP instance to hold path
|
|
367
511
|
'top': { val: () => top() },
|
|
512
|
+
|
|
513
|
+
// G8 phase 3: the placeholder. A BARE `_` is the hole; `"_"`
|
|
514
|
+
// quoted, and any longer bare word containing it, stay text.
|
|
515
|
+
// Reserving it is a breaking change, pinned by place.tsv.
|
|
516
|
+
'_': {
|
|
517
|
+
val: (r: Rule, ctx: JsonicContext) =>
|
|
518
|
+
addsite(new PlaceVal({}), r, ctx)
|
|
519
|
+
},
|
|
368
520
|
}
|
|
369
521
|
},
|
|
370
522
|
|
|
@@ -411,6 +563,14 @@ help isolate the syntax error.`,
|
|
|
411
563
|
move: MoveFuncVal,
|
|
412
564
|
path: PathFuncVal,
|
|
413
565
|
pref: PrefFuncVal,
|
|
566
|
+
|
|
567
|
+
// First-class paths and the container kinds
|
|
568
|
+
// (docs/design/PATHS.0.md). `path(p)` CAPTURES a path as a value;
|
|
569
|
+
// `path()`, `map()` and `list()` are kinds -- the vacuous
|
|
570
|
+
// constructor call admits its values and defaults to nothing,
|
|
571
|
+
// where the container LITERALS `{}`/`[]` default to empty.
|
|
572
|
+
map: MapFuncVal,
|
|
573
|
+
list: ListFuncVal,
|
|
414
574
|
close: CloseFuncVal,
|
|
415
575
|
open: OpenFuncVal,
|
|
416
576
|
super: SuperFuncVal,
|
|
@@ -441,6 +601,72 @@ help isolate the syntax error.`,
|
|
|
441
601
|
// reported with the author's own message, never simplified and
|
|
442
602
|
// never consulted for emptiness or subsumption.
|
|
443
603
|
must: MustConstraintVal,
|
|
604
|
+
|
|
605
|
+
// G3 phase 4: the deprecation mark. Unification-transparent; the
|
|
606
|
+
// record rides the result (Val.deprecation) and canon renders the
|
|
607
|
+
// call back (canonRiders).
|
|
608
|
+
deprecate: DeprecateFuncVal,
|
|
609
|
+
|
|
610
|
+
// G4 phase 2: the checked, typed, LINK-shaped reference. A
|
|
611
|
+
// constraint on a string field: the string must be a TREE ADDRESS
|
|
612
|
+
// (`$.a.b` or `.b`), the address must resolve, and the optional
|
|
613
|
+
// argument flows INTO the target. The field keeps the string.
|
|
614
|
+
refer: ReferFuncVal,
|
|
615
|
+
rel: RelFuncVal,
|
|
616
|
+
|
|
617
|
+
// RELATIONS P2 (docs/design/RELATIONS.0.md §3.3): the graph
|
|
618
|
+
// atoms, conjoined at the field whose key is the predicate they
|
|
619
|
+
// govern. Lattice-inert; the verdict lands at generation.
|
|
620
|
+
acyclic: AcyclicFuncVal,
|
|
621
|
+
inverse: InverseFuncVal,
|
|
622
|
+
|
|
623
|
+
// G8 phase 1: the generation combinators. `pack` makes one keyed
|
|
624
|
+
// child per child of its data, `each` one list element; both clone
|
|
625
|
+
// their template per destination exactly as a spread does, and both
|
|
626
|
+
// wait for the model to settle before they fire (the staging rule,
|
|
627
|
+
// G8 phase 0).
|
|
628
|
+
pack: PackFuncVal,
|
|
629
|
+
each: EachFuncVal,
|
|
630
|
+
|
|
631
|
+
// G8 phase 2: selection. `filter` keeps the children of a bag that
|
|
632
|
+
// unify with a condition; `match` picks the first arm whose
|
|
633
|
+
// pattern the scrutinee unifies with. Both select by
|
|
634
|
+
// UNIFIABILITY, tried in trial mode, so neither adds a predicate
|
|
635
|
+
// language to the one the lattice already is.
|
|
636
|
+
filter: FilterFuncVal,
|
|
637
|
+
match: MatchFuncVal,
|
|
638
|
+
|
|
639
|
+
// The arithmetic family (the review's finding I). Maths beyond `+`
|
|
640
|
+
// arrives as FUNCTIONS — `-` `*` `/` `%` stay reserved — and the
|
|
641
|
+
// family is numeric where the operator is polymorphic, which is
|
|
642
|
+
// what makes `add` more than a second spelling of `+`: it refuses
|
|
643
|
+
// the string concatenation that silently answers `"500m" + "500m"`.
|
|
644
|
+
// Every rule they obey is in ts/src/val/arith.ts.
|
|
645
|
+
add: AddFuncVal,
|
|
646
|
+
sub: SubFuncVal,
|
|
647
|
+
mul: MulFuncVal,
|
|
648
|
+
div: DivFuncVal,
|
|
649
|
+
mod: ModFuncVal,
|
|
650
|
+
rem: RemFuncVal,
|
|
651
|
+
|
|
652
|
+
// Aggregation over a finite, settled bag (the review's finding I).
|
|
653
|
+
// `least` and `greatest` rather than min and max, which are already
|
|
654
|
+
// the atoms for a lower and an upper BOUND -- an aggregate over a
|
|
655
|
+
// set and a bound on a value must not share a spelling.
|
|
656
|
+
sum: SumFuncVal,
|
|
657
|
+
least: LeastFuncVal,
|
|
658
|
+
greatest: GreatestFuncVal,
|
|
659
|
+
|
|
660
|
+
// Projection, which is what lets the aggregates reach a bag of
|
|
661
|
+
// RECORDS: `sum(pick($.lines, amountCents))`. Not a clever `each`
|
|
662
|
+
// template -- `each` MEETS each child, and a meet cannot select.
|
|
663
|
+
pick: PickFuncVal,
|
|
664
|
+
|
|
665
|
+
// G9 phase 2: the fold to a STRING. `sum` folds with `add`; this
|
|
666
|
+
// folds with `+`, so it inherits the one number-to-text rule and
|
|
667
|
+
// the language does not grow a second. It is the primitive that
|
|
668
|
+
// turns a bag of computed lines into a file.
|
|
669
|
+
join: JoinFuncVal,
|
|
444
670
|
}
|
|
445
671
|
|
|
446
672
|
|
|
@@ -454,6 +680,74 @@ help isolate the syntax error.`,
|
|
|
454
680
|
const incompleteNil = (r: Rule, ctx: JsonicContext) =>
|
|
455
681
|
addsite(new NilVal({ why: 'incomplete_expression' }), r, ctx)
|
|
456
682
|
|
|
683
|
+
// Build a call from a NAME and the argument terms as the author
|
|
684
|
+
// wrote them: the arity check, the comma-group rule and the
|
|
685
|
+
// raw-value conversion, stated once.
|
|
686
|
+
const buildCall = (r: Rule, ctx: JsonicContext,
|
|
687
|
+
fname: string, argterms: any[]): any => {
|
|
688
|
+
const funcval = funcMap[fname]
|
|
689
|
+
|
|
690
|
+
// Arity is known for every built-in, so a surplus or missing
|
|
691
|
+
// argument is a mistake in the SOURCE, refused here where the
|
|
692
|
+
// author can see it (issue #51). It was previously left to each
|
|
693
|
+
// function to notice or not: the two ports disagreed on `upper()`
|
|
694
|
+
// and on `close()`, and `min(1,2)` noticed nothing at all -- it
|
|
695
|
+
// built a constraint that merely refused to generate later, with a
|
|
696
|
+
// message about the map rather than about the call.
|
|
697
|
+
//
|
|
698
|
+
// Counted BEFORE the rawToVal pass below, which is what makes the
|
|
699
|
+
// count possible: a comma group arrives as a RAW array and a
|
|
700
|
+
// written list literal as a ListVal, and rawToVal turns the first
|
|
701
|
+
// into the second.
|
|
702
|
+
const arity = funcArity[fname]
|
|
703
|
+
if (null != arity) {
|
|
704
|
+
const got = writtenArgCount(argterms)
|
|
705
|
+
if (got < arity[0] || (-1 !== arity[1] && got > arity[1])) {
|
|
706
|
+
// details is assigned AFTER construction: the NilVal
|
|
707
|
+
// constructor does not read it from its spec (only NilVal.make
|
|
708
|
+
// does), so passing it in the spec left the hint's
|
|
709
|
+
// {func}/{want}/{got} placeholders un-injected and printed
|
|
710
|
+
// literally.
|
|
711
|
+
const nil: any = new NilVal({ why: 'func_arity' })
|
|
712
|
+
nil.details = {
|
|
713
|
+
func: fname,
|
|
714
|
+
want: arityText(arity[0], arity[1]),
|
|
715
|
+
got: '' + got,
|
|
716
|
+
}
|
|
717
|
+
return addsite(nil, r, ctx)
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// rawToVal EVERY argument. A degenerate expression can hand this
|
|
722
|
+
// handler raw parse values rather than Vals -- `pref(1-3)` arrives
|
|
723
|
+
// as the plain numbers 1 and -3 -- and a func's peg is unified
|
|
724
|
+
// element by element, so a raw one reached `arg.unify(...)` and
|
|
725
|
+
// threw. The unifier's catch-all turned that into an `internal`
|
|
726
|
+
// verdict: a crash reported as a unification result (issue #49).
|
|
727
|
+
// The Go port has always converted here (asVal in evaluate).
|
|
728
|
+
// A comma group is ONE raw-array term (see writtenArgCount).
|
|
729
|
+
// For a function whose arguments are distinct POSITIONS —
|
|
730
|
+
// deprecate's value and record, pack's and each's data and template
|
|
731
|
+
// — the group is expanded back into them here, while a written list
|
|
732
|
+
// literal, already a ListVal, stays one argument. The constraint
|
|
733
|
+
// atoms make the same move in their own constructor (atomArgs,
|
|
734
|
+
// ConstraintVal.ts), which is why they are not in this set:
|
|
735
|
+
// `neq(1,2)` is one argument LIST, not two positions, and expanding
|
|
736
|
+
// it here would take the list away from the code that reads it.
|
|
737
|
+
let terms = argterms
|
|
738
|
+
if (true === POSITIONAL_ARG_FUNCS[fname] && 1 === terms.length &&
|
|
739
|
+
Array.isArray(terms[0])) {
|
|
740
|
+
terms = terms[0]
|
|
741
|
+
}
|
|
742
|
+
const args = terms.map(rawToVal)
|
|
743
|
+
const val: any = null == funcval ?
|
|
744
|
+
new NilVal({ why: 'unknown_function' }) :
|
|
745
|
+
new funcval({ peg: args })
|
|
746
|
+
|
|
747
|
+
return val
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
|
|
457
751
|
let opmap: any = {
|
|
458
752
|
'conjunct-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
|
|
459
753
|
addsite(new ConjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
|
|
@@ -472,11 +766,50 @@ help isolate the syntax error.`,
|
|
|
472
766
|
|
|
473
767
|
'star-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
474
768
|
if (null == terms[0]) return incompleteNil(r, ctx)
|
|
769
|
+
|
|
770
|
+
// A PREFERENCE MARKS A VALUE, AND A BARE KEY IS NOT ONE.
|
|
771
|
+
// `*a: 1` has no braces, so the prefix took the whole IMPLICIT
|
|
772
|
+
// map as its operand and the document silently became
|
|
773
|
+
// `*{"a":1}` -- `*a: 1, b: 2` became a one-element LIST, losing
|
|
774
|
+
// `b` outright. Neither is anything the author wrote.
|
|
775
|
+
//
|
|
776
|
+
// The accident is confined to the first position of the implicit
|
|
777
|
+
// top-level map, which is the only place no brace has yet
|
|
778
|
+
// committed the rule to a map: `{*a: 1}` and `a: 1, *b: 2` are
|
|
779
|
+
// ALREADY parse errors. This makes the third spelling agree with
|
|
780
|
+
// them rather than inventing a meaning for it.
|
|
781
|
+
//
|
|
782
|
+
// A BRACED operand is untouched, and that is the whole of the
|
|
783
|
+
// distinction: `*{x:1}` and `*[1]` are the real spelling, they
|
|
784
|
+
// are what `*{x:1} | *{y:2}` needs, and the shared spec pins them
|
|
785
|
+
// (11 rows). The open token's own source text is what separates
|
|
786
|
+
// the two -- `{` or `[` for a braced bag, the first key or
|
|
787
|
+
// element for an implicit one.
|
|
788
|
+
const bag: any = terms[0]
|
|
789
|
+
if ((bag.isMap && '{' !== bag.site.src) ||
|
|
790
|
+
(bag.isList && '[' !== bag.site.src)) {
|
|
791
|
+
return addsite(new NilVal({ why: 'pref_implicit_bag' }), r, ctx)
|
|
792
|
+
}
|
|
793
|
+
|
|
475
794
|
return addsite(new PrefVal({ peg: terms[0] }), r, ctx)
|
|
476
795
|
},
|
|
477
796
|
|
|
478
797
|
'dollar-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
|
|
479
798
|
if (null == terms[0]) return incompleteNil(r, ctx)
|
|
799
|
+
// A refusal from the dot rule below (an alias used as a path
|
|
800
|
+
// segment) rides straight through: wrapping it in a VarVal would
|
|
801
|
+
// replace `alias_in_path` with a var whose peg is a nil.
|
|
802
|
+
if (terms[0]?.isNil) {
|
|
803
|
+
return terms[0]
|
|
804
|
+
}
|
|
805
|
+
// `$%foo` -- the sigil directly after the root -- reaches here
|
|
806
|
+
// as the alias reference rather than through the dot rule, and
|
|
807
|
+
// is refused for the same reason.
|
|
808
|
+
if (terms[0] instanceof RefVal &&
|
|
809
|
+
terms[0].peg.some((seg: any) =>
|
|
810
|
+
'string' === typeof seg && ALIAS_RE.test(seg))) {
|
|
811
|
+
return addsite(new NilVal({ why: 'alias_in_path' }), r, ctx)
|
|
812
|
+
}
|
|
480
813
|
// $.a.b absolute path
|
|
481
814
|
if (terms[0] instanceof RefVal) {
|
|
482
815
|
terms[0].absolute = true
|
|
@@ -538,50 +871,7 @@ help isolate the syntax error.`,
|
|
|
538
871
|
const fname = terms[0]
|
|
539
872
|
|
|
540
873
|
if ('' !== fname) {
|
|
541
|
-
|
|
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)
|
|
580
|
-
val = null == funcval ?
|
|
581
|
-
new NilVal({ why: 'unknown_function' }) :
|
|
582
|
-
new funcval({
|
|
583
|
-
peg: args
|
|
584
|
-
})
|
|
874
|
+
val = buildCall(r, ctx, fname, terms.slice(1))
|
|
585
875
|
}
|
|
586
876
|
// `a:()` — grouping parens with nothing inside.
|
|
587
877
|
if (null == val) return incompleteNil(r, ctx)
|
|
@@ -708,6 +998,8 @@ help isolate the syntax error.`,
|
|
|
708
998
|
|
|
709
999
|
const QM = jsonic.token.QM
|
|
710
1000
|
|
|
1001
|
+
const VL = jsonic.token.VL
|
|
1002
|
+
|
|
711
1003
|
const OPTKEY = [TX, ST, NR]
|
|
712
1004
|
|
|
713
1005
|
|
|
@@ -818,6 +1110,13 @@ help isolate the syntax error.`,
|
|
|
818
1110
|
valnode.site.row = st.rI
|
|
819
1111
|
valnode.site.col = st.cI
|
|
820
1112
|
valnode.site.url = ctx.meta.multisource && ctx.meta.multisource.path
|
|
1113
|
+
// No `?? ''` and no empty-text arm here: this branch runs only
|
|
1114
|
+
// for a rule that HAS an open token, and a token that opens a
|
|
1115
|
+
// value always carries text — the coverage gate refuses both
|
|
1116
|
+
// guards as dead. The unset case is the one above, where r.o0
|
|
1117
|
+
// itself can be absent.
|
|
1118
|
+
valnode.site.src = st.src
|
|
1119
|
+
valnode.site.len = st.src.length
|
|
821
1120
|
}
|
|
822
1121
|
// else { ERROR? }
|
|
823
1122
|
|
|
@@ -842,6 +1141,7 @@ help isolate the syntax error.`,
|
|
|
842
1141
|
|
|
843
1142
|
.bc((r: Rule, ctx: JsonicContext) => {
|
|
844
1143
|
const optionalKeys = r.u.aontu_optional_keys ?? []
|
|
1144
|
+
const aliasKeys = r.u.aontu_alias_keys ?? []
|
|
845
1145
|
|
|
846
1146
|
let mo = r.node
|
|
847
1147
|
|
|
@@ -908,6 +1208,7 @@ help isolate the syntax error.`,
|
|
|
908
1208
|
// TODO: needs addpath?
|
|
909
1209
|
let mopv = new MapVal({ peg: mop })
|
|
910
1210
|
mopv.optionalKeys = optionalKeys
|
|
1211
|
+
mopv.aliasKeys = aliasKeys
|
|
911
1212
|
|
|
912
1213
|
r.node =
|
|
913
1214
|
addsite(new ConjunctVal({ peg: [mopv, ...mo.___merge] }), r, ctx)
|
|
@@ -915,6 +1216,7 @@ help isolate the syntax error.`,
|
|
|
915
1216
|
else {
|
|
916
1217
|
r.node = addsite(new MapVal({ peg: mo }), r, ctx)
|
|
917
1218
|
r.node.optionalKeys = optionalKeys
|
|
1219
|
+
r.node.aliasKeys = aliasKeys
|
|
918
1220
|
}
|
|
919
1221
|
|
|
920
1222
|
return undefined
|
|
@@ -1065,6 +1367,33 @@ help isolate the syntax error.`,
|
|
|
1065
1367
|
.bc((rule: Rule) => {
|
|
1066
1368
|
// TRAVERSE PARENTS TO GET PATH
|
|
1067
1369
|
|
|
1370
|
+
// A DECLARATION IS A PAIR WHOSE KEY IS AN ALIAS NAME. The lexer
|
|
1371
|
+
// claims `%name` whole and hands it over as a #VL token whose
|
|
1372
|
+
// SOURCE is the name, so the key TEXT alone cannot be the test:
|
|
1373
|
+
// a quoted `"%a": 1` is an ordinary key that merely starts with
|
|
1374
|
+
// the sigil, and erasing that would be wrong. The token is what
|
|
1375
|
+
// separates them.
|
|
1376
|
+
//
|
|
1377
|
+
// Recorded on the enclosing map, never on the value, and that is
|
|
1378
|
+
// the point: a reference COPIES the value it resolves to, so a
|
|
1379
|
+
// mark riding the value would erase the referring field too.
|
|
1380
|
+
// Being a property of the map is also what carries it through a
|
|
1381
|
+
// meet, the way optional keys are carried.
|
|
1382
|
+
const ktkn: any = rule.o0
|
|
1383
|
+
if (null != ktkn && VL === ktkn.tin && ALIAS_RE.test('' + ktkn.src)) {
|
|
1384
|
+
const holder: any = rule.parent
|
|
1385
|
+
const aname = '' + ktkn.src
|
|
1386
|
+
|
|
1387
|
+
// Always recorded here; whether the map is ALLOWED to carry
|
|
1388
|
+
// declarations is decided on the VALUE (MapVal.unify), not at
|
|
1389
|
+
// the parse. The parse cannot see it: an INCLUDED file's
|
|
1390
|
+
// declarations are at the root of their own text, and only
|
|
1391
|
+
// once the loaded map is placed does it become apparent that
|
|
1392
|
+
// root is not the document's.
|
|
1393
|
+
holder.u.aontu_alias_keys = (holder.u.aontu_alias_keys || [])
|
|
1394
|
+
holder.u.aontu_alias_keys.push(aname)
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1068
1397
|
if (rule.u.spread) {
|
|
1069
1398
|
rule.node[SPREAD] =
|
|
1070
1399
|
(rule.node[SPREAD] || { o: rule.o0.src, v: [] })
|
|
@@ -1110,7 +1439,10 @@ help isolate the syntax error.`,
|
|
|
1110
1439
|
s: [QM, CL],
|
|
1111
1440
|
c: (r) => r.prev.u.aontu_optional,
|
|
1112
1441
|
p: 'val',
|
|
1113
|
-
u: {
|
|
1442
|
+
u: {
|
|
1443
|
+
spread: true, done: true, list: true, pair: true,
|
|
1444
|
+
aontu_optional_elem: true,
|
|
1445
|
+
},
|
|
1114
1446
|
a: (r) => {
|
|
1115
1447
|
pairkey(r.prev)
|
|
1116
1448
|
r.u.key = r.prev.u.key
|
|
@@ -1119,18 +1451,17 @@ help isolate the syntax error.`,
|
|
|
1119
1451
|
g: 'aontu-optional-elem'
|
|
1120
1452
|
},
|
|
1121
1453
|
|
|
1122
|
-
// A PLAIN pair in list position
|
|
1123
|
-
//
|
|
1124
|
-
//
|
|
1125
|
-
//
|
|
1126
|
-
//
|
|
1127
|
-
//
|
|
1128
|
-
//
|
|
1129
|
-
//
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1133
|
-
// and it made the two ports disagree on generate as well as canon.
|
|
1454
|
+
// A PLAIN pair in list position IS A SINGLE-KEY MAP ELEMENT:
|
|
1455
|
+
// `[a:1, b:2]` is `[{a:1}, {b:2}]` (the rule @tabnas/jsonic
|
|
1456
|
+
// spells as `list.pair`). This REVERSES issue #40's "a pair is
|
|
1457
|
+
// not an element": that rule was chosen because jsonic wrote
|
|
1458
|
+
// the pair at `node[key]` -- an array PROPERTY that never
|
|
1459
|
+
// showed up for a text key and an INDEX for a numeric one --
|
|
1460
|
+
// and inert beat that incoherence. But inert was itself a
|
|
1461
|
+
// silent drop: `x: [a:1, b:2]` evaluated to `x: []`, the
|
|
1462
|
+
// author's data gone at exit 0. The element is built in the
|
|
1463
|
+
// bc below, where the value is already a Val; the snapshot
|
|
1464
|
+
// still neutralises jsonic's raw slot write first.
|
|
1134
1465
|
{
|
|
1135
1466
|
s: [OPTKEY, CL], p: 'val',
|
|
1136
1467
|
u: { spread: true, done: true, list: true, pair: true },
|
|
@@ -1143,17 +1474,109 @@ help isolate the syntax error.`,
|
|
|
1143
1474
|
])
|
|
1144
1475
|
|
|
1145
1476
|
|
|
1146
|
-
|
|
1477
|
+
// NOTE: manually adjust path - the twin of the `pair` rule's hook
|
|
1478
|
+
// above, and for the same reason, one layer down.
|
|
1479
|
+
//
|
|
1480
|
+
// Every alt above contributes NO element: a `&:` spread is a
|
|
1481
|
+
// constraint on the elements, and a `k:v` pair in list position is
|
|
1482
|
+
// simply not one (the `aontu-plain-pair-elem` note above). The array
|
|
1483
|
+
// slot they briefly occupy is already given back by
|
|
1484
|
+
// restorePairSlot. The PATH index was not: @tabnas/path's
|
|
1485
|
+
// `@elem-ao` increments `r.k.index` for every elem rule it sees, so
|
|
1486
|
+
// each of these stole an index and every later element's path was
|
|
1487
|
+
// one too high — `[&: integer, 10, 20, "bad"]` reported the bad
|
|
1488
|
+
// value at `$.l.3` while `aontu get $.l.2` returned it, and on a
|
|
1489
|
+
// one-element list the path pointed off the end. Generation was
|
|
1490
|
+
// never wrong, which is why nothing caught it: the array is right
|
|
1491
|
+
// and only the labels on it were shifted (BUGS.md 44).
|
|
1492
|
+
//
|
|
1493
|
+
// Rewinding here rather than in the plugin keeps the plugin's rule
|
|
1494
|
+
// ("in an array, the path property is the element index") true —
|
|
1495
|
+
// these alts are the aontu-specific exceptions to what counts as an
|
|
1496
|
+
// element, so the correction belongs with the grammar that
|
|
1497
|
+
// introduces them. The child is re-pathed because the plugin has
|
|
1498
|
+
// already stamped it with the index being given back: a spread
|
|
1499
|
+
// takes the `'&'` segment its map twin takes, and a pair takes its
|
|
1500
|
+
// key, as a map entry would.
|
|
1501
|
+
.ao((r) => {
|
|
1502
|
+
// A pair IS an element now, so it keeps the index @tabnas/path
|
|
1503
|
+
// gave it, and its VALUE is pathed through both the index and
|
|
1504
|
+
// the key (`[a: $.nope]` fails at $.l.0.a). Only the `&:`
|
|
1505
|
+
// spread still contributes no element and gives its index back
|
|
1506
|
+
// (BUGS.md 44).
|
|
1507
|
+
if (0 < r.d && r.u.spread && !r.u.pair) {
|
|
1508
|
+
r.k.index = r.k.index - 1
|
|
1509
|
+
|
|
1510
|
+
const seg = '&'
|
|
1511
|
+
r.child.k.path = [...r.k.path, seg]
|
|
1512
|
+
r.child.k.key = seg
|
|
1513
|
+
}
|
|
1514
|
+
else if (0 < r.d && r.u.pair) {
|
|
1515
|
+
// The element's index is the array length: everything before
|
|
1516
|
+
// it is already pushed, and the pair's own map is pushed at
|
|
1517
|
+
// close. `r.k.index` is not usable here -- the path plugin
|
|
1518
|
+
// counts only the elements it pushes itself, and this one is
|
|
1519
|
+
// aontu's.
|
|
1520
|
+
const seg = '' + r.u.key
|
|
1521
|
+
r.child.k.path =
|
|
1522
|
+
[...r.k.path, '' + (r.node?.length ?? 0), seg]
|
|
1523
|
+
r.child.k.key = seg
|
|
1524
|
+
}
|
|
1525
|
+
})
|
|
1526
|
+
|
|
1527
|
+
.bc((rule: Rule, ctx: JsonicContext) => {
|
|
1147
1528
|
// TRAVERSE PARENTS TO GET PATH
|
|
1148
1529
|
|
|
1149
|
-
|
|
1530
|
+
// Only the `&:` alternative is a SPREAD. All four alts above set
|
|
1531
|
+
// `spread: true` -- it is what marks them as contributing no
|
|
1532
|
+
// element -- so this guard needs the narrower test, and `pair`
|
|
1533
|
+
// is what distinguishes a `k:v` in list position from a spread.
|
|
1534
|
+
//
|
|
1535
|
+
// Without it a pair BUILT the spread record, with `o` taken from
|
|
1536
|
+
// its own key rather than '&': `[x:1, &:integer, "bad"]` left
|
|
1537
|
+
// `{o:'x', v:[1, integer]}`, and ListVal's `'&' === spread.o`
|
|
1538
|
+
// then discarded the real constraint -- so the element spread
|
|
1539
|
+
// was silently dropped and the bad value generated (BUGS.md 46).
|
|
1540
|
+
// A pair alone did it too: `[x:1, 10]` produced a spread record
|
|
1541
|
+
// out of nothing.
|
|
1542
|
+
if (rule.u.spread && !rule.u.pair) {
|
|
1150
1543
|
rule.node[SPREAD] =
|
|
1151
1544
|
(rule.node[SPREAD] || { o: rule.o0.src, v: [] })
|
|
1152
1545
|
rule.node[SPREAD].v.push(rule.child.node)
|
|
1153
1546
|
}
|
|
1154
1547
|
|
|
1548
|
+
// The slot is given back BEFORE the element is added: the
|
|
1549
|
+
// restore undoes jsonic's raw write (a property for a text
|
|
1550
|
+
// key, an INDEX for a numeric one -- restoring length is what
|
|
1551
|
+
// keeps `[1:2]` from padding with a null), and the push then
|
|
1552
|
+
// appends cleanly after it.
|
|
1155
1553
|
restorePairSlot(rule)
|
|
1156
1554
|
|
|
1555
|
+
// THE SINGLE-KEY MAP ELEMENT, for both pair spellings. The
|
|
1556
|
+
// value is a Val already (`p: 'val'`), so the map is built
|
|
1557
|
+
// exactly as the map rule builds one -- and an elided value
|
|
1558
|
+
// (`[a:]`) is refused exactly as the map rule refuses one
|
|
1559
|
+
// (issue #48): a key with nothing after the colon is a
|
|
1560
|
+
// mistake, not an empty value.
|
|
1561
|
+
if (true === rule.u.pair) {
|
|
1562
|
+
const key = '' + rule.u.key
|
|
1563
|
+
let v: any = rule.child.node
|
|
1564
|
+
if (null == v) {
|
|
1565
|
+
v = addsite(new NilVal({ why: 'elided_value' }), rule, ctx)
|
|
1566
|
+
v.path = [...(rule.k?.path ?? []),
|
|
1567
|
+
'' + rule.node.length, key]
|
|
1568
|
+
}
|
|
1569
|
+
const mv: any = addsite(
|
|
1570
|
+
new MapVal({ peg: { [key]: v } }), rule, ctx)
|
|
1571
|
+
// `[a?: 1]` is `[{a?: 1}]`: the key is optional IN the
|
|
1572
|
+
// element, so the two spellings stay one rule apart rather
|
|
1573
|
+
// than two behaviours apart.
|
|
1574
|
+
if (true === rule.u.aontu_optional_elem) {
|
|
1575
|
+
mv.optionalKeys = [key]
|
|
1576
|
+
}
|
|
1577
|
+
rule.node.push(mv)
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1157
1580
|
return undefined
|
|
1158
1581
|
})
|
|
1159
1582
|
|
|
@@ -1166,19 +1589,231 @@ help isolate the syntax error.`,
|
|
|
1166
1589
|
|
|
1167
1590
|
|
|
1168
1591
|
|
|
1169
|
-
//
|
|
1170
|
-
//
|
|
1171
|
-
//
|
|
1172
|
-
//
|
|
1173
|
-
//
|
|
1174
|
-
//
|
|
1175
|
-
//
|
|
1592
|
+
// INCLUDE_KINDS IS THE RULE FOR WHAT AN INCLUDE MEANS (ADR-012,
|
|
1593
|
+
// use-cases/BUGS.md §49). An extension is on this list or it is not
|
|
1594
|
+
// read at all, and its entry says WHICH OF TWO THINGS the file is.
|
|
1595
|
+
//
|
|
1596
|
+
// `source` — Aontu, with everything the language has: types, defaults,
|
|
1597
|
+
// references, constraints, its own includes. Two extensions, and they
|
|
1598
|
+
// are the ones this project owns.
|
|
1599
|
+
//
|
|
1600
|
+
// A FORMAT NAME — configuration DATA, parsed by that format's own
|
|
1601
|
+
// parser into the JSON value it denotes, which then becomes Aontu
|
|
1602
|
+
// values like any other data. Every one of these formats maps onto
|
|
1603
|
+
// JSON, which is why one word covers them: a `.toml` file is a map of
|
|
1604
|
+
// scalars, lists and maps, and so is the `.aon` file that unifies with
|
|
1605
|
+
// it. What the format does NOT get is the language — a `&` in a YAML
|
|
1606
|
+
// file is a YAML anchor, not a spread key, because the YAML parser
|
|
1607
|
+
// reads it, not this one.
|
|
1608
|
+
//
|
|
1609
|
+
// The parsers are @tabnas's, one per format, and the Go port uses the
|
|
1610
|
+
// same ones (ADR-001): the two implementations agree because they are
|
|
1611
|
+
// running the same grammar, not because two hand-written readers were
|
|
1612
|
+
// kept in step.
|
|
1613
|
+
//
|
|
1614
|
+
// This table and go/source.go's includeKinds are the same table.
|
|
1615
|
+
const INCLUDE_KINDS: { [kind: string]: string } = {
|
|
1616
|
+
aon: 'source',
|
|
1617
|
+
aontu: 'source',
|
|
1618
|
+
|
|
1619
|
+
json: 'json',
|
|
1620
|
+
// JSON-LD is JSON: a `@context` is a key like any other here, and
|
|
1621
|
+
// what it MEANS is the vocabulary's business, not the reader's.
|
|
1622
|
+
jsonld: 'json',
|
|
1623
|
+
jsonc: 'jsonc',
|
|
1624
|
+
json5: 'json5',
|
|
1625
|
+
jsonic: 'jsonic',
|
|
1626
|
+
jsc: 'jsonic',
|
|
1627
|
+
toml: 'toml',
|
|
1628
|
+
yaml: 'yaml',
|
|
1629
|
+
yml: 'yaml',
|
|
1630
|
+
ini: 'ini',
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// `.csv` IS DELIBERATELY ABSENT, and the reason is ADR-001 rather than
|
|
1634
|
+
// taste. The two ports' CSV parsers disagree about what a CSV file IS:
|
|
1635
|
+
// one answers header-keyed records with string fields, the other raw
|
|
1636
|
+
// rows including the header, with numbers parsed. Admitting it would
|
|
1637
|
+
// admit a divergence into the one thing this project refuses to have
|
|
1638
|
+
// one in. Recorded in ADR-012 and pinned by file.tsv's load-ext-csv.
|
|
1639
|
+
|
|
1640
|
+
// The multisource kind of a path: the LAST segment's extension, without
|
|
1641
|
+
// its dot, lowercased -- `''` for a name that has none. The rule is
|
|
1642
|
+
// @tabnas/multisource's own extKind (and Go's filepath.Ext), copied
|
|
1643
|
+
// rather than imported because it decides what a source IS: a dot in a
|
|
1644
|
+
// parent folder (`/my.app/conf`) must not read as an extension.
|
|
1645
|
+
function extKindOf(full: string): string {
|
|
1646
|
+
const seg = (full.match(/[^\\/]*$/) as string[])[0]
|
|
1647
|
+
return (seg.match(/\.([^.]*)$/) || ['', ''])[1].toLowerCase()
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// The refusal message, naming the extension -- because the extension is
|
|
1651
|
+
// the whole reason, and a reader told only "not readable" has to guess
|
|
1652
|
+
// which part of the path the engine objected to. Byte-identical to Go's
|
|
1653
|
+
// extensionMsg.
|
|
1654
|
+
function extensionMsg(path: string, ext: string): string {
|
|
1655
|
+
const which = '' === ext ? 'no extension' : 'extension: .' + ext
|
|
1656
|
+
return 'include not readable: ' + path + ' (' + which + ')'
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// THE RULE ALSO HOLDS FOR A RESOLVER THIS ENGINE DID NOT WRITE.
|
|
1660
|
+
// gateExtension refuses an unlisted extension inside makeModelResolver,
|
|
1661
|
+
// which is the default; a HOST may supply its own through
|
|
1662
|
+
// `AontuOptions.resolver`, and that one has never heard of
|
|
1663
|
+
// INCLUDE_KINDS. Without this the host's resolution would fall to
|
|
1664
|
+
// multisource's own default for an unnamed kind, which hands the file
|
|
1665
|
+
// back as TEXT — or, for `.js`, EXECUTES it. So the two roads end in
|
|
1666
|
+
// one place: whatever chose the source, an extension off the list is
|
|
1667
|
+
// refused with the same code and the same message.
|
|
1668
|
+
const refuseProcessor = (res: any) => {
|
|
1669
|
+
// `full` is the one part a host resolution may leave out -- it is the
|
|
1670
|
+
// path the resolver CHOSE, and a resolver that answers from something
|
|
1671
|
+
// other than a filesystem need not have one. The written path always
|
|
1672
|
+
// reaches here, so it is the fallback.
|
|
1673
|
+
const err: any = new Error(extensionMsg(res.path, extKindOf(res.full ?? res.path)))
|
|
1674
|
+
err.code = 'include_extension'
|
|
1675
|
+
throw err
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
|
|
1679
|
+
// ONE READER PER FORMAT, BUILT ONCE. These are stateless parsers and
|
|
1680
|
+
// building a jsonic instance is not free, so they are made at module
|
|
1681
|
+
// load rather than per include. The file name is passed through so a
|
|
1682
|
+
// syntax error inside an included `.toml` names the `.toml`.
|
|
1683
|
+
const DATA_READERS: { [format: string]: (src: string, fileName: string) => any } =
|
|
1684
|
+
(() => {
|
|
1685
|
+
const viaPlugin = (plugin: any) => {
|
|
1686
|
+
const jsonic = Jsonic.make().use(plugin)
|
|
1687
|
+
return (src: string, fileName: string) => jsonic(src, { fileName })
|
|
1688
|
+
}
|
|
1689
|
+
const toml = viaPlugin(Toml)
|
|
1690
|
+
// The strict RFC 8259 reader is its own parser rather than a
|
|
1691
|
+
// plugin, and it is `make().parse` rather than the module's bare
|
|
1692
|
+
// `parse`: only the instance carries the meta bag, and without it
|
|
1693
|
+
// a syntax error in an included `.json` says `<no-file>`.
|
|
1694
|
+
const json = makeJsonParser()
|
|
1695
|
+
return {
|
|
1696
|
+
json: (src: string, fileName: string) => json.parse(src, { fileName }),
|
|
1697
|
+
jsonc: viaPlugin(Jsonc),
|
|
1698
|
+
json5: viaPlugin(Json5),
|
|
1699
|
+
// Plain jsonic needs no plugin: it IS the base parser.
|
|
1700
|
+
jsonic: (src: string, fileName: string) => Jsonic(src, { fileName }),
|
|
1701
|
+
toml: (src: string, fileName: string) => tomlDates(toml(src, fileName)),
|
|
1702
|
+
yaml: viaPlugin(Yaml),
|
|
1703
|
+
ini: viaPlugin(Ini),
|
|
1704
|
+
}
|
|
1705
|
+
})()
|
|
1706
|
+
|
|
1707
|
+
/**
|
|
1708
|
+
* A TOML document with its dates as the TEXT they were written as.
|
|
1709
|
+
*
|
|
1710
|
+
* TOML HAS DATES AND JSON DOES NOT, so the reader cannot hand one over
|
|
1711
|
+
* as itself: it answers with a marker object carrying the kind and the
|
|
1712
|
+
* source text. The value that reaches a document is that TEXT, which is
|
|
1713
|
+
* what a JSON document carries for a date anyway — and it is what the
|
|
1714
|
+
* Go port produces too, from a `*TomlTime` holding those same two
|
|
1715
|
+
* fields (`dataToValDepth`, go/source.go). Without this the same file
|
|
1716
|
+
* is a nested map in one port and a string in the other, which is the
|
|
1717
|
+
* class of divergence ADR-012 exists to stop.
|
|
1718
|
+
*
|
|
1719
|
+
* The guard is exact — one key, `__toml__`, holding a `kind` and a
|
|
1720
|
+
* `src` string — so a document whose own data happens to use the name
|
|
1721
|
+
* passes through untouched.
|
|
1722
|
+
*/
|
|
1723
|
+
function tomlDates(node: any): any {
|
|
1724
|
+
if (Array.isArray(node)) {
|
|
1725
|
+
return node.map(tomlDates)
|
|
1726
|
+
}
|
|
1727
|
+
if (null === node || 'object' !== typeof node) {
|
|
1728
|
+
return node
|
|
1729
|
+
}
|
|
1730
|
+
const keys = Object.keys(node)
|
|
1731
|
+
const mark = node.__toml__
|
|
1732
|
+
if (1 === keys.length && '__toml__' === keys[0] && null != mark &&
|
|
1733
|
+
'string' === typeof mark.kind && 'string' === typeof mark.src) {
|
|
1734
|
+
return mark.src
|
|
1735
|
+
}
|
|
1736
|
+
const out: Record<string, any> = {}
|
|
1737
|
+
for (const k of keys) {
|
|
1738
|
+
out[k] = tomlDates(node[k])
|
|
1739
|
+
}
|
|
1740
|
+
return out
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/**
|
|
1744
|
+
* Read one included file as DATA in the named format.
|
|
1745
|
+
*
|
|
1746
|
+
* The parser hands back the JSON value the file denotes — plain maps,
|
|
1747
|
+
* lists and scalars — and rawToVal turns that into Vals. THE
|
|
1748
|
+
* CONVERSION HAPPENS HERE, not at the top level, because an include is
|
|
1749
|
+
* usually not at the top level: `a: @"conf.toml"` puts the value under
|
|
1750
|
+
* a key, where a raw JavaScript object is something the tree cannot
|
|
1751
|
+
* unify with (the crash that was BUGS §49b).
|
|
1752
|
+
*/
|
|
1753
|
+
const dataProcessor = (format: string) => (res: any) => {
|
|
1754
|
+
res.val = rawToVal(DATA_READERS[format](res.src, res.path))
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
/**
|
|
1758
|
+
* The multisource processor map, built FROM the include table so the
|
|
1759
|
+
* two cannot drift: every extension the table names gets the reader
|
|
1760
|
+
* the table names for it, and the two kinds that are not in the table
|
|
1761
|
+
* refuse.
|
|
1762
|
+
*/
|
|
1763
|
+
function includeProcessors(): { [kind: string]: any } {
|
|
1764
|
+
const map: { [kind: string]: any } = {
|
|
1765
|
+
// multisource's fallback for an extension no entry names, so it is
|
|
1766
|
+
// the one that catches whatever the resolver's gate did not.
|
|
1767
|
+
'': refuseProcessor,
|
|
1768
|
+
// ... and the one upstream default that would EXECUTE the file.
|
|
1769
|
+
js: refuseProcessor,
|
|
1770
|
+
}
|
|
1771
|
+
const source = makeJsonicProcessor()
|
|
1772
|
+
for (const kind of Object.keys(INCLUDE_KINDS)) {
|
|
1773
|
+
const format = INCLUDE_KINDS[kind]
|
|
1774
|
+
map[kind] = 'source' === format ? source : dataProcessor(format)
|
|
1775
|
+
}
|
|
1776
|
+
return map
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
|
|
1780
|
+
// SECURITY: under the DEFAULT ('system') include capability this
|
|
1781
|
+
// resolver reads any file the process can reach — @"path" follows
|
|
1782
|
+
// relative paths (`@"../../etc/passwd.aon"`) and symlinks — so treat
|
|
1783
|
+
// opening an untrusted source as reading your disk. It no longer RUNS
|
|
1784
|
+
// one: @"pkg" could require() an arbitrary installed module until
|
|
1785
|
+
// ADR-012, which refuses a `.js` entry point by the same rule that
|
|
1786
|
+
// refuses `.txt`. The trust profile (G5, docs/trust.md) is the
|
|
1787
|
+
// confinement surface: `trust.include` of
|
|
1788
|
+
// 'none', `{ mem }` or `{ root }` restricts what `@"..."` may resolve,
|
|
1789
|
+
// and a denied resolution is a deterministic parse-stage
|
|
1790
|
+
// `include_denied` error.
|
|
1176
1791
|
function makeModelResolver(options: any) {
|
|
1177
1792
|
const useRequire = options.require || require
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1793
|
+
const capability = options.trust?.include ?? 'system'
|
|
1794
|
+
|
|
1795
|
+
const memCapability =
|
|
1796
|
+
'object' === typeof capability && null != (capability as any).mem
|
|
1797
|
+
const rootDir: string | undefined =
|
|
1798
|
+
'object' === typeof capability &&
|
|
1799
|
+
'string' === typeof (capability as any).root
|
|
1800
|
+
? pathResolve((capability as any).root) : undefined
|
|
1801
|
+
|
|
1802
|
+
// Under the mem capability the CAPABILITY's file set is the whole
|
|
1803
|
+
// world; otherwise the host-injected `options.resolver.mem` entries
|
|
1804
|
+
// remain available under every capability but 'none' — they are
|
|
1805
|
+
// host-provided, not document-requested, so confining them would
|
|
1806
|
+
// confine the host against itself.
|
|
1807
|
+
// THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts) rides the
|
|
1808
|
+
// memory leg: served from the engine itself, so it needs neither the
|
|
1809
|
+
// filesystem nor package resolution and is available under every
|
|
1810
|
+
// capability but `none` — which denies every include outright, that
|
|
1811
|
+
// being what `none` means. Host entries and the capability's own set
|
|
1812
|
+
// WIN over it: a caller that supplies its own `std/system` gets the
|
|
1813
|
+
// one it supplied.
|
|
1814
|
+
let memResolver = makeMemResolver(memCapability
|
|
1815
|
+
? { ...(capability as any).mem }
|
|
1816
|
+
: { ...(options.resolver?.mem || {}) })
|
|
1182
1817
|
|
|
1183
1818
|
// TODO: make this consistent with other resolvers
|
|
1184
1819
|
let fileResolver = makeFileResolver((spec: any) => {
|
|
@@ -1190,6 +1825,121 @@ function makeModelResolver(options: any) {
|
|
|
1190
1825
|
...(options.resolver?.pkg || {})
|
|
1191
1826
|
})
|
|
1192
1827
|
|
|
1828
|
+
// Confinement is realpath-then-prefix-check (docs/trust.md): the
|
|
1829
|
+
// RESOLVED file's real path must sit below the root's real path, so a
|
|
1830
|
+
// symlink inside the root pointing outside it is an escape, not a
|
|
1831
|
+
// loophole. A path realpath cannot resolve falls back to the lexical
|
|
1832
|
+
// form — the comparison is then against what the resolver actually
|
|
1833
|
+
// read.
|
|
1834
|
+
// Real fs, deliberately: `options.fs` is not a sandbox (it feeds
|
|
1835
|
+
// parse text; the file leg reads through its own channel), so the
|
|
1836
|
+
// containment check must see the same filesystem that leg read from.
|
|
1837
|
+
// A path that does not (fully) exist cannot be realpath'd whole, and
|
|
1838
|
+
// falling back to the LEXICAL form compares apples to oranges when
|
|
1839
|
+
// the root itself sits behind a symlink -- on macOS a root under
|
|
1840
|
+
// /var realpaths to /private/var, so a merely-missing file inside it
|
|
1841
|
+
// reads as an escape. Realpath the deepest EXISTING ancestor and
|
|
1842
|
+
// re-attach the rest, so both sides of the check are in real
|
|
1843
|
+
// coordinates. (The MCP server's own confinement carries the twin of
|
|
1844
|
+
// this rule; its CI failure is what found the shape.)
|
|
1845
|
+
const realpath = (p: string): string => {
|
|
1846
|
+
try {
|
|
1847
|
+
return realpathSync(p)
|
|
1848
|
+
}
|
|
1849
|
+
catch {
|
|
1850
|
+
const parent = pathDirname(p)
|
|
1851
|
+
if (parent === p) {
|
|
1852
|
+
return p
|
|
1853
|
+
}
|
|
1854
|
+
return pathJoin(realpath(parent), pathBasename(p))
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
const outsideRoot = (root: string, full: string): boolean => {
|
|
1859
|
+
const rootReal = realpath(root)
|
|
1860
|
+
const fullReal = realpath(full)
|
|
1861
|
+
return fullReal !== rootReal && !fullReal.startsWith(rootReal + pathSep)
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// A denial THROWS with the code; Lang.parse converts it to the
|
|
1865
|
+
// parse-stage `include_denied` nil (the same shape a syntax failure
|
|
1866
|
+
// takes). Raising beats injecting a nil value: a bare-member include
|
|
1867
|
+
// (`@"denied.aon"` at the top of a file) MERGES into the enclosing
|
|
1868
|
+
// map, and a nil contributes no keys, so an injected denial would
|
|
1869
|
+
// vanish and leave a plausible, silently-partial document.
|
|
1870
|
+
const deny = (path: string): never => {
|
|
1871
|
+
// Only 'none' and 'root' can deny: the mem capability's misses are
|
|
1872
|
+
// not-found (its set is the whole world), so there is no third arm.
|
|
1873
|
+
const capname = 'none' === capability ? 'none' : 'root:' + rootDir
|
|
1874
|
+
const err: any = new Error(
|
|
1875
|
+
'include denied: ' + path + ' (capability: ' + capname + ')')
|
|
1876
|
+
err.code = 'include_denied'
|
|
1877
|
+
throw err
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// AN UNREADABLE EXTENSION THROWS, exactly as a denial does, and for
|
|
1881
|
+
// the same reason: a bare-member include (`@"notes.txt"` at the top
|
|
1882
|
+
// of a file) MERGES into the enclosing map, and a nil contributes no
|
|
1883
|
+
// keys, so an injected refusal would vanish and leave a plausible,
|
|
1884
|
+
// silently-partial document. Lang.parse turns the throw into the
|
|
1885
|
+
// parse-stage `include_extension` nil.
|
|
1886
|
+
const refuseExtension = (path: string, full: string): never => {
|
|
1887
|
+
const err: any = new Error(extensionMsg(path, extKindOf(full)))
|
|
1888
|
+
err.code = 'include_extension'
|
|
1889
|
+
throw err
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// The gate every leg that RESOLVES A NAME passes through. The std and
|
|
1893
|
+
// module legs do not: both state `kind: 'aon'` because what they
|
|
1894
|
+
// serve is Aontu source by construction, not by its spelling.
|
|
1895
|
+
const gateExtension = (path: string, full: string): void => {
|
|
1896
|
+
if (undefined === INCLUDE_KINDS[extKindOf(full)]) {
|
|
1897
|
+
refuseExtension(path, full)
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
// The user cache: whatever the host named, else the platform rule
|
|
1902
|
+
// (`modCacheDir`, ts/src/mod.ts) the tooling writes by.
|
|
1903
|
+
const modCache = (opts: any): string | undefined => {
|
|
1904
|
+
const named = opts.mod?.cache
|
|
1905
|
+
return 'string' === typeof named ? named : modCacheDir()
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// The directory an include is being resolved FROM: the source that
|
|
1909
|
+
// holds it, or the entry path when the source is a string. Same base
|
|
1910
|
+
// the file leg computes (resolvePathSpec in @tabnas/multisource).
|
|
1911
|
+
const dirOf = (p: string | undefined): string =>
|
|
1912
|
+
null == p || '' === p ? pathResolve('.') : pathDirname(pathResolve(p))
|
|
1913
|
+
|
|
1914
|
+
// The module store reader: the host's filesystem when one was
|
|
1915
|
+
// injected, so a sandboxed evaluation stays in the filesystem the
|
|
1916
|
+
// host gave it.
|
|
1917
|
+
const modFs = (ctx: any): any => {
|
|
1918
|
+
const hostfs = ctx?.meta?.fs
|
|
1919
|
+
return null == hostfs ? { existsSync, readFileSync } : {
|
|
1920
|
+
existsSync: (p: string) => {
|
|
1921
|
+
try {
|
|
1922
|
+
hostfs.statSync(p)
|
|
1923
|
+
return true
|
|
1924
|
+
}
|
|
1925
|
+
catch {
|
|
1926
|
+
return false
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1929
|
+
readFileSync: (p: string, enc: string) => hostfs.readFileSync(p, enc),
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// The manifest sink rides the parse meta (Lang.parse seeds it, the
|
|
1934
|
+
// multisource plugin's child-meta spread carries it to every nested
|
|
1935
|
+
// include), so the recorded closure covers the whole include tree.
|
|
1936
|
+
const record = (ctx: any, path: string, cap: string) => {
|
|
1937
|
+
const manifest = ctx?.meta?.aontu?.manifest
|
|
1938
|
+
if (Array.isArray(manifest)) {
|
|
1939
|
+
manifest.push({ path, capability: cap })
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1193
1943
|
return function ModelResolver(
|
|
1194
1944
|
spec: any,
|
|
1195
1945
|
popts: any,
|
|
@@ -1209,10 +1959,69 @@ function makeModelResolver(options: any) {
|
|
|
1209
1959
|
return { found: false, path: '' + (path ?? ''), search: [] }
|
|
1210
1960
|
}
|
|
1211
1961
|
|
|
1962
|
+
if ('none' === capability) {
|
|
1963
|
+
deny(path)
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts): served from
|
|
1967
|
+
// the engine itself, so it needs neither the filesystem nor package
|
|
1968
|
+
// resolution and is available under every capability but `none` —
|
|
1969
|
+
// checked just above, that being what `none` means. Matched against
|
|
1970
|
+
// the name the author WROTE, before the memory leg, so the kind is
|
|
1971
|
+
// stated rather than guessed from an extension the bare name does
|
|
1972
|
+
// not have.
|
|
1973
|
+
const std = STD_SOURCES[path]
|
|
1974
|
+
if (null != std) {
|
|
1975
|
+
record(ctx, path, 'std')
|
|
1976
|
+
return { found: true, path, full: path, kind: 'aon', src: std, search: [] }
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1212
1979
|
let search: any = []
|
|
1213
1980
|
let res = memResolver(path, popts, rule, ctx, jsonic)
|
|
1214
1981
|
res.path = path
|
|
1215
1982
|
if (res.found) {
|
|
1983
|
+
// THE EXTENSION DECIDES HERE TOO. A virtual file set is still a
|
|
1984
|
+
// file set: its keys carry extensions, and the same rule has to
|
|
1985
|
+
// read them, or the mem capability becomes a way to include what
|
|
1986
|
+
// the filesystem would refuse.
|
|
1987
|
+
gateExtension(path, res.full ?? path)
|
|
1988
|
+
record(ctx, res.full ?? path, 'mem')
|
|
1989
|
+
return res
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
// THE MODULE LEG (G6 phase 2, ts/src/mod.ts): memory -> MODULE ->
|
|
1993
|
+
// filesystem -> package. Memory stays FIRST so a sandbox and the
|
|
1994
|
+
// spec suite can stub a module path without touching disk; a path
|
|
1995
|
+
// that is not module-shaped falls straight through, so no existing
|
|
1996
|
+
// include can be routed somewhere new by this.
|
|
1997
|
+
const modref = memCapability ? undefined : parseModuleRef(path)
|
|
1998
|
+
if (null != modref) {
|
|
1999
|
+
const msmeta = (ctx as any)?.meta?.multisource
|
|
2000
|
+
const from = dirOf(null != msmeta?.path ? msmeta.path : popts?.path)
|
|
2001
|
+
const found = resolveModule(modref, from, modFs(ctx), {
|
|
2002
|
+
// The user cache lives outside any confinement root, so it is
|
|
2003
|
+
// consulted only when nothing confines this evaluation. A
|
|
2004
|
+
// rooted profile sees the project's own `aon_vendor/` and
|
|
2005
|
+
// nothing else, which is what `root` means.
|
|
2006
|
+
...(null == rootDir ? { cache: modCache(options) } : {}),
|
|
2007
|
+
eval: options.mod?.eval,
|
|
2008
|
+
depth: options.mod?.depth,
|
|
2009
|
+
})
|
|
2010
|
+
if (null != rootDir && outsideRoot(rootDir, found.full)) {
|
|
2011
|
+
deny(path)
|
|
2012
|
+
}
|
|
2013
|
+
record(ctx, found.full, 'mod')
|
|
2014
|
+
return {
|
|
2015
|
+
found: true, path, full: found.full,
|
|
2016
|
+
kind: 'aon', src: found.src, search: [],
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
if (memCapability) {
|
|
2021
|
+
// A miss in the declared virtual set is NOT-FOUND, not denial:
|
|
2022
|
+
// the allowed mechanism ran and missed. Denial is reserved for a
|
|
2023
|
+
// capability refusing a mechanism outright.
|
|
2024
|
+
res.search = search.concat(res.search)
|
|
1216
2025
|
return res
|
|
1217
2026
|
}
|
|
1218
2027
|
|
|
@@ -1221,14 +2030,47 @@ function makeModelResolver(options: any) {
|
|
|
1221
2030
|
res = fileResolver(path, popts, rule, ctx, jsonic)
|
|
1222
2031
|
res.path = path
|
|
1223
2032
|
if (res.found) {
|
|
2033
|
+
// `res.full` asserted non-null: a FOUND file resolution always
|
|
2034
|
+
// carries the absolute path it read (and the pkg leg below is the
|
|
2035
|
+
// same), so a runtime fallback arm would be dead code.
|
|
2036
|
+
const full = res.full as string
|
|
2037
|
+
if (null != rootDir && outsideRoot(rootDir, full)) {
|
|
2038
|
+
deny(path)
|
|
2039
|
+
}
|
|
2040
|
+
// After the trust check, not before: a file outside the
|
|
2041
|
+
// confinement root is denied whatever it is called, and answering
|
|
2042
|
+
// "extension" there would say the file exists.
|
|
2043
|
+
gateExtension(path, full)
|
|
2044
|
+
// The warning window for the staged default flip (G5 phase 6):
|
|
2045
|
+
// under 'system', the CLI supplies trustWarn and the entry root,
|
|
2046
|
+
// and every resolution escaping that root names the flag a future
|
|
2047
|
+
// default will require.
|
|
2048
|
+
if (null == rootDir && null != options.trustWarn &&
|
|
2049
|
+
null != options.trustWarnRoot &&
|
|
2050
|
+
outsideRoot(options.trustWarnRoot, full)) {
|
|
2051
|
+
options.trustWarn('escape', full)
|
|
2052
|
+
}
|
|
2053
|
+
record(ctx, full, 'file')
|
|
1224
2054
|
return res
|
|
1225
2055
|
}
|
|
1226
2056
|
|
|
1227
2057
|
search = search.concat(res.search)
|
|
1228
2058
|
|
|
2059
|
+
if (null != rootDir) {
|
|
2060
|
+
// Package resolution is not part of the root capability; the
|
|
2061
|
+
// miss stands as not-found with the searched paths listed.
|
|
2062
|
+
res.search = search
|
|
2063
|
+
return res
|
|
2064
|
+
}
|
|
2065
|
+
|
|
1229
2066
|
res = pkgResolver(path, popts, rule, ctx, jsonic)
|
|
1230
2067
|
res.path = path
|
|
1231
2068
|
if (res.found) {
|
|
2069
|
+
gateExtension(path, res.full as string)
|
|
2070
|
+
if (null != options.trustWarn) {
|
|
2071
|
+
options.trustWarn('pkg', res.full as string)
|
|
2072
|
+
}
|
|
2073
|
+
record(ctx, res.full as string, 'pkg')
|
|
1232
2074
|
return res
|
|
1233
2075
|
}
|
|
1234
2076
|
|
|
@@ -1238,27 +2080,62 @@ function makeModelResolver(options: any) {
|
|
|
1238
2080
|
}
|
|
1239
2081
|
|
|
1240
2082
|
|
|
1241
|
-
//
|
|
2083
|
+
// THE SIGNATURE REGISTRY (docs/design/SIGNATURES.0.md). The call
|
|
2084
|
+
// surface is DECLARED in test/spec/signature.tsv and parsed by the
|
|
2085
|
+
// signature grammar (ts/src/sig.ts) from the build-time-inlined copy;
|
|
2086
|
+
// the arity table and the positional set below are DERIVED from the
|
|
2087
|
+
// parsed registry (funcSig, ts/src/sig.ts), so the declaration is the
|
|
2088
|
+
// one source. go/func.go derives the same two tables from the same
|
|
2089
|
+
// text.
|
|
2090
|
+
// The functions whose comma-separated arguments are distinct POSITIONS
|
|
2091
|
+
// rather than one argument list. See the func-paren handler above: this
|
|
2092
|
+
// is the set whose comma group is expanded back into separate `peg`
|
|
2093
|
+
// entries. Derived: two or more declared argument slots, excluding the
|
|
2094
|
+
// residual producers (`constraint` results) -- the constraint atoms
|
|
2095
|
+
// make the same expansion in their own constructor (`atomArgs`,
|
|
2096
|
+
// ConstraintVal.ts, deliberately before the settled check), which is
|
|
2097
|
+
// why they are not in this set; `must` is the load-bearing example.
|
|
2098
|
+
// Arithmetic is here because `sub` is not commutative: `sub(a, b)`
|
|
2099
|
+
// reaching the engine as one two-element list would lose which is
|
|
2100
|
+
// which.
|
|
2101
|
+
const POSITIONAL_ARG_FUNCS: Record<string, boolean> = {}
|
|
2102
|
+
for (const name in funcSig) {
|
|
2103
|
+
if (2 <= funcSig[name].args.length && 'constraint' !== funcSig[name].out) {
|
|
2104
|
+
POSITIONAL_ARG_FUNCS[name] = true
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
|
|
1242
2109
|
// [min, max]; a max of -1 is unbounded. Every name in funcMap has an
|
|
1243
2110
|
// entry, and the arity is a property of the language rather than of
|
|
1244
|
-
// either port -- go/func.go
|
|
1245
|
-
//
|
|
1246
|
-
//
|
|
1247
|
-
//
|
|
1248
|
-
//
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
2111
|
+
// either port -- go/func.go derives the same table. A required slot
|
|
2112
|
+
// counts toward the minimum; a rest slot makes the maximum unbounded
|
|
2113
|
+
// and counts its group size (one, for a plain rest type) toward the
|
|
2114
|
+
// minimum, which is what gives `match` its floor of three and `neq`
|
|
2115
|
+
// its floor of one.
|
|
2116
|
+
function sigArity(sig: FuncSig): [number, number] {
|
|
2117
|
+
let min = 0
|
|
2118
|
+
let max = 0
|
|
2119
|
+
for (const a of sig.args) {
|
|
2120
|
+
if (true === a.rest) {
|
|
2121
|
+
min += undefined === a.group ? 1 : a.group.length
|
|
2122
|
+
max = -1
|
|
2123
|
+
}
|
|
2124
|
+
else {
|
|
2125
|
+
if (true !== a.opt) {
|
|
2126
|
+
min++
|
|
2127
|
+
}
|
|
2128
|
+
if (-1 !== max) {
|
|
2129
|
+
max++
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return [min, max]
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
const funcArity: Record<string, [number, number]> = {}
|
|
2137
|
+
for (const name in funcSig) {
|
|
2138
|
+
funcArity[name] = sigArity(funcSig[name])
|
|
1262
2139
|
}
|
|
1263
2140
|
|
|
1264
2141
|
|
|
@@ -1293,8 +2170,10 @@ function arityText(lo: number, hi: number): string {
|
|
|
1293
2170
|
return 'one or more arguments'
|
|
1294
2171
|
}
|
|
1295
2172
|
if (lo !== hi) {
|
|
1296
|
-
return 'no arguments or one'
|
|
2173
|
+
return 0 === lo ? 'no arguments or one' : 'one argument or two'
|
|
1297
2174
|
}
|
|
2175
|
+
// The {0,0} arm returned with the container kinds and acyclic()
|
|
2176
|
+
// (ADR-015): `map(1)` must not claim map takes exactly one.
|
|
1298
2177
|
if (0 === hi) {
|
|
1299
2178
|
return 'no arguments'
|
|
1300
2179
|
}
|
|
@@ -1339,15 +2218,22 @@ function opCharHint(src: string): string {
|
|
|
1339
2218
|
|
|
1340
2219
|
|
|
1341
2220
|
function rawToVal(n: any): Val {
|
|
1342
|
-
if (
|
|
1343
|
-
return new NullVal({ peg: null })
|
|
1344
|
-
}
|
|
1345
|
-
if (true === n.isVal) {
|
|
2221
|
+
if (true === n?.isVal) {
|
|
1346
2222
|
return n
|
|
1347
2223
|
}
|
|
1348
2224
|
if (Array.isArray(n)) {
|
|
1349
2225
|
return new ListVal({ peg: n.map(rawToVal) })
|
|
1350
2226
|
}
|
|
2227
|
+
|
|
2228
|
+
// THE SCALAR ARMS ARE WHERE A CONFIG FILE BECOMES VALUES. Every
|
|
2229
|
+
// format on the include table is read by its own parser into plain
|
|
2230
|
+
// JavaScript -- a string, a number, a map -- and this is the walk
|
|
2231
|
+
// that turns that into Vals (dataProcessor, ADR-012). The two arms
|
|
2232
|
+
// above are the other caller: a raw expression TERM, which the
|
|
2233
|
+
// expression grammar hands over already built.
|
|
2234
|
+
if (null == n) {
|
|
2235
|
+
return new NullVal({ peg: null })
|
|
2236
|
+
}
|
|
1351
2237
|
const t = typeof n
|
|
1352
2238
|
if ('string' === t) {
|
|
1353
2239
|
return new StringVal({ peg: n })
|
|
@@ -1362,14 +2248,20 @@ function rawToVal(n: any): Val {
|
|
|
1362
2248
|
if ('boolean' === t) {
|
|
1363
2249
|
return new BooleanVal({ peg: n })
|
|
1364
2250
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
2251
|
+
// AND EVERYTHING ELSE IS A MAP, with no arm after it because there is
|
|
2252
|
+
// nothing after it. Every reader on the include table answers with
|
|
2253
|
+
// the JSON kinds and no others -- probed, including the two that
|
|
2254
|
+
// could plausibly escape them: a big integer comes back a `number`,
|
|
2255
|
+
// and a TOML date is normalised to its text before it gets here. The
|
|
2256
|
+
// one include that could hand over a function was `.js`, which
|
|
2257
|
+
// ADR-012 refuses. `parse_unknown` lived here for that case and has
|
|
2258
|
+
// no producer left in this port; the Go twin keeps its own, where the
|
|
2259
|
+
// type switch really can be handed something unaccounted for.
|
|
2260
|
+
const peg: Record<string, Val> = {}
|
|
2261
|
+
for (const k in n) {
|
|
2262
|
+
peg[k] = rawToVal(n[k])
|
|
1371
2263
|
}
|
|
1372
|
-
return new
|
|
2264
|
+
return new MapVal({ peg })
|
|
1373
2265
|
}
|
|
1374
2266
|
|
|
1375
2267
|
|
|
@@ -1401,11 +2293,22 @@ class Lang {
|
|
|
1401
2293
|
// works. `.jsonic` is retired (no longer auto-resolved); the
|
|
1402
2294
|
// default `['jsonic','jsc','json','js']` is overridden here.
|
|
1403
2295
|
// (Upstream option name is the misspelled `implictExt`.)
|
|
2296
|
+
//
|
|
2297
|
+
// Only these two are SEARCHED for a bare `@"name"`; `.json` and
|
|
2298
|
+
// `.jsonld` are read when NAMED, which is how a vendored
|
|
2299
|
+
// vocabulary is always written.
|
|
1404
2300
|
implictExt: ['aon', 'aontu'],
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
2301
|
+
// ONE ENTRY PER EXTENSION THE TABLE NAMES, built from it (see
|
|
2302
|
+
// includeProcessors) so the rule and its wiring cannot drift.
|
|
2303
|
+
//
|
|
2304
|
+
// The upstream defaults are REPLACED, not extended. Its `json`
|
|
2305
|
+
// entry is what made that extension the one that crashed: it
|
|
2306
|
+
// hands back a raw JS object where the aontu grammar produces
|
|
2307
|
+
// Vals, and the tree then met a value it could not convert
|
|
2308
|
+
// (BUGS §49b). Its `js` entry EXECUTES the file, which is not
|
|
2309
|
+
// something an extension should be able to ask for. And its
|
|
2310
|
+
// fallback hands any other file back as TEXT.
|
|
2311
|
+
processor: includeProcessors()
|
|
1409
2312
|
})
|
|
1410
2313
|
.use(AontuJsonic)
|
|
1411
2314
|
}
|
|
@@ -1421,7 +2324,13 @@ class Lang {
|
|
|
1421
2324
|
multisource: {
|
|
1422
2325
|
path: opts?.path ?? this.opts.path,
|
|
1423
2326
|
deps: (opts && opts.deps) || undefined
|
|
1424
|
-
}
|
|
2327
|
+
},
|
|
2328
|
+
// The include-manifest sink (G5, docs/trust.md): the resolver
|
|
2329
|
+
// records every resolved include here, and the plugin's
|
|
2330
|
+
// child-meta spread carries the same array to nested includes.
|
|
2331
|
+
aontu: {
|
|
2332
|
+
manifest: (opts as any)?.manifest,
|
|
2333
|
+
},
|
|
1425
2334
|
}
|
|
1426
2335
|
|
|
1427
2336
|
if (null != opts?.idcount) {
|
|
@@ -1449,16 +2358,46 @@ class Lang {
|
|
|
1449
2358
|
}
|
|
1450
2359
|
}
|
|
1451
2360
|
catch (e: any) {
|
|
1452
|
-
if (
|
|
2361
|
+
if ('include_denied' === e?.code || 'include_extension' === e?.code ||
|
|
2362
|
+
MODULE_REFUSAL_CODES.has(e?.code)) {
|
|
2363
|
+
// A denied include (G5), an include whose extension is not read
|
|
2364
|
+
// as Aontu source (ADR-012, INCLUDE_KINDS), and a module that is
|
|
2365
|
+
// missing, fails its pin, or names a path that escapes its store
|
|
2366
|
+
// (G6 phase 2) are refused the same way, for the same reason: the
|
|
2367
|
+
// resolver THROWS so a bare-member include cannot vanish in the
|
|
2368
|
+
// merge, and the code survives here as the parse-stage nil the
|
|
2369
|
+
// registry pins (errcodes.tsv).
|
|
1453
2370
|
val = new NilVal({
|
|
1454
2371
|
why: 'parse',
|
|
1455
2372
|
err: new NilVal({
|
|
1456
|
-
why:
|
|
1457
|
-
msg: e.message
|
|
2373
|
+
why: e.code,
|
|
2374
|
+
msg: e.message,
|
|
1458
2375
|
err: e,
|
|
1459
2376
|
})
|
|
1460
2377
|
})
|
|
1461
2378
|
}
|
|
2379
|
+
else if (e instanceof JsonicError || 'JsonicError' === e.constructor.name) {
|
|
2380
|
+
const syntax: any = new NilVal({
|
|
2381
|
+
why: 'syntax',
|
|
2382
|
+
msg: e.message + opCharHint(src),
|
|
2383
|
+
err: e,
|
|
2384
|
+
})
|
|
2385
|
+
// THE POSITION TRAVELS WITH IT. The parser knows exactly where
|
|
2386
|
+
// it stopped -- it draws a caret there -- and the rendered
|
|
2387
|
+
// message carried the only copy, so `vet --format json`
|
|
2388
|
+
// reported row -1, col -1 for a document whose fault the human
|
|
2389
|
+
// renderer located to the character. A machine-readable report
|
|
2390
|
+
// that says "somewhere in this file" is the one a repair loop
|
|
2391
|
+
// can do nothing with. Both numbers are already 1-based here,
|
|
2392
|
+
// which is the base a site uses (go/lang.go does the same).
|
|
2393
|
+
if ('number' === typeof e.lineNumber) {
|
|
2394
|
+
syntax.site.row = e.lineNumber
|
|
2395
|
+
}
|
|
2396
|
+
if ('number' === typeof e.columnNumber) {
|
|
2397
|
+
syntax.site.col = e.columnNumber
|
|
2398
|
+
}
|
|
2399
|
+
val = new NilVal({ why: 'parse', err: syntax })
|
|
2400
|
+
}
|
|
1462
2401
|
else {
|
|
1463
2402
|
throw e
|
|
1464
2403
|
}
|