@o3co/ts.hocon 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +10 -4
- package/README.md +18 -4
- package/dist/index.cjs +1196 -795
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1196 -795
- package/dist/{parse-C4VOYuEk.d.cts → parse-nNbyqnOh.d.cts} +4 -2
- package/dist/{parse-C4VOYuEk.d.ts → parse-nNbyqnOh.d.ts} +4 -2
- package/dist/zod.cjs +1195 -657
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +1195 -657
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/coerce.ts
|
|
2
|
+
var DECIMAL_NUMBER_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
2
3
|
var TRUTHY = /* @__PURE__ */ new Set(["true", "yes", "on"]);
|
|
3
4
|
var FALSY = /* @__PURE__ */ new Set(["false", "no", "off"]);
|
|
4
5
|
function coerceBoolean(value) {
|
|
@@ -8,7 +9,7 @@ function coerceBoolean(value) {
|
|
|
8
9
|
return void 0;
|
|
9
10
|
}
|
|
10
11
|
function coerceNumber(value) {
|
|
11
|
-
if (
|
|
12
|
+
if (!DECIMAL_NUMBER_RE.test(value)) return void 0;
|
|
12
13
|
return Number(value);
|
|
13
14
|
}
|
|
14
15
|
var DURATION_UNITS = {
|
|
@@ -164,6 +165,23 @@ var ConfigError = class extends Error {
|
|
|
164
165
|
}
|
|
165
166
|
};
|
|
166
167
|
|
|
168
|
+
// src/value/numeric-array.ts
|
|
169
|
+
function numericObjectToArray(value) {
|
|
170
|
+
if (value.kind !== "object") return null;
|
|
171
|
+
if (value.fields.size === 0) return null;
|
|
172
|
+
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
173
|
+
const eligible = [];
|
|
174
|
+
for (const [key, val] of value.fields) {
|
|
175
|
+
if (!canonicalInt.test(key)) continue;
|
|
176
|
+
const n = Number(key);
|
|
177
|
+
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
178
|
+
eligible.push({ n, v: val });
|
|
179
|
+
}
|
|
180
|
+
if (eligible.length === 0) return null;
|
|
181
|
+
eligible.sort((a, b) => a.n - b.n);
|
|
182
|
+
return eligible.map((e) => e.v);
|
|
183
|
+
}
|
|
184
|
+
|
|
167
185
|
// src/config.ts
|
|
168
186
|
var Config = class _Config {
|
|
169
187
|
constructor(root) {
|
|
@@ -176,39 +194,36 @@ var Config = class _Config {
|
|
|
176
194
|
}
|
|
177
195
|
getString(path2) {
|
|
178
196
|
const v = this.requireScalar(path2);
|
|
179
|
-
|
|
180
|
-
return v;
|
|
197
|
+
return v.raw;
|
|
181
198
|
}
|
|
182
199
|
getNumber(path2) {
|
|
183
200
|
const v = this.requireScalar(path2);
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
if (coerced !== void 0) return coerced;
|
|
188
|
-
}
|
|
189
|
-
throw new ConfigError(`expected number at ${path2}, got ${typeof v}`, path2);
|
|
201
|
+
const coerced = coerceNumber(v.raw);
|
|
202
|
+
if (coerced !== void 0) return coerced;
|
|
203
|
+
throw new ConfigError(`expected number at ${path2}, got ${v.valueType}`, path2);
|
|
190
204
|
}
|
|
191
205
|
getBoolean(path2) {
|
|
192
206
|
const v = this.requireScalar(path2);
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
if (coerced !== void 0) return coerced;
|
|
197
|
-
}
|
|
198
|
-
throw new ConfigError(`expected boolean at ${path2}, got ${typeof v}`, path2);
|
|
207
|
+
const coerced = coerceBoolean(v.raw);
|
|
208
|
+
if (coerced !== void 0) return coerced;
|
|
209
|
+
throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
|
|
199
210
|
}
|
|
200
211
|
getDuration(path2, unit) {
|
|
201
212
|
const v = this.requireScalar(path2);
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
213
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
214
|
+
throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
|
|
215
|
+
}
|
|
216
|
+
const result = parseDuration(v.raw, unit);
|
|
217
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
205
218
|
return result;
|
|
206
219
|
}
|
|
207
220
|
getBytes(path2, unit) {
|
|
208
221
|
const v = this.requireScalar(path2);
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
222
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
223
|
+
throw new ConfigError(`expected byte size at ${path2}, got ${v.valueType}`, path2);
|
|
224
|
+
}
|
|
225
|
+
const result = parseBytes(v.raw, unit);
|
|
226
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
212
227
|
return result;
|
|
213
228
|
}
|
|
214
229
|
getConfig(path2) {
|
|
@@ -220,6 +235,10 @@ var Config = class _Config {
|
|
|
220
235
|
getList(path2) {
|
|
221
236
|
const v = this.lookupNode(path2);
|
|
222
237
|
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
238
|
+
if (v.kind === "object") {
|
|
239
|
+
const converted = numericObjectToArray(v);
|
|
240
|
+
if (converted !== null) return converted.map(hoconToJs);
|
|
241
|
+
}
|
|
223
242
|
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
224
243
|
return v.items.map(hoconToJs);
|
|
225
244
|
}
|
|
@@ -251,7 +270,7 @@ var Config = class _Config {
|
|
|
251
270
|
const v = this.lookupNode(path2);
|
|
252
271
|
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
253
272
|
if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path2}, got ${v.kind}`, path2);
|
|
254
|
-
return v
|
|
273
|
+
return v;
|
|
255
274
|
}
|
|
256
275
|
};
|
|
257
276
|
function splitConfigPath(path2) {
|
|
@@ -293,10 +312,22 @@ function splitConfigPath(path2) {
|
|
|
293
312
|
}
|
|
294
313
|
return segments;
|
|
295
314
|
}
|
|
315
|
+
function scalarToJs(raw, valueType) {
|
|
316
|
+
switch (valueType) {
|
|
317
|
+
case "null":
|
|
318
|
+
return null;
|
|
319
|
+
case "boolean":
|
|
320
|
+
return raw === "true";
|
|
321
|
+
case "number":
|
|
322
|
+
return Number(raw);
|
|
323
|
+
case "string":
|
|
324
|
+
return raw;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
296
327
|
function hoconToJs(v) {
|
|
297
328
|
switch (v.kind) {
|
|
298
329
|
case "scalar":
|
|
299
|
-
return v.
|
|
330
|
+
return scalarToJs(v.raw, v.valueType);
|
|
300
331
|
case "array":
|
|
301
332
|
return v.items.map(hoconToJs);
|
|
302
333
|
case "object": {
|
|
@@ -320,281 +351,494 @@ function deepMergeHocon(receiver, fallback) {
|
|
|
320
351
|
}
|
|
321
352
|
|
|
322
353
|
// src/internal/lexer/lexer.ts
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
354
|
+
function isHoconWhitespace(ch) {
|
|
355
|
+
const cp = ch.codePointAt(0) ?? -1;
|
|
356
|
+
if (cp === 9 || cp === 10 || cp === 11 || cp === 12 || cp === 13) return true;
|
|
357
|
+
if (cp >= 28 && cp <= 31) return true;
|
|
358
|
+
if (cp === 32 || cp === 160 || cp === 65279) return true;
|
|
359
|
+
if (cp === 5760) return true;
|
|
360
|
+
if (cp >= 8192 && cp <= 8202) return true;
|
|
361
|
+
if (cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287) return true;
|
|
362
|
+
if (cp === 12288) return true;
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
function isHoconNewline(ch) {
|
|
366
|
+
return ch === "\n";
|
|
367
|
+
}
|
|
368
|
+
var SINGLE_CHAR_TOKENS = {
|
|
369
|
+
"{": "lbrace",
|
|
370
|
+
"}": "rbrace",
|
|
371
|
+
"[": "lbracket",
|
|
372
|
+
"]": "rbracket",
|
|
373
|
+
",": "comma",
|
|
374
|
+
":": "colon"
|
|
375
|
+
};
|
|
376
|
+
var Lexer = class {
|
|
377
|
+
constructor(input) {
|
|
378
|
+
this.input = input;
|
|
379
|
+
this.pos = 0;
|
|
380
|
+
this.line = 1;
|
|
381
|
+
this.col = 1;
|
|
382
|
+
this.hadSpace = false;
|
|
383
|
+
this.tokens = [];
|
|
384
|
+
if (input.charCodeAt(0) === 65279) this.input = input.slice(1);
|
|
385
|
+
}
|
|
386
|
+
tokenize() {
|
|
387
|
+
while (this.pos < this.input.length) {
|
|
388
|
+
const sl = this.line, sc = this.col;
|
|
389
|
+
const ch = this.peek();
|
|
390
|
+
if (isHoconNewline(ch)) {
|
|
391
|
+
this.advance();
|
|
392
|
+
if (this.tokens[this.tokens.length - 1]?.kind !== "newline") {
|
|
393
|
+
this.push("newline", "\n", sl, sc);
|
|
394
|
+
}
|
|
395
|
+
continue;
|
|
359
396
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
hadSpace = true;
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
if (ch === "#") {
|
|
368
|
-
while (pos < input.length && peek() !== "\n") advance();
|
|
369
|
-
hadSpace = true;
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
const single = { "{": "lbrace", "}": "rbrace", "[": "lbracket", "]": "rbracket", ",": "comma", ":": "colon" };
|
|
373
|
-
if (ch in single) {
|
|
374
|
-
advance();
|
|
375
|
-
push(single[ch], ch, sl, sc);
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
if (ch === "=") {
|
|
379
|
-
advance();
|
|
380
|
-
push("equals", "=", sl, sc);
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
if (ch === "+" && peek(1) === "=") {
|
|
384
|
-
advance();
|
|
385
|
-
advance();
|
|
386
|
-
push("plus_equals", "+=", sl, sc);
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
if (ch === "$" && peek(1) === "{") {
|
|
390
|
-
advance();
|
|
391
|
-
advance();
|
|
392
|
-
const optional = peek() === "?";
|
|
393
|
-
if (optional) advance();
|
|
394
|
-
let path2 = "";
|
|
395
|
-
while (pos < input.length && peek() !== "}") {
|
|
396
|
-
if (peek() === "\n") throw new ParseError("unterminated substitution", sl, sc);
|
|
397
|
-
path2 += advance();
|
|
397
|
+
if (isHoconWhitespace(ch)) {
|
|
398
|
+
this.advance();
|
|
399
|
+
this.hadSpace = true;
|
|
400
|
+
continue;
|
|
398
401
|
}
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
402
|
+
if (ch === "/" && this.peek(1) === "/") {
|
|
403
|
+
while (this.pos < this.input.length && this.peek() !== "\n") this.advance();
|
|
404
|
+
this.hadSpace = true;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (ch === "#") {
|
|
408
|
+
while (this.pos < this.input.length && this.peek() !== "\n") this.advance();
|
|
409
|
+
this.hadSpace = true;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (ch in SINGLE_CHAR_TOKENS) {
|
|
413
|
+
this.advance();
|
|
414
|
+
this.push(SINGLE_CHAR_TOKENS[ch], ch, sl, sc);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (ch === "=") {
|
|
418
|
+
this.advance();
|
|
419
|
+
this.push("equals", "=", sl, sc);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (ch === "+" && this.peek(1) === "=") {
|
|
423
|
+
this.advance();
|
|
424
|
+
this.advance();
|
|
425
|
+
this.push("plus_equals", "+=", sl, sc);
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
if (ch === "$" && this.peek(1) === "{") {
|
|
429
|
+
this.advance();
|
|
430
|
+
this.advance();
|
|
431
|
+
const payload = this.parseSubstBody(sl, sc);
|
|
432
|
+
const value = payload.segments.map((s) => {
|
|
433
|
+
const t = s.text;
|
|
434
|
+
if (t === "" || t.includes(".") || t.includes(" ") || t.includes(" ") || t.includes('"') || t.includes("\\") || t !== t.trim()) {
|
|
435
|
+
const escaped = t.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
436
|
+
return `"${escaped}"`;
|
|
416
437
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
438
|
+
return t;
|
|
439
|
+
}).join(".");
|
|
440
|
+
this.pushSubst(payload, value, sl, sc);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (ch === '"' && this.peek(1) === '"' && this.peek(2) === '"') {
|
|
444
|
+
this.advance();
|
|
445
|
+
this.advance();
|
|
446
|
+
this.advance();
|
|
447
|
+
let value = "";
|
|
448
|
+
let closed = false;
|
|
449
|
+
while (this.pos < this.input.length) {
|
|
450
|
+
if (this.peek() === '"') {
|
|
451
|
+
let quoteCount = 0;
|
|
452
|
+
while (this.pos < this.input.length && this.peek() === '"') {
|
|
453
|
+
quoteCount++;
|
|
454
|
+
this.advance();
|
|
455
|
+
}
|
|
456
|
+
if (quoteCount >= 3) {
|
|
457
|
+
for (let i = 0; i < quoteCount - 3; i++) value += '"';
|
|
458
|
+
closed = true;
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
for (let i = 0; i < quoteCount; i++) value += '"';
|
|
462
|
+
continue;
|
|
421
463
|
}
|
|
422
|
-
|
|
423
|
-
|
|
464
|
+
value += this.advance();
|
|
465
|
+
}
|
|
466
|
+
if (!closed) {
|
|
467
|
+
throw new ParseError("unterminated triple-quoted string", sl, sc);
|
|
424
468
|
}
|
|
425
|
-
value
|
|
469
|
+
if (value.startsWith("\n")) value = value.slice(1);
|
|
470
|
+
this.push("triple_string", value, sl, sc, true);
|
|
471
|
+
continue;
|
|
426
472
|
}
|
|
427
|
-
if (
|
|
428
|
-
|
|
473
|
+
if (ch === '"') {
|
|
474
|
+
this.advance();
|
|
475
|
+
const value = this.readQuotedStringBody(sl, sc);
|
|
476
|
+
this.push("string", value, sl, sc, true);
|
|
477
|
+
continue;
|
|
429
478
|
}
|
|
430
|
-
if (
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
479
|
+
if (isUnquotedStart(ch)) {
|
|
480
|
+
if (ch === "-" && !isDecimalDigit(this.peek(1))) {
|
|
481
|
+
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
482
|
+
throw new ParseError(
|
|
483
|
+
`unquoted string cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
484
|
+
sl,
|
|
485
|
+
sc
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
let value = "";
|
|
489
|
+
while (this.pos < this.input.length && isUnquotedContinue(this.peek(), () => this.peek(1))) {
|
|
490
|
+
value += this.advance();
|
|
491
|
+
}
|
|
492
|
+
this.push("unquoted", value.trimEnd(), sl, sc);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
throw new ParseError(`unexpected character: ${JSON.stringify(ch)}`, sl, sc);
|
|
496
|
+
}
|
|
497
|
+
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false });
|
|
498
|
+
return this.tokens;
|
|
499
|
+
}
|
|
500
|
+
peek(offset = 0) {
|
|
501
|
+
return this.input[this.pos + offset] ?? "";
|
|
502
|
+
}
|
|
503
|
+
advance() {
|
|
504
|
+
const ch = this.input[this.pos++] ?? "";
|
|
505
|
+
if (ch === "\n") {
|
|
506
|
+
this.line++;
|
|
507
|
+
this.col = 1;
|
|
508
|
+
} else {
|
|
509
|
+
this.col++;
|
|
510
|
+
}
|
|
511
|
+
return ch;
|
|
512
|
+
}
|
|
513
|
+
push(kind, value, l, c, isQuoted = false) {
|
|
514
|
+
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
|
|
515
|
+
this.hadSpace = false;
|
|
516
|
+
}
|
|
517
|
+
pushSubst(payload, value, l, c) {
|
|
518
|
+
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, subst: payload });
|
|
519
|
+
this.hadSpace = false;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Read body of a quoted string. Opening `"` must already be consumed.
|
|
523
|
+
* Returns decoded string. Throws ParseError on unterminated/invalid-escape.
|
|
524
|
+
* openLine/openCol are the position of the opening `"` for error reporting.
|
|
525
|
+
*/
|
|
526
|
+
readQuotedStringBody(openLine, openCol) {
|
|
527
|
+
let value = "";
|
|
528
|
+
while (this.pos < this.input.length && this.peek() !== '"') {
|
|
529
|
+
if (this.peek() === "\n") throw new ParseError("unterminated string", openLine, openCol);
|
|
530
|
+
if (this.peek() === "\\") {
|
|
531
|
+
const escCol = this.col;
|
|
532
|
+
this.advance();
|
|
533
|
+
if (this.pos >= this.input.length) {
|
|
534
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
535
|
+
}
|
|
536
|
+
const esc = this.advance();
|
|
537
|
+
switch (esc) {
|
|
538
|
+
case "n":
|
|
539
|
+
value += "\n";
|
|
540
|
+
break;
|
|
541
|
+
case "t":
|
|
542
|
+
value += " ";
|
|
543
|
+
break;
|
|
544
|
+
case "r":
|
|
545
|
+
value += "\r";
|
|
546
|
+
break;
|
|
547
|
+
case '"':
|
|
548
|
+
value += '"';
|
|
549
|
+
break;
|
|
550
|
+
case "\\":
|
|
551
|
+
value += "\\";
|
|
552
|
+
break;
|
|
553
|
+
case "/":
|
|
554
|
+
value += "/";
|
|
555
|
+
break;
|
|
556
|
+
case "b":
|
|
557
|
+
value += "\b";
|
|
558
|
+
break;
|
|
559
|
+
case "f":
|
|
560
|
+
value += "\f";
|
|
561
|
+
break;
|
|
562
|
+
case "u": {
|
|
563
|
+
if (this.pos + 4 > this.input.length) {
|
|
564
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
478
565
|
}
|
|
479
|
-
|
|
480
|
-
|
|
566
|
+
const hex = this.input.slice(this.pos, this.pos + 4);
|
|
567
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
568
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
569
|
+
}
|
|
570
|
+
const code = parseInt(hex, 16);
|
|
571
|
+
value += String.fromCharCode(code);
|
|
572
|
+
for (let i = 0; i < 4; i++) this.advance();
|
|
573
|
+
break;
|
|
481
574
|
}
|
|
482
|
-
|
|
483
|
-
|
|
575
|
+
default:
|
|
576
|
+
throw new ParseError(`unknown escape sequence: \\${esc}`, openLine, escCol);
|
|
484
577
|
}
|
|
578
|
+
} else {
|
|
579
|
+
value += this.advance();
|
|
485
580
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
581
|
+
}
|
|
582
|
+
if (this.pos >= this.input.length || this.peek() !== '"') {
|
|
583
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
584
|
+
}
|
|
585
|
+
this.advance();
|
|
586
|
+
return value;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Parse the body of a `${...}` substitution (called after `${` has been consumed).
|
|
590
|
+
* Implements Appendix A state machine from the cross-language spec.
|
|
591
|
+
*/
|
|
592
|
+
parseSubstBody(startLine, startCol) {
|
|
593
|
+
let optional = false;
|
|
594
|
+
if (this.peek() === "?") {
|
|
595
|
+
this.advance();
|
|
596
|
+
optional = true;
|
|
597
|
+
}
|
|
598
|
+
let curText = "";
|
|
599
|
+
let curStarted = false;
|
|
600
|
+
let curLine = 0;
|
|
601
|
+
let curCol = 0;
|
|
602
|
+
let pendingWs = "";
|
|
603
|
+
const segments = [];
|
|
604
|
+
let lastDot = null;
|
|
605
|
+
while (true) {
|
|
606
|
+
if (this.pos >= this.input.length) {
|
|
607
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
608
|
+
}
|
|
609
|
+
const ch = this.peek();
|
|
610
|
+
if (ch === "}") {
|
|
611
|
+
this.advance();
|
|
612
|
+
pendingWs = "";
|
|
613
|
+
break;
|
|
614
|
+
} else if (ch === '"') {
|
|
615
|
+
const qLine = startLine;
|
|
616
|
+
const qCol = this.col;
|
|
617
|
+
if (curStarted) {
|
|
618
|
+
curText += pendingWs;
|
|
619
|
+
}
|
|
620
|
+
pendingWs = "";
|
|
621
|
+
this.advance();
|
|
622
|
+
const decoded = this.readQuotedStringBody(qLine, qCol);
|
|
623
|
+
curText += decoded;
|
|
624
|
+
if (!curStarted) {
|
|
625
|
+
curLine = qLine;
|
|
626
|
+
curCol = qCol;
|
|
627
|
+
curStarted = true;
|
|
628
|
+
}
|
|
629
|
+
} else if (isUnquotedSubstChar(ch)) {
|
|
630
|
+
if (ch === "-" && !curStarted && !isDecimalDigit(this.peek(1))) {
|
|
631
|
+
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
632
|
+
throw new ParseError(
|
|
633
|
+
`unquoted path segment cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
634
|
+
startLine,
|
|
635
|
+
this.col
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
const uCol = this.col;
|
|
639
|
+
if (curStarted) {
|
|
640
|
+
curText += pendingWs;
|
|
641
|
+
}
|
|
642
|
+
pendingWs = "";
|
|
643
|
+
if (!curStarted) {
|
|
644
|
+
curLine = startLine;
|
|
645
|
+
curCol = uCol;
|
|
646
|
+
curStarted = true;
|
|
647
|
+
}
|
|
648
|
+
while (this.pos < this.input.length && isUnquotedSubstChar(this.peek())) {
|
|
649
|
+
curText += this.advance();
|
|
650
|
+
}
|
|
651
|
+
} else if (ch === ".") {
|
|
652
|
+
const dotCol = this.col;
|
|
653
|
+
pendingWs = "";
|
|
654
|
+
if (!curStarted) {
|
|
655
|
+
throw new ParseError("empty segment in path", startLine, dotCol);
|
|
656
|
+
}
|
|
657
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
658
|
+
curText = "";
|
|
659
|
+
curStarted = false;
|
|
660
|
+
curLine = 0;
|
|
661
|
+
curCol = 0;
|
|
662
|
+
lastDot = [startLine, dotCol];
|
|
663
|
+
this.advance();
|
|
664
|
+
} else if (isHoconNewline(ch)) {
|
|
665
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
666
|
+
} else if (isHoconWhitespace(ch)) {
|
|
667
|
+
pendingWs += ch;
|
|
668
|
+
this.advance();
|
|
669
|
+
} else {
|
|
670
|
+
throw new ParseError(`unexpected character in substitution path: ${JSON.stringify(ch)}`, startLine, this.col);
|
|
495
671
|
}
|
|
496
|
-
push("unquoted", value.trimEnd(), sl, sc);
|
|
497
|
-
continue;
|
|
498
672
|
}
|
|
499
|
-
|
|
673
|
+
if (curStarted) {
|
|
674
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
675
|
+
} else if (segments.length === 0) {
|
|
676
|
+
throw new ParseError("empty substitution path", startLine, startCol);
|
|
677
|
+
} else {
|
|
678
|
+
const [errLine, errCol] = lastDot ?? [startLine, startCol];
|
|
679
|
+
throw new ParseError("empty segment in path", errLine, errCol);
|
|
680
|
+
}
|
|
681
|
+
return { segments, optional };
|
|
500
682
|
}
|
|
501
|
-
|
|
502
|
-
|
|
683
|
+
};
|
|
684
|
+
function tokenize(input) {
|
|
685
|
+
return new Lexer(input).tokenize();
|
|
686
|
+
}
|
|
687
|
+
function isUnquotedSubstChar(ch) {
|
|
688
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
689
|
+
if ('"\\'.includes(ch)) return false;
|
|
690
|
+
if ("{}[]".includes(ch)) return false;
|
|
691
|
+
if (":=,+#`^?!@*&$.".includes(ch)) return false;
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
function isDecimalDigit(ch) {
|
|
695
|
+
return ch >= "0" && ch <= "9";
|
|
503
696
|
}
|
|
504
697
|
function isUnquotedStart(ch) {
|
|
505
|
-
|
|
698
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
699
|
+
if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
|
|
700
|
+
return true;
|
|
506
701
|
}
|
|
507
702
|
function isUnquotedContinue(ch, nextFn) {
|
|
508
|
-
if (ch === "" ||
|
|
703
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
704
|
+
if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
|
|
509
705
|
if (ch === "+" && nextFn() === "=") return false;
|
|
510
706
|
if (ch === "/" && nextFn() === "/") return false;
|
|
511
707
|
return true;
|
|
512
708
|
}
|
|
513
709
|
|
|
514
710
|
// src/internal/parser/parser.ts
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
711
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
|
|
712
|
+
var Parser = class {
|
|
713
|
+
constructor(tokens) {
|
|
714
|
+
this.tokens = tokens;
|
|
715
|
+
this.pos = 0;
|
|
716
|
+
}
|
|
717
|
+
parse() {
|
|
718
|
+
this.skip("newline");
|
|
719
|
+
const t = this.peek();
|
|
720
|
+
if (t.kind === "lbrace") {
|
|
721
|
+
this.advance();
|
|
722
|
+
const first = this.parseObject(true);
|
|
723
|
+
const allFields = first.kind === "object" ? [...first.fields] : [];
|
|
724
|
+
while (true) {
|
|
725
|
+
this.skip("newline");
|
|
726
|
+
if (this.peek().kind === "eof") break;
|
|
727
|
+
if (this.peek().kind === "lbrace") {
|
|
728
|
+
this.advance();
|
|
729
|
+
const extra = this.parseObject(true);
|
|
730
|
+
if (extra.kind === "object") allFields.push(...extra.fields);
|
|
731
|
+
} else {
|
|
732
|
+
const rest = this.parseObject(false);
|
|
733
|
+
if (rest.kind === "object") allFields.push(...rest.fields);
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
this.skip("newline");
|
|
738
|
+
const remaining = this.peek();
|
|
739
|
+
if (remaining.kind !== "eof") {
|
|
740
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
|
|
741
|
+
}
|
|
742
|
+
return { kind: "object", fields: allFields, pos: first.pos };
|
|
743
|
+
}
|
|
744
|
+
return this.parseObject(false);
|
|
745
|
+
}
|
|
746
|
+
peek(offset = 0) {
|
|
747
|
+
return this.tokens[this.pos + offset] ?? EOF_TOKEN;
|
|
519
748
|
}
|
|
520
|
-
|
|
521
|
-
const
|
|
522
|
-
if (pos < tokens.length) pos++;
|
|
523
|
-
return
|
|
749
|
+
advance() {
|
|
750
|
+
const t = this.tokens[this.pos];
|
|
751
|
+
if (this.pos < this.tokens.length) this.pos++;
|
|
752
|
+
return t ?? EOF_TOKEN;
|
|
524
753
|
}
|
|
525
|
-
|
|
526
|
-
while (kinds.includes(peek().kind)) advance();
|
|
754
|
+
skip(...kinds) {
|
|
755
|
+
while (kinds.includes(this.peek().kind)) this.advance();
|
|
527
756
|
}
|
|
528
|
-
|
|
529
|
-
const
|
|
530
|
-
return { line:
|
|
757
|
+
currentPos() {
|
|
758
|
+
const t = this.peek();
|
|
759
|
+
return { line: t.line, col: t.col };
|
|
531
760
|
}
|
|
532
|
-
|
|
533
|
-
const p = currentPos();
|
|
761
|
+
parseObject(expectClosingBrace) {
|
|
762
|
+
const p = this.currentPos();
|
|
534
763
|
const fields = [];
|
|
535
764
|
while (true) {
|
|
536
|
-
skip("newline");
|
|
537
|
-
const
|
|
538
|
-
if (
|
|
539
|
-
if (
|
|
540
|
-
advance();
|
|
541
|
-
fields.push(parseInclude());
|
|
542
|
-
skip("newline");
|
|
543
|
-
if (peek().kind === "comma") advance();
|
|
544
|
-
skip("newline");
|
|
765
|
+
this.skip("newline");
|
|
766
|
+
const t = this.peek();
|
|
767
|
+
if (t.kind === "eof" || t.kind === "rbrace") break;
|
|
768
|
+
if (t.kind === "unquoted" && t.value === "include") {
|
|
769
|
+
this.advance();
|
|
770
|
+
fields.push(this.parseInclude());
|
|
771
|
+
this.skip("newline");
|
|
772
|
+
if (this.peek().kind === "comma") this.advance();
|
|
773
|
+
this.skip("newline");
|
|
545
774
|
continue;
|
|
546
775
|
}
|
|
547
|
-
const keyPos = currentPos();
|
|
548
|
-
const key = parseKey();
|
|
549
|
-
skip("newline");
|
|
776
|
+
const keyPos = this.currentPos();
|
|
777
|
+
const key = this.parseKey();
|
|
778
|
+
this.skip("newline");
|
|
550
779
|
let append = false;
|
|
551
|
-
const sep = peek();
|
|
552
|
-
if (sep.kind === "equals") advance();
|
|
780
|
+
const sep = this.peek();
|
|
781
|
+
if (sep.kind === "equals") this.advance();
|
|
553
782
|
else if (sep.kind === "plus_equals") {
|
|
554
|
-
advance();
|
|
783
|
+
this.advance();
|
|
555
784
|
append = true;
|
|
556
|
-
} else if (sep.kind === "colon") advance();
|
|
785
|
+
} else if (sep.kind === "colon") this.advance();
|
|
557
786
|
else if (sep.kind === "lbrace") {
|
|
558
787
|
} else if (sep.kind !== "newline" && sep.kind !== "eof") {
|
|
559
788
|
throw new ParseError(`unexpected token after key: ${sep.kind}`, sep.line, sep.col);
|
|
560
789
|
}
|
|
561
|
-
skip("newline");
|
|
562
|
-
const value = parseValue();
|
|
790
|
+
this.skip("newline");
|
|
791
|
+
const value = this.parseValue();
|
|
563
792
|
fields.push({ key, value, append, pos: keyPos });
|
|
564
|
-
skip("newline");
|
|
565
|
-
if (peek().kind === "comma") advance();
|
|
566
|
-
skip("newline");
|
|
793
|
+
this.skip("newline");
|
|
794
|
+
if (this.peek().kind === "comma") this.advance();
|
|
795
|
+
this.skip("newline");
|
|
567
796
|
}
|
|
568
797
|
if (expectClosingBrace) {
|
|
569
|
-
const
|
|
570
|
-
if (
|
|
571
|
-
advance();
|
|
798
|
+
const t = this.peek();
|
|
799
|
+
if (t.kind !== "rbrace") throw new ParseError("expected }", t.line, t.col);
|
|
800
|
+
this.advance();
|
|
572
801
|
}
|
|
573
802
|
return { kind: "object", fields, pos: p };
|
|
574
803
|
}
|
|
575
|
-
|
|
804
|
+
parseKey() {
|
|
576
805
|
const segments = [];
|
|
577
806
|
let trailingDot = false;
|
|
578
807
|
while (true) {
|
|
579
|
-
const
|
|
580
|
-
if (
|
|
581
|
-
advance();
|
|
582
|
-
segments.push(
|
|
808
|
+
const t = this.peek();
|
|
809
|
+
if (t.kind === "string") {
|
|
810
|
+
this.advance();
|
|
811
|
+
segments.push(t.value);
|
|
583
812
|
trailingDot = false;
|
|
584
|
-
} else if (
|
|
585
|
-
advance();
|
|
586
|
-
const
|
|
813
|
+
} else if (t.kind === "unquoted") {
|
|
814
|
+
this.advance();
|
|
815
|
+
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
816
|
+
const parts = raw.split(".");
|
|
587
817
|
const filtered = parts.filter((s) => s.length > 0);
|
|
818
|
+
for (const seg of filtered) {
|
|
819
|
+
if (seg[0] === "-" && !(seg.length >= 2 && seg[1] >= "0" && seg[1] <= "9")) {
|
|
820
|
+
const after = seg.length >= 2 ? JSON.stringify(seg[1]) : "EOF";
|
|
821
|
+
throw new ParseError(
|
|
822
|
+
`unquoted key segment cannot begin with '-' unless followed by a digit (got '-' then ${after} in ${JSON.stringify(seg)}, HOCON.md L270-276)`,
|
|
823
|
+
t.line,
|
|
824
|
+
t.col
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
588
828
|
segments.push(...filtered);
|
|
589
|
-
trailingDot =
|
|
829
|
+
trailingDot = raw.endsWith(".");
|
|
590
830
|
} else {
|
|
591
|
-
if (segments.length === 0) throw new ParseError(`expected key, got ${
|
|
831
|
+
if (segments.length === 0) throw new ParseError(`expected key, got ${t.kind}`, t.line, t.col);
|
|
592
832
|
break;
|
|
593
833
|
}
|
|
594
834
|
if (trailingDot) continue;
|
|
595
|
-
const next = peek();
|
|
835
|
+
const next = this.peek();
|
|
596
836
|
if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
|
|
597
|
-
advance();
|
|
837
|
+
this.advance();
|
|
838
|
+
trailingDot = true;
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
598
842
|
trailingDot = true;
|
|
599
843
|
continue;
|
|
600
844
|
}
|
|
@@ -602,147 +846,230 @@ function parseTokens(tokens) {
|
|
|
602
846
|
}
|
|
603
847
|
return segments;
|
|
604
848
|
}
|
|
605
|
-
|
|
606
|
-
const p = currentPos();
|
|
607
|
-
skip("newline");
|
|
608
|
-
const
|
|
849
|
+
parseInclude() {
|
|
850
|
+
const p = this.currentPos();
|
|
851
|
+
this.skip("newline");
|
|
852
|
+
const t = this.peek();
|
|
609
853
|
let path2;
|
|
610
854
|
let required = false;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
855
|
+
let isFile = false;
|
|
856
|
+
if (t.kind === "unquoted" && (t.value === "required(" || t.value === "required" || t.value.startsWith("required("))) {
|
|
857
|
+
if (t.value === "required") {
|
|
858
|
+
const next2 = this.peek(1);
|
|
614
859
|
if (next2.kind !== "unquoted" || !next2.value.startsWith("(")) {
|
|
615
|
-
throw new ParseError("include required must be followed by (",
|
|
860
|
+
throw new ParseError("include required must be followed by (", t.line, t.col);
|
|
616
861
|
}
|
|
617
862
|
}
|
|
618
863
|
required = true;
|
|
619
|
-
advance();
|
|
620
|
-
const innerPrefix =
|
|
864
|
+
this.advance();
|
|
865
|
+
const innerPrefix = t.value.startsWith("required(") ? t.value.slice("required(".length) : "";
|
|
621
866
|
if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
|
|
622
|
-
throw new ParseError("include url(...) and classpath(...) are not supported",
|
|
867
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", t.line, t.col);
|
|
623
868
|
}
|
|
624
|
-
|
|
869
|
+
if (innerPrefix.startsWith("file")) {
|
|
870
|
+
isFile = true;
|
|
871
|
+
}
|
|
872
|
+
const next = this.peek();
|
|
625
873
|
if (next.kind === "unquoted" && (next.value === "url" || next.value.startsWith("url(") || next.value === "classpath" || next.value.startsWith("classpath("))) {
|
|
626
874
|
throw new ParseError("include url(...) and classpath(...) are not supported", next.line, next.col);
|
|
627
875
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
while (peek().kind !== "
|
|
632
|
-
|
|
633
|
-
path2 = advance().value;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
throw new ParseError("include
|
|
642
|
-
|
|
643
|
-
|
|
876
|
+
if (next.kind === "unquoted" && (next.value === "file(" || next.value === "file" || next.value.startsWith("file("))) {
|
|
877
|
+
isFile = true;
|
|
878
|
+
}
|
|
879
|
+
while (this.peek().kind !== "string" && this.peek().kind !== "eof") this.advance();
|
|
880
|
+
if (this.peek().kind === "eof") throw new ParseError("expected include path", t.line, t.col);
|
|
881
|
+
path2 = this.advance().value;
|
|
882
|
+
while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") this.advance();
|
|
883
|
+
} else if (t.kind === "string") {
|
|
884
|
+
path2 = this.advance().value;
|
|
885
|
+
} else if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
|
|
886
|
+
isFile = true;
|
|
887
|
+
this.advance();
|
|
888
|
+
while (this.peek().kind !== "string" && this.peek().kind !== "eof") this.advance();
|
|
889
|
+
if (this.peek().kind === "eof") throw new ParseError("expected include path", t.line, t.col);
|
|
890
|
+
path2 = this.advance().value;
|
|
891
|
+
while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") this.advance();
|
|
892
|
+
} else if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
|
|
893
|
+
throw new ParseError("include url(...) is not supported", t.line, t.col);
|
|
894
|
+
} else if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
|
|
895
|
+
throw new ParseError("include classpath(...) is not supported", t.line, t.col);
|
|
644
896
|
} else {
|
|
645
|
-
throw new ParseError(`expected include path, got ${
|
|
897
|
+
throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
|
|
646
898
|
}
|
|
647
899
|
return {
|
|
648
900
|
key: [],
|
|
649
|
-
value: { kind: "include", path: path2, required, pos: p },
|
|
901
|
+
value: { kind: "include", path: path2, required, ...isFile ? { isFile: true } : {}, pos: p },
|
|
650
902
|
append: false,
|
|
651
903
|
pos: p
|
|
652
904
|
};
|
|
653
905
|
}
|
|
654
|
-
|
|
655
|
-
const p = currentPos();
|
|
906
|
+
parseValue() {
|
|
907
|
+
const p = this.currentPos();
|
|
656
908
|
const parts = [];
|
|
657
909
|
while (true) {
|
|
658
|
-
const
|
|
659
|
-
if (
|
|
660
|
-
const hadSpace =
|
|
910
|
+
const t = this.peek();
|
|
911
|
+
if (t.kind === "eof" || t.kind === "newline" || t.kind === "rbrace" || t.kind === "rbracket" || t.kind === "comma") break;
|
|
912
|
+
const hadSpace = t.precedingSpace && parts.length > 0;
|
|
661
913
|
let node;
|
|
662
|
-
if (
|
|
663
|
-
advance();
|
|
664
|
-
node = parseObject(true);
|
|
665
|
-
} else if (
|
|
666
|
-
advance();
|
|
667
|
-
node = parseArray();
|
|
668
|
-
} else if (
|
|
669
|
-
advance();
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
914
|
+
if (t.kind === "lbrace") {
|
|
915
|
+
this.advance();
|
|
916
|
+
node = this.parseObject(true);
|
|
917
|
+
} else if (t.kind === "lbracket") {
|
|
918
|
+
this.advance();
|
|
919
|
+
node = this.parseArray();
|
|
920
|
+
} else if (t.kind === "subst") {
|
|
921
|
+
this.advance();
|
|
922
|
+
if (!t.subst) {
|
|
923
|
+
throw new ParseError(`internal: subst token missing payload`, t.line, t.col);
|
|
924
|
+
}
|
|
925
|
+
const payload = t.subst;
|
|
926
|
+
node = { kind: "subst", segments: payload.segments, optional: payload.optional, pos: { line: t.line, col: t.col } };
|
|
927
|
+
} else if (t.kind === "string" || t.kind === "triple_string") {
|
|
928
|
+
this.advance();
|
|
929
|
+
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
930
|
+
} else if (t.kind === "unquoted") {
|
|
931
|
+
this.advance();
|
|
932
|
+
node = { kind: "scalar", raw: t.value, valueType: this.scalarValueType(t.value), pos: { line: t.line, col: t.col } };
|
|
933
|
+
} else if ((t.kind === "colon" || t.kind === "equals") && parts.length > 0) {
|
|
934
|
+
this.advance();
|
|
935
|
+
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
680
936
|
} else {
|
|
681
937
|
break;
|
|
682
938
|
}
|
|
683
939
|
if (hadSpace) {
|
|
684
|
-
parts.push({ kind: "scalar",
|
|
940
|
+
parts.push({ kind: "scalar", raw: " ", valueType: "string", pos: { line: t.line, col: t.col }, _separator: true });
|
|
685
941
|
}
|
|
686
942
|
parts.push(node);
|
|
687
943
|
}
|
|
688
|
-
if (parts.length === 0) throw new ParseError("expected value", peek().line, peek().col);
|
|
944
|
+
if (parts.length === 0) throw new ParseError("expected value", this.peek().line, this.peek().col);
|
|
689
945
|
if (parts.length === 1) return parts[0];
|
|
690
946
|
return { kind: "concat", nodes: parts, pos: p };
|
|
691
947
|
}
|
|
692
|
-
|
|
693
|
-
const p = currentPos();
|
|
948
|
+
parseArray() {
|
|
949
|
+
const p = this.currentPos();
|
|
694
950
|
const items = [];
|
|
695
951
|
while (true) {
|
|
696
|
-
skip("newline");
|
|
697
|
-
if (peek().kind === "rbracket" || peek().kind === "eof") break;
|
|
698
|
-
items.push(parseValue());
|
|
699
|
-
skip("newline");
|
|
700
|
-
if (peek().kind === "comma") advance();
|
|
701
|
-
skip("newline");
|
|
702
|
-
}
|
|
703
|
-
const
|
|
704
|
-
if (
|
|
705
|
-
advance();
|
|
952
|
+
this.skip("newline");
|
|
953
|
+
if (this.peek().kind === "rbracket" || this.peek().kind === "eof") break;
|
|
954
|
+
items.push(this.parseValue());
|
|
955
|
+
this.skip("newline");
|
|
956
|
+
if (this.peek().kind === "comma") this.advance();
|
|
957
|
+
this.skip("newline");
|
|
958
|
+
}
|
|
959
|
+
const t = this.peek();
|
|
960
|
+
if (t.kind !== "rbracket") throw new ParseError("expected ]", t.line, t.col);
|
|
961
|
+
this.advance();
|
|
706
962
|
return { kind: "array", items, pos: p };
|
|
707
963
|
}
|
|
708
|
-
|
|
709
|
-
if (raw === "true") return
|
|
710
|
-
if (raw === "
|
|
711
|
-
if (raw ===
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
return
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
964
|
+
scalarValueType(raw) {
|
|
965
|
+
if (raw === "true" || raw === "false") return "boolean";
|
|
966
|
+
if (raw === "null") return "null";
|
|
967
|
+
if (raw.length > 0 && (raw.charCodeAt(0) >= 48 && raw.charCodeAt(0) <= 57 || raw.charCodeAt(0) === 45)) {
|
|
968
|
+
if (DECIMAL_NUMBER_RE.test(raw)) return "number";
|
|
969
|
+
}
|
|
970
|
+
return "string";
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
function parseTokens(tokens) {
|
|
974
|
+
return new Parser(tokens).parse();
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// src/internal/resolver/types.ts
|
|
978
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
979
|
+
function isSubst(v) {
|
|
980
|
+
return v._kind === "subst-placeholder";
|
|
981
|
+
}
|
|
982
|
+
function isConcat(v) {
|
|
983
|
+
return v._kind === "concat-placeholder";
|
|
984
|
+
}
|
|
985
|
+
function isAppend(v) {
|
|
986
|
+
return v._kind === "append-placeholder";
|
|
987
|
+
}
|
|
988
|
+
function isResObj(v) {
|
|
989
|
+
return v._kind === "res-obj";
|
|
990
|
+
}
|
|
991
|
+
function makeResObj() {
|
|
992
|
+
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// src/internal/resolver/utils.ts
|
|
996
|
+
function segmentsToKey(segments) {
|
|
997
|
+
return segments.map((s) => {
|
|
998
|
+
const t = s.text;
|
|
999
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
1000
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1001
|
+
}
|
|
1002
|
+
return t;
|
|
1003
|
+
}).join(".");
|
|
1004
|
+
}
|
|
1005
|
+
function lookupPath(root, segments) {
|
|
1006
|
+
const [head, ...tail] = segments;
|
|
1007
|
+
if (head === void 0) return void 0;
|
|
1008
|
+
const key = head.text;
|
|
1009
|
+
const val = root.fields.get(key);
|
|
1010
|
+
if (val === void 0) return void 0;
|
|
1011
|
+
if (tail.length === 0) return val;
|
|
1012
|
+
if (isResObj(val)) return lookupPath(val, tail);
|
|
1013
|
+
return void 0;
|
|
1014
|
+
}
|
|
1015
|
+
function lookupResObj(root, segments) {
|
|
1016
|
+
let cur = root;
|
|
1017
|
+
for (const seg of segments) {
|
|
1018
|
+
const val = cur.fields.get(seg.text);
|
|
1019
|
+
if (val === void 0 || !isResObj(val)) return void 0;
|
|
1020
|
+
cur = val;
|
|
1021
|
+
}
|
|
1022
|
+
return cur;
|
|
1023
|
+
}
|
|
1024
|
+
function deepMergeHoconValues(base, overlay) {
|
|
1025
|
+
const merged = new Map(base.fields);
|
|
1026
|
+
for (const [k, v] of overlay.fields) {
|
|
1027
|
+
const existing = merged.get(k);
|
|
1028
|
+
if (existing?.kind === "object" && v.kind === "object") {
|
|
1029
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
1030
|
+
} else {
|
|
1031
|
+
merged.set(k, v);
|
|
734
1032
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1033
|
+
}
|
|
1034
|
+
return { kind: "object", fields: merged };
|
|
1035
|
+
}
|
|
1036
|
+
function deepMergeResObjInto(dst, src) {
|
|
1037
|
+
for (const [k, srcVal] of src.fields) {
|
|
1038
|
+
const dstVal = dst.fields.get(k);
|
|
1039
|
+
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
1040
|
+
deepMergeResObjInto(dstVal, srcVal);
|
|
1041
|
+
} else {
|
|
1042
|
+
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
1043
|
+
dst.fields.set(k, srcVal);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
for (const [k, srcPrior] of src.priorValues) {
|
|
1047
|
+
if (!dst.priorValues.has(k)) {
|
|
1048
|
+
dst.priorValues.set(k, srcPrior);
|
|
739
1049
|
}
|
|
740
|
-
return { kind: "object", fields: allFields, pos: first.pos };
|
|
741
1050
|
}
|
|
742
|
-
|
|
1051
|
+
}
|
|
1052
|
+
function hoconValueToResObj(hv) {
|
|
1053
|
+
const obj = makeResObj();
|
|
1054
|
+
if (hv.kind !== "object") return obj;
|
|
1055
|
+
for (const [key, val] of hv.fields) {
|
|
1056
|
+
if (val.kind === "object") {
|
|
1057
|
+
obj.fields.set(key, hoconValueToResObj(val));
|
|
1058
|
+
} else {
|
|
1059
|
+
obj.fields.set(key, val);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return obj;
|
|
1063
|
+
}
|
|
1064
|
+
function isFileNotFoundError(e) {
|
|
1065
|
+
if (!(e instanceof Error)) return false;
|
|
1066
|
+
const code = e.code;
|
|
1067
|
+
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
1068
|
+
const msg = e.message.toLowerCase();
|
|
1069
|
+
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
743
1070
|
}
|
|
744
1071
|
|
|
745
|
-
// src/internal/resolver/
|
|
1072
|
+
// src/internal/resolver/include-loader.ts
|
|
746
1073
|
import * as nodePath from "path";
|
|
747
1074
|
|
|
748
1075
|
// src/internal/properties/properties.ts
|
|
@@ -789,7 +1116,7 @@ function recordToHoconValue(obj) {
|
|
|
789
1116
|
const fields = /* @__PURE__ */ new Map();
|
|
790
1117
|
for (const [key, val] of Object.entries(obj)) {
|
|
791
1118
|
if (typeof val === "string") {
|
|
792
|
-
fields.set(key, { kind: "scalar",
|
|
1119
|
+
fields.set(key, { kind: "scalar", raw: val, valueType: "string" });
|
|
793
1120
|
} else if (val !== null && typeof val === "object") {
|
|
794
1121
|
fields.set(key, recordToHoconValue(val));
|
|
795
1122
|
}
|
|
@@ -797,497 +1124,571 @@ function recordToHoconValue(obj) {
|
|
|
797
1124
|
return { kind: "object", fields };
|
|
798
1125
|
}
|
|
799
1126
|
|
|
800
|
-
// src/internal/resolver/
|
|
801
|
-
var
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1127
|
+
// src/internal/resolver/include-loader.ts
|
|
1128
|
+
var IncludeLoader = class {
|
|
1129
|
+
constructor(opts) {
|
|
1130
|
+
this.opts = opts;
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Resolve an include path to an absolute path.
|
|
1134
|
+
* - file() includes resolve relative to CWD (or as absolute paths),
|
|
1135
|
+
* NOT relative to the including file's directory.
|
|
1136
|
+
* - Bare includes resolve relative to the including file's directory (baseDir).
|
|
1137
|
+
*/
|
|
1138
|
+
resolveIncludePath(includePath, baseDir, isFile) {
|
|
1139
|
+
return isFile ? nodePath.resolve(includePath) : baseDir ? nodePath.resolve(baseDir, includePath) : nodePath.resolve(includePath);
|
|
1140
|
+
}
|
|
1141
|
+
load(includePath, required, isFile) {
|
|
1142
|
+
const { baseDir, includeStack = [] } = this.opts;
|
|
1143
|
+
const absPath = this.resolveIncludePath(includePath, baseDir, !!isFile);
|
|
1144
|
+
if (includeStack.includes(absPath)) {
|
|
1145
|
+
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
1146
|
+
}
|
|
1147
|
+
if (includeStack.length >= 50) {
|
|
1148
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
1149
|
+
}
|
|
1150
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
1151
|
+
if (hasExplicitExt) {
|
|
1152
|
+
const result = this.loadSingle(absPath);
|
|
1153
|
+
if (result !== void 0) return result;
|
|
1154
|
+
if (required) {
|
|
1155
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1156
|
+
}
|
|
1157
|
+
return makeResObj();
|
|
1158
|
+
}
|
|
1159
|
+
const merged = makeResObj();
|
|
1160
|
+
let foundAny = false;
|
|
1161
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
1162
|
+
for (const ext of probeExts) {
|
|
1163
|
+
const obj = this.loadSingle(`${absPath}${ext}`);
|
|
1164
|
+
if (obj !== void 0) {
|
|
1165
|
+
deepMergeResObjInto(merged, obj);
|
|
1166
|
+
foundAny = true;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (!foundAny && required) {
|
|
1170
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1171
|
+
}
|
|
1172
|
+
return merged;
|
|
832
1173
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1174
|
+
async loadAsync(includePath, required, isFile) {
|
|
1175
|
+
const { baseDir, includeStack = [] } = this.opts;
|
|
1176
|
+
const absPath = this.resolveIncludePath(includePath, baseDir, !!isFile);
|
|
1177
|
+
if (includeStack.includes(absPath)) {
|
|
1178
|
+
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
1179
|
+
}
|
|
1180
|
+
if (includeStack.length >= 50) {
|
|
1181
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
1182
|
+
}
|
|
1183
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
1184
|
+
if (hasExplicitExt) {
|
|
1185
|
+
const result = await this.loadSingleAsync(absPath);
|
|
1186
|
+
if (result !== void 0) return result;
|
|
1187
|
+
if (required) {
|
|
1188
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1189
|
+
}
|
|
1190
|
+
return makeResObj();
|
|
1191
|
+
}
|
|
1192
|
+
const merged = makeResObj();
|
|
1193
|
+
let foundAny = false;
|
|
1194
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
1195
|
+
for (const ext of probeExts) {
|
|
1196
|
+
const obj = await this.loadSingleAsync(`${absPath}${ext}`);
|
|
1197
|
+
if (obj !== void 0) {
|
|
1198
|
+
deepMergeResObjInto(merged, obj);
|
|
1199
|
+
foundAny = true;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (!foundAny && required) {
|
|
1203
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1204
|
+
}
|
|
1205
|
+
return merged;
|
|
836
1206
|
}
|
|
837
|
-
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
obj.priorValues.set(head, existing2);
|
|
859
|
-
obj.fields.set(head, {
|
|
860
|
-
_kind: "append-placeholder",
|
|
861
|
-
existing: existing2,
|
|
862
|
-
elem: astToResolverValue(field.value, opts)
|
|
1207
|
+
loadSingle(candidate) {
|
|
1208
|
+
const { readFileSync: readFileSync2, includeStack = [], env } = this.opts;
|
|
1209
|
+
if (includeStack.includes(candidate)) {
|
|
1210
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
1211
|
+
}
|
|
1212
|
+
let content;
|
|
1213
|
+
try {
|
|
1214
|
+
content = readFileSync2(candidate);
|
|
1215
|
+
} catch (e) {
|
|
1216
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
1217
|
+
throw e;
|
|
1218
|
+
}
|
|
1219
|
+
if (candidate.endsWith(".properties")) {
|
|
1220
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1221
|
+
}
|
|
1222
|
+
const ast = parseTokens(tokenize(content));
|
|
1223
|
+
return this.onBuildResObj(ast, {
|
|
1224
|
+
env,
|
|
1225
|
+
baseDir: nodePath.dirname(candidate),
|
|
1226
|
+
readFileSync: readFileSync2,
|
|
1227
|
+
includeStack: [...includeStack, candidate]
|
|
863
1228
|
});
|
|
864
|
-
return;
|
|
865
1229
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
872
|
-
deepMergeResObjInto(existing, newVal);
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
obj.fields.set(head, newVal);
|
|
876
|
-
}
|
|
877
|
-
function astToResolverValue(ast, opts) {
|
|
878
|
-
switch (ast.kind) {
|
|
879
|
-
case "scalar": {
|
|
880
|
-
const sv = { kind: "scalar", value: ast.value };
|
|
881
|
-
if (ast._separator) separatorValues.add(sv);
|
|
882
|
-
return sv;
|
|
1230
|
+
async loadSingleAsync(candidate) {
|
|
1231
|
+
const { readFile, readFileSync: readFileSync2, includeStack = [], env } = this.opts;
|
|
1232
|
+
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1233
|
+
if (includeStack.includes(candidate)) {
|
|
1234
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
883
1235
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
return
|
|
1236
|
+
let content;
|
|
1237
|
+
try {
|
|
1238
|
+
content = await read(candidate);
|
|
1239
|
+
} catch (e) {
|
|
1240
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
1241
|
+
throw e;
|
|
1242
|
+
}
|
|
1243
|
+
if (candidate.endsWith(".properties")) {
|
|
1244
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
889
1245
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1246
|
+
const ast = parseTokens(tokenize(content));
|
|
1247
|
+
return this.onBuildResObjAsync(ast, {
|
|
1248
|
+
env,
|
|
1249
|
+
baseDir: nodePath.dirname(candidate),
|
|
1250
|
+
readFileSync: readFileSync2,
|
|
1251
|
+
readFile,
|
|
1252
|
+
includeStack: [...includeStack, candidate]
|
|
1253
|
+
});
|
|
896
1254
|
}
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
// src/internal/resolver/structure-builder.ts
|
|
1258
|
+
var StructureBuilder = class _StructureBuilder {
|
|
1259
|
+
constructor(opts) {
|
|
1260
|
+
this.opts = opts;
|
|
1261
|
+
this.loader = new IncludeLoader(opts);
|
|
1262
|
+
this.loader.onBuildResObj = (a, o) => new _StructureBuilder(o).build(a);
|
|
1263
|
+
this.loader.onBuildResObjAsync = async (a, o) => new _StructureBuilder(o).buildAsync(a);
|
|
1264
|
+
}
|
|
1265
|
+
build(ast, pathPrefix = []) {
|
|
1266
|
+
if (ast.kind !== "object") {
|
|
1267
|
+
throw new ResolveError("root AST must be an object", "", ast.pos.line, ast.pos.col);
|
|
1268
|
+
}
|
|
1269
|
+
const obj = makeResObj();
|
|
1270
|
+
for (const field of ast.fields) {
|
|
1271
|
+
this.applyField(obj, field, pathPrefix);
|
|
906
1272
|
}
|
|
1273
|
+
return obj;
|
|
907
1274
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
for (const [k, srcVal] of src.fields) {
|
|
912
|
-
const dstVal = dst.fields.get(k);
|
|
913
|
-
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
914
|
-
deepMergeResObjInto(dstVal, srcVal);
|
|
915
|
-
} else {
|
|
916
|
-
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
917
|
-
dst.fields.set(k, srcVal);
|
|
1275
|
+
async buildAsync(ast, pathPrefix = []) {
|
|
1276
|
+
if (ast.kind !== "object") {
|
|
1277
|
+
throw new ResolveError("root AST must be an object", "", ast.pos.line, ast.pos.col);
|
|
918
1278
|
}
|
|
1279
|
+
const obj = makeResObj();
|
|
1280
|
+
for (const field of ast.fields) {
|
|
1281
|
+
await this.applyFieldAsync(obj, field, pathPrefix);
|
|
1282
|
+
}
|
|
1283
|
+
return obj;
|
|
919
1284
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
if (resolved !== void 0) {
|
|
926
|
-
result.set(key, resolved);
|
|
927
|
-
} else {
|
|
928
|
-
const prior = obj.priorValues.get(key);
|
|
929
|
-
if (prior !== void 0) {
|
|
930
|
-
const priorResolved = resolveVal(prior, obj, root, resolving, resolvedCache, opts);
|
|
931
|
-
if (priorResolved !== void 0) result.set(key, priorResolved);
|
|
1285
|
+
applyField(obj, field, pathPrefix) {
|
|
1286
|
+
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1287
|
+
const included = this.loader.load(field.value.path, field.value.required, field.value.isFile);
|
|
1288
|
+
if (pathPrefix.length > 0) {
|
|
1289
|
+
this.relativizeResObj(included, pathPrefix);
|
|
932
1290
|
}
|
|
1291
|
+
deepMergeResObjInto(obj, included);
|
|
1292
|
+
return;
|
|
933
1293
|
}
|
|
1294
|
+
const [head, ...tail] = field.key;
|
|
1295
|
+
if (head === void 0) return;
|
|
1296
|
+
if (tail.length > 0) {
|
|
1297
|
+
const syntheticAst = {
|
|
1298
|
+
kind: "object",
|
|
1299
|
+
fields: [{ key: tail, value: field.value, append: field.append, pos: field.pos }],
|
|
1300
|
+
pos: field.pos
|
|
1301
|
+
};
|
|
1302
|
+
this.applyField(obj, { key: [head], value: syntheticAst, append: false, pos: field.pos }, pathPrefix);
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const childPrefix = [...pathPrefix, head];
|
|
1306
|
+
if (field.append) {
|
|
1307
|
+
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1308
|
+
obj.priorValues.set(head, existing2);
|
|
1309
|
+
obj.fields.set(head, {
|
|
1310
|
+
_kind: "append-placeholder",
|
|
1311
|
+
existing: existing2,
|
|
1312
|
+
elem: this.astToResolverValue(field.value, childPrefix)
|
|
1313
|
+
});
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
const existing = obj.fields.get(head);
|
|
1317
|
+
const newVal = this.astToResolverValue(field.value, childPrefix);
|
|
1318
|
+
if (existing !== void 0) {
|
|
1319
|
+
obj.priorValues.set(head, existing);
|
|
1320
|
+
}
|
|
1321
|
+
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1322
|
+
deepMergeResObjInto(existing, newVal);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
obj.fields.set(head, newVal);
|
|
934
1326
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (isAppend(v)) return resolveAppend(v, scope, root, resolving, resolvedCache, opts);
|
|
941
|
-
if (isResObj(v)) return resolveResObj(v, root, resolving, resolvedCache, opts);
|
|
942
|
-
const hv = v;
|
|
943
|
-
if (hv.kind === "array") {
|
|
944
|
-
return {
|
|
945
|
-
kind: "array",
|
|
946
|
-
items: hv.items.map(
|
|
947
|
-
(item) => resolveVal(item, scope, root, resolving, resolvedCache, opts) ?? { kind: "scalar", value: null }
|
|
948
|
-
)
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
return hv;
|
|
952
|
-
}
|
|
953
|
-
function resolveSubst(s, scope, root, resolving, resolvedCache, opts) {
|
|
954
|
-
if (resolvedCache.has(s.path)) return resolvedCache.get(s.path);
|
|
955
|
-
if (resolving.has(s.path)) {
|
|
956
|
-
const rootSeg = parseSubstPath(s.path)[0] ?? "";
|
|
957
|
-
const prior = scope.priorValues.get(rootSeg) ?? root.priorValues.get(rootSeg);
|
|
958
|
-
if (prior !== void 0) {
|
|
959
|
-
return resolveVal(prior, scope, root, new Set(resolving), resolvedCache, opts);
|
|
960
|
-
}
|
|
961
|
-
if (s.optional) return void 0;
|
|
962
|
-
throw new ResolveError(`circular substitution: ${s.path}`, s.path, s.line, s.col);
|
|
963
|
-
}
|
|
964
|
-
resolving.add(s.path);
|
|
965
|
-
try {
|
|
966
|
-
const found = lookupPath(root, parseSubstPath(s.path));
|
|
967
|
-
if (found !== void 0) {
|
|
968
|
-
if (isSubst(found) || isConcat(found)) {
|
|
969
|
-
const rootSeg = parseSubstPath(s.path)[0] ?? "";
|
|
970
|
-
const prior = scope.priorValues.get(rootSeg) ?? root.priorValues.get(rootSeg);
|
|
971
|
-
if (prior !== void 0) {
|
|
972
|
-
const result2 = resolveVal(prior, scope, root, resolving, resolvedCache, opts);
|
|
973
|
-
if (result2 !== void 0) resolvedCache.set(s.path, result2);
|
|
974
|
-
return result2;
|
|
975
|
-
}
|
|
1327
|
+
async applyFieldAsync(obj, field, pathPrefix) {
|
|
1328
|
+
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1329
|
+
const included = await this.loader.loadAsync(field.value.path, field.value.required, field.value.isFile);
|
|
1330
|
+
if (pathPrefix.length > 0) {
|
|
1331
|
+
this.relativizeResObj(included, pathPrefix);
|
|
976
1332
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1333
|
+
deepMergeResObjInto(obj, included);
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
const [head, ...tail] = field.key;
|
|
1337
|
+
if (head === void 0) return;
|
|
1338
|
+
if (tail.length > 0) {
|
|
1339
|
+
const syntheticAst = {
|
|
1340
|
+
kind: "object",
|
|
1341
|
+
fields: [{ key: tail, value: field.value, append: field.append, pos: field.pos }],
|
|
1342
|
+
pos: field.pos
|
|
1343
|
+
};
|
|
1344
|
+
await this.applyFieldAsync(obj, { key: [head], value: syntheticAst, append: false, pos: field.pos }, pathPrefix);
|
|
1345
|
+
return;
|
|
980
1346
|
}
|
|
981
|
-
const
|
|
982
|
-
if (
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
1347
|
+
const childPrefix = [...pathPrefix, head];
|
|
1348
|
+
if (field.append) {
|
|
1349
|
+
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1350
|
+
obj.priorValues.set(head, existing2);
|
|
1351
|
+
obj.fields.set(head, {
|
|
1352
|
+
_kind: "append-placeholder",
|
|
1353
|
+
existing: existing2,
|
|
1354
|
+
elem: await this.astToResolverValueAsync(field.value, childPrefix)
|
|
1355
|
+
});
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const existing = obj.fields.get(head);
|
|
1359
|
+
const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
|
|
1360
|
+
if (existing !== void 0) {
|
|
1361
|
+
obj.priorValues.set(head, existing);
|
|
1362
|
+
}
|
|
1363
|
+
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1364
|
+
deepMergeResObjInto(existing, newVal);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
obj.fields.set(head, newVal);
|
|
1368
|
+
}
|
|
1369
|
+
astToResolverValue(ast, pathPrefix) {
|
|
1370
|
+
switch (ast.kind) {
|
|
1371
|
+
case "scalar": {
|
|
1372
|
+
const sv = { kind: "scalar", raw: ast.raw, valueType: ast.valueType };
|
|
1373
|
+
if (ast._separator) separatorValues.add(sv);
|
|
1374
|
+
return sv;
|
|
1375
|
+
}
|
|
1376
|
+
case "array":
|
|
1377
|
+
return { kind: "array", items: ast.items.map((i) => this.astToResolverValue(i, pathPrefix)) };
|
|
1378
|
+
case "object": {
|
|
1379
|
+
const inner = this.build(ast, pathPrefix);
|
|
1380
|
+
return inner;
|
|
1381
|
+
}
|
|
1382
|
+
case "subst":
|
|
1383
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1384
|
+
case "concat":
|
|
1385
|
+
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)) };
|
|
1386
|
+
case "include":
|
|
1387
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
986
1388
|
}
|
|
987
|
-
if (s.optional) return void 0;
|
|
988
|
-
throw new ResolveError(`could not resolve substitution: \${${s.path}}`, s.path, s.line, s.col);
|
|
989
|
-
} finally {
|
|
990
|
-
resolving.delete(s.path);
|
|
991
1389
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
const existing = merged.get(k);
|
|
1004
|
-
if (existing?.kind === "object" && val.kind === "object") {
|
|
1005
|
-
merged.set(k, deepMergeHoconValues(existing, val));
|
|
1006
|
-
} else {
|
|
1007
|
-
merged.set(k, val);
|
|
1008
|
-
}
|
|
1390
|
+
async astToResolverValueAsync(ast, pathPrefix) {
|
|
1391
|
+
switch (ast.kind) {
|
|
1392
|
+
case "scalar": {
|
|
1393
|
+
const sv = { kind: "scalar", raw: ast.raw, valueType: ast.valueType };
|
|
1394
|
+
if (ast._separator) separatorValues.add(sv);
|
|
1395
|
+
return sv;
|
|
1396
|
+
}
|
|
1397
|
+
case "array": {
|
|
1398
|
+
const items = [];
|
|
1399
|
+
for (const i of ast.items) {
|
|
1400
|
+
items.push(await this.astToResolverValueAsync(i, pathPrefix));
|
|
1009
1401
|
}
|
|
1402
|
+
return { kind: "array", items };
|
|
1010
1403
|
}
|
|
1404
|
+
case "object":
|
|
1405
|
+
return await this.buildAsync(ast, pathPrefix);
|
|
1406
|
+
case "subst":
|
|
1407
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1408
|
+
case "concat": {
|
|
1409
|
+
const nodes = [];
|
|
1410
|
+
for (const n of ast.nodes) {
|
|
1411
|
+
nodes.push(await this.astToResolverValueAsync(n, pathPrefix));
|
|
1412
|
+
}
|
|
1413
|
+
return { _kind: "concat-placeholder", nodes };
|
|
1414
|
+
}
|
|
1415
|
+
case "include":
|
|
1416
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
1011
1417
|
}
|
|
1012
|
-
return { kind: "object", fields: merged };
|
|
1013
1418
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1419
|
+
// ---- Relativize substitution paths for nested includes ----
|
|
1420
|
+
relativizeSubstPaths(val, prefixSegments) {
|
|
1421
|
+
if (isSubst(val)) {
|
|
1422
|
+
const prefixAsSegments = prefixSegments.map((text) => ({ text, line: 0, col: 0 }));
|
|
1423
|
+
val.segments = [...prefixAsSegments, ...val.segments];
|
|
1424
|
+
val.prefixLen += prefixSegments.length;
|
|
1425
|
+
return;
|
|
1019
1426
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
function deepMergeHoconValues(base, overlay) {
|
|
1026
|
-
const merged = new Map(base.fields);
|
|
1027
|
-
for (const [k, v] of overlay.fields) {
|
|
1028
|
-
const existing = merged.get(k);
|
|
1029
|
-
if (existing?.kind === "object" && v.kind === "object") {
|
|
1030
|
-
merged.set(k, deepMergeHoconValues(existing, v));
|
|
1031
|
-
} else {
|
|
1032
|
-
merged.set(k, v);
|
|
1427
|
+
if (isConcat(val)) {
|
|
1428
|
+
for (const node of val.nodes) {
|
|
1429
|
+
this.relativizeSubstPaths(node, prefixSegments);
|
|
1430
|
+
}
|
|
1431
|
+
return;
|
|
1033
1432
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
const existing = resolveVal(a.existing, scope, root, resolving, resolvedCache, opts) ?? { kind: "array", items: [] };
|
|
1039
|
-
const elem = resolveVal(a.elem, scope, root, resolving, resolvedCache, opts);
|
|
1040
|
-
const items = existing.kind === "array" ? [...existing.items] : [existing];
|
|
1041
|
-
if (elem !== void 0) items.push(elem);
|
|
1042
|
-
return { kind: "array", items };
|
|
1043
|
-
}
|
|
1044
|
-
function lookupPath(root, segments) {
|
|
1045
|
-
const [head, ...tail] = segments;
|
|
1046
|
-
if (head === void 0 || head === "") {
|
|
1047
|
-
if (segments.length > 0) {
|
|
1048
|
-
const val2 = root.fields.get("");
|
|
1049
|
-
if (val2 === void 0) return void 0;
|
|
1050
|
-
if (tail.length === 0) return val2;
|
|
1051
|
-
if (isResObj(val2)) return lookupPath(val2, tail);
|
|
1052
|
-
return void 0;
|
|
1433
|
+
if (isAppend(val)) {
|
|
1434
|
+
this.relativizeSubstPaths(val.existing, prefixSegments);
|
|
1435
|
+
this.relativizeSubstPaths(val.elem, prefixSegments);
|
|
1436
|
+
return;
|
|
1053
1437
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
function parseSubstPath(raw) {
|
|
1063
|
-
const segments = [];
|
|
1064
|
-
let i = 0;
|
|
1065
|
-
while (i < raw.length) {
|
|
1066
|
-
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
1067
|
-
if (i >= raw.length) break;
|
|
1068
|
-
if (raw[i] === '"') {
|
|
1069
|
-
i++;
|
|
1070
|
-
let seg = "";
|
|
1071
|
-
while (i < raw.length && raw[i] !== '"') {
|
|
1072
|
-
seg += raw[i];
|
|
1073
|
-
i++;
|
|
1074
|
-
}
|
|
1075
|
-
if (i < raw.length) i++;
|
|
1076
|
-
segments.push(seg);
|
|
1077
|
-
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
1078
|
-
if (i < raw.length && raw[i] === ".") i++;
|
|
1079
|
-
} else if (raw[i] === ".") {
|
|
1080
|
-
segments.push("");
|
|
1081
|
-
i++;
|
|
1082
|
-
} else {
|
|
1083
|
-
let seg = "";
|
|
1084
|
-
while (i < raw.length && raw[i] !== ".") {
|
|
1085
|
-
seg += raw[i];
|
|
1086
|
-
i++;
|
|
1438
|
+
if (isResObj(val)) {
|
|
1439
|
+
this.relativizeResObj(val, prefixSegments);
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
const hv = val;
|
|
1443
|
+
if (hv.kind === "array") {
|
|
1444
|
+
for (const item of hv.items) {
|
|
1445
|
+
this.relativizeSubstPaths(item, prefixSegments);
|
|
1087
1446
|
}
|
|
1088
|
-
segments.push(seg.trim());
|
|
1089
|
-
if (i < raw.length && raw[i] === ".") i++;
|
|
1090
1447
|
}
|
|
1091
1448
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
if (!(e instanceof Error)) return false;
|
|
1096
|
-
const code = e.code;
|
|
1097
|
-
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
1098
|
-
const msg = e.message.toLowerCase();
|
|
1099
|
-
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
1100
|
-
}
|
|
1101
|
-
function loadSingleInclude(candidate, opts) {
|
|
1102
|
-
const { readFileSync: readFileSync2, includeStack = [], env } = opts;
|
|
1103
|
-
if (includeStack.includes(candidate)) {
|
|
1104
|
-
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
1105
|
-
}
|
|
1106
|
-
let content;
|
|
1107
|
-
try {
|
|
1108
|
-
content = readFileSync2(candidate);
|
|
1109
|
-
} catch (e) {
|
|
1110
|
-
if (isFileNotFoundError(e)) return void 0;
|
|
1111
|
-
throw e;
|
|
1112
|
-
}
|
|
1113
|
-
if (candidate.endsWith(".properties")) {
|
|
1114
|
-
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1115
|
-
}
|
|
1116
|
-
const ast = parseTokens(tokenize(content));
|
|
1117
|
-
return buildResObj(ast, {
|
|
1118
|
-
env,
|
|
1119
|
-
baseDir: nodePath.dirname(candidate),
|
|
1120
|
-
readFileSync: readFileSync2,
|
|
1121
|
-
includeStack: [...includeStack, candidate]
|
|
1122
|
-
});
|
|
1123
|
-
}
|
|
1124
|
-
function loadInclude(includePath, required, opts) {
|
|
1125
|
-
const { baseDir, includeStack = [] } = opts;
|
|
1126
|
-
const absPath = baseDir ? nodePath.resolve(baseDir, includePath) : nodePath.resolve(includePath);
|
|
1127
|
-
if (includeStack.includes(absPath)) {
|
|
1128
|
-
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
1129
|
-
}
|
|
1130
|
-
if (includeStack.length >= 50) {
|
|
1131
|
-
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
1132
|
-
}
|
|
1133
|
-
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
1134
|
-
if (hasExplicitExt) {
|
|
1135
|
-
const result = loadSingleInclude(absPath, opts);
|
|
1136
|
-
if (result !== void 0) return result;
|
|
1137
|
-
if (required) {
|
|
1138
|
-
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1449
|
+
relativizeResObj(obj, prefixSegments) {
|
|
1450
|
+
for (const val of obj.fields.values()) {
|
|
1451
|
+
this.relativizeSubstPaths(val, prefixSegments);
|
|
1139
1452
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const merged = makeResObj();
|
|
1143
|
-
let foundAny = false;
|
|
1144
|
-
const probeExts = [".properties", ".json", ".conf"];
|
|
1145
|
-
for (const ext of probeExts) {
|
|
1146
|
-
const obj = loadSingleInclude(`${absPath}${ext}`, opts);
|
|
1147
|
-
if (obj !== void 0) {
|
|
1148
|
-
deepMergeResObjInto(merged, obj);
|
|
1149
|
-
foundAny = true;
|
|
1453
|
+
for (const val of obj.priorValues.values()) {
|
|
1454
|
+
this.relativizeSubstPaths(val, prefixSegments);
|
|
1150
1455
|
}
|
|
1151
1456
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
return;
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1457
|
+
};
|
|
1458
|
+
|
|
1459
|
+
// src/internal/resolver/substitution-resolver.ts
|
|
1460
|
+
var SubstitutionResolver = class {
|
|
1461
|
+
constructor(root, opts) {
|
|
1462
|
+
this.root = root;
|
|
1463
|
+
this.opts = opts;
|
|
1464
|
+
this.resolving = /* @__PURE__ */ new Set();
|
|
1465
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1466
|
+
}
|
|
1467
|
+
resolve() {
|
|
1468
|
+
return this.resolveResObj(this.root);
|
|
1469
|
+
}
|
|
1470
|
+
resolveResObj(obj) {
|
|
1471
|
+
const result = /* @__PURE__ */ new Map();
|
|
1472
|
+
for (const [key, val] of obj.fields) {
|
|
1473
|
+
const resolved = this.resolveVal(val, obj);
|
|
1474
|
+
if (resolved !== void 0) {
|
|
1475
|
+
if (resolved.kind === "object") {
|
|
1476
|
+
const prior = obj.priorValues.get(key);
|
|
1477
|
+
if (prior !== void 0) {
|
|
1478
|
+
const priorResolved = this.resolveVal(prior, obj);
|
|
1479
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
1480
|
+
result.set(
|
|
1481
|
+
key,
|
|
1482
|
+
deepMergeHoconValues(
|
|
1483
|
+
priorResolved,
|
|
1484
|
+
resolved
|
|
1485
|
+
)
|
|
1486
|
+
);
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
result.set(key, resolved);
|
|
1492
|
+
} else {
|
|
1493
|
+
const prior = obj.priorValues.get(key);
|
|
1494
|
+
if (prior !== void 0) {
|
|
1495
|
+
const priorResolved = this.resolveVal(prior, obj);
|
|
1496
|
+
if (priorResolved !== void 0) result.set(key, priorResolved);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return { kind: "object", fields: result };
|
|
1501
|
+
}
|
|
1502
|
+
resolveVal(v, scope) {
|
|
1503
|
+
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
1504
|
+
if (isConcat(v)) return this.resolveConcat(v.nodes, scope);
|
|
1505
|
+
if (isAppend(v)) return this.resolveAppend(v, scope);
|
|
1506
|
+
if (isResObj(v)) return this.resolveResObj(v);
|
|
1507
|
+
const hv = v;
|
|
1508
|
+
if (hv.kind === "array") {
|
|
1509
|
+
return {
|
|
1510
|
+
kind: "array",
|
|
1511
|
+
items: hv.items.map(
|
|
1512
|
+
(item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
|
|
1513
|
+
)
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
return hv;
|
|
1517
|
+
}
|
|
1518
|
+
segmentsEqual(a, b) {
|
|
1519
|
+
return a.length === b.length && a.every((seg, i) => seg.text === b[i]?.text);
|
|
1520
|
+
}
|
|
1521
|
+
resolveSubst(s, scope) {
|
|
1522
|
+
const key = segmentsToKey(s.segments);
|
|
1523
|
+
if (this.cache.has(key)) return this.cache.get(key);
|
|
1524
|
+
if (this.resolving.has(key)) {
|
|
1525
|
+
let prior;
|
|
1526
|
+
if (s.prefixLen > 0) {
|
|
1527
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1528
|
+
const parentScope = lookupResObj(
|
|
1529
|
+
this.root,
|
|
1530
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
1531
|
+
);
|
|
1532
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
1533
|
+
} else {
|
|
1534
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1535
|
+
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1216
1536
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
for (const n of ast.nodes) {
|
|
1226
|
-
nodes.push(await astToResolverValueAsync(n, opts));
|
|
1537
|
+
if (prior !== void 0) {
|
|
1538
|
+
const saved = this.resolving;
|
|
1539
|
+
this.resolving = new Set(saved);
|
|
1540
|
+
try {
|
|
1541
|
+
return this.resolveVal(prior, scope);
|
|
1542
|
+
} finally {
|
|
1543
|
+
this.resolving = saved;
|
|
1544
|
+
}
|
|
1227
1545
|
}
|
|
1228
|
-
|
|
1546
|
+
if (s.optional) return void 0;
|
|
1547
|
+
throw new ResolveError(
|
|
1548
|
+
`circular substitution: ${key}`,
|
|
1549
|
+
key,
|
|
1550
|
+
s.line,
|
|
1551
|
+
s.col
|
|
1552
|
+
);
|
|
1229
1553
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1554
|
+
this.resolving.add(key);
|
|
1555
|
+
try {
|
|
1556
|
+
const found = lookupPath(this.root, s.segments);
|
|
1557
|
+
if (found !== void 0) {
|
|
1558
|
+
if (isSubst(found) || isConcat(found)) {
|
|
1559
|
+
const isSelfRef = isSubst(found) ? this.segmentsEqual(found.segments, s.segments) : isConcat(found) && found.nodes.some(
|
|
1560
|
+
(n) => isSubst(n) && this.segmentsEqual(n.segments, s.segments)
|
|
1561
|
+
);
|
|
1562
|
+
if (isSelfRef) {
|
|
1563
|
+
let prior;
|
|
1564
|
+
if (s.prefixLen > 0) {
|
|
1565
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1566
|
+
const parentScope = lookupResObj(
|
|
1567
|
+
this.root,
|
|
1568
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
1569
|
+
);
|
|
1570
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
1571
|
+
} else {
|
|
1572
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1573
|
+
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1574
|
+
}
|
|
1575
|
+
if (prior !== void 0) {
|
|
1576
|
+
const result2 = this.resolveVal(prior, scope);
|
|
1577
|
+
if (result2 !== void 0) this.cache.set(key, result2);
|
|
1578
|
+
return result2;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
let result = this.resolveVal(found, scope);
|
|
1583
|
+
const effectiveLen = s.segments.length - s.prefixLen;
|
|
1584
|
+
if (effectiveLen === 1 && result !== void 0 && result.kind === "object") {
|
|
1585
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1586
|
+
let prior;
|
|
1587
|
+
if (s.prefixLen > 0) {
|
|
1588
|
+
const parentScope = lookupResObj(
|
|
1589
|
+
this.root,
|
|
1590
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
1591
|
+
);
|
|
1592
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
1593
|
+
} else {
|
|
1594
|
+
prior = scope.priorValues.get(leafSeg) ?? this.root.priorValues.get(leafSeg);
|
|
1595
|
+
}
|
|
1596
|
+
if (prior !== void 0) {
|
|
1597
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
1598
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
1599
|
+
result = deepMergeHoconValues(
|
|
1600
|
+
priorResolved,
|
|
1601
|
+
result
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
1607
|
+
return result;
|
|
1608
|
+
}
|
|
1609
|
+
const envKey = s.segments.map((seg) => seg.text).join(".");
|
|
1610
|
+
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
|
|
1611
|
+
if (envVal !== void 0) {
|
|
1612
|
+
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
1613
|
+
this.cache.set(key, result);
|
|
1614
|
+
return result;
|
|
1615
|
+
}
|
|
1616
|
+
if (s.optional) return void 0;
|
|
1617
|
+
throw new ResolveError(
|
|
1618
|
+
`could not resolve substitution: \${${key}}`,
|
|
1619
|
+
key,
|
|
1620
|
+
s.line,
|
|
1621
|
+
s.col
|
|
1622
|
+
);
|
|
1623
|
+
} finally {
|
|
1624
|
+
this.resolving.delete(key);
|
|
1274
1625
|
}
|
|
1275
|
-
return makeResObj();
|
|
1276
1626
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
const
|
|
1282
|
-
if (
|
|
1283
|
-
|
|
1284
|
-
|
|
1627
|
+
resolveConcat(nodes, scope) {
|
|
1628
|
+
const resolved = nodes.map((n) => this.resolveVal(n, scope)).filter((v) => v !== void 0);
|
|
1629
|
+
if (resolved.length === 0) return { kind: "scalar", raw: "null", valueType: "null" };
|
|
1630
|
+
if (resolved.length === 1) return resolved[0];
|
|
1631
|
+
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
1632
|
+
if (nonSep.length === 0) {
|
|
1633
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
1634
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1285
1635
|
}
|
|
1636
|
+
const joinPair = (left, right) => {
|
|
1637
|
+
if (left.kind === "object" && right.kind === "object") {
|
|
1638
|
+
return deepMergeHoconValues(left, right);
|
|
1639
|
+
}
|
|
1640
|
+
if (left.kind === "array" && right.kind === "object") {
|
|
1641
|
+
const converted = numericObjectToArray(right);
|
|
1642
|
+
if (converted !== null) {
|
|
1643
|
+
return { kind: "array", items: [...left.items, ...converted] };
|
|
1644
|
+
}
|
|
1645
|
+
return { kind: "array", items: [...left.items, right] };
|
|
1646
|
+
}
|
|
1647
|
+
if (left.kind === "object" && right.kind === "array") {
|
|
1648
|
+
const converted = numericObjectToArray(left);
|
|
1649
|
+
if (converted !== null) {
|
|
1650
|
+
return { kind: "array", items: [...converted, ...right.items] };
|
|
1651
|
+
}
|
|
1652
|
+
return { kind: "array", items: [left, ...right.items] };
|
|
1653
|
+
}
|
|
1654
|
+
if (left.kind === "array" && right.kind === "array") {
|
|
1655
|
+
return { kind: "array", items: [...left.items, ...right.items] };
|
|
1656
|
+
}
|
|
1657
|
+
if (left.kind === "array") {
|
|
1658
|
+
return { kind: "array", items: [...left.items, right] };
|
|
1659
|
+
}
|
|
1660
|
+
if (right.kind === "array") {
|
|
1661
|
+
return { kind: "array", items: [left, ...right.items] };
|
|
1662
|
+
}
|
|
1663
|
+
const leftStr = left.kind === "scalar" ? left.raw : JSON.stringify(left);
|
|
1664
|
+
const rightStr = right.kind === "scalar" ? right.raw : JSON.stringify(right);
|
|
1665
|
+
return { kind: "scalar", raw: leftStr + rightStr, valueType: "string" };
|
|
1666
|
+
};
|
|
1667
|
+
const [head, ...tail] = nonSep;
|
|
1668
|
+
const folded = tail.reduce(joinPair, head);
|
|
1669
|
+
if (folded.kind === "scalar") {
|
|
1670
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
1671
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1672
|
+
}
|
|
1673
|
+
return folded;
|
|
1286
1674
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1675
|
+
resolveAppend(a, scope) {
|
|
1676
|
+
const existing = this.resolveVal(a.existing, scope) ?? { kind: "array", items: [] };
|
|
1677
|
+
const elem = this.resolveVal(a.elem, scope);
|
|
1678
|
+
const items = existing.kind === "array" ? [...existing.items] : [existing];
|
|
1679
|
+
if (elem !== void 0) items.push(elem);
|
|
1680
|
+
return { kind: "array", items };
|
|
1289
1681
|
}
|
|
1290
|
-
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
// src/internal/resolver/resolver.ts
|
|
1685
|
+
function resolve2(ast, opts) {
|
|
1686
|
+
const root = new StructureBuilder(opts).build(ast);
|
|
1687
|
+
return new SubstitutionResolver(root, opts).resolve();
|
|
1688
|
+
}
|
|
1689
|
+
async function resolveAsync(ast, opts) {
|
|
1690
|
+
const root = await new StructureBuilder(opts).buildAsync(ast);
|
|
1691
|
+
return new SubstitutionResolver(root, opts).resolve();
|
|
1291
1692
|
}
|
|
1292
1693
|
|
|
1293
1694
|
// src/parse.ts
|