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