@voxgig/sdkgen 4.5.2 → 4.6.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/bin/voxgig-sdkgen +1 -1
- package/dist/action/check.js +15 -0
- package/dist/action/check.js.map +1 -1
- package/dist/cmp/AgentGuide.js +1 -1
- package/dist/cmp/AgentGuide.js.map +1 -1
- package/dist/cmp/AgentGuideContent.d.ts +1 -1
- package/dist/cmp/AgentGuideContent.js +8 -2
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +17 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/FeatureDocs.d.ts +1 -1
- package/dist/cmp/FeatureDocs.js +6 -1
- package/dist/cmp/FeatureDocs.js.map +1 -1
- package/dist/cmp/ReadmeExplanation.js +3 -1
- package/dist/cmp/ReadmeExplanation.js.map +1 -1
- package/dist/cmp/ReadmeFeatures.js +1 -1
- package/dist/cmp/ReadmeFeatures.js.map +1 -1
- package/dist/cmp/ReadmeRefFeatures.js +1 -1
- package/dist/cmp/ReadmeRefFeatures.js.map +1 -1
- package/dist/cmp/ReadmeTop.js +1 -1
- package/dist/helpers/applicability.d.ts +6 -0
- package/dist/helpers/applicability.js +103 -0
- package/dist/helpers/applicability.js.map +1 -0
- package/dist/helpers/collectDeps.js +4 -2
- package/dist/helpers/collectDeps.js.map +1 -1
- package/dist/helpers/serverVars.d.ts +2 -1
- package/dist/helpers/serverVars.js +17 -0
- package/dist/helpers/serverVars.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +9 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.js +7 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +39 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/feature-index.aon +1 -0
- package/project/.sdk/model/feature/secrets.aon +118 -0
- package/project/.sdk/model/target/ts.aon +8 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
- package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +6 -2
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
- package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +5 -1
- package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
- package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
- package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
- package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
- package/project/.sdk/src/cmp/perl/Main_perl.ts +5 -1
- package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
- package/project/.sdk/src/cmp/php/Main_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +5 -1
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
- package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +5 -1
- package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +5 -1
- package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +5 -1
- package/project/.sdk/src/cmp/ts/Main_ts.ts +57 -1
- package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +67 -7
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +27 -4
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
- package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
- package/project/.sdk/tm/c/utility/make_options.c +22 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
- package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
- package/project/.sdk/tm/go/core/context.go +1 -1
- package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
- package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
- package/project/.sdk/tm/go/feature/test_feature.go +12 -5
- package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
- package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
- package/project/.sdk/tm/go/test/runner_test.go +46 -0
- package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
- package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
- package/project/.sdk/tm/go/utility/fetcher.go +2 -2
- package/project/.sdk/tm/go/utility/make_options.go +24 -5
- package/project/.sdk/tm/go/utility/make_point.go +1 -1
- package/project/.sdk/tm/go/utility/make_spec.go +17 -1
- package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
- package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
- package/project/.sdk/tm/go/utility/transform_request.go +29 -1
- package/project/.sdk/tm/go/utility/transform_response.go +20 -1
- package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
- package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
- package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
- package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
- package/project/.sdk/tm/py/test/runner.py +32 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
- package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +25 -0
- package/project/.sdk/tm/scala/utility/Make.scala +17 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
- package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +1221 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +381 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +151 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +42 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
- package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
- package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
- package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
- package/project/.sdk/tm/ts/test/omni.ts +86 -0
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
- package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
- package/project/.sdk/tm/ts/test/utility.ts +122 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +692 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +238 -0
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
- package/project/.sdk/tm/zig/core/utility.zig +23 -0
- package/project/sdkgen-package.json +2 -1
- package/src/action/check.ts +18 -0
- package/src/cmp/AgentGuide.ts +1 -1
- package/src/cmp/AgentGuideContent.ts +9 -2
- package/src/cmp/Feature.ts +19 -0
- package/src/cmp/FeatureDocs.ts +7 -1
- package/src/cmp/ReadmeExplanation.ts +4 -1
- package/src/cmp/ReadmeFeatures.ts +1 -1
- package/src/cmp/ReadmeRefFeatures.ts +1 -1
- package/src/cmp/ReadmeTop.ts +1 -1
- package/src/helpers/applicability.ts +123 -0
- package/src/helpers/collectDeps.ts +5 -1
- package/src/helpers/serverVars.ts +19 -0
- package/src/sdkgen.ts +9 -1
- package/src/utility.ts +10 -1
- package/project/.sdk/tm/ts/test/runner.ts +0 -425
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
// VENDORED: @voxgig/struct 0.3.2 (typescript/src/StructUtility.ts)
|
|
2
|
+
// Source: https://github.com/voxgig/struct @ 94409354d0a60fda8098c83e290583b7dee76c84
|
|
3
|
+
// License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
1
4
|
/* Copyright (c) 2025-2026 Voxgig Ltd. MIT LICENSE. */
|
|
2
5
|
|
|
3
|
-
// VERSION: @voxgig/struct 0.
|
|
6
|
+
// VERSION: @voxgig/struct 0.3.2
|
|
4
7
|
|
|
5
8
|
/* Voxgig Struct
|
|
6
9
|
* =============
|
|
@@ -53,7 +56,6 @@
|
|
|
53
56
|
*
|
|
54
57
|
*/
|
|
55
58
|
|
|
56
|
-
|
|
57
59
|
// String constants are explicitly defined.
|
|
58
60
|
|
|
59
61
|
// Mode value for inject step (bitfield).
|
|
@@ -137,12 +139,20 @@ const TYPENAME = [
|
|
|
137
139
|
S_function,
|
|
138
140
|
S_symbol,
|
|
139
141
|
S_null,
|
|
140
|
-
'',
|
|
141
|
-
'',
|
|
142
|
+
'',
|
|
143
|
+
'',
|
|
144
|
+
'',
|
|
145
|
+
'',
|
|
146
|
+
'',
|
|
147
|
+
'',
|
|
148
|
+
'',
|
|
142
149
|
S_list,
|
|
143
150
|
S_map,
|
|
144
151
|
S_instance,
|
|
145
|
-
'',
|
|
152
|
+
'',
|
|
153
|
+
'',
|
|
154
|
+
'',
|
|
155
|
+
'',
|
|
146
156
|
S_scalar,
|
|
147
157
|
S_node,
|
|
148
158
|
]
|
|
@@ -154,23 +164,19 @@ const NONE = undefined
|
|
|
154
164
|
const SKIP = { '`$SKIP`': true }
|
|
155
165
|
const DELETE = { '`$DELETE`': true }
|
|
156
166
|
|
|
157
|
-
|
|
158
167
|
// Regular expression constants
|
|
159
|
-
const R_INTEGER_KEY = /^[-0-9]+$/
|
|
160
|
-
const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const R_BT_ESCAPE = /\$BT/g // Backtick escape sequence.
|
|
172
|
-
const R_DS_ESCAPE = /\$DS/g // Dollar sign escape sequence.
|
|
173
|
-
const R_INJECTION_PARTIAL = /`([^`]+)`/g // Partial string injection pattern.
|
|
168
|
+
const R_INTEGER_KEY = /^[-0-9]+$/ // Match integer keys (including <0).
|
|
169
|
+
const R_ESCAPE_REGEXP = /[.*+?^${}()|[\]\\]/g // Chars that need escaping in regexp.
|
|
170
|
+
const R_QUOTES = /"/g // Double quotes for removal.
|
|
171
|
+
const R_DOT = /\./g // Dots in path strings.
|
|
172
|
+
const R_CLONE_REF = /^`\$REF:([0-9]+)`$/ // Copy reference in cloning.
|
|
173
|
+
const R_META_PATH = /^([^$]+)\$([=~])(.+)$/ // Meta path syntax.
|
|
174
|
+
const R_DOUBLE_DOLLAR = /\$\$/g // Double dollar escape sequence.
|
|
175
|
+
const R_TRANSFORM_NAME = /`\$([A-Z]+)`/g // Transform command names.
|
|
176
|
+
const R_INJECTION_FULL = /^`(\$[A-Z]+|[^`]*)[0-9]*`$/ // Full string injection pattern.
|
|
177
|
+
const R_BT_ESCAPE = /\$BT/g // Backtick escape sequence.
|
|
178
|
+
const R_DS_ESCAPE = /\$DS/g // Dollar sign escape sequence.
|
|
179
|
+
const R_INJECTION_PARTIAL = /`([^`]+)`/g // Partial string injection pattern.
|
|
174
180
|
|
|
175
181
|
// Default max depth (for walk etc).
|
|
176
182
|
const MAXDEPTH = 32
|
|
@@ -181,7 +187,6 @@ type PropKey = string | number
|
|
|
181
187
|
// Type that can be indexed by both string and number keys.
|
|
182
188
|
type Indexable = { [key: string]: any } & { [key: number]: any }
|
|
183
189
|
|
|
184
|
-
|
|
185
190
|
// For each key in a node (map or list), perform value injections in
|
|
186
191
|
// three phases: on key value, before child, and then on key value again.
|
|
187
192
|
// This mode is passed via the Injection structure.
|
|
@@ -191,38 +196,40 @@ type InjectMode = number
|
|
|
191
196
|
// - `a.b.c`: insert value at {a:{b:{c:1}}}
|
|
192
197
|
// - `$FOO`: apply transform FOO
|
|
193
198
|
type Injector = (
|
|
194
|
-
inj: Injection,
|
|
195
|
-
val: any,
|
|
196
|
-
ref: string,
|
|
197
|
-
store: any,
|
|
199
|
+
inj: Injection, // Injection state.
|
|
200
|
+
val: any, // Injection value specification.
|
|
201
|
+
ref: string, // Original injection reference string.
|
|
202
|
+
store: any, // Current source root value.
|
|
198
203
|
) => any
|
|
199
204
|
|
|
200
205
|
// Apply a custom modification to injections.
|
|
201
206
|
type Modify = (
|
|
202
|
-
val: any,
|
|
203
|
-
key?: PropKey,
|
|
204
|
-
parent?: any,
|
|
205
|
-
inj?: Injection,
|
|
206
|
-
store?: any,
|
|
207
|
+
val: any, // Value.
|
|
208
|
+
key?: PropKey, // Value key, if any,
|
|
209
|
+
parent?: any, // Parent node, if any.
|
|
210
|
+
inj?: Injection, // Injection state, if any.
|
|
211
|
+
store?: any, // Store, if any
|
|
207
212
|
) => void
|
|
208
213
|
|
|
209
214
|
// Function applied to each node and leaf when walking a node structure depth first.
|
|
210
215
|
// For {a:{b:1}} the call sequence args will be: b, 1, {b:1}, [a,b].
|
|
216
|
+
// NOTE: the `path` array passed to the callback is reused across calls during
|
|
217
|
+
// a single walk (one shared array per depth). It is valid only for the
|
|
218
|
+
// duration of the callback invocation. If a callback needs to retain the
|
|
219
|
+
// path, it MUST clone it (e.g. `path.slice()`).
|
|
211
220
|
type WalkApply = (
|
|
212
|
-
// Map keys are strings, list keys are numbers, top key is NONE
|
|
221
|
+
// Map keys are strings, list keys are numbers, top key is NONE
|
|
213
222
|
key: string | number | undefined,
|
|
214
223
|
val: any,
|
|
215
224
|
parent: any,
|
|
216
|
-
path: string[]
|
|
225
|
+
path: string[],
|
|
217
226
|
) => any
|
|
218
227
|
|
|
219
|
-
|
|
220
228
|
// Return type string for narrowest type.
|
|
221
229
|
function typename(t: number) {
|
|
222
230
|
return getelem(TYPENAME, Math.clz32(t), TYPENAME[0])
|
|
223
231
|
}
|
|
224
232
|
|
|
225
|
-
|
|
226
233
|
// Get a defined value. Returns alt if val is undefined.
|
|
227
234
|
function getdef(val: any, alt: any) {
|
|
228
235
|
if (NONE === val) {
|
|
@@ -231,7 +238,6 @@ function getdef(val: any, alt: any) {
|
|
|
231
238
|
return val
|
|
232
239
|
}
|
|
233
240
|
|
|
234
|
-
|
|
235
241
|
// Value is a node - defined, and a map (hash) or list (array).
|
|
236
242
|
// NOTE: typescript
|
|
237
243
|
// things
|
|
@@ -239,47 +245,43 @@ function isnode(val: any): val is Indexable {
|
|
|
239
245
|
return null != val && S_object == typeof val
|
|
240
246
|
}
|
|
241
247
|
|
|
242
|
-
|
|
243
248
|
// Value is a defined map (hash) with string keys.
|
|
244
249
|
function ismap(val: any): val is { [key: string]: any } {
|
|
245
250
|
return null != val && S_object == typeof val && !Array.isArray(val)
|
|
246
251
|
}
|
|
247
252
|
|
|
248
|
-
|
|
249
253
|
// Value is a defined list (array) with integer keys (indexes).
|
|
250
254
|
function islist(val: any): val is any[] {
|
|
251
255
|
return Array.isArray(val)
|
|
252
256
|
}
|
|
253
257
|
|
|
254
|
-
|
|
255
258
|
// Value is a defined string (non-empty) or integer key.
|
|
256
259
|
function iskey(key: any): key is PropKey {
|
|
257
260
|
const keytype = typeof key
|
|
258
261
|
return (S_string === keytype && S_MT !== key) || S_number === keytype
|
|
259
262
|
}
|
|
260
263
|
|
|
261
|
-
|
|
262
264
|
// Check for an "empty" value - undefined, empty string, array, object.
|
|
263
265
|
function isempty(val: any) {
|
|
264
|
-
return
|
|
266
|
+
return (
|
|
267
|
+
null == val ||
|
|
268
|
+
S_MT === val ||
|
|
265
269
|
(Array.isArray(val) && 0 === val.length) ||
|
|
266
270
|
(S_object === typeof val && 0 === Object.keys(val).length)
|
|
271
|
+
)
|
|
267
272
|
}
|
|
268
273
|
|
|
269
|
-
|
|
270
274
|
// Value is a function.
|
|
271
|
-
function isfunc(val: any): val is
|
|
275
|
+
function isfunc(val: any): val is (...args: any[]) => any {
|
|
272
276
|
return S_function === typeof val
|
|
273
277
|
}
|
|
274
278
|
|
|
275
|
-
|
|
276
279
|
// The integer size of the value. For arrays and strings, the length,
|
|
277
280
|
// for numbers, the integer part, for boolean, true is 1 and falso 0, for all other values, 0.
|
|
278
281
|
function size(val: any): number {
|
|
279
282
|
if (islist(val)) {
|
|
280
283
|
return val.length
|
|
281
|
-
}
|
|
282
|
-
else if (ismap(val)) {
|
|
284
|
+
} else if (ismap(val)) {
|
|
283
285
|
return Object.keys(val).length
|
|
284
286
|
}
|
|
285
287
|
|
|
@@ -287,19 +289,15 @@ function size(val: any): number {
|
|
|
287
289
|
|
|
288
290
|
if (S_string == valtype) {
|
|
289
291
|
return val.length
|
|
290
|
-
}
|
|
291
|
-
else if (S_number == typeof val) {
|
|
292
|
+
} else if (S_number == typeof val) {
|
|
292
293
|
return Math.floor(val)
|
|
293
|
-
}
|
|
294
|
-
else if (S_boolean == typeof val) {
|
|
294
|
+
} else if (S_boolean == typeof val) {
|
|
295
295
|
return true === val ? 1 : 0
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
296
|
+
} else {
|
|
298
297
|
return 0
|
|
299
298
|
}
|
|
300
299
|
}
|
|
301
300
|
|
|
302
|
-
|
|
303
301
|
// Extract part of an array or string into a new value, from the start
|
|
304
302
|
// point to the end point. If no end is specified, extract to the
|
|
305
303
|
// full length of the value. Negative arguments count from the end of
|
|
@@ -307,7 +305,7 @@ function size(val: any): number {
|
|
|
307
305
|
// is inclusive, and end is *exclusive*.
|
|
308
306
|
// NOTE: input lists are not mutated by default. Use the mutate
|
|
309
307
|
// argument to mutate lists in place.
|
|
310
|
-
function slice<V
|
|
308
|
+
function slice<V>(val: V, start?: number, end?: number, mutate?: boolean): V {
|
|
311
309
|
if (S_number === typeof val) {
|
|
312
310
|
start = null == start || S_number !== typeof start ? Number.MIN_SAFE_INTEGER : start
|
|
313
311
|
end = (null == end || S_number !== typeof end ? Number.MAX_SAFE_INTEGER : end) - 1
|
|
@@ -327,21 +325,16 @@ function slice<V extends any>(val: V, start?: number, end?: number, mutate?: boo
|
|
|
327
325
|
end = 0
|
|
328
326
|
}
|
|
329
327
|
start = 0
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
else if (null != end) {
|
|
328
|
+
} else if (null != end) {
|
|
333
329
|
if (end < 0) {
|
|
334
330
|
end = vlen + end
|
|
335
331
|
if (end < 0) {
|
|
336
332
|
end = 0
|
|
337
333
|
}
|
|
338
|
-
}
|
|
339
|
-
else if (vlen < end) {
|
|
334
|
+
} else if (vlen < end) {
|
|
340
335
|
end = vlen
|
|
341
336
|
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
else {
|
|
337
|
+
} else {
|
|
345
338
|
end = vlen
|
|
346
339
|
}
|
|
347
340
|
|
|
@@ -355,21 +348,17 @@ function slice<V extends any>(val: V, start?: number, end?: number, mutate?: boo
|
|
|
355
348
|
for (let i = 0, j = start; j < end; i++, j++) {
|
|
356
349
|
val[i] = val[j]
|
|
357
350
|
}
|
|
358
|
-
val.length =
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
351
|
+
val.length = end - start
|
|
352
|
+
} else {
|
|
361
353
|
val = val.slice(start, end) as V
|
|
362
354
|
}
|
|
363
|
-
}
|
|
364
|
-
else if (S_string === typeof val) {
|
|
355
|
+
} else if (S_string === typeof val) {
|
|
365
356
|
val = (val as string).substring(start, end) as V
|
|
366
357
|
}
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
358
|
+
} else {
|
|
369
359
|
if (islist(val)) {
|
|
370
360
|
val = [] as V
|
|
371
|
-
}
|
|
372
|
-
else if (S_string === typeof val) {
|
|
361
|
+
} else if (S_string === typeof val) {
|
|
373
362
|
val = S_MT as V
|
|
374
363
|
}
|
|
375
364
|
}
|
|
@@ -378,19 +367,16 @@ function slice<V extends any>(val: V, start?: number, end?: number, mutate?: boo
|
|
|
378
367
|
return val
|
|
379
368
|
}
|
|
380
369
|
|
|
381
|
-
|
|
382
370
|
// String padding.
|
|
383
371
|
function pad(str: any, padding?: number, padchar?: string): string {
|
|
384
372
|
str = S_string === typeof str ? str : stringify(str)
|
|
385
373
|
padding = null == padding ? 44 : padding
|
|
386
|
-
padchar = null == padchar ? S_SP : (
|
|
374
|
+
padchar = null == padchar ? S_SP : (padchar + S_SP)[0]
|
|
387
375
|
return -1 < padding ? str.padEnd(padding, padchar) : str.padStart(0 - padding, padchar)
|
|
388
376
|
}
|
|
389
377
|
|
|
390
|
-
|
|
391
378
|
// Determine the type of a value as a bit code.
|
|
392
379
|
function typify(value: any): number {
|
|
393
|
-
|
|
394
380
|
if (undefined === value) {
|
|
395
381
|
return T_noval
|
|
396
382
|
}
|
|
@@ -399,41 +385,30 @@ function typify(value: any): number {
|
|
|
399
385
|
|
|
400
386
|
if (null === value) {
|
|
401
387
|
return T_scalar | T_null
|
|
402
|
-
}
|
|
403
|
-
else if (S_number === typestr) {
|
|
388
|
+
} else if (S_number === typestr) {
|
|
404
389
|
if (Number.isInteger(value)) {
|
|
405
390
|
return T_scalar | T_number | T_integer
|
|
406
|
-
}
|
|
407
|
-
else if (isNaN(value)) {
|
|
391
|
+
} else if (isNaN(value)) {
|
|
408
392
|
return T_noval
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
393
|
+
} else {
|
|
411
394
|
return T_scalar | T_number | T_decimal
|
|
412
395
|
}
|
|
413
|
-
}
|
|
414
|
-
else if (S_string === typestr) {
|
|
396
|
+
} else if (S_string === typestr) {
|
|
415
397
|
return T_scalar | T_string
|
|
416
|
-
}
|
|
417
|
-
else if (S_boolean === typestr) {
|
|
398
|
+
} else if (S_boolean === typestr) {
|
|
418
399
|
return T_scalar | T_boolean
|
|
419
|
-
}
|
|
420
|
-
else if (S_function === typestr) {
|
|
400
|
+
} else if (S_function === typestr) {
|
|
421
401
|
return T_scalar | T_function
|
|
422
402
|
}
|
|
423
403
|
|
|
424
404
|
// For languages that have symbolic atoms.
|
|
425
405
|
else if (S_symbol === typestr) {
|
|
426
406
|
return T_scalar | T_symbol
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
else if (Array.isArray(value)) {
|
|
407
|
+
} else if (Array.isArray(value)) {
|
|
430
408
|
return T_node | T_list
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
else if (S_object === typestr) {
|
|
434
|
-
|
|
409
|
+
} else if (S_object === typestr) {
|
|
435
410
|
if (value.constructor instanceof Function) {
|
|
436
|
-
|
|
411
|
+
const cname = value.constructor.name
|
|
437
412
|
if ('Object' !== cname && 'Array' !== cname) {
|
|
438
413
|
return T_node | T_instance
|
|
439
414
|
}
|
|
@@ -446,7 +421,6 @@ function typify(value: any): number {
|
|
|
446
421
|
return T_any
|
|
447
422
|
}
|
|
448
423
|
|
|
449
|
-
|
|
450
424
|
// Get a list element. The key should be an integer, or a string
|
|
451
425
|
// that can parse to an integer only. Negative integers count from the end of the list.
|
|
452
426
|
function getelem(val: any, key: any, alt?: any) {
|
|
@@ -457,8 +431,8 @@ function getelem(val: any, key: any, alt?: any) {
|
|
|
457
431
|
}
|
|
458
432
|
|
|
459
433
|
if (islist(val)) {
|
|
460
|
-
|
|
461
|
-
if (Number.isInteger(nkey) && ('' + key)
|
|
434
|
+
const nkey = parseInt(key)
|
|
435
|
+
if (Number.isInteger(nkey) && re_test(R_INTEGER_KEY, '' + key)) {
|
|
462
436
|
if (nkey < 0) {
|
|
463
437
|
key = val.length + nkey
|
|
464
438
|
}
|
|
@@ -466,14 +440,14 @@ function getelem(val: any, key: any, alt?: any) {
|
|
|
466
440
|
}
|
|
467
441
|
}
|
|
468
442
|
|
|
469
|
-
|
|
443
|
+
// null at a slot counts as "no value" — same Group A rule as getprop.
|
|
444
|
+
if (null == out) {
|
|
470
445
|
return 0 < (T_function & typify(alt)) ? alt() : alt
|
|
471
446
|
}
|
|
472
447
|
|
|
473
448
|
return out
|
|
474
449
|
}
|
|
475
450
|
|
|
476
|
-
|
|
477
451
|
// Safely get a property of a node. Undefined arguments return undefined.
|
|
478
452
|
// If the key is not found, return the alternative value, if any.
|
|
479
453
|
function getprop(val: any, key: any, alt?: any) {
|
|
@@ -487,13 +461,27 @@ function getprop(val: any, key: any, alt?: any) {
|
|
|
487
461
|
out = val[key]
|
|
488
462
|
}
|
|
489
463
|
|
|
490
|
-
|
|
464
|
+
// JSON null at a key is treated as "no value" for the default-substitution
|
|
465
|
+
// rule. This unifies cross-port behaviour: every host language conflates
|
|
466
|
+
// null and absent at the value level either always or via positional
|
|
467
|
+
// checks; the library follows that constraint on observation.
|
|
468
|
+
if (null == out) {
|
|
491
469
|
return alt
|
|
492
470
|
}
|
|
493
471
|
|
|
494
472
|
return out
|
|
495
473
|
}
|
|
496
474
|
|
|
475
|
+
// Internal: literal value lookup that preserves stored JSON null. Group B
|
|
476
|
+
// callers (validate / transform commands / builders / inject internals) use
|
|
477
|
+
// this when they need to inspect the raw stored value at a slot regardless
|
|
478
|
+
// of whether it is null. The public getprop / getelem / haskey APIs treat
|
|
479
|
+
// null as absent (Group A) per UNDEF_SPEC.md.
|
|
480
|
+
function _lookup(val: any, key: any): any {
|
|
481
|
+
if (NONE === val || NONE === key) return NONE
|
|
482
|
+
if (isnode(val)) return val[key]
|
|
483
|
+
return NONE
|
|
484
|
+
}
|
|
497
485
|
|
|
498
486
|
// Convert different types of keys to string representation.
|
|
499
487
|
// String keys are returned as is.
|
|
@@ -509,42 +497,38 @@ function strkey(key: any = NONE): string {
|
|
|
509
497
|
|
|
510
498
|
if (0 < (T_string & t)) {
|
|
511
499
|
return key
|
|
512
|
-
}
|
|
513
|
-
else if (0 < (T_boolean & t)) {
|
|
500
|
+
} else if (0 < (T_boolean & t)) {
|
|
514
501
|
return S_MT
|
|
515
|
-
}
|
|
516
|
-
else if (0 < (T_number & t)) {
|
|
502
|
+
} else if (0 < (T_number & t)) {
|
|
517
503
|
return key % 1 === 0 ? String(key) : String(Math.floor(key))
|
|
518
504
|
}
|
|
519
505
|
|
|
520
506
|
return S_MT
|
|
521
507
|
}
|
|
522
508
|
|
|
523
|
-
|
|
524
509
|
// Sorted keys of a map, or indexes (as strings) of a list.
|
|
525
510
|
// Root utility - only uses language facilities.
|
|
526
511
|
function keysof(val: any): string[] {
|
|
527
|
-
return !isnode(val)
|
|
528
|
-
|
|
512
|
+
return !isnode(val)
|
|
513
|
+
? []
|
|
514
|
+
: ismap(val)
|
|
515
|
+
? Object.keys(val).sort()
|
|
516
|
+
: (val as any).map((_n: any, i: number) => S_MT + i)
|
|
529
517
|
}
|
|
530
518
|
|
|
531
|
-
|
|
532
519
|
// Value of property with name key in node val is defined.
|
|
533
520
|
// Root utility - only uses language facilities.
|
|
534
521
|
function haskey(val: any, key: any) {
|
|
535
|
-
|
|
522
|
+
// null at a key counts as "no value" — same rule as getprop.
|
|
523
|
+
return null != getprop(val, key)
|
|
536
524
|
}
|
|
537
525
|
|
|
538
|
-
|
|
539
526
|
// List the sorted keys of a map or list as an array of tuples of the form [key, value].
|
|
540
527
|
// As with keysof, list indexes are converted to strings.
|
|
541
528
|
// Root utility - only uses language facilities.
|
|
542
|
-
function items(val: any): [string, any][]
|
|
543
|
-
function items<T>(val: any, apply: (item: [string, any]) => T): T[]
|
|
544
|
-
function items(
|
|
545
|
-
val: any,
|
|
546
|
-
apply?: (item: [string, any]) => any
|
|
547
|
-
): any[] {
|
|
529
|
+
function items(val: any): [string, any][]
|
|
530
|
+
function items<T>(val: any, apply: (item: [string, any]) => T): T[]
|
|
531
|
+
function items(val: any, apply?: (item: [string, any]) => any): any[] {
|
|
548
532
|
let out: [string, any][] = keysof(val).map((k: any) => [k, val[k]])
|
|
549
533
|
if (null != apply) {
|
|
550
534
|
out = out.map(apply)
|
|
@@ -552,7 +536,6 @@ function items(
|
|
|
552
536
|
return out
|
|
553
537
|
}
|
|
554
538
|
|
|
555
|
-
|
|
556
539
|
// To replicate the array spread operator:
|
|
557
540
|
// a=1, b=[2,3], c=[4,5]
|
|
558
541
|
// [a,...b,c] -> [1,2,3,[4,5]]
|
|
@@ -565,12 +548,11 @@ function flatten(list: any[], depth?: number) {
|
|
|
565
548
|
return list.flat(getdef(depth, 1))
|
|
566
549
|
}
|
|
567
550
|
|
|
568
|
-
|
|
569
551
|
// Filter item values using check function.
|
|
570
552
|
function filter(val: any, check: (item: [string, any]) => boolean): any[] {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
553
|
+
const all = items(val)
|
|
554
|
+
const numall = size(all)
|
|
555
|
+
const out = []
|
|
574
556
|
for (let i = 0; i < numall; i++) {
|
|
575
557
|
if (check(all[i])) {
|
|
576
558
|
out.push(all[i][1])
|
|
@@ -579,13 +561,15 @@ function filter(val: any, check: (item: [string, any]) => boolean): any[] {
|
|
|
579
561
|
return out
|
|
580
562
|
}
|
|
581
563
|
|
|
582
|
-
|
|
583
564
|
// Escape regular expression.
|
|
584
565
|
function escre(s: string) {
|
|
585
|
-
|
|
586
|
-
return replace(s, R_ESCAPE_REGEXP, '\\$&')
|
|
566
|
+
return re_replace(R_ESCAPE_REGEXP, s, '\\$&')
|
|
587
567
|
}
|
|
588
568
|
|
|
569
|
+
// Alias so call sites read naturally next to the other re_* helpers.
|
|
570
|
+
function re_escape(s: string) {
|
|
571
|
+
return escre(s)
|
|
572
|
+
}
|
|
589
573
|
|
|
590
574
|
// Escape URLs.
|
|
591
575
|
function escurl(s: string) {
|
|
@@ -593,24 +577,89 @@ function escurl(s: string) {
|
|
|
593
577
|
return encodeURIComponent(s)
|
|
594
578
|
}
|
|
595
579
|
|
|
580
|
+
// ===========================================================================
|
|
581
|
+
// Regex utility — uniform API. Every port exposes these same names. The
|
|
582
|
+
// dialect is the RE2 subset documented in /REGEX.md. Internal library code
|
|
583
|
+
// uses these helpers instead of host-language regex methods directly.
|
|
584
|
+
// ===========================================================================
|
|
585
|
+
|
|
586
|
+
// Compile a regex (or return as-is if already compiled). Cached behaviour is
|
|
587
|
+
// up to the port; in TS we just construct a RegExp.
|
|
588
|
+
function re_compile(pattern: string | RegExp, flags?: string): RegExp {
|
|
589
|
+
if (pattern instanceof RegExp) {
|
|
590
|
+
return pattern
|
|
591
|
+
}
|
|
592
|
+
return new RegExp(pattern, flags)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// First match. Returns [whole, capture1, ...] or null.
|
|
596
|
+
function re_find(pattern: string | RegExp, input: string): RegExpMatchArray | null {
|
|
597
|
+
const re = pattern instanceof RegExp ? pattern : new RegExp(pattern)
|
|
598
|
+
return input.match(re)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// All non-overlapping matches, left to right. Each element is the same shape
|
|
602
|
+
// as re_find's array.
|
|
603
|
+
function re_find_all(pattern: string | RegExp, input: string): RegExpMatchArray[] {
|
|
604
|
+
let re: RegExp
|
|
605
|
+
if (pattern instanceof RegExp) {
|
|
606
|
+
re = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags + 'g')
|
|
607
|
+
} else {
|
|
608
|
+
re = new RegExp(pattern, 'g')
|
|
609
|
+
}
|
|
610
|
+
const out: RegExpMatchArray[] = []
|
|
611
|
+
let m: RegExpExecArray | null
|
|
612
|
+
while ((m = re.exec(input)) !== null) {
|
|
613
|
+
out.push(m as unknown as RegExpMatchArray)
|
|
614
|
+
if (m[0] === '') re.lastIndex++ // avoid infinite loop on empty match
|
|
615
|
+
}
|
|
616
|
+
return out
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Replace every match. `replacement` is either a string (with $& and $1..$9)
|
|
620
|
+
// or a callback receiving the same array as re_find returns.
|
|
621
|
+
function re_replace(
|
|
622
|
+
pattern: string | RegExp,
|
|
623
|
+
input: string,
|
|
624
|
+
replacement: string | ((m: RegExpMatchArray) => string),
|
|
625
|
+
): string {
|
|
626
|
+
let re: RegExp
|
|
627
|
+
if (pattern instanceof RegExp) {
|
|
628
|
+
re = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags + 'g')
|
|
629
|
+
} else {
|
|
630
|
+
re = new RegExp(pattern, 'g')
|
|
631
|
+
}
|
|
632
|
+
if (typeof replacement === 'function') {
|
|
633
|
+
return input.replace(re, (...args: any[]) => {
|
|
634
|
+
// Strip the trailing offset + full string args String.prototype.replace passes.
|
|
635
|
+
// The callback below receives just the capture groups (whole at [0]).
|
|
636
|
+
const groups = args.slice(0, -2) as unknown as RegExpMatchArray
|
|
637
|
+
return replacement(groups)
|
|
638
|
+
})
|
|
639
|
+
}
|
|
640
|
+
return input.replace(re, replacement)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Boolean test for any match.
|
|
644
|
+
function re_test(pattern: string | RegExp, input: string): boolean {
|
|
645
|
+
const re = pattern instanceof RegExp ? pattern : new RegExp(pattern)
|
|
646
|
+
return re.test(input)
|
|
647
|
+
}
|
|
596
648
|
|
|
597
649
|
// Replace a search string (all), or a regexp, in a source string.
|
|
598
650
|
function replace(s: string, from: string | RegExp, to: any) {
|
|
599
651
|
let rs = s
|
|
600
|
-
|
|
652
|
+
const ts = typify(s)
|
|
601
653
|
if (0 === (T_string & ts)) {
|
|
602
654
|
rs = stringify(s)
|
|
603
|
-
}
|
|
604
|
-
else if (0 < ((T_noval | T_null) & ts)) {
|
|
655
|
+
} else if (0 < ((T_noval | T_null) & ts)) {
|
|
605
656
|
rs = S_MT
|
|
606
|
-
}
|
|
607
|
-
else {
|
|
657
|
+
} else {
|
|
608
658
|
rs = stringify(s)
|
|
609
659
|
}
|
|
610
660
|
return rs.replace(from, to)
|
|
611
661
|
}
|
|
612
662
|
|
|
613
|
-
|
|
614
663
|
// Concatenate url part strings, merging sep char as needed.
|
|
615
664
|
function join(arr: any[], sep?: string, url?: boolean) {
|
|
616
665
|
const sarr = size(arr)
|
|
@@ -619,41 +668,45 @@ function join(arr: any[], sep?: string, url?: boolean) {
|
|
|
619
668
|
const out = filter(
|
|
620
669
|
items(
|
|
621
670
|
// filter(arr, (n) => null != n[1] && S_MT !== n[1]),
|
|
622
|
-
filter(arr, (n) =>
|
|
671
|
+
filter(arr, (n) => 0 < (T_string & typify(n[1])) && S_MT !== n[1]),
|
|
623
672
|
(n) => {
|
|
624
|
-
|
|
673
|
+
const i = +n[0]
|
|
625
674
|
let s = n[1]
|
|
626
675
|
|
|
627
676
|
if (NONE !== sepre && S_MT !== sepre) {
|
|
628
677
|
if (url && 0 === i) {
|
|
629
|
-
s =
|
|
678
|
+
s = re_replace(re_compile(sepre + '+$'), s, S_MT)
|
|
630
679
|
return s
|
|
631
680
|
}
|
|
632
681
|
|
|
633
682
|
if (0 < i) {
|
|
634
|
-
s =
|
|
683
|
+
s = re_replace(re_compile('^' + sepre + '+'), s, S_MT)
|
|
635
684
|
}
|
|
636
685
|
|
|
637
686
|
if (i < sarr - 1 || !url) {
|
|
638
|
-
s =
|
|
687
|
+
s = re_replace(re_compile(sepre + '+$'), s, S_MT)
|
|
639
688
|
}
|
|
640
689
|
|
|
641
|
-
s =
|
|
642
|
-
'
|
|
690
|
+
s = re_replace(
|
|
691
|
+
re_compile('([^' + sepre + '])' + sepre + '+([^' + sepre + '])'),
|
|
692
|
+
s,
|
|
693
|
+
'$1' + sepdef + '$2',
|
|
694
|
+
)
|
|
643
695
|
}
|
|
644
696
|
|
|
645
697
|
return s
|
|
646
|
-
}
|
|
647
|
-
|
|
698
|
+
},
|
|
699
|
+
),
|
|
700
|
+
(n) => S_MT !== n[1],
|
|
701
|
+
).join(sepdef)
|
|
648
702
|
|
|
649
703
|
return out
|
|
650
704
|
}
|
|
651
705
|
|
|
652
|
-
|
|
653
706
|
// Output JSON in a "standard" format, with 2 space indents, each property on a new line,
|
|
654
707
|
// and spaces after {[: and before ]}. Any "wierd" values (NaN, etc) are output as null.
|
|
655
708
|
// In general, the behaivor of of JavaScript's JSON.stringify(val,null,2) is followed.
|
|
656
|
-
function jsonify(val: any, flags?: { indent?: number
|
|
709
|
+
function jsonify(val: any, flags?: { indent?: number; offset?: number }) {
|
|
657
710
|
let str = S_null
|
|
658
711
|
|
|
659
712
|
if (null != val) {
|
|
@@ -667,14 +720,14 @@ function jsonify(val: any, flags?: { indent?: number, offset?: number }) {
|
|
|
667
720
|
if (0 < offset) {
|
|
668
721
|
// Left offset entire indented JSON so that it aligns with surrounding code
|
|
669
722
|
// indented by offset. Assume first brace is on line with asignment, so not offset.
|
|
670
|
-
str =
|
|
723
|
+
str =
|
|
724
|
+
'{\n' +
|
|
671
725
|
join(
|
|
672
|
-
items(
|
|
673
|
-
|
|
674
|
-
|
|
726
|
+
items(slice(str.split('\n'), 1), (n: any) => pad(n[1], 0 - offset - size(n[1]))),
|
|
727
|
+
'\n',
|
|
728
|
+
)
|
|
675
729
|
}
|
|
676
|
-
}
|
|
677
|
-
catch (e: any) {
|
|
730
|
+
} catch {
|
|
678
731
|
str = '__JSONIFY_FAILED__'
|
|
679
732
|
}
|
|
680
733
|
}
|
|
@@ -682,7 +735,6 @@ function jsonify(val: any, flags?: { indent?: number, offset?: number }) {
|
|
|
682
735
|
return str
|
|
683
736
|
}
|
|
684
737
|
|
|
685
|
-
|
|
686
738
|
// Safely stringify a value for humans (NOT JSON!).
|
|
687
739
|
function stringify(val: any, maxlen?: number, pretty?: any): string {
|
|
688
740
|
let valstr = S_MT
|
|
@@ -694,66 +746,73 @@ function stringify(val: any, maxlen?: number, pretty?: any): string {
|
|
|
694
746
|
|
|
695
747
|
if (S_string === typeof val) {
|
|
696
748
|
valstr = val
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
749
|
+
} else {
|
|
699
750
|
try {
|
|
700
|
-
valstr = JSON.stringify(val, function(_key: string, val: any) {
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
751
|
+
valstr = JSON.stringify(val, function (_key: string, val: any) {
|
|
752
|
+
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
|
|
753
|
+
// Emit map keys in sorted order (deterministic output). Building the
|
|
754
|
+
// sorted copy directly avoids the intermediate key/tuple arrays that
|
|
755
|
+
// items() would allocate for every node.
|
|
756
|
+
const keys = Object.keys(val).sort()
|
|
706
757
|
const sortedObj: any = {}
|
|
707
|
-
|
|
708
|
-
sortedObj[
|
|
709
|
-
}
|
|
758
|
+
for (let i = 0; i < keys.length; i++) {
|
|
759
|
+
sortedObj[keys[i]] = val[keys[i]]
|
|
760
|
+
}
|
|
710
761
|
return sortedObj
|
|
711
762
|
}
|
|
712
763
|
return val
|
|
713
764
|
})
|
|
714
|
-
valstr =
|
|
715
|
-
}
|
|
716
|
-
catch (err: any) {
|
|
765
|
+
valstr = re_replace(R_QUOTES, valstr, S_MT)
|
|
766
|
+
} catch {
|
|
717
767
|
valstr = '__STRINGIFY_FAILED__'
|
|
718
768
|
}
|
|
719
769
|
}
|
|
720
770
|
|
|
721
771
|
if (null != maxlen && -1 < maxlen) {
|
|
722
|
-
|
|
723
|
-
valstr = maxlen < valstr.length ?
|
|
772
|
+
const js = valstr.substring(0, maxlen)
|
|
773
|
+
valstr = maxlen < valstr.length ? js.substring(0, maxlen - 3) + '...' : valstr
|
|
724
774
|
}
|
|
725
775
|
|
|
726
776
|
if (pretty) {
|
|
727
777
|
// Indicate deeper JSON levels with different terminal colors (simplistic wrt strings).
|
|
728
|
-
|
|
778
|
+
const c = items(
|
|
729
779
|
[81, 118, 213, 39, 208, 201, 45, 190, 129, 51, 160, 121, 226, 33, 207, 69],
|
|
730
|
-
(n) => '\x1b[38;5;' + n[1] + 'm'
|
|
731
|
-
|
|
780
|
+
(n) => '\x1b[38;5;' + n[1] + 'm',
|
|
781
|
+
)
|
|
782
|
+
const r = '\x1b[0m'
|
|
783
|
+
let d = 0,
|
|
784
|
+
o = c[0],
|
|
785
|
+
t = o
|
|
732
786
|
for (const ch of valstr) {
|
|
733
787
|
if (ch === '{' || ch === '[') {
|
|
734
|
-
d
|
|
788
|
+
d++
|
|
789
|
+
o = c[d % c.length]
|
|
790
|
+
t += o + ch
|
|
735
791
|
} else if (ch === '}' || ch === ']') {
|
|
736
|
-
t += o + ch
|
|
792
|
+
t += o + ch
|
|
793
|
+
d--
|
|
794
|
+
o = c[d % c.length]
|
|
737
795
|
} else {
|
|
738
796
|
t += o + ch
|
|
739
797
|
}
|
|
740
798
|
}
|
|
741
799
|
return t + r
|
|
742
|
-
|
|
743
800
|
}
|
|
744
801
|
|
|
745
802
|
return valstr
|
|
746
803
|
}
|
|
747
804
|
|
|
748
|
-
|
|
749
805
|
// Build a human friendly path string.
|
|
750
806
|
function pathify(val: any, startin?: number, endin?: number) {
|
|
751
807
|
let pathstr: string | undefined = NONE
|
|
752
808
|
|
|
753
|
-
let path: any[] | undefined = islist(val)
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
809
|
+
let path: any[] | undefined = islist(val)
|
|
810
|
+
? val
|
|
811
|
+
: S_string == typeof val
|
|
812
|
+
? [val]
|
|
813
|
+
: S_number == typeof val
|
|
814
|
+
? [val]
|
|
815
|
+
: NONE
|
|
757
816
|
|
|
758
817
|
const start = null == startin ? 0 : -1 < startin ? startin : 0
|
|
759
818
|
const end = null == endin ? 0 : -1 < endin ? endin : 0
|
|
@@ -762,15 +821,17 @@ function pathify(val: any, startin?: number, endin?: number) {
|
|
|
762
821
|
path = slice(path, start, path.length - end)
|
|
763
822
|
if (0 === path.length) {
|
|
764
823
|
pathstr = '<root>'
|
|
765
|
-
}
|
|
766
|
-
else {
|
|
824
|
+
} else {
|
|
767
825
|
pathstr = join(
|
|
768
826
|
items(
|
|
769
|
-
filter(path, (n) => iskey(n[1])),
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
}
|
|
827
|
+
filter(path, (n) => iskey(n[1])),
|
|
828
|
+
(n) => {
|
|
829
|
+
const p = n[1]
|
|
830
|
+
return S_number === typeof p ? S_MT + Math.floor(p) : re_replace(R_DOT, p, S_MT)
|
|
831
|
+
},
|
|
832
|
+
),
|
|
833
|
+
S_DT,
|
|
834
|
+
)
|
|
774
835
|
}
|
|
775
836
|
}
|
|
776
837
|
|
|
@@ -781,34 +842,58 @@ function pathify(val: any, startin?: number, endin?: number) {
|
|
|
781
842
|
return pathstr
|
|
782
843
|
}
|
|
783
844
|
|
|
784
|
-
|
|
785
845
|
// Clone a JSON-like data structure.
|
|
786
846
|
// NOTE: function and instance values are copied, *not* cloned.
|
|
787
847
|
function clone(val: any): any {
|
|
788
848
|
const refs: any[] = []
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
849
|
+
// Copy (don't clone) functions and non-plain-object instances by reference.
|
|
850
|
+
// This inlines `0 < ((T_function | T_instance) & typify(v))` — the same set
|
|
851
|
+
// of values (functions, class instances, bigints) — without a typify() call
|
|
852
|
+
// per value, which dominated clone's cost.
|
|
853
|
+
const replacer: any = (_k: any, v: any) => {
|
|
854
|
+
const t = typeof v
|
|
855
|
+
if (
|
|
856
|
+
S_function === t ||
|
|
857
|
+
'bigint' === t ||
|
|
858
|
+
(S_object === t &&
|
|
859
|
+
null !== v &&
|
|
860
|
+
!Array.isArray(v) &&
|
|
861
|
+
v.constructor instanceof Function &&
|
|
862
|
+
'Object' !== v.constructor.name &&
|
|
863
|
+
'Array' !== v.constructor.name)
|
|
864
|
+
) {
|
|
865
|
+
return (refs.push(v), '`$REF:' + (refs.length - 1) + '`')
|
|
866
|
+
}
|
|
867
|
+
return v
|
|
868
|
+
}
|
|
869
|
+
const reviver: any = (_k: any, v: any, m: any) =>
|
|
870
|
+
S_string === typeof v ? ((m = re_find(R_CLONE_REF, v)), m ? refs[m[1]] : v) : v
|
|
871
|
+
if (NONE === val) {
|
|
872
|
+
return NONE
|
|
873
|
+
}
|
|
874
|
+
const json = JSON.stringify(val, replacer)
|
|
875
|
+
// A reviver forces V8 off its native JSON.parse fast path (a large cost). It
|
|
876
|
+
// is only needed to swap `$REF:n` placeholders back for the copied-by-ref
|
|
877
|
+
// functions/instances the replacer captured — so when none were captured,
|
|
878
|
+
// parse without it.
|
|
879
|
+
return 0 === refs.length ? JSON.parse(json) : JSON.parse(json, reviver)
|
|
796
880
|
}
|
|
797
881
|
|
|
798
|
-
|
|
799
882
|
// Define a JSON Object using function arguments.
|
|
800
883
|
function jm(...kv: any[]): Record<string, any> {
|
|
801
884
|
const kvsize = size(kv)
|
|
802
885
|
const o: any = {}
|
|
803
886
|
for (let i = 0; i < kvsize; i += 2) {
|
|
804
|
-
|
|
887
|
+
// Builders are Group B (value processing): preserve literal stored
|
|
888
|
+
// values, including null. Direct array index distinguishes "slot exists
|
|
889
|
+
// with value null" from "slot beyond end of kv".
|
|
890
|
+
let k = i < kv.length ? kv[i] : '$KEY' + i
|
|
805
891
|
k = 'string' === typeof k ? k : stringify(k)
|
|
806
|
-
o[k] =
|
|
892
|
+
o[k] = i + 1 < kv.length ? kv[i + 1] : null
|
|
807
893
|
}
|
|
808
894
|
return o
|
|
809
895
|
}
|
|
810
896
|
|
|
811
|
-
|
|
812
897
|
// Define a JSON Array using function arguments.
|
|
813
898
|
function jt(...v: any[]): any[] {
|
|
814
899
|
const vsize = size(v)
|
|
@@ -819,8 +904,7 @@ function jt(...v: any[]): any[] {
|
|
|
819
904
|
return a
|
|
820
905
|
}
|
|
821
906
|
|
|
822
|
-
|
|
823
|
-
// Safely delete a property from an object or array element.
|
|
907
|
+
// Safely delete a property from an object or array element.
|
|
824
908
|
// Undefined arguments and invalid keys are ignored.
|
|
825
909
|
// Returns the (possibly modified) parent.
|
|
826
910
|
// For objects, the property is deleted using the delete operator.
|
|
@@ -831,11 +915,25 @@ function delprop<PARENT>(parent: PARENT, key: any): PARENT {
|
|
|
831
915
|
return parent
|
|
832
916
|
}
|
|
833
917
|
|
|
918
|
+
// A condensed structure is a DAG: one subtree can be reached by many
|
|
919
|
+
// references, so an in-place write would be visible through all of them.
|
|
920
|
+
// Raising is the decided contract (design Q1) - it can be relaxed to
|
|
921
|
+
// copy-on-write later without breaking any caller, where the reverse is not
|
|
922
|
+
// true. expand() gives an ordinary mutable copy.
|
|
923
|
+
//
|
|
924
|
+
// Checked AFTER the key validation: both helpers define an invalid key as
|
|
925
|
+
// ignored, so a call that cannot write must not start throwing merely
|
|
926
|
+
// because the store is condensed.
|
|
927
|
+
if (iscondensed(parent as any)) {
|
|
928
|
+
throw new Error(
|
|
929
|
+
'struct: condensed structures are immutable - ' + 'expand() first, then modify the copy',
|
|
930
|
+
)
|
|
931
|
+
}
|
|
932
|
+
|
|
834
933
|
if (ismap(parent)) {
|
|
835
934
|
key = strkey(key)
|
|
836
935
|
delete (parent as any)[key]
|
|
837
|
-
}
|
|
838
|
-
else if (islist(parent)) {
|
|
936
|
+
} else if (islist(parent)) {
|
|
839
937
|
// Ensure key is an integer.
|
|
840
938
|
let keyI = +key
|
|
841
939
|
|
|
@@ -859,7 +957,6 @@ function delprop<PARENT>(parent: PARENT, key: any): PARENT {
|
|
|
859
957
|
return parent
|
|
860
958
|
}
|
|
861
959
|
|
|
862
|
-
|
|
863
960
|
// Safely set a property. Undefined arguments and invalid keys are ignored.
|
|
864
961
|
// Returns the (possibly modified) parent.
|
|
865
962
|
// If the parent is a list, and the key is negative, prepend the value.
|
|
@@ -870,12 +967,26 @@ function setprop<PARENT>(parent: PARENT, key: any, val: any): PARENT {
|
|
|
870
967
|
return parent
|
|
871
968
|
}
|
|
872
969
|
|
|
970
|
+
// A condensed structure is a DAG: one subtree can be reached by many
|
|
971
|
+
// references, so an in-place write would be visible through all of them.
|
|
972
|
+
// Raising is the decided contract (design Q1) - it can be relaxed to
|
|
973
|
+
// copy-on-write later without breaking any caller, where the reverse is not
|
|
974
|
+
// true. expand() gives an ordinary mutable copy.
|
|
975
|
+
//
|
|
976
|
+
// Checked AFTER the key validation: both helpers define an invalid key as
|
|
977
|
+
// ignored, so a call that cannot write must not start throwing merely
|
|
978
|
+
// because the store is condensed.
|
|
979
|
+
if (iscondensed(parent as any)) {
|
|
980
|
+
throw new Error(
|
|
981
|
+
'struct: condensed structures are immutable - ' + 'expand() first, then modify the copy',
|
|
982
|
+
)
|
|
983
|
+
}
|
|
984
|
+
|
|
873
985
|
if (ismap(parent)) {
|
|
874
986
|
key = S_MT + key
|
|
875
987
|
const pany = parent as any
|
|
876
988
|
pany[key] = val
|
|
877
|
-
}
|
|
878
|
-
else if (islist(parent)) {
|
|
989
|
+
} else if (islist(parent)) {
|
|
879
990
|
// Ensure key is an integer.
|
|
880
991
|
let keyI = +key
|
|
881
992
|
|
|
@@ -901,8 +1012,12 @@ function setprop<PARENT>(parent: PARENT, key: any, val: any): PARENT {
|
|
|
901
1012
|
return parent
|
|
902
1013
|
}
|
|
903
1014
|
|
|
904
|
-
|
|
905
1015
|
// Walk a data structure depth first, applying a function to each value.
|
|
1016
|
+
// The `path` argument passed to the before/after callbacks is a single
|
|
1017
|
+
// mutable array per depth, shared across all callback invocations for the
|
|
1018
|
+
// lifetime of this top-level walk call. Callbacks that need to store the
|
|
1019
|
+
// path MUST clone it (e.g. `path.slice()`); the contents will otherwise
|
|
1020
|
+
// be overwritten by subsequent visits.
|
|
906
1021
|
function walk(
|
|
907
1022
|
// These arguments are the public interface.
|
|
908
1023
|
val: any,
|
|
@@ -919,25 +1034,51 @@ function walk(
|
|
|
919
1034
|
// These areguments are used for recursive state.
|
|
920
1035
|
key?: string | number,
|
|
921
1036
|
parent?: any,
|
|
922
|
-
path?: string[]
|
|
1037
|
+
path?: string[],
|
|
1038
|
+
pool?: string[][],
|
|
923
1039
|
): any {
|
|
1040
|
+
if (NONE === pool) {
|
|
1041
|
+
pool = [[]]
|
|
1042
|
+
}
|
|
924
1043
|
if (NONE === path) {
|
|
925
|
-
path = []
|
|
1044
|
+
path = pool[0]
|
|
926
1045
|
}
|
|
927
1046
|
|
|
1047
|
+
const depth = path.length
|
|
1048
|
+
|
|
928
1049
|
let out = null == before ? val : before(key, val, parent, path)
|
|
929
1050
|
|
|
930
1051
|
maxdepth = null != maxdepth && 0 <= maxdepth ? maxdepth : MAXDEPTH
|
|
931
|
-
if (0 === maxdepth || (
|
|
1052
|
+
if (0 === maxdepth || (0 < maxdepth && maxdepth <= depth)) {
|
|
932
1053
|
return out
|
|
933
1054
|
}
|
|
934
1055
|
|
|
935
1056
|
if (isnode(out)) {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1057
|
+
const childDepth = depth + 1
|
|
1058
|
+
let childPath = pool[childDepth]
|
|
1059
|
+
if (NONE === childPath) {
|
|
1060
|
+
childPath = new Array(childDepth)
|
|
1061
|
+
pool[childDepth] = childPath
|
|
1062
|
+
}
|
|
1063
|
+
// Sync prefix [0..depth-1] from the current path. Only needed once per
|
|
1064
|
+
// parent: siblings share the same prefix and will each overwrite slot
|
|
1065
|
+
// [depth] below.
|
|
1066
|
+
for (let i = 0; i < depth; i++) {
|
|
1067
|
+
childPath[i] = path[i]
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Iterate the sorted keys directly (as items() would order them) without
|
|
1071
|
+
// building the intermediate [key, value] tuple array items() allocates for
|
|
1072
|
+
// every node.
|
|
1073
|
+
const ckeys = keysof(out)
|
|
1074
|
+
for (let cI = 0; cI < ckeys.length; cI++) {
|
|
1075
|
+
const ckey = ckeys[cI]
|
|
1076
|
+
childPath[depth] = S_MT + ckey
|
|
1077
|
+
setprop(
|
|
1078
|
+
out,
|
|
1079
|
+
ckey,
|
|
1080
|
+
walk((out as any)[ckey], before, after, maxdepth, ckey, out, childPath, pool),
|
|
1081
|
+
)
|
|
941
1082
|
}
|
|
942
1083
|
}
|
|
943
1084
|
|
|
@@ -946,7 +1087,6 @@ function walk(
|
|
|
946
1087
|
return out
|
|
947
1088
|
}
|
|
948
1089
|
|
|
949
|
-
|
|
950
1090
|
// Merge a list of values into each other. Later values have
|
|
951
1091
|
// precedence. Nodes override scalars. Node kinds (list or map)
|
|
952
1092
|
// override each other, and do *not* merge. The first element is
|
|
@@ -966,8 +1106,7 @@ function merge(val: any, maxdepth?: number): any {
|
|
|
966
1106
|
|
|
967
1107
|
if (0 === lenlist) {
|
|
968
1108
|
return NONE
|
|
969
|
-
}
|
|
970
|
-
else if (1 === lenlist) {
|
|
1109
|
+
} else if (1 === lenlist) {
|
|
971
1110
|
return list[0]
|
|
972
1111
|
}
|
|
973
1112
|
|
|
@@ -975,25 +1114,19 @@ function merge(val: any, maxdepth?: number): any {
|
|
|
975
1114
|
out = getprop(list, 0, {})
|
|
976
1115
|
|
|
977
1116
|
for (let oI = 1; oI < lenlist; oI++) {
|
|
978
|
-
|
|
1117
|
+
const obj = list[oI]
|
|
979
1118
|
|
|
980
1119
|
if (!isnode(obj)) {
|
|
981
1120
|
// Nodes win.
|
|
982
1121
|
out = obj
|
|
983
|
-
}
|
|
984
|
-
else {
|
|
1122
|
+
} else {
|
|
985
1123
|
// Current value at path end in overriding node.
|
|
986
|
-
|
|
1124
|
+
const cur: any[] = [out]
|
|
987
1125
|
|
|
988
1126
|
// Current value at path end in destination node.
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
function before(
|
|
992
|
-
key: string | number | undefined,
|
|
993
|
-
val: any,
|
|
994
|
-
_parent: any,
|
|
995
|
-
path: string[]
|
|
996
|
-
) {
|
|
1127
|
+
const dst: any[] = [out]
|
|
1128
|
+
|
|
1129
|
+
function before(key: string | number | undefined, val: any, _parent: any, path: string[]) {
|
|
997
1130
|
const pI = size(path)
|
|
998
1131
|
|
|
999
1132
|
if (md <= pI) {
|
|
@@ -1007,18 +1140,19 @@ function merge(val: any, maxdepth?: number): any {
|
|
|
1007
1140
|
|
|
1008
1141
|
// Descend into override node - Set up correct target in `after` function.
|
|
1009
1142
|
else {
|
|
1010
|
-
|
|
1011
1143
|
// Descend into destination node using same key.
|
|
1012
1144
|
dst[pI] = 0 < pI ? getprop(dst[pI - 1], key) : dst[pI]
|
|
1013
1145
|
const tval = dst[pI]
|
|
1014
1146
|
|
|
1147
|
+
const vtype = typify(val)
|
|
1148
|
+
|
|
1015
1149
|
// Destination empty, so create node (unless override is class instance).
|
|
1016
|
-
if (NONE === tval && 0 === (T_instance &
|
|
1150
|
+
if (NONE === tval && 0 === (T_instance & vtype)) {
|
|
1017
1151
|
cur[pI] = islist(val) ? [] : {}
|
|
1018
1152
|
}
|
|
1019
1153
|
|
|
1020
1154
|
// Matching override and destination so continue with their values.
|
|
1021
|
-
else if (
|
|
1155
|
+
else if (vtype === typify(tval)) {
|
|
1022
1156
|
cur[pI] = tval
|
|
1023
1157
|
}
|
|
1024
1158
|
|
|
@@ -1038,12 +1172,7 @@ function merge(val: any, maxdepth?: number): any {
|
|
|
1038
1172
|
return val
|
|
1039
1173
|
}
|
|
1040
1174
|
|
|
1041
|
-
function after(
|
|
1042
|
-
key: string | number | undefined,
|
|
1043
|
-
_val: any,
|
|
1044
|
-
_parent: any,
|
|
1045
|
-
path: string[]
|
|
1046
|
-
) {
|
|
1175
|
+
function after(key: string | number | undefined, _val: any, _parent: any, path: string[]) {
|
|
1047
1176
|
const cI = size(path)
|
|
1048
1177
|
const target = cur[cI - 1]
|
|
1049
1178
|
const value = cur[cI]
|
|
@@ -1069,20 +1198,31 @@ function merge(val: any, maxdepth?: number): any {
|
|
|
1069
1198
|
return out
|
|
1070
1199
|
}
|
|
1071
1200
|
|
|
1072
|
-
|
|
1073
1201
|
// Set a value using a path. Missing path parts are created.
|
|
1074
1202
|
// String paths create only maps. Use a string list to create list parts.
|
|
1075
1203
|
function setpath(
|
|
1076
1204
|
store: any,
|
|
1077
1205
|
path: number | string | string[],
|
|
1078
1206
|
val: any,
|
|
1079
|
-
injdef?: Partial<Injection
|
|
1207
|
+
injdef?: Partial<Injection>,
|
|
1080
1208
|
) {
|
|
1209
|
+
// Same contract as setprop/delprop: a condensed store is immutable.
|
|
1210
|
+
if (iscondensed(store)) {
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
'struct: condensed structures are immutable - ' + 'expand() first, then modify the copy',
|
|
1213
|
+
)
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1081
1216
|
const pathType = typify(path)
|
|
1082
1217
|
|
|
1083
|
-
const parts =
|
|
1084
|
-
0 < (
|
|
1085
|
-
|
|
1218
|
+
const parts =
|
|
1219
|
+
0 < (T_list & pathType)
|
|
1220
|
+
? path
|
|
1221
|
+
: 0 < (T_string & pathType)
|
|
1222
|
+
? (path as string).split(S_DT)
|
|
1223
|
+
: 0 < (T_number & pathType)
|
|
1224
|
+
? [path]
|
|
1225
|
+
: NONE
|
|
1086
1226
|
|
|
1087
1227
|
if (NONE === parts) {
|
|
1088
1228
|
return NONE
|
|
@@ -1104,26 +1244,47 @@ function setpath(
|
|
|
1104
1244
|
|
|
1105
1245
|
if (DELETE === val) {
|
|
1106
1246
|
delprop(parent, getelem(parts, -1))
|
|
1107
|
-
}
|
|
1108
|
-
else {
|
|
1247
|
+
} else {
|
|
1109
1248
|
setprop(parent, getelem(parts, -1), val)
|
|
1110
1249
|
}
|
|
1111
1250
|
|
|
1112
1251
|
return parent
|
|
1113
1252
|
}
|
|
1114
1253
|
|
|
1115
|
-
|
|
1116
1254
|
function getpath(store: any, path: number | string | string[], injdef?: Partial<Injection>) {
|
|
1117
|
-
|
|
1118
1255
|
// Operate on a string array.
|
|
1119
|
-
const parts = islist(path)
|
|
1120
|
-
|
|
1121
|
-
|
|
1256
|
+
const parts = islist(path)
|
|
1257
|
+
? path
|
|
1258
|
+
: 'string' === typeof path
|
|
1259
|
+
? path.split(S_DT)
|
|
1260
|
+
: 'number' === typeof path
|
|
1261
|
+
? [strkey(path)]
|
|
1262
|
+
: NONE
|
|
1122
1263
|
|
|
1123
1264
|
if (NONE === parts) {
|
|
1124
1265
|
return NONE
|
|
1125
1266
|
}
|
|
1126
1267
|
|
|
1268
|
+
// TRANSPARENT CONDENSED READ. A condensed store answers the same path with
|
|
1269
|
+
// the same value as the ordinary node it was built from - the caller does
|
|
1270
|
+
// not need to know which it holds. Only the nodes ON the path are touched,
|
|
1271
|
+
// so this never materialises the whole structure.
|
|
1272
|
+
if (iscondensed(store)) {
|
|
1273
|
+
// The fast path applies only when there are no injection options to
|
|
1274
|
+
// honour. base/dparent/dpath/meta change what the walk below resolves
|
|
1275
|
+
// against, and silently ignoring them would make a condensed store
|
|
1276
|
+
// answer differently from the node it was built from - the opposite of
|
|
1277
|
+
// the transparent-read contract. Rare enough to be worth materialising.
|
|
1278
|
+
if (null == injdef || null == getprop(injdef, S_base)) {
|
|
1279
|
+
const found = condenseResolve(store, (store as any)[S_condroot], parts)
|
|
1280
|
+
const val = 0 > found ? undefined : condenseMaterialise(store, found)
|
|
1281
|
+
// Group A null-as-absent: an ordinary getpath observes a stored null
|
|
1282
|
+
// through getprop and reports it as absent, so this must too.
|
|
1283
|
+
return null == val ? undefined : val
|
|
1284
|
+
}
|
|
1285
|
+
store = expand(store)
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1127
1288
|
// let root = store
|
|
1128
1289
|
let val = store
|
|
1129
1290
|
const base = getprop(injdef, S_base)
|
|
@@ -1134,9 +1295,7 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
|
|
|
1134
1295
|
// An empty path (incl empty string) just finds the store.
|
|
1135
1296
|
if (null == path || null == store || (1 === numparts && S_MT === parts[0])) {
|
|
1136
1297
|
val = src
|
|
1137
|
-
}
|
|
1138
|
-
else if (0 < numparts) {
|
|
1139
|
-
|
|
1298
|
+
} else if (0 < numparts) {
|
|
1140
1299
|
// Check for $ACTIONs
|
|
1141
1300
|
if (1 === numparts) {
|
|
1142
1301
|
val = getprop(store, parts[0])
|
|
@@ -1145,7 +1304,7 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
|
|
|
1145
1304
|
if (!isfunc(val)) {
|
|
1146
1305
|
val = src
|
|
1147
1306
|
|
|
1148
|
-
const m = parts[0]
|
|
1307
|
+
const m = re_find(R_META_PATH, parts[0])
|
|
1149
1308
|
if (m && injdef && injdef.meta) {
|
|
1150
1309
|
val = getprop(injdef.meta, m[1])
|
|
1151
1310
|
parts[0] = m[3]
|
|
@@ -1158,25 +1317,23 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
|
|
|
1158
1317
|
|
|
1159
1318
|
if (injdef && S_DKEY === part) {
|
|
1160
1319
|
part = getprop(injdef, S_key)
|
|
1161
|
-
}
|
|
1162
|
-
else if (injdef && part.startsWith('$GET:')) {
|
|
1320
|
+
} else if (injdef && part.startsWith('$GET:')) {
|
|
1163
1321
|
// $GET:path$ -> get store value, use as path part (string)
|
|
1164
1322
|
part = stringify(getpath(src, slice(part, 5, -1)))
|
|
1165
|
-
}
|
|
1166
|
-
else if (injdef && part.startsWith('$REF:')) {
|
|
1323
|
+
} else if (injdef && part.startsWith('$REF:')) {
|
|
1167
1324
|
// $REF:refpath$ -> get spec value, use as path part (string)
|
|
1168
1325
|
part = stringify(getpath(getprop(store, S_DSPEC), slice(part, 5, -1)))
|
|
1169
|
-
}
|
|
1170
|
-
else if (injdef && part.startsWith('$META:')) {
|
|
1326
|
+
} else if (injdef && part.startsWith('$META:')) {
|
|
1171
1327
|
// $META:metapath$ -> get meta value, use as path part (string)
|
|
1172
1328
|
part = stringify(getpath(getprop(injdef, 'meta'), slice(part, 6, -1)))
|
|
1173
1329
|
}
|
|
1174
1330
|
|
|
1175
|
-
// $$ escapes $
|
|
1176
|
-
|
|
1331
|
+
// $$ escapes $ (skip the regex for the common no-`$$` segment).
|
|
1332
|
+
if (-1 !== part.indexOf('$$')) {
|
|
1333
|
+
part = re_replace(R_DOUBLE_DOLLAR, part, '$')
|
|
1334
|
+
}
|
|
1177
1335
|
|
|
1178
1336
|
if (S_MT === part) {
|
|
1179
|
-
|
|
1180
1337
|
let ascends = 0
|
|
1181
1338
|
while (S_MT === parts[1 + pI]) {
|
|
1182
1339
|
ascends++
|
|
@@ -1190,26 +1347,22 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
|
|
|
1190
1347
|
|
|
1191
1348
|
if (0 === ascends) {
|
|
1192
1349
|
val = dparent
|
|
1193
|
-
}
|
|
1194
|
-
else {
|
|
1350
|
+
} else {
|
|
1195
1351
|
// const fullpath = slice(dpath, 0 - ascends).concat(parts.slice(pI + 1))
|
|
1196
1352
|
const fullpath = flatten([slice(dpath, 0 - ascends), parts.slice(pI + 1)])
|
|
1197
1353
|
|
|
1198
1354
|
if (ascends <= size(dpath)) {
|
|
1199
1355
|
val = getpath(store, fullpath)
|
|
1200
|
-
}
|
|
1201
|
-
else {
|
|
1356
|
+
} else {
|
|
1202
1357
|
val = NONE
|
|
1203
1358
|
}
|
|
1204
1359
|
|
|
1205
1360
|
break
|
|
1206
1361
|
}
|
|
1207
|
-
}
|
|
1208
|
-
else {
|
|
1362
|
+
} else {
|
|
1209
1363
|
val = dparent
|
|
1210
1364
|
}
|
|
1211
|
-
}
|
|
1212
|
-
else {
|
|
1365
|
+
} else {
|
|
1213
1366
|
val = getprop(val, part)
|
|
1214
1367
|
}
|
|
1215
1368
|
}
|
|
@@ -1228,16 +1381,11 @@ function getpath(store: any, path: number | string | string[], injdef?: Partial<
|
|
|
1228
1381
|
return val
|
|
1229
1382
|
}
|
|
1230
1383
|
|
|
1231
|
-
|
|
1232
1384
|
// Inject values from a data store into a node recursively, resolving
|
|
1233
1385
|
// paths against the store, or current if they are local. The modify
|
|
1234
1386
|
// argument allows custom modification of the result. The inj
|
|
1235
1387
|
// (Injection) argument is used to maintain recursive state.
|
|
1236
|
-
function inject(
|
|
1237
|
-
val: any,
|
|
1238
|
-
store: any,
|
|
1239
|
-
injdef?: Partial<Injection>,
|
|
1240
|
-
) {
|
|
1388
|
+
function inject(val: any, store: any, injdef?: Partial<Injection>) {
|
|
1241
1389
|
const valtype = typeof val
|
|
1242
1390
|
let inj: Injection = injdef as Injection
|
|
1243
1391
|
|
|
@@ -1265,7 +1413,6 @@ function inject(
|
|
|
1265
1413
|
|
|
1266
1414
|
// Descend into node.
|
|
1267
1415
|
if (isnode(val)) {
|
|
1268
|
-
|
|
1269
1416
|
// Keys are sorted alphanumerically to ensure determinism.
|
|
1270
1417
|
// Injection transforms ($FOO) are processed *after* other keys.
|
|
1271
1418
|
// NOTE: the optional digits suffix of the transform can thus be
|
|
@@ -1276,11 +1423,10 @@ function inject(
|
|
|
1276
1423
|
|
|
1277
1424
|
if (ismap(val)) {
|
|
1278
1425
|
nodekeys = flatten([
|
|
1279
|
-
filter(nodekeys, (n => !n[1].includes(S_DS))
|
|
1280
|
-
filter(nodekeys, (n => n[1].includes(S_DS))
|
|
1426
|
+
filter(nodekeys, (n) => !n[1].includes(S_DS)),
|
|
1427
|
+
filter(nodekeys, (n) => n[1].includes(S_DS)),
|
|
1281
1428
|
])
|
|
1282
|
-
}
|
|
1283
|
-
else {
|
|
1429
|
+
} else {
|
|
1284
1430
|
nodekeys = keysof(val)
|
|
1285
1431
|
}
|
|
1286
1432
|
|
|
@@ -1289,7 +1435,6 @@ function inject(
|
|
|
1289
1435
|
// 2. inj.mode=M_VAL - The child value is injected.
|
|
1290
1436
|
// 3. inj.mode=M_KEYPOST - Key string is injected again, allowing child mutation.
|
|
1291
1437
|
for (let nkI = 0; nkI < nodekeys.length; nkI++) {
|
|
1292
|
-
|
|
1293
1438
|
const childinj = inj.child(nkI, nodekeys)
|
|
1294
1439
|
const nodekey = childinj.key
|
|
1295
1440
|
childinj.mode = M_KEYPRE
|
|
@@ -1336,17 +1481,11 @@ function inject(
|
|
|
1336
1481
|
|
|
1337
1482
|
// Custom modification.
|
|
1338
1483
|
if (inj.modify && SKIP !== val) {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
inj.modify(
|
|
1344
|
-
mval,
|
|
1345
|
-
mkey,
|
|
1346
|
-
mparent,
|
|
1347
|
-
inj,
|
|
1348
|
-
store
|
|
1349
|
-
)
|
|
1484
|
+
const mkey = inj.key
|
|
1485
|
+
const mparent = inj.parent
|
|
1486
|
+
const mval = getprop(mparent, mkey)
|
|
1487
|
+
|
|
1488
|
+
inj.modify(mval, mkey, mparent, inj, store)
|
|
1350
1489
|
}
|
|
1351
1490
|
|
|
1352
1491
|
// console.log('INJ-VAL', val)
|
|
@@ -1355,10 +1494,9 @@ function inject(
|
|
|
1355
1494
|
|
|
1356
1495
|
// Original val reference may no longer be correct.
|
|
1357
1496
|
// This return value is only used as the top level result.
|
|
1358
|
-
return
|
|
1497
|
+
return _lookup(inj.parent, S_DTOP)
|
|
1359
1498
|
}
|
|
1360
1499
|
|
|
1361
|
-
|
|
1362
1500
|
// The transform_* functions are special command inject handlers (see Injector).
|
|
1363
1501
|
|
|
1364
1502
|
// Delete a key from a map or list.
|
|
@@ -1367,7 +1505,6 @@ const transform_DELETE: Injector = (inj: Injection) => {
|
|
|
1367
1505
|
return NONE
|
|
1368
1506
|
}
|
|
1369
1507
|
|
|
1370
|
-
|
|
1371
1508
|
// Copy value from source data.
|
|
1372
1509
|
const transform_COPY: Injector = (inj: Injection, _val: any) => {
|
|
1373
1510
|
const ijname = 'COPY'
|
|
@@ -1376,13 +1513,12 @@ const transform_COPY: Injector = (inj: Injection, _val: any) => {
|
|
|
1376
1513
|
return NONE
|
|
1377
1514
|
}
|
|
1378
1515
|
|
|
1379
|
-
|
|
1516
|
+
const out = _lookup(inj.dparent, inj.key)
|
|
1380
1517
|
inj.setval(out)
|
|
1381
1518
|
|
|
1382
1519
|
return out
|
|
1383
1520
|
}
|
|
1384
1521
|
|
|
1385
|
-
|
|
1386
1522
|
// As a value, inject the key of the parent node.
|
|
1387
1523
|
// As a key, defined the name of the key property in the source object.
|
|
1388
1524
|
const transform_KEY: Injector = (inj: Injection) => {
|
|
@@ -1394,7 +1530,7 @@ const transform_KEY: Injector = (inj: Injection) => {
|
|
|
1394
1530
|
}
|
|
1395
1531
|
|
|
1396
1532
|
// Key is defined by $KEY meta property.
|
|
1397
|
-
const keyspec =
|
|
1533
|
+
const keyspec = _lookup(parent, S_BKEY)
|
|
1398
1534
|
if (NONE !== keyspec) {
|
|
1399
1535
|
delprop(parent, S_BKEY)
|
|
1400
1536
|
return getprop(inj.dparent, keyspec)
|
|
@@ -1402,10 +1538,9 @@ const transform_KEY: Injector = (inj: Injection) => {
|
|
|
1402
1538
|
|
|
1403
1539
|
// Key is defined within general purpose $META object.
|
|
1404
1540
|
// return getprop(getprop(parent, S_BANNO), S_KEY, getprop(path, path.length - 2))
|
|
1405
|
-
return
|
|
1541
|
+
return _lookup(_lookup(parent, S_BANNO), S_KEY) ?? getelem(path, -2)
|
|
1406
1542
|
}
|
|
1407
1543
|
|
|
1408
|
-
|
|
1409
1544
|
// Annotate node. Does nothing itself, just used by
|
|
1410
1545
|
// other injectors, and is removed when called.
|
|
1411
1546
|
const transform_ANNO: Injector = (inj: Injection) => {
|
|
@@ -1414,10 +1549,9 @@ const transform_ANNO: Injector = (inj: Injection) => {
|
|
|
1414
1549
|
return NONE
|
|
1415
1550
|
}
|
|
1416
1551
|
|
|
1417
|
-
|
|
1418
|
-
// Merge a list of objects into the current object.
|
|
1552
|
+
// Merge a list of objects into the current object.
|
|
1419
1553
|
// Must be a key in an object. The value is merged over the current object.
|
|
1420
|
-
// If the value is an array, the elements are first merged using `merge`.
|
|
1554
|
+
// If the value is an array, the elements are first merged using `merge`.
|
|
1421
1555
|
// If the value is the empty string, merge the top level store.
|
|
1422
1556
|
// Format: { '`$MERGE`': '`source-path`' | ['`source-paths`', ...] }
|
|
1423
1557
|
const transform_MERGE: Injector = (inj: Injection) => {
|
|
@@ -1450,15 +1584,9 @@ const transform_MERGE: Injector = (inj: Injection) => {
|
|
|
1450
1584
|
return out
|
|
1451
1585
|
}
|
|
1452
1586
|
|
|
1453
|
-
|
|
1454
1587
|
// Convert a node to a list.
|
|
1455
1588
|
// Format: ['`$EACH`', '`source-path-of-node`', child-template]
|
|
1456
|
-
const transform_EACH: Injector = (
|
|
1457
|
-
inj: Injection,
|
|
1458
|
-
_val: any,
|
|
1459
|
-
_ref: string,
|
|
1460
|
-
store: any
|
|
1461
|
-
) => {
|
|
1589
|
+
const transform_EACH: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
|
|
1462
1590
|
const ijname = 'EACH'
|
|
1463
1591
|
|
|
1464
1592
|
if (!checkPlacement(M_VAL, ijname, T_list, inj)) {
|
|
@@ -1487,24 +1615,28 @@ const transform_EACH: Injector = (
|
|
|
1487
1615
|
let tval: any = []
|
|
1488
1616
|
|
|
1489
1617
|
const tkey = getelem(inj.path, -2)
|
|
1490
|
-
const target = getelem(inj.nodes, -
|
|
1618
|
+
const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1))
|
|
1491
1619
|
|
|
1492
1620
|
// Create clones of the child template for each value of the current soruce.
|
|
1493
1621
|
if (0 < (T_list & srctype)) {
|
|
1494
1622
|
tval = items(src, () => clone(child))
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1623
|
+
} else if (0 < (T_map & srctype)) {
|
|
1624
|
+
tval = items(src, (n) =>
|
|
1625
|
+
merge(
|
|
1626
|
+
[
|
|
1627
|
+
clone(child),
|
|
1628
|
+
// Make a note of the key for $KEY transforms.
|
|
1629
|
+
{ [S_BANNO]: { KEY: n[0] } },
|
|
1630
|
+
],
|
|
1631
|
+
1,
|
|
1632
|
+
),
|
|
1633
|
+
)
|
|
1502
1634
|
}
|
|
1503
1635
|
|
|
1504
1636
|
let rval = []
|
|
1505
1637
|
|
|
1506
1638
|
if (0 < size(tval)) {
|
|
1507
|
-
tcur = null == src ? NONE :
|
|
1639
|
+
tcur = null == src ? NONE : items(src, (n: [string, any]) => n[1])
|
|
1508
1640
|
|
|
1509
1641
|
const ckey = getelem(inj.path, -2)
|
|
1510
1642
|
|
|
@@ -1542,16 +1674,10 @@ const transform_EACH: Injector = (
|
|
|
1542
1674
|
return rval[0]
|
|
1543
1675
|
}
|
|
1544
1676
|
|
|
1545
|
-
|
|
1546
1677
|
// Convert a node to a map.
|
|
1547
1678
|
// Format: { '`$PACK`':['source-path', child-template]}
|
|
1548
|
-
const transform_PACK: Injector = (
|
|
1549
|
-
|
|
1550
|
-
_val: any,
|
|
1551
|
-
_ref: string,
|
|
1552
|
-
store: any
|
|
1553
|
-
) => {
|
|
1554
|
-
const { mode, key, path, parent, nodes } = inj
|
|
1679
|
+
const transform_PACK: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
|
|
1680
|
+
const { key, path, parent, nodes } = inj
|
|
1555
1681
|
|
|
1556
1682
|
const ijname = 'EACH'
|
|
1557
1683
|
|
|
@@ -1583,8 +1709,7 @@ const transform_PACK: Injector = (
|
|
|
1583
1709
|
setprop(item[1], S_BANNO, { KEY: item[0] })
|
|
1584
1710
|
return item[1]
|
|
1585
1711
|
})
|
|
1586
|
-
}
|
|
1587
|
-
else {
|
|
1712
|
+
} else {
|
|
1588
1713
|
src = NONE
|
|
1589
1714
|
}
|
|
1590
1715
|
}
|
|
@@ -1600,7 +1725,7 @@ const transform_PACK: Injector = (
|
|
|
1600
1725
|
const child = getprop(childspec, S_BVAL, childspec)
|
|
1601
1726
|
|
|
1602
1727
|
// Build parallel target object.
|
|
1603
|
-
|
|
1728
|
+
const tval: any = {}
|
|
1604
1729
|
|
|
1605
1730
|
items(src, (item: [string, any]) => {
|
|
1606
1731
|
const srckey = item[0]
|
|
@@ -1610,8 +1735,7 @@ const transform_PACK: Injector = (
|
|
|
1610
1735
|
if (NONE !== keypath) {
|
|
1611
1736
|
if (keypath.startsWith('`')) {
|
|
1612
1737
|
key = inject(keypath, merge([{}, store, { $TOP: srcnode }], 1))
|
|
1613
|
-
}
|
|
1614
|
-
else {
|
|
1738
|
+
} else {
|
|
1615
1739
|
key = getpath(srcnode, keypath, inj)
|
|
1616
1740
|
}
|
|
1617
1741
|
}
|
|
@@ -1622,8 +1746,7 @@ const transform_PACK: Injector = (
|
|
|
1622
1746
|
const anno = getprop(srcnode, S_BANNO)
|
|
1623
1747
|
if (NONE === anno) {
|
|
1624
1748
|
delprop(tchild, S_BANNO)
|
|
1625
|
-
}
|
|
1626
|
-
else {
|
|
1749
|
+
} else {
|
|
1627
1750
|
setprop(tchild, S_BANNO, anno)
|
|
1628
1751
|
}
|
|
1629
1752
|
})
|
|
@@ -1631,14 +1754,15 @@ const transform_PACK: Injector = (
|
|
|
1631
1754
|
let rval = {}
|
|
1632
1755
|
|
|
1633
1756
|
if (!isempty(tval)) {
|
|
1634
|
-
|
|
1635
1757
|
// Build parallel source object.
|
|
1636
|
-
|
|
1758
|
+
const tsrc: any = {}
|
|
1637
1759
|
src.reduce((a: any, n: any, i: any) => {
|
|
1638
|
-
|
|
1639
|
-
keypath
|
|
1640
|
-
|
|
1641
|
-
|
|
1760
|
+
const kn =
|
|
1761
|
+
null == keypath
|
|
1762
|
+
? i
|
|
1763
|
+
: keypath.startsWith('`')
|
|
1764
|
+
? inject(keypath, merge([{}, store, { $TOP: n }], 1))
|
|
1765
|
+
: getpath(n, keypath, inj)
|
|
1642
1766
|
|
|
1643
1767
|
setprop(a, kn, n)
|
|
1644
1768
|
return a
|
|
@@ -1678,16 +1802,10 @@ const transform_PACK: Injector = (
|
|
|
1678
1802
|
return NONE
|
|
1679
1803
|
}
|
|
1680
1804
|
|
|
1681
|
-
|
|
1682
1805
|
// TODO: not found ref should removed key (setprop NONE)
|
|
1683
1806
|
// Reference original spec (enables recursice transformations)
|
|
1684
1807
|
// Format: ['`$REF`', '`spec-path`']
|
|
1685
|
-
const transform_REF: Injector = (
|
|
1686
|
-
inj: Injection,
|
|
1687
|
-
val: any,
|
|
1688
|
-
_ref: string,
|
|
1689
|
-
store: any
|
|
1690
|
-
) => {
|
|
1808
|
+
const transform_REF: Injector = (inj: Injection, val: any, _ref: string, store: any) => {
|
|
1691
1809
|
const { nodes } = inj
|
|
1692
1810
|
|
|
1693
1811
|
if (M_VAL !== inj.mode) {
|
|
@@ -1695,7 +1813,7 @@ const transform_REF: Injector = (
|
|
|
1695
1813
|
}
|
|
1696
1814
|
|
|
1697
1815
|
// Get arguments: ['`$REF`', 'ref-path'].
|
|
1698
|
-
const refpath =
|
|
1816
|
+
const refpath = _lookup(inj.parent, 1)
|
|
1699
1817
|
inj.keyI = size(inj.keys)
|
|
1700
1818
|
|
|
1701
1819
|
// Spec reference.
|
|
@@ -1720,12 +1838,12 @@ const transform_REF: Injector = (
|
|
|
1720
1838
|
})
|
|
1721
1839
|
}
|
|
1722
1840
|
|
|
1723
|
-
|
|
1841
|
+
const tref = clone(ref)
|
|
1724
1842
|
|
|
1725
1843
|
const cpath = slice(inj.path, -3)
|
|
1726
1844
|
const tpath = slice(inj.path, -1)
|
|
1727
|
-
|
|
1728
|
-
|
|
1845
|
+
const tcur = getpath(store, cpath)
|
|
1846
|
+
const tval = getpath(store, tpath)
|
|
1729
1847
|
let rval = NONE
|
|
1730
1848
|
|
|
1731
1849
|
if (!hasSubRef || NONE !== tval) {
|
|
@@ -1742,8 +1860,7 @@ const transform_REF: Injector = (
|
|
|
1742
1860
|
inject(tref, store, tinj)
|
|
1743
1861
|
|
|
1744
1862
|
rval = tinj.val
|
|
1745
|
-
}
|
|
1746
|
-
else {
|
|
1863
|
+
} else {
|
|
1747
1864
|
rval = NONE
|
|
1748
1865
|
}
|
|
1749
1866
|
|
|
@@ -1756,13 +1873,7 @@ const transform_REF: Injector = (
|
|
|
1756
1873
|
return val
|
|
1757
1874
|
}
|
|
1758
1875
|
|
|
1759
|
-
|
|
1760
|
-
const transform_FORMAT: Injector = (
|
|
1761
|
-
inj: Injection,
|
|
1762
|
-
_val: any,
|
|
1763
|
-
_ref: string,
|
|
1764
|
-
store: any
|
|
1765
|
-
) => {
|
|
1876
|
+
const transform_FORMAT: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
|
|
1766
1877
|
// console.log('FORMAT-START', inj, _val)
|
|
1767
1878
|
|
|
1768
1879
|
// Remove remaining keys to avoid spurious processing.
|
|
@@ -1774,24 +1885,24 @@ const transform_FORMAT: Injector = (
|
|
|
1774
1885
|
|
|
1775
1886
|
// Get arguments: ['`$FORMAT`', 'name', child].
|
|
1776
1887
|
// TODO: EACH and PACK should accept customm functions too
|
|
1777
|
-
const name =
|
|
1778
|
-
const child =
|
|
1888
|
+
const name = _lookup(inj.parent, 1)
|
|
1889
|
+
const child = _lookup(inj.parent, 2)
|
|
1779
1890
|
|
|
1780
1891
|
// Source data.
|
|
1781
1892
|
const tkey = getelem(inj.path, -2)
|
|
1782
|
-
const target = getelem(inj.nodes, -
|
|
1893
|
+
const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1))
|
|
1783
1894
|
|
|
1784
1895
|
const cinj = injectChild(child, store, inj)
|
|
1785
1896
|
const resolved = cinj.val
|
|
1786
1897
|
|
|
1787
|
-
|
|
1898
|
+
const formatter = 0 < (T_function & typify(name)) ? name : getprop(FORMATTER, name)
|
|
1788
1899
|
|
|
1789
1900
|
if (NONE === formatter) {
|
|
1790
1901
|
inj.errs.push('$FORMAT: unknown format: ' + name + '.')
|
|
1791
1902
|
return NONE
|
|
1792
1903
|
}
|
|
1793
1904
|
|
|
1794
|
-
|
|
1905
|
+
const out = walk(resolved, formatter)
|
|
1795
1906
|
|
|
1796
1907
|
setprop(target, tkey, out)
|
|
1797
1908
|
// _updateAncestors(inj, target, tkey, out)
|
|
@@ -1799,17 +1910,15 @@ const transform_FORMAT: Injector = (
|
|
|
1799
1910
|
return out
|
|
1800
1911
|
}
|
|
1801
1912
|
|
|
1802
|
-
|
|
1803
1913
|
const FORMATTER: Record<string, WalkApply> = {
|
|
1804
1914
|
identity: (_k: any, v: any) => v,
|
|
1805
|
-
upper: (_k: any, v: any) => isnode(v) ? v : ('' + v).toUpperCase(),
|
|
1806
|
-
lower: (_k: any, v: any) => isnode(v) ? v : ('' + v).toLowerCase(),
|
|
1807
|
-
string: (_k: any, v: any) => isnode(v) ? v :
|
|
1915
|
+
upper: (_k: any, v: any) => (isnode(v) ? v : ('' + v).toUpperCase()),
|
|
1916
|
+
lower: (_k: any, v: any) => (isnode(v) ? v : ('' + v).toLowerCase()),
|
|
1917
|
+
string: (_k: any, v: any) => (isnode(v) ? v : '' + v),
|
|
1808
1918
|
number: (_k: any, v: any) => {
|
|
1809
1919
|
if (isnode(v)) {
|
|
1810
1920
|
return v
|
|
1811
|
-
}
|
|
1812
|
-
else {
|
|
1921
|
+
} else {
|
|
1813
1922
|
let n = Number(v)
|
|
1814
1923
|
if (isNaN(n)) {
|
|
1815
1924
|
n = 0
|
|
@@ -1820,8 +1929,7 @@ const FORMATTER: Record<string, WalkApply> = {
|
|
|
1820
1929
|
integer: (_k: any, v: any) => {
|
|
1821
1930
|
if (isnode(v)) {
|
|
1822
1931
|
return v
|
|
1823
|
-
}
|
|
1824
|
-
else {
|
|
1932
|
+
} else {
|
|
1825
1933
|
let n = Number(v)
|
|
1826
1934
|
if (isNaN(n)) {
|
|
1827
1935
|
n = 0
|
|
@@ -1830,17 +1938,15 @@ const FORMATTER: Record<string, WalkApply> = {
|
|
|
1830
1938
|
}
|
|
1831
1939
|
},
|
|
1832
1940
|
concat: (k: any, v: any) =>
|
|
1833
|
-
null == k && islist(v)
|
|
1941
|
+
null == k && islist(v)
|
|
1942
|
+
? join(
|
|
1943
|
+
items(v, (n) => (isnode(n[1]) ? S_MT : S_MT + n[1])),
|
|
1944
|
+
S_MT,
|
|
1945
|
+
)
|
|
1946
|
+
: v,
|
|
1834
1947
|
}
|
|
1835
1948
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
const transform_APPLY: Injector = (
|
|
1839
|
-
inj: Injection,
|
|
1840
|
-
_val: any,
|
|
1841
|
-
_ref: string,
|
|
1842
|
-
store: any
|
|
1843
|
-
) => {
|
|
1949
|
+
const transform_APPLY: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
|
|
1844
1950
|
const ijname = 'APPLY'
|
|
1845
1951
|
|
|
1846
1952
|
if (!checkPlacement(M_VAL, ijname, T_list, inj)) {
|
|
@@ -1855,7 +1961,7 @@ const transform_APPLY: Injector = (
|
|
|
1855
1961
|
}
|
|
1856
1962
|
|
|
1857
1963
|
const tkey = getelem(inj.path, -2)
|
|
1858
|
-
const target = getelem(inj.nodes, -
|
|
1964
|
+
const target = getelem(inj.nodes, -2, () => getelem(inj.nodes, -1))
|
|
1859
1965
|
|
|
1860
1966
|
const cinj = injectChild(child, store, inj)
|
|
1861
1967
|
const resolved = cinj.val
|
|
@@ -1867,14 +1973,13 @@ const transform_APPLY: Injector = (
|
|
|
1867
1973
|
return out
|
|
1868
1974
|
}
|
|
1869
1975
|
|
|
1870
|
-
|
|
1871
1976
|
// Transform data using spec.
|
|
1872
1977
|
// Only operates on static JSON-like data.
|
|
1873
1978
|
// Arrays are treated as if they are objects with indices as keys.
|
|
1874
1979
|
function transform(
|
|
1875
1980
|
data: any, // Source data to transform into new data (original not mutated)
|
|
1876
1981
|
spec: any, // Transform specification; output follows this shape
|
|
1877
|
-
injdef?: Partial<Injection
|
|
1982
|
+
injdef?: Partial<Injection>,
|
|
1878
1983
|
) {
|
|
1879
1984
|
// Clone the spec so that the clone can be modified in place as the transform result.
|
|
1880
1985
|
const origspec = spec
|
|
@@ -1886,57 +1991,64 @@ function transform(
|
|
|
1886
1991
|
const errs = injdef?.errs || []
|
|
1887
1992
|
|
|
1888
1993
|
const extraTransforms: any = {}
|
|
1889
|
-
const extraData =
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1994
|
+
const extraData =
|
|
1995
|
+
null == extra
|
|
1996
|
+
? NONE
|
|
1997
|
+
: items(extra).reduce(
|
|
1998
|
+
(a: any, n: any[]) => (
|
|
1999
|
+
n[0].startsWith(S_DS) ? (extraTransforms[n[0]] = n[1]) : (a[n[0]] = n[1]),
|
|
2000
|
+
a
|
|
2001
|
+
),
|
|
2002
|
+
{},
|
|
2003
|
+
)
|
|
2004
|
+
|
|
2005
|
+
const dataClone = merge([isempty(extraData) ? NONE : clone(extraData), clone(data)])
|
|
1897
2006
|
|
|
1898
2007
|
// Define a top level store that provides transform operations.
|
|
1899
|
-
const store = merge(
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
2008
|
+
const store = merge(
|
|
2009
|
+
[
|
|
2010
|
+
{
|
|
2011
|
+
// The inject function recognises this special location for the root of the source data.
|
|
2012
|
+
// NOTE: to escape data that contains "`$FOO`" keys at the top level,
|
|
2013
|
+
// place that data inside a holding map: { myholder: mydata }.
|
|
2014
|
+
$TOP: dataClone,
|
|
2015
|
+
|
|
2016
|
+
$SPEC: () => origspec,
|
|
2017
|
+
|
|
2018
|
+
// Escape backtick (this also works inside backticks).
|
|
2019
|
+
$BT: () => S_BT,
|
|
2020
|
+
|
|
2021
|
+
// Escape dollar sign (this also works inside backticks).
|
|
2022
|
+
$DS: () => S_DS,
|
|
2023
|
+
|
|
2024
|
+
// Insert current date and time as an ISO string.
|
|
2025
|
+
$WHEN: () => new Date().toISOString(),
|
|
2026
|
+
|
|
2027
|
+
$DELETE: transform_DELETE,
|
|
2028
|
+
$COPY: transform_COPY,
|
|
2029
|
+
$KEY: transform_KEY,
|
|
2030
|
+
$ANNO: transform_ANNO,
|
|
2031
|
+
$MERGE: transform_MERGE,
|
|
2032
|
+
$EACH: transform_EACH,
|
|
2033
|
+
$PACK: transform_PACK,
|
|
2034
|
+
$REF: transform_REF,
|
|
2035
|
+
$FORMAT: transform_FORMAT,
|
|
2036
|
+
$APPLY: transform_APPLY,
|
|
2037
|
+
},
|
|
2038
|
+
|
|
2039
|
+
// Custom extra transforms, if any.
|
|
2040
|
+
extraTransforms,
|
|
2041
|
+
|
|
2042
|
+
{
|
|
2043
|
+
$ERRS: errs,
|
|
2044
|
+
},
|
|
2045
|
+
],
|
|
2046
|
+
1,
|
|
2047
|
+
)
|
|
1936
2048
|
|
|
1937
2049
|
const out = inject(spec, store, injdef)
|
|
1938
2050
|
|
|
1939
|
-
const generr =
|
|
2051
|
+
const generr = 0 < size(errs) && !collect
|
|
1940
2052
|
if (generr) {
|
|
1941
2053
|
throw new Error(join(errs, ' | '))
|
|
1942
2054
|
}
|
|
@@ -1944,20 +2056,19 @@ function transform(
|
|
|
1944
2056
|
return out
|
|
1945
2057
|
}
|
|
1946
2058
|
|
|
1947
|
-
|
|
1948
2059
|
// A required string value. NOTE: Rejects empty strings.
|
|
1949
2060
|
const validate_STRING: Injector = (inj: Injection) => {
|
|
1950
|
-
|
|
2061
|
+
const out = _lookup(inj.dparent, inj.key)
|
|
1951
2062
|
|
|
1952
2063
|
const t = typify(out)
|
|
1953
2064
|
if (0 === (T_string & t)) {
|
|
1954
|
-
|
|
2065
|
+
const msg = _invalidTypeMsg(inj.path, S_string, t, out, 'V1010')
|
|
1955
2066
|
inj.errs.push(msg)
|
|
1956
2067
|
return NONE
|
|
1957
2068
|
}
|
|
1958
2069
|
|
|
1959
2070
|
if (S_MT === out) {
|
|
1960
|
-
|
|
2071
|
+
const msg = 'Empty string at ' + pathify(inj.path, 1)
|
|
1961
2072
|
inj.errs.push(msg)
|
|
1962
2073
|
return NONE
|
|
1963
2074
|
}
|
|
@@ -1965,13 +2076,10 @@ const validate_STRING: Injector = (inj: Injection) => {
|
|
|
1965
2076
|
return out
|
|
1966
2077
|
}
|
|
1967
2078
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
2079
|
const validate_TYPE: Injector = (inj: Injection, _val: any, ref: string) => {
|
|
1972
2080
|
const tname = slice(ref, 1).toLowerCase()
|
|
1973
2081
|
const typev = 1 << (31 - TYPENAME.indexOf(tname))
|
|
1974
|
-
|
|
2082
|
+
const out = _lookup(inj.dparent, inj.key)
|
|
1975
2083
|
|
|
1976
2084
|
const t = typify(out)
|
|
1977
2085
|
|
|
@@ -1985,15 +2093,12 @@ const validate_TYPE: Injector = (inj: Injection, _val: any, ref: string) => {
|
|
|
1985
2093
|
return out
|
|
1986
2094
|
}
|
|
1987
2095
|
|
|
1988
|
-
|
|
1989
2096
|
// Allow any value.
|
|
1990
2097
|
const validate_ANY: Injector = (inj: Injection) => {
|
|
1991
|
-
|
|
2098
|
+
const out = _lookup(inj.dparent, inj.key)
|
|
1992
2099
|
return out
|
|
1993
2100
|
}
|
|
1994
2101
|
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
2102
|
// Specify child values for map or list.
|
|
1998
2103
|
// Map syntax: {'`$CHILD`': child-template }
|
|
1999
2104
|
// List syntax: ['`$CHILD`', child-template ]
|
|
@@ -2012,15 +2117,13 @@ const validate_CHILD: Injector = (inj: Injection) => {
|
|
|
2012
2117
|
|
|
2013
2118
|
if (NONE == tval) {
|
|
2014
2119
|
tval = {}
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2017
|
-
inj.errs.push(_invalidTypeMsg(
|
|
2018
|
-
slice(inj.path, -1), S_object, typify(tval), tval), 'V0220')
|
|
2120
|
+
} else if (!ismap(tval)) {
|
|
2121
|
+
inj.errs.push(_invalidTypeMsg(slice(inj.path, -1), S_object, typify(tval), tval), 'V0220')
|
|
2019
2122
|
return NONE
|
|
2020
2123
|
}
|
|
2021
2124
|
|
|
2022
2125
|
const ckeys = keysof(tval)
|
|
2023
|
-
for (
|
|
2126
|
+
for (const ckey of ckeys) {
|
|
2024
2127
|
setprop(parent, ckey, clone(childtm))
|
|
2025
2128
|
|
|
2026
2129
|
// NOTE: modifying inj! This extends the child value loop in inject.
|
|
@@ -2034,14 +2137,13 @@ const validate_CHILD: Injector = (inj: Injection) => {
|
|
|
2034
2137
|
|
|
2035
2138
|
// List syntax.
|
|
2036
2139
|
if (M_VAL === mode) {
|
|
2037
|
-
|
|
2038
2140
|
if (!islist(parent)) {
|
|
2039
2141
|
// $CHILD was not inside a list.
|
|
2040
2142
|
inj.errs.push('Invalid $CHILD as value')
|
|
2041
2143
|
return NONE
|
|
2042
2144
|
}
|
|
2043
2145
|
|
|
2044
|
-
const childtm =
|
|
2146
|
+
const childtm = _lookup(parent, 1)
|
|
2045
2147
|
|
|
2046
2148
|
if (NONE === inj.dparent) {
|
|
2047
2149
|
// Empty list as default.
|
|
@@ -2052,46 +2154,58 @@ const validate_CHILD: Injector = (inj: Injection) => {
|
|
|
2052
2154
|
|
|
2053
2155
|
if (!islist(inj.dparent)) {
|
|
2054
2156
|
const msg = _invalidTypeMsg(
|
|
2055
|
-
slice(inj.path, -1),
|
|
2157
|
+
slice(inj.path, -1),
|
|
2158
|
+
S_list,
|
|
2159
|
+
typify(inj.dparent),
|
|
2160
|
+
inj.dparent,
|
|
2161
|
+
'V0230',
|
|
2162
|
+
)
|
|
2056
2163
|
inj.errs.push(msg)
|
|
2057
2164
|
inj.keyI = size(parent)
|
|
2058
2165
|
return inj.dparent
|
|
2059
2166
|
}
|
|
2060
2167
|
|
|
2061
|
-
// Clone children
|
|
2168
|
+
// Clone children and reset inj key index.
|
|
2062
2169
|
// The inject child loop will now iterate over the cloned children,
|
|
2063
|
-
// validating them
|
|
2170
|
+
// validating them against the current list values.
|
|
2064
2171
|
items(inj.dparent, (n) => setprop(parent, n[0], clone(childtm)))
|
|
2065
2172
|
slice(parent, 0, inj.dparent.length, true)
|
|
2066
|
-
inj.keyI = 0
|
|
2067
2173
|
|
|
2068
|
-
|
|
2069
|
-
|
|
2174
|
+
// NOTE: modifying inj! This extends the child value loop in inject
|
|
2175
|
+
// to cover every cloned child.
|
|
2176
|
+
for (let ckeyI = size(keys); ckeyI < size(parent); ckeyI++) {
|
|
2177
|
+
keys.push(strkey(ckeyI))
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
// Restart the child value loop at the first element (the loop
|
|
2181
|
+
// increments keyI on resume) so that the first element is also
|
|
2182
|
+
// validated against the child template.
|
|
2183
|
+
inj.keyI = -1
|
|
2184
|
+
|
|
2185
|
+
// SKIP leaves the cloned child template in place at the first
|
|
2186
|
+
// element so the resumed loop can validate it.
|
|
2187
|
+
return SKIP
|
|
2070
2188
|
}
|
|
2071
2189
|
|
|
2072
2190
|
return NONE
|
|
2073
2191
|
}
|
|
2074
2192
|
|
|
2075
|
-
|
|
2076
2193
|
// TODO: implement SOME, ALL
|
|
2077
2194
|
// FIX: ONE should mean exactly one, not at least one (=SOME)
|
|
2078
2195
|
// TODO: implement a generate validate_ALT to do all of these
|
|
2079
2196
|
// Match at least one of the specified shapes.
|
|
2080
2197
|
// Syntax: ['`$ONE`', alt0, alt1, ...]
|
|
2081
|
-
const validate_ONE: Injector = (
|
|
2082
|
-
inj: Injection,
|
|
2083
|
-
_val: any,
|
|
2084
|
-
_ref: string,
|
|
2085
|
-
store: any
|
|
2086
|
-
) => {
|
|
2198
|
+
const validate_ONE: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
|
|
2087
2199
|
const { mode, parent, keyI } = inj
|
|
2088
2200
|
|
|
2089
2201
|
// Only operate in val mode, since parent is a list.
|
|
2090
2202
|
if (M_VAL === mode) {
|
|
2091
2203
|
if (!islist(parent) || 0 !== keyI) {
|
|
2092
|
-
inj.errs.push(
|
|
2093
|
-
|
|
2094
|
-
|
|
2204
|
+
inj.errs.push(
|
|
2205
|
+
'The $ONE validator at field ' +
|
|
2206
|
+
pathify(inj.path, 1, 1) +
|
|
2207
|
+
' must be the first element of an array.',
|
|
2208
|
+
)
|
|
2095
2209
|
return
|
|
2096
2210
|
}
|
|
2097
2211
|
|
|
@@ -2103,19 +2217,20 @@ const validate_ONE: Injector = (
|
|
|
2103
2217
|
inj.path = slice(inj.path, -1)
|
|
2104
2218
|
inj.key = getelem(inj.path, -1)
|
|
2105
2219
|
|
|
2106
|
-
|
|
2220
|
+
const tvals = slice(parent, 1)
|
|
2107
2221
|
if (0 === size(tvals)) {
|
|
2108
|
-
inj.errs.push(
|
|
2109
|
-
|
|
2110
|
-
|
|
2222
|
+
inj.errs.push(
|
|
2223
|
+
'The $ONE validator at field ' +
|
|
2224
|
+
pathify(inj.path, 1, 1) +
|
|
2225
|
+
' must have at least one argument.',
|
|
2226
|
+
)
|
|
2111
2227
|
return
|
|
2112
2228
|
}
|
|
2113
2229
|
|
|
2114
2230
|
// See if we can find a match.
|
|
2115
|
-
for (
|
|
2116
|
-
|
|
2231
|
+
for (const tval of tvals) {
|
|
2117
2232
|
// If match, then errs.length = 0
|
|
2118
|
-
|
|
2233
|
+
const terrs: any[] = []
|
|
2119
2234
|
|
|
2120
2235
|
const vstore = merge([{}, store], 1)
|
|
2121
2236
|
vstore.$TOP = inj.dparent
|
|
@@ -2135,27 +2250,38 @@ const validate_ONE: Injector = (
|
|
|
2135
2250
|
}
|
|
2136
2251
|
|
|
2137
2252
|
// There was no match.
|
|
2138
|
-
const valdesc =
|
|
2139
|
-
|
|
2140
|
-
|
|
2253
|
+
const valdesc = replace(
|
|
2254
|
+
join(
|
|
2255
|
+
items(tvals, (n) => stringify(n[1])),
|
|
2256
|
+
', ',
|
|
2257
|
+
),
|
|
2258
|
+
R_TRANSFORM_NAME,
|
|
2259
|
+
(_m: any, p1: string) => p1.toLowerCase(),
|
|
2260
|
+
)
|
|
2141
2261
|
|
|
2142
|
-
inj.errs.push(
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2262
|
+
inj.errs.push(
|
|
2263
|
+
_invalidTypeMsg(
|
|
2264
|
+
inj.path,
|
|
2265
|
+
(1 < size(tvals) ? 'one of ' : '') + valdesc,
|
|
2266
|
+
typify(inj.dparent),
|
|
2267
|
+
inj.dparent,
|
|
2268
|
+
'V0210',
|
|
2269
|
+
),
|
|
2270
|
+
)
|
|
2146
2271
|
}
|
|
2147
2272
|
}
|
|
2148
2273
|
|
|
2149
|
-
|
|
2150
2274
|
const validate_EXACT: Injector = (inj: Injection) => {
|
|
2151
2275
|
const { mode, parent, key, keyI } = inj
|
|
2152
2276
|
|
|
2153
2277
|
// Only operate in val mode, since parent is a list.
|
|
2154
2278
|
if (M_VAL === mode) {
|
|
2155
2279
|
if (!islist(parent) || 0 !== keyI) {
|
|
2156
|
-
inj.errs.push(
|
|
2157
|
-
|
|
2158
|
-
|
|
2280
|
+
inj.errs.push(
|
|
2281
|
+
'The $EXACT validator at field ' +
|
|
2282
|
+
pathify(inj.path, 1, 1) +
|
|
2283
|
+
' must be the first element of an array.',
|
|
2284
|
+
)
|
|
2159
2285
|
return
|
|
2160
2286
|
}
|
|
2161
2287
|
|
|
@@ -2168,17 +2294,19 @@ const validate_EXACT: Injector = (inj: Injection) => {
|
|
|
2168
2294
|
inj.path = slice(inj.path, 0, -1)
|
|
2169
2295
|
inj.key = getelem(inj.path, -1)
|
|
2170
2296
|
|
|
2171
|
-
|
|
2297
|
+
const tvals = slice(parent, 1)
|
|
2172
2298
|
if (0 === size(tvals)) {
|
|
2173
|
-
inj.errs.push(
|
|
2174
|
-
|
|
2175
|
-
|
|
2299
|
+
inj.errs.push(
|
|
2300
|
+
'The $EXACT validator at field ' +
|
|
2301
|
+
pathify(inj.path, 1, 1) +
|
|
2302
|
+
' must have at least one argument.',
|
|
2303
|
+
)
|
|
2176
2304
|
return
|
|
2177
2305
|
}
|
|
2178
2306
|
|
|
2179
2307
|
// See if we can find an exact value match.
|
|
2180
2308
|
let currentstr: string | undefined = undefined
|
|
2181
|
-
for (
|
|
2309
|
+
for (const tval of tvals) {
|
|
2182
2310
|
let exactmatch = tval === inj.dparent
|
|
2183
2311
|
|
|
2184
2312
|
if (!exactmatch && isnode(tval)) {
|
|
@@ -2193,31 +2321,35 @@ const validate_EXACT: Injector = (inj: Injection) => {
|
|
|
2193
2321
|
}
|
|
2194
2322
|
|
|
2195
2323
|
// There was no match.
|
|
2196
|
-
const valdesc =
|
|
2197
|
-
|
|
2198
|
-
|
|
2324
|
+
const valdesc = replace(
|
|
2325
|
+
join(
|
|
2326
|
+
items(tvals, (n) => stringify(n[1])),
|
|
2327
|
+
', ',
|
|
2328
|
+
),
|
|
2329
|
+
R_TRANSFORM_NAME,
|
|
2330
|
+
(_m: any, p1: string) => p1.toLowerCase(),
|
|
2331
|
+
)
|
|
2199
2332
|
|
|
2200
|
-
inj.errs.push(
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2333
|
+
inj.errs.push(
|
|
2334
|
+
_invalidTypeMsg(
|
|
2335
|
+
inj.path,
|
|
2336
|
+
(1 < size(inj.path) ? '' : 'value ') +
|
|
2337
|
+
'exactly equal to ' +
|
|
2338
|
+
(1 === size(tvals) ? '' : 'one of ') +
|
|
2339
|
+
valdesc,
|
|
2340
|
+
typify(inj.dparent),
|
|
2341
|
+
inj.dparent,
|
|
2342
|
+
'V0110',
|
|
2343
|
+
),
|
|
2344
|
+
)
|
|
2345
|
+
} else {
|
|
2207
2346
|
delprop(parent, key)
|
|
2208
2347
|
}
|
|
2209
2348
|
}
|
|
2210
2349
|
|
|
2211
|
-
|
|
2212
2350
|
// This is the "modify" argument to inject. Use this to perform
|
|
2213
2351
|
// generic validation. Runs *after* any special commands.
|
|
2214
|
-
const _validation: Modify = (
|
|
2215
|
-
pval: any,
|
|
2216
|
-
key?: any,
|
|
2217
|
-
parent?: any,
|
|
2218
|
-
inj?: Injection,
|
|
2219
|
-
) => {
|
|
2220
|
-
|
|
2352
|
+
const _validation: Modify = (pval: any, key?: any, parent?: any, inj?: Injection) => {
|
|
2221
2353
|
if (NONE === inj) {
|
|
2222
2354
|
return
|
|
2223
2355
|
}
|
|
@@ -2263,40 +2395,37 @@ const _validation: Modify = (
|
|
|
2263
2395
|
// Empty spec object {} means object can be open (any keys).
|
|
2264
2396
|
if (0 < size(pkeys) && true !== getprop(pval, '`$OPEN`')) {
|
|
2265
2397
|
const badkeys = []
|
|
2266
|
-
for (
|
|
2267
|
-
if
|
|
2398
|
+
for (const ckey of ckeys) {
|
|
2399
|
+
// Literal presence: _validation needs to know if the SHAPE declares
|
|
2400
|
+
// this key, regardless of whether the validator stored null in that
|
|
2401
|
+
// slot. The Group A haskey would miss null-valued slots.
|
|
2402
|
+
if (NONE === _lookup(pval, ckey)) {
|
|
2268
2403
|
badkeys.push(ckey)
|
|
2269
2404
|
}
|
|
2270
2405
|
}
|
|
2271
2406
|
|
|
2272
2407
|
// Closed object, so reject extra keys not in shape.
|
|
2273
2408
|
if (0 < size(badkeys)) {
|
|
2274
|
-
const msg =
|
|
2275
|
-
'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ')
|
|
2409
|
+
const msg = 'Unexpected keys at field ' + pathify(inj.path, 1) + S_VIZ + join(badkeys, ', ')
|
|
2276
2410
|
inj.errs.push(msg)
|
|
2277
2411
|
}
|
|
2278
|
-
}
|
|
2279
|
-
else {
|
|
2412
|
+
} else {
|
|
2280
2413
|
// Object is open, so merge in extra keys.
|
|
2281
2414
|
merge([pval, cval])
|
|
2282
2415
|
if (isnode(pval)) {
|
|
2283
2416
|
delprop(pval, '`$OPEN`')
|
|
2284
2417
|
}
|
|
2285
2418
|
}
|
|
2286
|
-
}
|
|
2287
|
-
else if (islist(cval)) {
|
|
2419
|
+
} else if (islist(cval)) {
|
|
2288
2420
|
if (!islist(pval)) {
|
|
2289
2421
|
inj.errs.push(_invalidTypeMsg(inj.path, typename(ptype), ctype, cval, 'V0030'))
|
|
2290
2422
|
}
|
|
2291
|
-
}
|
|
2292
|
-
else if (exact) {
|
|
2423
|
+
} else if (exact) {
|
|
2293
2424
|
if (cval !== pval) {
|
|
2294
2425
|
const pathmsg = 1 < size(inj.path) ? 'at field ' + pathify(inj.path, 1) + S_VIZ : S_MT
|
|
2295
|
-
inj.errs.push('Value ' + pathmsg + cval +
|
|
2296
|
-
' should equal ' + pval + S_DT)
|
|
2426
|
+
inj.errs.push('Value ' + pathmsg + cval + ' should equal ' + pval + S_DT)
|
|
2297
2427
|
}
|
|
2298
|
-
}
|
|
2299
|
-
else {
|
|
2428
|
+
} else {
|
|
2300
2429
|
// Spec value was a default, copy over data
|
|
2301
2430
|
setprop(parent, key, cval)
|
|
2302
2431
|
}
|
|
@@ -2304,8 +2433,6 @@ const _validation: Modify = (
|
|
|
2304
2433
|
return
|
|
2305
2434
|
}
|
|
2306
2435
|
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
2436
|
// Validate a data structure against a shape specification. The shape
|
|
2310
2437
|
// specification follows the "by example" principle. Plain data in
|
|
2311
2438
|
// teh shape is treated as default values that also specify the
|
|
@@ -2319,51 +2446,54 @@ const _validation: Modify = (
|
|
|
2319
2446
|
function validate(
|
|
2320
2447
|
data: any, // Source data to transform into new data (original not mutated)
|
|
2321
2448
|
spec: any, // Transform specification; output follows this shape
|
|
2322
|
-
injdef?: Partial<Injection
|
|
2449
|
+
injdef?: Partial<Injection>,
|
|
2323
2450
|
) {
|
|
2324
2451
|
const extra = injdef?.extra
|
|
2325
2452
|
|
|
2326
2453
|
const collect = null != injdef?.errs
|
|
2327
2454
|
const errs = injdef?.errs || []
|
|
2328
2455
|
|
|
2329
|
-
const store = merge(
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2456
|
+
const store = merge(
|
|
2457
|
+
[
|
|
2458
|
+
{
|
|
2459
|
+
// Remove the transform commands.
|
|
2460
|
+
$DELETE: null,
|
|
2461
|
+
$COPY: null,
|
|
2462
|
+
$KEY: null,
|
|
2463
|
+
$META: null,
|
|
2464
|
+
$MERGE: null,
|
|
2465
|
+
$EACH: null,
|
|
2466
|
+
$PACK: null,
|
|
2467
|
+
|
|
2468
|
+
$STRING: validate_STRING,
|
|
2469
|
+
$NUMBER: validate_TYPE,
|
|
2470
|
+
$INTEGER: validate_TYPE,
|
|
2471
|
+
$DECIMAL: validate_TYPE,
|
|
2472
|
+
$BOOLEAN: validate_TYPE,
|
|
2473
|
+
$NULL: validate_TYPE,
|
|
2474
|
+
$NIL: validate_TYPE,
|
|
2475
|
+
$MAP: validate_TYPE,
|
|
2476
|
+
$LIST: validate_TYPE,
|
|
2477
|
+
$FUNCTION: validate_TYPE,
|
|
2478
|
+
$INSTANCE: validate_TYPE,
|
|
2479
|
+
$ANY: validate_ANY,
|
|
2480
|
+
$CHILD: validate_CHILD,
|
|
2481
|
+
$ONE: validate_ONE,
|
|
2482
|
+
$EXACT: validate_EXACT,
|
|
2483
|
+
},
|
|
2484
|
+
|
|
2485
|
+
getdef(extra, {}),
|
|
2486
|
+
|
|
2487
|
+
// A special top level value to collect errors.
|
|
2488
|
+
// NOTE: collecterrs parameter always wins.
|
|
2489
|
+
{
|
|
2490
|
+
$ERRS: errs,
|
|
2491
|
+
},
|
|
2492
|
+
],
|
|
2493
|
+
1,
|
|
2494
|
+
)
|
|
2495
|
+
|
|
2496
|
+
const meta = getprop(injdef, 'meta', {})
|
|
2367
2497
|
setprop(meta, S_BEXACT, getprop(meta, S_BEXACT, false))
|
|
2368
2498
|
|
|
2369
2499
|
const out = transform(data, spec, {
|
|
@@ -2374,7 +2504,7 @@ function validate(
|
|
|
2374
2504
|
errs,
|
|
2375
2505
|
})
|
|
2376
2506
|
|
|
2377
|
-
const generr =
|
|
2507
|
+
const generr = 0 < size(errs) && !collect
|
|
2378
2508
|
if (generr) {
|
|
2379
2509
|
throw new Error(join(errs, ' | '))
|
|
2380
2510
|
}
|
|
@@ -2382,10 +2512,9 @@ function validate(
|
|
|
2382
2512
|
return out
|
|
2383
2513
|
}
|
|
2384
2514
|
|
|
2385
|
-
|
|
2386
2515
|
const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
|
|
2387
2516
|
if (M_KEYPRE === inj.mode) {
|
|
2388
|
-
const terms =
|
|
2517
|
+
const terms = _lookup(inj.parent, inj.key)
|
|
2389
2518
|
|
|
2390
2519
|
const ppath = slice(inj.path, -1)
|
|
2391
2520
|
const point = getpath(store, ppath)
|
|
@@ -2393,8 +2522,8 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an
|
|
|
2393
2522
|
const vstore = merge([{}, store], 1)
|
|
2394
2523
|
vstore.$TOP = point
|
|
2395
2524
|
|
|
2396
|
-
for (
|
|
2397
|
-
|
|
2525
|
+
for (const term of terms) {
|
|
2526
|
+
const terrs: any[] = []
|
|
2398
2527
|
|
|
2399
2528
|
validate(point, term, {
|
|
2400
2529
|
extra: vstore,
|
|
@@ -2404,7 +2533,8 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an
|
|
|
2404
2533
|
|
|
2405
2534
|
if (0 != size(terrs)) {
|
|
2406
2535
|
inj.errs.push(
|
|
2407
|
-
'AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms)
|
|
2536
|
+
'AND:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms),
|
|
2537
|
+
)
|
|
2408
2538
|
}
|
|
2409
2539
|
}
|
|
2410
2540
|
|
|
@@ -2414,10 +2544,9 @@ const select_AND: Injector = (inj: Injection, _val: any, _ref: string, store: an
|
|
|
2414
2544
|
}
|
|
2415
2545
|
}
|
|
2416
2546
|
|
|
2417
|
-
|
|
2418
2547
|
const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
|
|
2419
2548
|
if (M_KEYPRE === inj.mode) {
|
|
2420
|
-
const terms =
|
|
2549
|
+
const terms = _lookup(inj.parent, inj.key)
|
|
2421
2550
|
|
|
2422
2551
|
const ppath = slice(inj.path, -1)
|
|
2423
2552
|
const point = getpath(store, ppath)
|
|
@@ -2425,8 +2554,8 @@ const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any
|
|
|
2425
2554
|
const vstore = merge([{}, store], 1)
|
|
2426
2555
|
vstore.$TOP = point
|
|
2427
2556
|
|
|
2428
|
-
for (
|
|
2429
|
-
|
|
2557
|
+
for (const term of terms) {
|
|
2558
|
+
const terrs: any[] = []
|
|
2430
2559
|
|
|
2431
2560
|
validate(point, term, {
|
|
2432
2561
|
extra: vstore,
|
|
@@ -2443,15 +2572,13 @@ const select_OR: Injector = (inj: Injection, _val: any, _ref: string, store: any
|
|
|
2443
2572
|
}
|
|
2444
2573
|
}
|
|
2445
2574
|
|
|
2446
|
-
inj.errs.push(
|
|
2447
|
-
'OR:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms))
|
|
2575
|
+
inj.errs.push('OR:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(terms))
|
|
2448
2576
|
}
|
|
2449
2577
|
}
|
|
2450
2578
|
|
|
2451
|
-
|
|
2452
2579
|
const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: any) => {
|
|
2453
2580
|
if (M_KEYPRE === inj.mode) {
|
|
2454
|
-
const term =
|
|
2581
|
+
const term = _lookup(inj.parent, inj.key)
|
|
2455
2582
|
|
|
2456
2583
|
const ppath = slice(inj.path, -1)
|
|
2457
2584
|
const point = getpath(store, ppath)
|
|
@@ -2459,7 +2586,7 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an
|
|
|
2459
2586
|
const vstore = merge([{}, store], 1)
|
|
2460
2587
|
vstore.$TOP = point
|
|
2461
2588
|
|
|
2462
|
-
|
|
2589
|
+
const terrs: any[] = []
|
|
2463
2590
|
|
|
2464
2591
|
validate(point, term, {
|
|
2465
2592
|
extra: vstore,
|
|
@@ -2468,8 +2595,7 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an
|
|
|
2468
2595
|
})
|
|
2469
2596
|
|
|
2470
2597
|
if (0 == size(terrs)) {
|
|
2471
|
-
inj.errs.push(
|
|
2472
|
-
'NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term))
|
|
2598
|
+
inj.errs.push('NOT:' + pathify(ppath) + S_VIZ + stringify(point) + ' fail:' + stringify(term))
|
|
2473
2599
|
}
|
|
2474
2600
|
|
|
2475
2601
|
const gkey = getelem(inj.path, -2)
|
|
@@ -2478,10 +2604,9 @@ const select_NOT: Injector = (inj: Injection, _val: any, _ref: string, store: an
|
|
|
2478
2604
|
}
|
|
2479
2605
|
}
|
|
2480
2606
|
|
|
2481
|
-
|
|
2482
2607
|
const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any) => {
|
|
2483
2608
|
if (M_KEYPRE === inj.mode) {
|
|
2484
|
-
const term =
|
|
2609
|
+
const term = _lookup(inj.parent, inj.key)
|
|
2485
2610
|
// const src = getprop(store, inj.base, store)
|
|
2486
2611
|
const gkey = getelem(inj.path, -2)
|
|
2487
2612
|
|
|
@@ -2494,17 +2619,13 @@ const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any
|
|
|
2494
2619
|
|
|
2495
2620
|
if ('$GT' === ref && point > term) {
|
|
2496
2621
|
pass = true
|
|
2497
|
-
}
|
|
2498
|
-
else if ('$LT' === ref && point < term) {
|
|
2622
|
+
} else if ('$LT' === ref && point < term) {
|
|
2499
2623
|
pass = true
|
|
2500
|
-
}
|
|
2501
|
-
else if ('$GTE' === ref && point >= term) {
|
|
2624
|
+
} else if ('$GTE' === ref && point >= term) {
|
|
2502
2625
|
pass = true
|
|
2503
|
-
}
|
|
2504
|
-
else if ('$LTE' === ref && point <= term) {
|
|
2626
|
+
} else if ('$LTE' === ref && point <= term) {
|
|
2505
2627
|
pass = true
|
|
2506
|
-
}
|
|
2507
|
-
else if ('$LIKE' === ref && stringify(point).match(RegExp(term))) {
|
|
2628
|
+
} else if ('$LIKE' === ref && re_test(re_compile(term), stringify(point))) {
|
|
2508
2629
|
pass = true
|
|
2509
2630
|
}
|
|
2510
2631
|
|
|
@@ -2512,17 +2633,23 @@ const select_CMP: Injector = (inj: Injection, _val: any, ref: string, store: any
|
|
|
2512
2633
|
// Update spec to match found value so that _validate does not complain.
|
|
2513
2634
|
const gp = getelem(inj.nodes, -2)
|
|
2514
2635
|
setprop(gp, gkey, point)
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2636
|
+
} else {
|
|
2637
|
+
inj.errs.push(
|
|
2638
|
+
'CMP: ' +
|
|
2639
|
+
pathify(ppath) +
|
|
2640
|
+
S_VIZ +
|
|
2641
|
+
stringify(point) +
|
|
2642
|
+
' fail:' +
|
|
2643
|
+
ref +
|
|
2644
|
+
' ' +
|
|
2645
|
+
stringify(term),
|
|
2646
|
+
)
|
|
2519
2647
|
}
|
|
2520
2648
|
}
|
|
2521
2649
|
|
|
2522
2650
|
return NONE
|
|
2523
2651
|
}
|
|
2524
2652
|
|
|
2525
|
-
|
|
2526
2653
|
// Select children from a top-level object that match a MongoDB-style query.
|
|
2527
2654
|
// Supports $and, $or, and equality comparisons.
|
|
2528
2655
|
// For arrays, children are elements; for objects, children are values.
|
|
@@ -2533,12 +2660,11 @@ function select(children: any, query: any): any[] {
|
|
|
2533
2660
|
}
|
|
2534
2661
|
|
|
2535
2662
|
if (ismap(children)) {
|
|
2536
|
-
children = items(children, n => {
|
|
2663
|
+
children = items(children, (n) => {
|
|
2537
2664
|
setprop(n[1], S_DKEY, n[0])
|
|
2538
2665
|
return n[1]
|
|
2539
2666
|
})
|
|
2540
|
-
}
|
|
2541
|
-
else {
|
|
2667
|
+
} else {
|
|
2542
2668
|
children = items(children, (n) => (setprop(n[1], S_DKEY, +n[0]), n[1]))
|
|
2543
2669
|
}
|
|
2544
2670
|
|
|
@@ -2555,7 +2681,7 @@ function select(children: any, query: any): any[] {
|
|
|
2555
2681
|
$GTE: select_CMP,
|
|
2556
2682
|
$LTE: select_CMP,
|
|
2557
2683
|
$LIKE: select_CMP,
|
|
2558
|
-
}
|
|
2684
|
+
},
|
|
2559
2685
|
}
|
|
2560
2686
|
|
|
2561
2687
|
const q = clone(query)
|
|
@@ -2580,26 +2706,25 @@ function select(children: any, query: any): any[] {
|
|
|
2580
2706
|
return results
|
|
2581
2707
|
}
|
|
2582
2708
|
|
|
2583
|
-
|
|
2584
2709
|
// Injection state used for recursive injection into JSON - like data structures.
|
|
2585
2710
|
class Injection {
|
|
2586
|
-
mode: InjectMode
|
|
2587
|
-
full: boolean
|
|
2588
|
-
keyI: number
|
|
2589
|
-
keys: string[]
|
|
2590
|
-
key: string
|
|
2591
|
-
val: any
|
|
2592
|
-
parent: any
|
|
2593
|
-
path: string[]
|
|
2594
|
-
nodes: any[]
|
|
2595
|
-
handler: Injector
|
|
2596
|
-
errs: any[]
|
|
2711
|
+
mode: InjectMode // Injection mode: M_KEYPRE, M_VAL, M_KEYPOST.
|
|
2712
|
+
full: boolean // Transform escape was full key name.
|
|
2713
|
+
keyI: number // Index of parent key in list of parent keys.
|
|
2714
|
+
keys: string[] // List of parent keys.
|
|
2715
|
+
key: string // Current parent key.
|
|
2716
|
+
val: any // Current child value.
|
|
2717
|
+
parent: any // Current parent (in transform specification).
|
|
2718
|
+
path: string[] // Path to current node.
|
|
2719
|
+
nodes: any[] // Stack of ancestor nodes.
|
|
2720
|
+
handler: Injector // Custom handler for injections.
|
|
2721
|
+
errs: any[] // Error collector.
|
|
2597
2722
|
meta: Record<string, any> // Custom meta data. NOTE: do not merge, values must remain as-is.
|
|
2598
|
-
dparent: any
|
|
2599
|
-
dpath: string[]
|
|
2600
|
-
base?: string
|
|
2601
|
-
modify?: Modify
|
|
2602
|
-
prior?: Injection
|
|
2723
|
+
dparent: any // Current data parent node (contains current data value).
|
|
2724
|
+
dpath: string[] // Current data value path
|
|
2725
|
+
base?: string // Base key for data in store, if any.
|
|
2726
|
+
modify?: Modify // Modify injection output.
|
|
2727
|
+
prior?: Injection // Parent (aka prior) injection.
|
|
2603
2728
|
extra?: any
|
|
2604
2729
|
|
|
2605
2730
|
constructor(val: any, parent: any) {
|
|
@@ -2622,42 +2747,56 @@ class Injection {
|
|
|
2622
2747
|
this.meta = {}
|
|
2623
2748
|
}
|
|
2624
2749
|
|
|
2625
|
-
|
|
2626
2750
|
toString(prefix?: string) {
|
|
2627
|
-
return
|
|
2751
|
+
return (
|
|
2752
|
+
'INJ' +
|
|
2753
|
+
(null == prefix ? '' : S_FS + prefix) +
|
|
2754
|
+
S_CN +
|
|
2628
2755
|
pad(pathify(this.path, 1)) +
|
|
2629
|
-
MODENAME[this.mode] +
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
'
|
|
2633
|
-
|
|
2634
|
-
|
|
2756
|
+
MODENAME[this.mode] +
|
|
2757
|
+
(this.full ? '/full' : '') +
|
|
2758
|
+
S_CN +
|
|
2759
|
+
'key=' +
|
|
2760
|
+
this.keyI +
|
|
2761
|
+
S_FS +
|
|
2762
|
+
this.key +
|
|
2763
|
+
S_FS +
|
|
2764
|
+
S_OS +
|
|
2765
|
+
this.keys +
|
|
2766
|
+
S_CS +
|
|
2767
|
+
' p=' +
|
|
2768
|
+
stringify(this.parent, -1, 1) +
|
|
2769
|
+
' m=' +
|
|
2770
|
+
stringify(this.meta, -1, 1) +
|
|
2771
|
+
' d/' +
|
|
2772
|
+
pathify(this.dpath, 1) +
|
|
2773
|
+
'=' +
|
|
2774
|
+
stringify(this.dparent, -1, 1) +
|
|
2775
|
+
' r=' +
|
|
2776
|
+
stringify(this.nodes[0]?.[S_DTOP], -1, 1)
|
|
2777
|
+
)
|
|
2635
2778
|
}
|
|
2636
2779
|
|
|
2637
|
-
|
|
2638
2780
|
descend() {
|
|
2639
2781
|
this.meta.__d++
|
|
2640
2782
|
const parentkey = getelem(this.path, -2)
|
|
2641
2783
|
|
|
2642
2784
|
// Resolve current node in store for local paths.
|
|
2643
2785
|
if (NONE === this.dparent) {
|
|
2644
|
-
|
|
2645
2786
|
// Even if there's no data, dpath should continue to match path, so that
|
|
2646
2787
|
// relative paths work properly.
|
|
2647
2788
|
if (1 < size(this.dpath)) {
|
|
2648
2789
|
this.dpath = flatten([this.dpath, parentkey])
|
|
2649
2790
|
}
|
|
2650
|
-
}
|
|
2651
|
-
else {
|
|
2791
|
+
} else {
|
|
2652
2792
|
// this.dparent is the containing node of the current store value.
|
|
2653
2793
|
if (null != parentkey) {
|
|
2654
2794
|
this.dparent = getprop(this.dparent, parentkey)
|
|
2655
2795
|
|
|
2656
|
-
|
|
2796
|
+
const lastpart = getelem(this.dpath, -1)
|
|
2657
2797
|
if (lastpart === '$:' + parentkey) {
|
|
2658
2798
|
this.dpath = slice(this.dpath, -1)
|
|
2659
|
-
}
|
|
2660
|
-
else {
|
|
2799
|
+
} else {
|
|
2661
2800
|
this.dpath = flatten([this.dpath, parentkey])
|
|
2662
2801
|
}
|
|
2663
2802
|
}
|
|
@@ -2667,7 +2806,6 @@ class Injection {
|
|
|
2667
2806
|
return this.dparent
|
|
2668
2807
|
}
|
|
2669
2808
|
|
|
2670
|
-
|
|
2671
2809
|
child(keyI: number, keys: string[]) {
|
|
2672
2810
|
const key = strkey(keys[keyI])
|
|
2673
2811
|
const val = this.val
|
|
@@ -2694,20 +2832,17 @@ class Injection {
|
|
|
2694
2832
|
return cinj
|
|
2695
2833
|
}
|
|
2696
2834
|
|
|
2697
|
-
|
|
2698
2835
|
setval(val: any, ancestor?: number) {
|
|
2699
2836
|
let parent = NONE
|
|
2700
2837
|
if (null == ancestor || ancestor < 2) {
|
|
2701
|
-
parent =
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
else {
|
|
2838
|
+
parent =
|
|
2839
|
+
NONE === val
|
|
2840
|
+
? (this.parent = delprop(this.parent, this.key))
|
|
2841
|
+
: setprop(this.parent, this.key, val)
|
|
2842
|
+
} else {
|
|
2706
2843
|
const aval = getelem(this.nodes, 0 - ancestor)
|
|
2707
2844
|
const akey = getelem(this.path, 0 - ancestor)
|
|
2708
|
-
parent = NONE === val ?
|
|
2709
|
-
delprop(aval, akey) :
|
|
2710
|
-
setprop(aval, akey, val)
|
|
2845
|
+
parent = NONE === val ? delprop(aval, akey) : setprop(aval, akey, val)
|
|
2711
2846
|
}
|
|
2712
2847
|
|
|
2713
2848
|
// console.log('SETVAL', val, this.key, this.parent)
|
|
@@ -2715,42 +2850,36 @@ class Injection {
|
|
|
2715
2850
|
}
|
|
2716
2851
|
}
|
|
2717
2852
|
|
|
2718
|
-
|
|
2719
2853
|
// Internal utilities
|
|
2720
2854
|
// ==================
|
|
2721
2855
|
|
|
2722
|
-
|
|
2723
2856
|
// // Update all references to target in inj.nodes.
|
|
2724
2857
|
// function _updateAncestors(_inj: Injection, target: any, tkey: any, tval: any) {
|
|
2725
2858
|
// // SetProp is sufficient in TypeScript as target reference remains consistent even for lists.
|
|
2726
2859
|
// setprop(target, tkey, tval)
|
|
2727
2860
|
// }
|
|
2728
2861
|
|
|
2729
|
-
|
|
2730
2862
|
// Build a type validation error message.
|
|
2731
2863
|
function _invalidTypeMsg(path: any, needtype: string, vt: number, v: any, _whence?: string) {
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
return
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2864
|
+
const vs = null == v ? 'no value' : stringify(v)
|
|
2865
|
+
|
|
2866
|
+
return (
|
|
2867
|
+
'Expected ' +
|
|
2868
|
+
(1 < size(path) ? 'field ' + pathify(path, 1) + ' to be ' : '') +
|
|
2869
|
+
needtype +
|
|
2870
|
+
', but found ' +
|
|
2871
|
+
(null != v ? typename(vt) + S_VIZ : '') +
|
|
2872
|
+
vs +
|
|
2739
2873
|
// Uncomment to help debug validation errors.
|
|
2740
2874
|
// ' [' + _whence + ']' +
|
|
2741
2875
|
|
|
2742
2876
|
'.'
|
|
2877
|
+
)
|
|
2743
2878
|
}
|
|
2744
2879
|
|
|
2745
|
-
|
|
2746
2880
|
// Default inject handler for transforms. If the path resolves to a function,
|
|
2747
2881
|
// call the function passing the injection inj. This is how transforms operate.
|
|
2748
|
-
const _injecthandler: Injector = (
|
|
2749
|
-
inj: Injection,
|
|
2750
|
-
val: any,
|
|
2751
|
-
ref: string,
|
|
2752
|
-
store: any
|
|
2753
|
-
): any => {
|
|
2882
|
+
const _injecthandler: Injector = (inj: Injection, val: any, ref: string, store: any): any => {
|
|
2754
2883
|
let out = val
|
|
2755
2884
|
const iscmd = isfunc(val) && (NONE === ref || ref.startsWith(S_DS))
|
|
2756
2885
|
|
|
@@ -2769,37 +2898,28 @@ const _injecthandler: Injector = (
|
|
|
2769
2898
|
return out
|
|
2770
2899
|
}
|
|
2771
2900
|
|
|
2772
|
-
|
|
2773
|
-
const _validatehandler: Injector = (
|
|
2774
|
-
inj: Injection,
|
|
2775
|
-
val: any,
|
|
2776
|
-
ref: string,
|
|
2777
|
-
store: any
|
|
2778
|
-
): any => {
|
|
2901
|
+
const _validatehandler: Injector = (inj: Injection, val: any, ref: string, store: any): any => {
|
|
2779
2902
|
let out = val
|
|
2780
2903
|
|
|
2781
|
-
const m =
|
|
2904
|
+
const m = re_find(R_META_PATH, ref)
|
|
2782
2905
|
const ismetapath = null != m
|
|
2783
2906
|
|
|
2784
2907
|
if (ismetapath) {
|
|
2785
2908
|
if ('=' === m[2]) {
|
|
2786
2909
|
inj.setval([S_BEXACT, val])
|
|
2787
|
-
}
|
|
2788
|
-
else {
|
|
2910
|
+
} else {
|
|
2789
2911
|
inj.setval(val)
|
|
2790
2912
|
}
|
|
2791
2913
|
inj.keyI = -1
|
|
2792
2914
|
|
|
2793
2915
|
out = SKIP
|
|
2794
|
-
}
|
|
2795
|
-
else {
|
|
2916
|
+
} else {
|
|
2796
2917
|
out = _injecthandler(inj, val, ref, store)
|
|
2797
2918
|
}
|
|
2798
2919
|
|
|
2799
2920
|
return out
|
|
2800
2921
|
}
|
|
2801
2922
|
|
|
2802
|
-
|
|
2803
2923
|
// Inject values from a data store into a string. Not a public utility - used by
|
|
2804
2924
|
// `inject`. Inject are marked with `path` where path is resolved
|
|
2805
2925
|
// with getpath against the store or current (if defined)
|
|
@@ -2809,11 +2929,7 @@ const _validatehandler: Injector = (
|
|
|
2809
2929
|
// upper case letters only, and 999 is any digits, which are
|
|
2810
2930
|
// discarded. This syntax specifies the name of a transform, and
|
|
2811
2931
|
// optionally allows transforms to be ordered by alphanumeric sorting.
|
|
2812
|
-
function _injectstr(
|
|
2813
|
-
val: string,
|
|
2814
|
-
store: any,
|
|
2815
|
-
inj?: Injection
|
|
2816
|
-
): any {
|
|
2932
|
+
function _injectstr(val: string, store: any, inj?: Injection): any {
|
|
2817
2933
|
// Can't inject into non-strings
|
|
2818
2934
|
if (S_string !== typeof val || S_MT === val) {
|
|
2819
2935
|
return S_MT
|
|
@@ -2822,7 +2938,7 @@ function _injectstr(
|
|
|
2822
2938
|
let out: any = val
|
|
2823
2939
|
|
|
2824
2940
|
// Pattern examples: "`a.b.c`", "`$NAME`", "`$NAME1`"
|
|
2825
|
-
const m =
|
|
2941
|
+
const m = re_find(R_INJECTION_FULL, val)
|
|
2826
2942
|
|
|
2827
2943
|
// Full string of the val is an injection.
|
|
2828
2944
|
if (m) {
|
|
@@ -2833,20 +2949,18 @@ function _injectstr(
|
|
|
2833
2949
|
|
|
2834
2950
|
// Special escapes inside injection.
|
|
2835
2951
|
if (3 < size(pathref)) {
|
|
2836
|
-
pathref =
|
|
2952
|
+
pathref = re_replace(R_DS_ESCAPE, re_replace(R_BT_ESCAPE, pathref, S_BT), S_DS)
|
|
2837
2953
|
}
|
|
2838
2954
|
|
|
2839
2955
|
// Get the extracted path reference.
|
|
2840
2956
|
out = getpath(store, pathref, inj)
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2843
|
-
else {
|
|
2957
|
+
} else {
|
|
2844
2958
|
// Check for injections within the string.
|
|
2845
2959
|
const partial = (_m: string, ref: string) => {
|
|
2846
2960
|
// Special escapes inside injection.
|
|
2847
2961
|
|
|
2848
2962
|
if (3 < size(ref)) {
|
|
2849
|
-
ref =
|
|
2963
|
+
ref = re_replace(R_DS_ESCAPE, re_replace(R_BT_ESCAPE, ref, S_BT), S_DS)
|
|
2850
2964
|
}
|
|
2851
2965
|
|
|
2852
2966
|
if (inj) {
|
|
@@ -2859,7 +2973,7 @@ function _injectstr(
|
|
|
2859
2973
|
return NONE === found ? S_MT : S_string === typeof found ? found : JSON.stringify(found)
|
|
2860
2974
|
}
|
|
2861
2975
|
|
|
2862
|
-
out =
|
|
2976
|
+
out = re_replace(R_INJECTION_PARTIAL, val, (m) => partial('', m[1]))
|
|
2863
2977
|
|
|
2864
2978
|
// Also call the inj handler on the entire string, providing the
|
|
2865
2979
|
// option for custom injection.
|
|
@@ -2872,11 +2986,9 @@ function _injectstr(
|
|
|
2872
2986
|
return out
|
|
2873
2987
|
}
|
|
2874
2988
|
|
|
2875
|
-
|
|
2876
2989
|
// Handler Utilities
|
|
2877
2990
|
// =================
|
|
2878
2991
|
|
|
2879
|
-
|
|
2880
2992
|
const MODENAME: any = {
|
|
2881
2993
|
[M_VAL]: 'val',
|
|
2882
2994
|
[M_KEYPRE]: 'key:pre',
|
|
@@ -2893,28 +3005,44 @@ function checkPlacement(
|
|
|
2893
3005
|
modes: InjectMode,
|
|
2894
3006
|
ijname: string,
|
|
2895
3007
|
parentTypes: number,
|
|
2896
|
-
inj: Injection
|
|
3008
|
+
inj: Injection,
|
|
2897
3009
|
): boolean {
|
|
2898
3010
|
if (0 === (modes & inj.mode)) {
|
|
2899
|
-
inj.errs.push(
|
|
2900
|
-
'
|
|
2901
|
-
|
|
2902
|
-
|
|
3011
|
+
inj.errs.push(
|
|
3012
|
+
'$' +
|
|
3013
|
+
ijname +
|
|
3014
|
+
': invalid placement as ' +
|
|
3015
|
+
PLACEMENT[inj.mode] +
|
|
3016
|
+
', expected: ' +
|
|
3017
|
+
join(
|
|
3018
|
+
items(
|
|
3019
|
+
[M_KEYPRE, M_KEYPOST, M_VAL].filter((m) => modes & m),
|
|
3020
|
+
(n: any) => PLACEMENT[n[1]],
|
|
3021
|
+
),
|
|
3022
|
+
',',
|
|
3023
|
+
) +
|
|
3024
|
+
'.',
|
|
3025
|
+
)
|
|
2903
3026
|
return false
|
|
2904
3027
|
}
|
|
2905
3028
|
if (!isempty(parentTypes)) {
|
|
2906
3029
|
const ptype = typify(inj.parent)
|
|
2907
3030
|
if (0 === (parentTypes & ptype)) {
|
|
2908
|
-
inj.errs.push(
|
|
2909
|
-
'
|
|
3031
|
+
inj.errs.push(
|
|
3032
|
+
'$' +
|
|
3033
|
+
ijname +
|
|
3034
|
+
': invalid placement in parent ' +
|
|
3035
|
+
typename(ptype) +
|
|
3036
|
+
', expected: ' +
|
|
3037
|
+
typename(parentTypes) +
|
|
3038
|
+
'.',
|
|
3039
|
+
)
|
|
2910
3040
|
return false
|
|
2911
|
-
|
|
2912
3041
|
}
|
|
2913
3042
|
}
|
|
2914
3043
|
return true
|
|
2915
3044
|
}
|
|
2916
3045
|
|
|
2917
|
-
|
|
2918
3046
|
// function injectorArgs(argTypes: number[], inj: Injection): any {
|
|
2919
3047
|
function injectorArgs(argTypes: number[], args: any[]): any {
|
|
2920
3048
|
const numargs = size(argTypes)
|
|
@@ -2925,9 +3053,16 @@ function injectorArgs(argTypes: number[], args: any[]): any {
|
|
|
2925
3053
|
const arg = args[argI]
|
|
2926
3054
|
const argType = typify(arg)
|
|
2927
3055
|
if (0 === (argTypes[argI] & argType)) {
|
|
2928
|
-
found[0] =
|
|
2929
|
-
'
|
|
2930
|
-
|
|
3056
|
+
found[0] =
|
|
3057
|
+
'invalid argument: ' +
|
|
3058
|
+
stringify(arg, 22) +
|
|
3059
|
+
' (' +
|
|
3060
|
+
typename(argType) +
|
|
3061
|
+
' at position ' +
|
|
3062
|
+
(1 + argI) +
|
|
3063
|
+
') is not of type: ' +
|
|
3064
|
+
typename(argTypes[argI]) +
|
|
3065
|
+
'.'
|
|
2931
3066
|
break
|
|
2932
3067
|
}
|
|
2933
3068
|
found[1 + argI] = arg
|
|
@@ -2935,7 +3070,6 @@ function injectorArgs(argTypes: number[], args: any[]): any {
|
|
|
2935
3070
|
return found
|
|
2936
3071
|
}
|
|
2937
3072
|
|
|
2938
|
-
|
|
2939
3073
|
function injectChild(child: any, store: any, inj: Injection): Injection {
|
|
2940
3074
|
let cinj = inj
|
|
2941
3075
|
|
|
@@ -2945,8 +3079,7 @@ function injectChild(child: any, store: any, inj: Injection): Injection {
|
|
|
2945
3079
|
cinj = inj.prior.prior.child(inj.prior.keyI, inj.prior.keys)
|
|
2946
3080
|
cinj.val = child
|
|
2947
3081
|
setprop(cinj.parent, inj.prior.key, child)
|
|
2948
|
-
}
|
|
2949
|
-
else {
|
|
3082
|
+
} else {
|
|
2950
3083
|
cinj = inj.prior.child(inj.keyI, inj.keys)
|
|
2951
3084
|
cinj.val = child
|
|
2952
3085
|
setprop(cinj.parent, inj.key, child)
|
|
@@ -2959,12 +3092,14 @@ function injectChild(child: any, store: any, inj: Injection): Injection {
|
|
|
2959
3092
|
return cinj
|
|
2960
3093
|
}
|
|
2961
3094
|
|
|
2962
|
-
|
|
2963
3095
|
class StructUtility {
|
|
2964
3096
|
clone = clone
|
|
3097
|
+
condense = condense
|
|
3098
|
+
condenseview = condenseview
|
|
2965
3099
|
delprop = delprop
|
|
2966
3100
|
escre = escre
|
|
2967
3101
|
escurl = escurl
|
|
3102
|
+
expand = expand
|
|
2968
3103
|
filter = filter
|
|
2969
3104
|
flatten = flatten
|
|
2970
3105
|
getdef = getdef
|
|
@@ -2974,6 +3109,7 @@ class StructUtility {
|
|
|
2974
3109
|
haskey = haskey
|
|
2975
3110
|
inject = inject
|
|
2976
3111
|
isempty = isempty
|
|
3112
|
+
iscondensed = iscondensed
|
|
2977
3113
|
isfunc = isfunc
|
|
2978
3114
|
iskey = iskey
|
|
2979
3115
|
islist = islist
|
|
@@ -2999,6 +3135,13 @@ class StructUtility {
|
|
|
2999
3135
|
validate = validate
|
|
3000
3136
|
walk = walk
|
|
3001
3137
|
|
|
3138
|
+
re_compile = re_compile
|
|
3139
|
+
re_find = re_find
|
|
3140
|
+
re_find_all = re_find_all
|
|
3141
|
+
re_replace = re_replace
|
|
3142
|
+
re_test = re_test
|
|
3143
|
+
re_escape = re_escape
|
|
3144
|
+
|
|
3002
3145
|
SKIP = SKIP
|
|
3003
3146
|
DELETE = DELETE
|
|
3004
3147
|
|
|
@@ -3027,9 +3170,481 @@ class StructUtility {
|
|
|
3027
3170
|
injectChild = injectChild
|
|
3028
3171
|
}
|
|
3029
3172
|
|
|
3173
|
+
// ---------------------------------------------------------------------------
|
|
3174
|
+
// CONDENSED STRUCTURES
|
|
3175
|
+
//
|
|
3176
|
+
// A condensed structure holds the same information as a JSON-shaped node in a
|
|
3177
|
+
// fraction of the space, and can be read WITHOUT materialising it. Three
|
|
3178
|
+
// mechanisms, all in one format:
|
|
3179
|
+
//
|
|
3180
|
+
// intern a symbol table holds each distinct map key and string value once
|
|
3181
|
+
// share identical subtrees become ONE node with many references (a DAG)
|
|
3182
|
+
// lazy a reader resolves a path by walking integer indexes, touching
|
|
3183
|
+
// only the nodes on that path
|
|
3184
|
+
//
|
|
3185
|
+
// The shape:
|
|
3186
|
+
//
|
|
3187
|
+
// { "$condense": 1, format version
|
|
3188
|
+
// "sym": [ ... ], distinct strings, sorted (so lookup can bisect)
|
|
3189
|
+
// "node": [ ... ], integer-encoded nodes, children before parents
|
|
3190
|
+
// "root": 12 } index into node[]
|
|
3191
|
+
//
|
|
3192
|
+
// Each node is one of:
|
|
3193
|
+
//
|
|
3194
|
+
// map [0, k0, v0, k1, v1, ...] k ascending symbol ids, v node refs
|
|
3195
|
+
// list [1, v0, v1, ...] node refs
|
|
3196
|
+
// string [2, symId]
|
|
3197
|
+
// number [3, value]
|
|
3198
|
+
// true [4] false [5] null [6]
|
|
3199
|
+
//
|
|
3200
|
+
// Two properties are load-bearing rather than incidental:
|
|
3201
|
+
//
|
|
3202
|
+
// Map keys ascend, because keysof() sorts. That makes output byte-stable
|
|
3203
|
+
// (generators depend on it) and lets a key lookup bisect rather than scan.
|
|
3204
|
+
//
|
|
3205
|
+
// References are integer indexes into a post-order table, so every ref
|
|
3206
|
+
// points BACKWARDS. Sharing is therefore free — two identical subtrees
|
|
3207
|
+
// intern to one node and two refs, with no dedup pass at read time — and a
|
|
3208
|
+
// reader can never loop.
|
|
3209
|
+
//
|
|
3210
|
+
// The transport is JSON arrays of integers on purpose. The FORMAT is defined
|
|
3211
|
+
// here, but TOKENIZING is left to each language's own JSON parser: mature,
|
|
3212
|
+
// fast, memory-safe, already present, and nothing to keep in parity across
|
|
3213
|
+
// ports. A binary profile ($condense: 2) is a future option, deliberately
|
|
3214
|
+
// deferred - it is worth perhaps 2-3x and costs a hand-written decoder per
|
|
3215
|
+
// language.
|
|
3216
|
+
//
|
|
3217
|
+
// IMMUTABILITY. Sharing makes a condensed structure a DAG, so a mutation
|
|
3218
|
+
// through one reference would be visible through every other. Rather than
|
|
3219
|
+
// make that hazard the caller's problem, it is designed out: mutating
|
|
3220
|
+
// helpers RAISE on a condensed node, and expand()/value() always return an
|
|
3221
|
+
// independent copy. ref() is the one opt-in escape hatch and is named to be
|
|
3222
|
+
// alarming.
|
|
3223
|
+
|
|
3224
|
+
const CONDENSE_VERSION = 1
|
|
3225
|
+
|
|
3226
|
+
const CONDENSE_MAP = 0
|
|
3227
|
+
const CONDENSE_LIST = 1
|
|
3228
|
+
const CONDENSE_STR = 2
|
|
3229
|
+
const CONDENSE_NUM = 3
|
|
3230
|
+
const CONDENSE_TRUE = 4
|
|
3231
|
+
const CONDENSE_FALSE = 5
|
|
3232
|
+
const CONDENSE_NULL = 6
|
|
3233
|
+
|
|
3234
|
+
const S_condense = '$condense'
|
|
3235
|
+
const S_condsym = 'sym'
|
|
3236
|
+
const S_condnode = 'node'
|
|
3237
|
+
const S_condroot = 'root'
|
|
3238
|
+
|
|
3239
|
+
// Is this a condensed structure? Checked structurally rather than by a marker
|
|
3240
|
+
// alone, so an ordinary map that happens to carry a `$condense` key is not
|
|
3241
|
+
// mistaken for one.
|
|
3242
|
+
// Compare by UNICODE CODE POINT, not by UTF-16 code unit.
|
|
3243
|
+
//
|
|
3244
|
+
// JavaScript's default string sort compares UTF-16 code units, which orders
|
|
3245
|
+
// an astral character BEFORE a high-BMP one; Python, Go and Rust all compare
|
|
3246
|
+
// by code point (equivalently, by UTF-8 bytes). A symbol table sorted the
|
|
3247
|
+
// JavaScript way would make two conforming ports emit different bytes for the
|
|
3248
|
+
// same input, which is exactly the byte-stability the format promises. Code
|
|
3249
|
+
// point order is the portable choice, so it is specified rather than
|
|
3250
|
+
// inherited.
|
|
3251
|
+
function condenseCmp(a: string, b: string): number {
|
|
3252
|
+
let i = 0
|
|
3253
|
+
let j = 0
|
|
3254
|
+
while (i < a.length && j < b.length) {
|
|
3255
|
+
const ca = a.codePointAt(i) as number
|
|
3256
|
+
const cb = b.codePointAt(j) as number
|
|
3257
|
+
if (ca !== cb) {
|
|
3258
|
+
return ca < cb ? -1 : 1
|
|
3259
|
+
}
|
|
3260
|
+
i += 0xffff < ca ? 2 : 1
|
|
3261
|
+
j += 0xffff < cb ? 2 : 1
|
|
3262
|
+
}
|
|
3263
|
+
return a.length - i - (b.length - j)
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
// Assign a key that may be `__proto__`.
|
|
3267
|
+
//
|
|
3268
|
+
// Plain assignment to `__proto__` invokes the inherited setter instead of
|
|
3269
|
+
// creating an own property, so a model carrying that key or string value
|
|
3270
|
+
// would be silently corrupted - it is valid JSON and Seneca-adjacent data
|
|
3271
|
+
// really does use `$`-ish and `__`-ish names.
|
|
3272
|
+
function condenseSet(obj: any, key: string, val: any) {
|
|
3273
|
+
if ('__proto__' === key) {
|
|
3274
|
+
Object.defineProperty(obj, key, {
|
|
3275
|
+
value: val,
|
|
3276
|
+
enumerable: true,
|
|
3277
|
+
writable: true,
|
|
3278
|
+
configurable: true,
|
|
3279
|
+
})
|
|
3280
|
+
} else {
|
|
3281
|
+
obj[key] = val
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
function iscondensed(val: any): boolean {
|
|
3286
|
+
// The version must MATCH, not merely be numeric: a future profile would
|
|
3287
|
+
// otherwise be decoded under this one's schema.
|
|
3288
|
+
return (
|
|
3289
|
+
ismap(val) &&
|
|
3290
|
+
CONDENSE_VERSION === (val as any)[S_condense] &&
|
|
3291
|
+
islist((val as any)[S_condsym]) &&
|
|
3292
|
+
islist((val as any)[S_condnode]) &&
|
|
3293
|
+
'number' === typeof (val as any)[S_condroot]
|
|
3294
|
+
)
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
// Condense any JSON-shaped node. Pure: the same input always produces a
|
|
3298
|
+
// byte-identical result, on every port.
|
|
3299
|
+
function condense(val: any): any {
|
|
3300
|
+
// Pass one: every distinct map key and string value, once, sorted.
|
|
3301
|
+
const symset: { [sym: string]: boolean } = Object.create(null)
|
|
3302
|
+
|
|
3303
|
+
const collect = (n: any) => {
|
|
3304
|
+
if (ismap(n)) {
|
|
3305
|
+
const keys = keysof(n)
|
|
3306
|
+
for (let i = 0; i < keys.length; i++) {
|
|
3307
|
+
symset[keys[i]] = true
|
|
3308
|
+
collect((n as any)[keys[i]])
|
|
3309
|
+
}
|
|
3310
|
+
} else if (islist(n)) {
|
|
3311
|
+
for (let i = 0; i < n.length; i++) {
|
|
3312
|
+
collect(n[i])
|
|
3313
|
+
}
|
|
3314
|
+
} else if ('string' === typeof n) {
|
|
3315
|
+
symset[n] = true
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
collect(val)
|
|
3319
|
+
|
|
3320
|
+
const sym = Object.keys(symset).sort(condenseCmp)
|
|
3321
|
+
const symid: { [sym: string]: number } = Object.create(null)
|
|
3322
|
+
for (let i = 0; i < sym.length; i++) {
|
|
3323
|
+
symid[sym[i]] = i
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
// Pass two: build post-order, interning on the canonical encoding. Because
|
|
3327
|
+
// children are built first, identical subtrees have already been interned
|
|
3328
|
+
// by the time a parent references them, so sharing costs nothing extra.
|
|
3329
|
+
const node: any[][] = []
|
|
3330
|
+
const seen: { [enc: string]: number } = Object.create(null)
|
|
3331
|
+
|
|
3332
|
+
const intern = (enc: any[]): number => {
|
|
3333
|
+
// The kind tag leads every encoding, so a string node and a number node
|
|
3334
|
+
// can never collide on the same joined key.
|
|
3335
|
+
const enckey = enc.join(',')
|
|
3336
|
+
const hit = seen[enckey]
|
|
3337
|
+
if (undefined !== hit) {
|
|
3338
|
+
return hit
|
|
3339
|
+
}
|
|
3340
|
+
const idx = node.length
|
|
3341
|
+
node.push(enc)
|
|
3342
|
+
seen[enckey] = idx
|
|
3343
|
+
return idx
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
const build = (n: any): number => {
|
|
3347
|
+
if (ismap(n)) {
|
|
3348
|
+
const enc: any[] = [CONDENSE_MAP]
|
|
3349
|
+
// keysof sorts, so the key ids ascend and lookup can bisect.
|
|
3350
|
+
const keys = keysof(n)
|
|
3351
|
+
for (let i = 0; i < keys.length; i++) {
|
|
3352
|
+
enc.push(symid[keys[i]])
|
|
3353
|
+
enc.push(build((n as any)[keys[i]]))
|
|
3354
|
+
}
|
|
3355
|
+
return intern(enc)
|
|
3356
|
+
}
|
|
3357
|
+
if (islist(n)) {
|
|
3358
|
+
const enc: any[] = [CONDENSE_LIST]
|
|
3359
|
+
for (let i = 0; i < n.length; i++) {
|
|
3360
|
+
enc.push(build(n[i]))
|
|
3361
|
+
}
|
|
3362
|
+
return intern(enc)
|
|
3363
|
+
}
|
|
3364
|
+
if ('string' === typeof n) {
|
|
3365
|
+
return intern([CONDENSE_STR, symid[n]])
|
|
3366
|
+
}
|
|
3367
|
+
if ('number' === typeof n) {
|
|
3368
|
+
return intern([CONDENSE_NUM, n])
|
|
3369
|
+
}
|
|
3370
|
+
if (true === n) {
|
|
3371
|
+
return intern([CONDENSE_TRUE])
|
|
3372
|
+
}
|
|
3373
|
+
if (false === n) {
|
|
3374
|
+
return intern([CONDENSE_FALSE])
|
|
3375
|
+
}
|
|
3376
|
+
return intern([CONDENSE_NULL])
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
const root = build(val)
|
|
3380
|
+
|
|
3381
|
+
const out: any = {}
|
|
3382
|
+
out[S_condense] = CONDENSE_VERSION
|
|
3383
|
+
out[S_condsym] = sym
|
|
3384
|
+
out[S_condnode] = node
|
|
3385
|
+
out[S_condroot] = root
|
|
3386
|
+
return out
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
// Resolve a symbol to its id by bisecting the sorted symbol table. -1 when
|
|
3390
|
+
// the string does not occur anywhere in the structure, which is itself a fast
|
|
3391
|
+
// negative answer for a key lookup.
|
|
3392
|
+
function condenseSymId(cond: any, key: string): number {
|
|
3393
|
+
const sym = (cond as any)[S_condsym]
|
|
3394
|
+
let lo = 0
|
|
3395
|
+
let hi = sym.length - 1
|
|
3396
|
+
while (lo <= hi) {
|
|
3397
|
+
const mid = (lo + hi) >> 1
|
|
3398
|
+
if (sym[mid] === key) {
|
|
3399
|
+
return mid
|
|
3400
|
+
}
|
|
3401
|
+
if (0 > condenseCmp(sym[mid], key)) {
|
|
3402
|
+
lo = mid + 1
|
|
3403
|
+
} else {
|
|
3404
|
+
hi = mid - 1
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
return -1
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
// The node index of `key` inside node `idx`, or -1. Touches only this node.
|
|
3411
|
+
function condenseChild(cond: any, idx: number, key: any): number {
|
|
3412
|
+
const enc = (cond as any)[S_condnode][idx]
|
|
3413
|
+
if (null == enc) {
|
|
3414
|
+
return -1
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
if (CONDENSE_MAP === enc[0]) {
|
|
3418
|
+
const symid = condenseSymId(cond, S_MT + key)
|
|
3419
|
+
if (0 > symid) {
|
|
3420
|
+
return -1
|
|
3421
|
+
}
|
|
3422
|
+
// Pairs live at [1,2], [3,4], ... with keys ascending: bisect the pairs.
|
|
3423
|
+
let lo = 0
|
|
3424
|
+
let hi = (enc.length - 1) / 2 - 1
|
|
3425
|
+
while (lo <= hi) {
|
|
3426
|
+
const mid = (lo + hi) >> 1
|
|
3427
|
+
const k = enc[1 + mid * 2]
|
|
3428
|
+
if (k === symid) {
|
|
3429
|
+
return enc[2 + mid * 2]
|
|
3430
|
+
}
|
|
3431
|
+
if (k < symid) {
|
|
3432
|
+
lo = mid + 1
|
|
3433
|
+
} else {
|
|
3434
|
+
hi = mid - 1
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
return -1
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
if (CONDENSE_LIST === enc[0]) {
|
|
3441
|
+
// Canonical integer keys ONLY. Unary + would accept "01", "1e0", " 1"
|
|
3442
|
+
// and "+1", which an ordinary list lookup treats as plain (absent)
|
|
3443
|
+
// properties - condensing must not change which value a path selects.
|
|
3444
|
+
const k = S_MT + key
|
|
3445
|
+
if (!/^(0|[1-9][0-9]*)$/.test(k)) {
|
|
3446
|
+
return -1
|
|
3447
|
+
}
|
|
3448
|
+
const i = +k
|
|
3449
|
+
if (i >= enc.length - 1) {
|
|
3450
|
+
return -1
|
|
3451
|
+
}
|
|
3452
|
+
return enc[1 + i]
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
return -1
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
// Walk a path from a starting node, returning the node index or -1.
|
|
3459
|
+
function condenseResolve(cond: any, idx: number, path: any): number {
|
|
3460
|
+
const parts = islist(path)
|
|
3461
|
+
? path
|
|
3462
|
+
: 'string' === typeof path
|
|
3463
|
+
? '' === path
|
|
3464
|
+
? []
|
|
3465
|
+
: path.split(S_DT)
|
|
3466
|
+
: null == path
|
|
3467
|
+
? []
|
|
3468
|
+
: [S_MT + path]
|
|
3469
|
+
|
|
3470
|
+
let at = idx
|
|
3471
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3472
|
+
if (0 > at) {
|
|
3473
|
+
return -1
|
|
3474
|
+
}
|
|
3475
|
+
if (S_MT === parts[i]) {
|
|
3476
|
+
continue
|
|
3477
|
+
}
|
|
3478
|
+
at = condenseChild(cond, at, parts[i])
|
|
3479
|
+
}
|
|
3480
|
+
return at
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
// Materialise node `idx` as an ordinary JSON-shaped value.
|
|
3484
|
+
//
|
|
3485
|
+
// Containers are rebuilt on every call and never memoised: sharing is an
|
|
3486
|
+
// implementation detail, and handing the same object out twice would let a
|
|
3487
|
+
// caller mutating one reference corrupt every other. The cost is that
|
|
3488
|
+
// expansion reproduces the original tree - which is exactly the size it was
|
|
3489
|
+
// before condensing, so it is bounded by the input.
|
|
3490
|
+
function condenseMaterialise(cond: any, idx: number): any {
|
|
3491
|
+
const sym = (cond as any)[S_condsym]
|
|
3492
|
+
const node = (cond as any)[S_condnode]
|
|
3493
|
+
|
|
3494
|
+
const rebuild = (i: number): any => {
|
|
3495
|
+
const enc = node[i]
|
|
3496
|
+
if (null == enc) {
|
|
3497
|
+
return undefined
|
|
3498
|
+
}
|
|
3499
|
+
const kind = enc[0]
|
|
3500
|
+
if (CONDENSE_MAP === kind) {
|
|
3501
|
+
const out: any = {}
|
|
3502
|
+
for (let j = 1; j < enc.length; j += 2) {
|
|
3503
|
+
condenseSet(out, sym[enc[j]], rebuild(enc[j + 1]))
|
|
3504
|
+
}
|
|
3505
|
+
return out
|
|
3506
|
+
}
|
|
3507
|
+
if (CONDENSE_LIST === kind) {
|
|
3508
|
+
const out: any[] = []
|
|
3509
|
+
for (let j = 1; j < enc.length; j++) {
|
|
3510
|
+
out.push(rebuild(enc[j]))
|
|
3511
|
+
}
|
|
3512
|
+
return out
|
|
3513
|
+
}
|
|
3514
|
+
if (CONDENSE_STR === kind) {
|
|
3515
|
+
return sym[enc[1]]
|
|
3516
|
+
}
|
|
3517
|
+
if (CONDENSE_NUM === kind) {
|
|
3518
|
+
return enc[1]
|
|
3519
|
+
}
|
|
3520
|
+
if (CONDENSE_TRUE === kind) {
|
|
3521
|
+
return true
|
|
3522
|
+
}
|
|
3523
|
+
if (CONDENSE_FALSE === kind) {
|
|
3524
|
+
return false
|
|
3525
|
+
}
|
|
3526
|
+
return null
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
return 0 > idx ? undefined : rebuild(idx)
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
// Fully materialise a condensed structure. The escape hatch: correct for any
|
|
3533
|
+
// caller, but it gives up every benefit of condensing, so prefer a view or a
|
|
3534
|
+
// path read.
|
|
3535
|
+
function expand(cond: any): any {
|
|
3536
|
+
if (!iscondensed(cond)) {
|
|
3537
|
+
return cond
|
|
3538
|
+
}
|
|
3539
|
+
return condenseMaterialise(cond, (cond as any)[S_condroot])
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
// The keys of node `idx`, WITHOUT materialising any of its values. This is
|
|
3543
|
+
// the point of the whole exercise for enumeration: listing the names in a
|
|
3544
|
+
// large collection should cost a symbol-table read, not a full decode.
|
|
3545
|
+
function condenseKeys(cond: any, idx: number): string[] {
|
|
3546
|
+
const enc = (cond as any)[S_condnode][idx]
|
|
3547
|
+
if (null == enc) {
|
|
3548
|
+
return []
|
|
3549
|
+
}
|
|
3550
|
+
const sym = (cond as any)[S_condsym]
|
|
3551
|
+
const out: string[] = []
|
|
3552
|
+
if (CONDENSE_MAP === enc[0]) {
|
|
3553
|
+
for (let j = 1; j < enc.length; j += 2) {
|
|
3554
|
+
out.push(sym[enc[j]])
|
|
3555
|
+
}
|
|
3556
|
+
return out
|
|
3557
|
+
}
|
|
3558
|
+
if (CONDENSE_LIST === enc[0]) {
|
|
3559
|
+
for (let j = 1; j < enc.length; j++) {
|
|
3560
|
+
out.push(S_MT + (j - 1))
|
|
3561
|
+
}
|
|
3562
|
+
return out
|
|
3563
|
+
}
|
|
3564
|
+
return []
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3567
|
+
// A cursor into a condensed structure. Reads resolve by integer index and
|
|
3568
|
+
// touch only the nodes on the path, so the cost of a lookup is proportional
|
|
3569
|
+
// to path depth and result size, never to total structure size.
|
|
3570
|
+
type CondenseView = {
|
|
3571
|
+
get: (path?: any) => any
|
|
3572
|
+
keys: (path?: any) => string[]
|
|
3573
|
+
has: (path?: any) => boolean
|
|
3574
|
+
at: (path?: any) => CondenseView
|
|
3575
|
+
value: () => any
|
|
3576
|
+
size: () => number
|
|
3577
|
+
exists: () => boolean
|
|
3578
|
+
ref: () => any
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
function condenseview(cond: any, idx?: number, memo?: any): CondenseView {
|
|
3582
|
+
const start = null == idx ? (iscondensed(cond) ? (cond as any)[S_condroot] : -1) : idx
|
|
3583
|
+
|
|
3584
|
+
// Shared materialisations, handed out ONLY by ref(). Threaded through at()
|
|
3585
|
+
// so every cursor into the same structure shares one memo - otherwise
|
|
3586
|
+
// ref() would hand out a different object per cursor and quietly stop
|
|
3587
|
+
// being the shared-reference escape hatch it claims to be.
|
|
3588
|
+
const refmemo: { [idx: string]: any } = null == memo ? {} : memo
|
|
3589
|
+
|
|
3590
|
+
const view: CondenseView = {
|
|
3591
|
+
// The value at path, materialised. Scalars come back directly; a
|
|
3592
|
+
// container comes back as an independent copy.
|
|
3593
|
+
get: (path?: any) => condenseMaterialise(cond, condenseResolve(cond, start, path)),
|
|
3594
|
+
|
|
3595
|
+
// Key names at path, with no value decoding at all.
|
|
3596
|
+
keys: (path?: any) => {
|
|
3597
|
+
const at = condenseResolve(cond, start, path)
|
|
3598
|
+
return 0 > at ? [] : condenseKeys(cond, at)
|
|
3599
|
+
},
|
|
3600
|
+
|
|
3601
|
+
has: (path?: any) => 0 <= condenseResolve(cond, start, path),
|
|
3602
|
+
|
|
3603
|
+
// Chainable cursor. Cheap: no decoding happens here.
|
|
3604
|
+
at: (path?: any) => condenseview(cond, condenseResolve(cond, start, path), refmemo),
|
|
3605
|
+
|
|
3606
|
+
value: () => condenseMaterialise(cond, start),
|
|
3607
|
+
|
|
3608
|
+
size: () => {
|
|
3609
|
+
const enc = 0 > start ? null : (cond as any)[S_condnode][start]
|
|
3610
|
+
if (null == enc) {
|
|
3611
|
+
return 0
|
|
3612
|
+
}
|
|
3613
|
+
if (CONDENSE_MAP === enc[0]) {
|
|
3614
|
+
return (enc.length - 1) / 2
|
|
3615
|
+
}
|
|
3616
|
+
if (CONDENSE_LIST === enc[0]) {
|
|
3617
|
+
return enc.length - 1
|
|
3618
|
+
}
|
|
3619
|
+
return 0
|
|
3620
|
+
},
|
|
3621
|
+
|
|
3622
|
+
exists: () => 0 <= start,
|
|
3623
|
+
|
|
3624
|
+
// DANGER, and named to say so. Returns a SHARED materialisation: the same
|
|
3625
|
+
// object for the same node, every time. Mutating it corrupts every other
|
|
3626
|
+
// reader of that node, including ones reached by a different path,
|
|
3627
|
+
// because the structure is a DAG. Use value() unless a profile says this
|
|
3628
|
+
// matters.
|
|
3629
|
+
ref: () => {
|
|
3630
|
+
const k = S_MT + start
|
|
3631
|
+
if (undefined === refmemo[k]) {
|
|
3632
|
+
refmemo[k] = condenseMaterialise(cond, start)
|
|
3633
|
+
}
|
|
3634
|
+
return refmemo[k]
|
|
3635
|
+
},
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
return view
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3030
3641
|
export {
|
|
3031
3642
|
StructUtility,
|
|
3032
3643
|
clone,
|
|
3644
|
+
condense,
|
|
3645
|
+
condenseview,
|
|
3646
|
+
expand,
|
|
3647
|
+
iscondensed,
|
|
3033
3648
|
delprop,
|
|
3034
3649
|
escre,
|
|
3035
3650
|
escurl,
|
|
@@ -3066,13 +3681,16 @@ export {
|
|
|
3066
3681
|
typename,
|
|
3067
3682
|
validate,
|
|
3068
3683
|
walk,
|
|
3069
|
-
|
|
3684
|
+
re_compile,
|
|
3685
|
+
re_find,
|
|
3686
|
+
re_find_all,
|
|
3687
|
+
re_replace,
|
|
3688
|
+
re_test,
|
|
3689
|
+
re_escape,
|
|
3070
3690
|
SKIP,
|
|
3071
3691
|
DELETE,
|
|
3072
|
-
|
|
3073
3692
|
jm,
|
|
3074
3693
|
jt,
|
|
3075
|
-
|
|
3076
3694
|
T_any,
|
|
3077
3695
|
T_noval,
|
|
3078
3696
|
T_boolean,
|
|
@@ -3088,20 +3706,13 @@ export {
|
|
|
3088
3706
|
T_instance,
|
|
3089
3707
|
T_scalar,
|
|
3090
3708
|
T_node,
|
|
3091
|
-
|
|
3092
3709
|
M_KEYPRE,
|
|
3093
3710
|
M_KEYPOST,
|
|
3094
3711
|
M_VAL,
|
|
3095
|
-
|
|
3096
3712
|
MODENAME,
|
|
3097
|
-
|
|
3098
3713
|
checkPlacement,
|
|
3099
3714
|
injectorArgs,
|
|
3100
3715
|
injectChild,
|
|
3101
3716
|
}
|
|
3102
3717
|
|
|
3103
|
-
export type {
|
|
3104
|
-
Injection,
|
|
3105
|
-
Injector,
|
|
3106
|
-
WalkApply
|
|
3107
|
-
}
|
|
3718
|
+
export type { Injection, Injector, WalkApply }
|