@o3co/ts.hocon 1.1.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 +325 -162
- package/dist/index.js +325 -162
- package/dist/zod.cjs +325 -162
- package/dist/zod.js +325 -162
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -208,6 +208,23 @@ var ConfigError = class extends Error {
|
|
|
208
208
|
}
|
|
209
209
|
};
|
|
210
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
|
+
|
|
211
228
|
// src/config.ts
|
|
212
229
|
var Config = class _Config {
|
|
213
230
|
constructor(root) {
|
|
@@ -261,6 +278,10 @@ var Config = class _Config {
|
|
|
261
278
|
getList(path2) {
|
|
262
279
|
const v = this.lookupNode(path2);
|
|
263
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
|
+
}
|
|
264
285
|
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
265
286
|
return v.items.map(hoconToJs);
|
|
266
287
|
}
|
|
@@ -373,6 +394,20 @@ function deepMergeHocon(receiver, fallback) {
|
|
|
373
394
|
}
|
|
374
395
|
|
|
375
396
|
// src/internal/lexer/lexer.ts
|
|
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
|
+
}
|
|
376
411
|
var SINGLE_CHAR_TOKENS = {
|
|
377
412
|
"{": "lbrace",
|
|
378
413
|
"}": "rbrace",
|
|
@@ -395,18 +430,18 @@ var Lexer = class {
|
|
|
395
430
|
while (this.pos < this.input.length) {
|
|
396
431
|
const sl = this.line, sc = this.col;
|
|
397
432
|
const ch = this.peek();
|
|
398
|
-
if (ch
|
|
399
|
-
this.advance();
|
|
400
|
-
this.hadSpace = true;
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
if (ch === "\n") {
|
|
433
|
+
if (isHoconNewline(ch)) {
|
|
404
434
|
this.advance();
|
|
405
435
|
if (this.tokens[this.tokens.length - 1]?.kind !== "newline") {
|
|
406
436
|
this.push("newline", "\n", sl, sc);
|
|
407
437
|
}
|
|
408
438
|
continue;
|
|
409
439
|
}
|
|
440
|
+
if (isHoconWhitespace(ch)) {
|
|
441
|
+
this.advance();
|
|
442
|
+
this.hadSpace = true;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
410
445
|
if (ch === "/" && this.peek(1) === "/") {
|
|
411
446
|
while (this.pos < this.input.length && this.peek() !== "\n") this.advance();
|
|
412
447
|
this.hadSpace = true;
|
|
@@ -436,16 +471,16 @@ var Lexer = class {
|
|
|
436
471
|
if (ch === "$" && this.peek(1) === "{") {
|
|
437
472
|
this.advance();
|
|
438
473
|
this.advance();
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
this.
|
|
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}"`;
|
|
480
|
+
}
|
|
481
|
+
return t;
|
|
482
|
+
}).join(".");
|
|
483
|
+
this.pushSubst(payload, value, sl, sc);
|
|
449
484
|
continue;
|
|
450
485
|
}
|
|
451
486
|
if (ch === '"' && this.peek(1) === '"' && this.peek(2) === '"') {
|
|
@@ -480,62 +515,19 @@ var Lexer = class {
|
|
|
480
515
|
}
|
|
481
516
|
if (ch === '"') {
|
|
482
517
|
this.advance();
|
|
483
|
-
|
|
484
|
-
while (this.pos < this.input.length && this.peek() !== '"') {
|
|
485
|
-
if (this.peek() === "\n") throw new ParseError("unterminated string", sl, sc);
|
|
486
|
-
if (this.peek() === "\\") {
|
|
487
|
-
this.advance();
|
|
488
|
-
const esc = this.advance();
|
|
489
|
-
switch (esc) {
|
|
490
|
-
case "n":
|
|
491
|
-
value += "\n";
|
|
492
|
-
break;
|
|
493
|
-
case "t":
|
|
494
|
-
value += " ";
|
|
495
|
-
break;
|
|
496
|
-
case "r":
|
|
497
|
-
value += "\r";
|
|
498
|
-
break;
|
|
499
|
-
case '"':
|
|
500
|
-
value += '"';
|
|
501
|
-
break;
|
|
502
|
-
case "\\":
|
|
503
|
-
value += "\\";
|
|
504
|
-
break;
|
|
505
|
-
case "/":
|
|
506
|
-
value += "/";
|
|
507
|
-
break;
|
|
508
|
-
case "b":
|
|
509
|
-
value += "\b";
|
|
510
|
-
break;
|
|
511
|
-
case "f":
|
|
512
|
-
value += "\f";
|
|
513
|
-
break;
|
|
514
|
-
case "u": {
|
|
515
|
-
if (this.pos + 4 > this.input.length) {
|
|
516
|
-
throw new ParseError("Invalid unicode escape: not enough characters", this.line, this.col);
|
|
517
|
-
}
|
|
518
|
-
const hex = this.input.slice(this.pos, this.pos + 4);
|
|
519
|
-
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
520
|
-
throw new ParseError(`Invalid unicode escape: \\u${hex}`, this.line, this.col);
|
|
521
|
-
}
|
|
522
|
-
value += String.fromCharCode(parseInt(hex, 16));
|
|
523
|
-
for (let i = 0; i < 4; i++) this.advance();
|
|
524
|
-
break;
|
|
525
|
-
}
|
|
526
|
-
default:
|
|
527
|
-
throw new ParseError(`Unknown escape sequence: \\${esc}`, this.line, this.col);
|
|
528
|
-
}
|
|
529
|
-
} else {
|
|
530
|
-
value += this.advance();
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
if (this.peek() !== '"') throw new ParseError("unterminated string", sl, sc);
|
|
534
|
-
this.advance();
|
|
518
|
+
const value = this.readQuotedStringBody(sl, sc);
|
|
535
519
|
this.push("string", value, sl, sc, true);
|
|
536
520
|
continue;
|
|
537
521
|
}
|
|
538
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
|
+
}
|
|
539
531
|
let value = "";
|
|
540
532
|
while (this.pos < this.input.length && isUnquotedContinue(this.peek(), () => this.peek(1))) {
|
|
541
533
|
value += this.advance();
|
|
@@ -565,22 +557,201 @@ var Lexer = class {
|
|
|
565
557
|
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
|
|
566
558
|
this.hadSpace = false;
|
|
567
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);
|
|
608
|
+
}
|
|
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;
|
|
617
|
+
}
|
|
618
|
+
default:
|
|
619
|
+
throw new ParseError(`unknown escape sequence: \\${esc}`, openLine, escCol);
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
value += this.advance();
|
|
623
|
+
}
|
|
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);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
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 };
|
|
725
|
+
}
|
|
568
726
|
};
|
|
569
727
|
function tokenize(input) {
|
|
570
728
|
return new Lexer(input).tokenize();
|
|
571
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";
|
|
739
|
+
}
|
|
572
740
|
function isUnquotedStart(ch) {
|
|
573
|
-
|
|
741
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
742
|
+
if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
|
|
743
|
+
return true;
|
|
574
744
|
}
|
|
575
745
|
function isUnquotedContinue(ch, nextFn) {
|
|
576
|
-
if (ch === "" ||
|
|
746
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
747
|
+
if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
|
|
577
748
|
if (ch === "+" && nextFn() === "=") return false;
|
|
578
749
|
if (ch === "/" && nextFn() === "/") return false;
|
|
579
750
|
return true;
|
|
580
751
|
}
|
|
581
752
|
|
|
582
753
|
// src/internal/parser/parser.ts
|
|
583
|
-
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false };
|
|
754
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
|
|
584
755
|
var Parser = class {
|
|
585
756
|
constructor(tokens) {
|
|
586
757
|
this.tokens = tokens;
|
|
@@ -684,10 +855,21 @@ var Parser = class {
|
|
|
684
855
|
trailingDot = false;
|
|
685
856
|
} else if (t.kind === "unquoted") {
|
|
686
857
|
this.advance();
|
|
687
|
-
const
|
|
858
|
+
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
859
|
+
const parts = raw.split(".");
|
|
688
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
|
+
}
|
|
689
871
|
segments.push(...filtered);
|
|
690
|
-
trailingDot =
|
|
872
|
+
trailingDot = raw.endsWith(".");
|
|
691
873
|
} else {
|
|
692
874
|
if (segments.length === 0) throw new ParseError(`expected key, got ${t.kind}`, t.line, t.col);
|
|
693
875
|
break;
|
|
@@ -699,6 +881,10 @@ var Parser = class {
|
|
|
699
881
|
trailingDot = true;
|
|
700
882
|
continue;
|
|
701
883
|
}
|
|
884
|
+
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
885
|
+
trailingDot = true;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
702
888
|
break;
|
|
703
889
|
}
|
|
704
890
|
return segments;
|
|
@@ -774,9 +960,13 @@ var Parser = class {
|
|
|
774
960
|
} else if (t.kind === "lbracket") {
|
|
775
961
|
this.advance();
|
|
776
962
|
node = this.parseArray();
|
|
777
|
-
} else if (t.kind === "subst"
|
|
963
|
+
} else if (t.kind === "subst") {
|
|
778
964
|
this.advance();
|
|
779
|
-
|
|
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 } };
|
|
780
970
|
} else if (t.kind === "string" || t.kind === "triple_string") {
|
|
781
971
|
this.advance();
|
|
782
972
|
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
@@ -846,64 +1036,20 @@ function makeResObj() {
|
|
|
846
1036
|
}
|
|
847
1037
|
|
|
848
1038
|
// src/internal/resolver/utils.ts
|
|
849
|
-
function parseSubstPath(raw) {
|
|
850
|
-
const segments = [];
|
|
851
|
-
let i = 0;
|
|
852
|
-
while (i < raw.length) {
|
|
853
|
-
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
854
|
-
if (i >= raw.length) break;
|
|
855
|
-
if (raw[i] === '"') {
|
|
856
|
-
i++;
|
|
857
|
-
let seg = "";
|
|
858
|
-
while (i < raw.length && raw[i] !== '"') {
|
|
859
|
-
if (raw[i] === "\\" && i + 1 < raw.length && (raw[i + 1] === '"' || raw[i + 1] === "\\")) {
|
|
860
|
-
seg += raw[i + 1];
|
|
861
|
-
i += 2;
|
|
862
|
-
} else {
|
|
863
|
-
seg += raw[i];
|
|
864
|
-
i++;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
if (i < raw.length) i++;
|
|
868
|
-
segments.push(seg);
|
|
869
|
-
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
870
|
-
if (i < raw.length && raw[i] === ".") i++;
|
|
871
|
-
} else if (raw[i] === ".") {
|
|
872
|
-
segments.push("");
|
|
873
|
-
i++;
|
|
874
|
-
} else {
|
|
875
|
-
let seg = "";
|
|
876
|
-
while (i < raw.length && raw[i] !== ".") {
|
|
877
|
-
seg += raw[i];
|
|
878
|
-
i++;
|
|
879
|
-
}
|
|
880
|
-
segments.push(seg.trim());
|
|
881
|
-
if (i < raw.length && raw[i] === ".") i++;
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
return segments;
|
|
885
|
-
}
|
|
886
1039
|
function segmentsToKey(segments) {
|
|
887
1040
|
return segments.map((s) => {
|
|
888
|
-
|
|
889
|
-
|
|
1041
|
+
const t = s.text;
|
|
1042
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
1043
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
890
1044
|
}
|
|
891
|
-
return
|
|
1045
|
+
return t;
|
|
892
1046
|
}).join(".");
|
|
893
1047
|
}
|
|
894
1048
|
function lookupPath(root, segments) {
|
|
895
1049
|
const [head, ...tail] = segments;
|
|
896
|
-
if (head === void 0
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
if (val2 === void 0) return void 0;
|
|
900
|
-
if (tail.length === 0) return val2;
|
|
901
|
-
if (isResObj(val2)) return lookupPath(val2, tail);
|
|
902
|
-
return void 0;
|
|
903
|
-
}
|
|
904
|
-
return void 0;
|
|
905
|
-
}
|
|
906
|
-
const val = root.fields.get(head);
|
|
1050
|
+
if (head === void 0) return void 0;
|
|
1051
|
+
const key = head.text;
|
|
1052
|
+
const val = root.fields.get(key);
|
|
907
1053
|
if (val === void 0) return void 0;
|
|
908
1054
|
if (tail.length === 0) return val;
|
|
909
1055
|
if (isResObj(val)) return lookupPath(val, tail);
|
|
@@ -912,7 +1058,7 @@ function lookupPath(root, segments) {
|
|
|
912
1058
|
function lookupResObj(root, segments) {
|
|
913
1059
|
let cur = root;
|
|
914
1060
|
for (const seg of segments) {
|
|
915
|
-
const val = cur.fields.get(seg);
|
|
1061
|
+
const val = cur.fields.get(seg.text);
|
|
916
1062
|
if (val === void 0 || !isResObj(val)) return void 0;
|
|
917
1063
|
cur = val;
|
|
918
1064
|
}
|
|
@@ -1189,7 +1335,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1189
1335
|
return;
|
|
1190
1336
|
}
|
|
1191
1337
|
const [head, ...tail] = field.key;
|
|
1192
|
-
if (
|
|
1338
|
+
if (head === void 0) return;
|
|
1193
1339
|
if (tail.length > 0) {
|
|
1194
1340
|
const syntheticAst = {
|
|
1195
1341
|
kind: "object",
|
|
@@ -1231,7 +1377,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1231
1377
|
return;
|
|
1232
1378
|
}
|
|
1233
1379
|
const [head, ...tail] = field.key;
|
|
1234
|
-
if (
|
|
1380
|
+
if (head === void 0) return;
|
|
1235
1381
|
if (tail.length > 0) {
|
|
1236
1382
|
const syntheticAst = {
|
|
1237
1383
|
kind: "object",
|
|
@@ -1277,7 +1423,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1277
1423
|
return inner;
|
|
1278
1424
|
}
|
|
1279
1425
|
case "subst":
|
|
1280
|
-
return { _kind: "subst-placeholder", segments:
|
|
1426
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1281
1427
|
case "concat":
|
|
1282
1428
|
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)) };
|
|
1283
1429
|
case "include":
|
|
@@ -1301,7 +1447,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1301
1447
|
case "object":
|
|
1302
1448
|
return await this.buildAsync(ast, pathPrefix);
|
|
1303
1449
|
case "subst":
|
|
1304
|
-
return { _kind: "subst-placeholder", segments:
|
|
1450
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1305
1451
|
case "concat": {
|
|
1306
1452
|
const nodes = [];
|
|
1307
1453
|
for (const n of ast.nodes) {
|
|
@@ -1316,7 +1462,8 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1316
1462
|
// ---- Relativize substitution paths for nested includes ----
|
|
1317
1463
|
relativizeSubstPaths(val, prefixSegments) {
|
|
1318
1464
|
if (isSubst(val)) {
|
|
1319
|
-
|
|
1465
|
+
const prefixAsSegments = prefixSegments.map((text) => ({ text, line: 0, col: 0 }));
|
|
1466
|
+
val.segments = [...prefixAsSegments, ...val.segments];
|
|
1320
1467
|
val.prefixLen += prefixSegments.length;
|
|
1321
1468
|
return;
|
|
1322
1469
|
}
|
|
@@ -1412,7 +1559,7 @@ var SubstitutionResolver = class {
|
|
|
1412
1559
|
return hv;
|
|
1413
1560
|
}
|
|
1414
1561
|
segmentsEqual(a, b) {
|
|
1415
|
-
return a.length === b.length && a.every((seg, i) => seg === b[i]);
|
|
1562
|
+
return a.length === b.length && a.every((seg, i) => seg.text === b[i]?.text);
|
|
1416
1563
|
}
|
|
1417
1564
|
resolveSubst(s, scope) {
|
|
1418
1565
|
const key = segmentsToKey(s.segments);
|
|
@@ -1420,14 +1567,14 @@ var SubstitutionResolver = class {
|
|
|
1420
1567
|
if (this.resolving.has(key)) {
|
|
1421
1568
|
let prior;
|
|
1422
1569
|
if (s.prefixLen > 0) {
|
|
1423
|
-
const leafSeg = s.segments[s.segments.length - 1] ?? "";
|
|
1570
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1424
1571
|
const parentScope = lookupResObj(
|
|
1425
1572
|
this.root,
|
|
1426
1573
|
s.segments.slice(0, s.segments.length - 1)
|
|
1427
1574
|
);
|
|
1428
1575
|
prior = parentScope?.priorValues.get(leafSeg);
|
|
1429
1576
|
} else {
|
|
1430
|
-
const rootSeg = s.segments[0] ?? "";
|
|
1577
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1431
1578
|
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1432
1579
|
}
|
|
1433
1580
|
if (prior !== void 0) {
|
|
@@ -1458,14 +1605,14 @@ var SubstitutionResolver = class {
|
|
|
1458
1605
|
if (isSelfRef) {
|
|
1459
1606
|
let prior;
|
|
1460
1607
|
if (s.prefixLen > 0) {
|
|
1461
|
-
const leafSeg = s.segments[s.segments.length - 1] ?? "";
|
|
1608
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1462
1609
|
const parentScope = lookupResObj(
|
|
1463
1610
|
this.root,
|
|
1464
1611
|
s.segments.slice(0, s.segments.length - 1)
|
|
1465
1612
|
);
|
|
1466
1613
|
prior = parentScope?.priorValues.get(leafSeg);
|
|
1467
1614
|
} else {
|
|
1468
|
-
const rootSeg = s.segments[0] ?? "";
|
|
1615
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1469
1616
|
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1470
1617
|
}
|
|
1471
1618
|
if (prior !== void 0) {
|
|
@@ -1478,7 +1625,7 @@ var SubstitutionResolver = class {
|
|
|
1478
1625
|
let result = this.resolveVal(found, scope);
|
|
1479
1626
|
const effectiveLen = s.segments.length - s.prefixLen;
|
|
1480
1627
|
if (effectiveLen === 1 && result !== void 0 && result.kind === "object") {
|
|
1481
|
-
const leafSeg = s.segments[s.segments.length - 1] ?? "";
|
|
1628
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1482
1629
|
let prior;
|
|
1483
1630
|
if (s.prefixLen > 0) {
|
|
1484
1631
|
const parentScope = lookupResObj(
|
|
@@ -1502,8 +1649,8 @@ var SubstitutionResolver = class {
|
|
|
1502
1649
|
if (result !== void 0) this.cache.set(key, result);
|
|
1503
1650
|
return result;
|
|
1504
1651
|
}
|
|
1505
|
-
const envKey = s.segments.join(".");
|
|
1506
|
-
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).join(".")] : void 0);
|
|
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);
|
|
1507
1654
|
if (envVal !== void 0) {
|
|
1508
1655
|
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
1509
1656
|
this.cache.set(key, result);
|
|
@@ -1525,32 +1672,48 @@ var SubstitutionResolver = class {
|
|
|
1525
1672
|
if (resolved.length === 0) return { kind: "scalar", raw: "null", valueType: "null" };
|
|
1526
1673
|
if (resolved.length === 1) return resolved[0];
|
|
1527
1674
|
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
1528
|
-
if (nonSep.length
|
|
1529
|
-
const
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
}
|
|
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" };
|
|
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] };
|
|
1540
1687
|
}
|
|
1688
|
+
return { kind: "array", items: [...left.items, right] };
|
|
1541
1689
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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] };
|
|
1549
1699
|
}
|
|
1550
|
-
|
|
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" };
|
|
1551
1715
|
}
|
|
1552
|
-
|
|
1553
|
-
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1716
|
+
return folded;
|
|
1554
1717
|
}
|
|
1555
1718
|
resolveAppend(a, scope) {
|
|
1556
1719
|
const existing = this.resolveVal(a.existing, scope) ?? { kind: "array", items: [] };
|