scon-notation 1.0.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/src/decoder.js ADDED
@@ -0,0 +1,761 @@
1
+ // src/decoder.js
2
+ // Port of bX\Scon\Decoder — SCON string → JS object
3
+
4
+ import { SchemaRegistry } from './schema-registry.js';
5
+ import { Minifier } from './minifier.js';
6
+
7
+ const COMMA = ',';
8
+ const TAB = '\t';
9
+ const PIPE = '|';
10
+ const DQUOTE = '"';
11
+ const COLON = ':';
12
+ const OBRACK = '[';
13
+ const CBRACK = ']';
14
+ const OBRACE = '{';
15
+ const CBRACE = '}';
16
+ const LIST_PREFIX = '- ';
17
+ const BACKSLASH = '\\';
18
+ const SEMICOLON = ';';
19
+
20
+ export class Decoder {
21
+
22
+ constructor(options = {}) {
23
+ // Si indent no se provee, auto-detectar del documento
24
+ this._indentAutoDetect = !('indent' in options);
25
+ this.indent = options.indent ?? 1;
26
+ this.strict = options.strict ?? true;
27
+ this.registry = new SchemaRegistry();
28
+ this.directives = {};
29
+ }
30
+
31
+ getRegistry() { return this.registry; }
32
+ getDirectives() { return this.directives; }
33
+
34
+ // Decode SCON string to JS object/array
35
+ decode(sconString) {
36
+ // Expand if minified
37
+ if (this._isMinified(sconString)) {
38
+ sconString = Minifier.expand(sconString, this.indent);
39
+ }
40
+
41
+ // Auto-detect indent: primera línea que empiece con espacios
42
+ if (this._indentAutoDetect) {
43
+ const m = sconString.match(/\n( +)\S/);
44
+ if (m) this.indent = m[1].length;
45
+ this._indentAutoDetect = false;
46
+ }
47
+
48
+ const lines = sconString.split('\n');
49
+ const parsedLines = [];
50
+
51
+ // First pass: extract header, directives, and definitions
52
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
53
+ const line = lines[lineNum];
54
+ const trimmed = line.trim();
55
+
56
+ // Skip empty lines and comments
57
+ if (trimmed === '' || trimmed[0] === '#') {
58
+ continue;
59
+ }
60
+
61
+ // Directives (@@)
62
+ if (trimmed.startsWith('@@')) {
63
+ this._parseDirective(trimmed);
64
+ continue;
65
+ }
66
+
67
+ // Schema definition (s:name ...)
68
+ let match = trimmed.match(/^s:(\S+)\s+/);
69
+ if (match) {
70
+ const name = match[1];
71
+ const defStr = trimmed.slice(match[0].length);
72
+ const def = this._parseInlineValue(defStr);
73
+ this.registry.register('s', name, (typeof def === 'object' && def !== null) ? def : {});
74
+ continue;
75
+ }
76
+
77
+ // Response group definition (r:name ...)
78
+ match = trimmed.match(/^r:(\S+)\s+/);
79
+ if (match) {
80
+ const name = match[1];
81
+ const defStr = trimmed.slice(match[0].length);
82
+ const def = this._parseResponseGroup(defStr);
83
+ this.registry.register('r', name, def);
84
+ continue;
85
+ }
86
+
87
+ // Security group definition (sec:name ...)
88
+ match = trimmed.match(/^sec:(\S+)\s+/);
89
+ if (match) {
90
+ const name = match[1];
91
+ const defStr = trimmed.slice(match[0].length);
92
+ const def = this._parseInlineValue(defStr);
93
+ this.registry.register('sec', name, (typeof def === 'object' && def !== null) ? def : {});
94
+ continue;
95
+ }
96
+
97
+ // @use import
98
+ if (trimmed.startsWith('@use ')) {
99
+ if (!this.directives.imports) this.directives.imports = [];
100
+ this.directives.imports.push(trimmed);
101
+ continue;
102
+ }
103
+
104
+ // Body line
105
+ const depth = this._calculateDepth(line);
106
+ parsedLines.push({ depth, content: line.trimStart(), lineNum });
107
+ }
108
+
109
+ if (parsedLines.length === 0) return [];
110
+
111
+ // Second pass: parse body with ref resolution
112
+ const first = parsedLines[0];
113
+
114
+ if (this._isArrayHeader(first.content)) {
115
+ const header = this._parseArrayHeader(first.content);
116
+ if (header.key === null) {
117
+ return this._decodeArrayFromHeader(0, parsedLines);
118
+ }
119
+ }
120
+
121
+ // Explicit empty object marker
122
+ if (parsedLines.length === 1 && first.content === '{}') {
123
+ return {};
124
+ }
125
+
126
+ if (parsedLines.length === 1 && !this._isKeyValueLine(first.content)) {
127
+ const val = this._parsePrimitive(first.content);
128
+ return Array.isArray(val) ? val : [val];
129
+ }
130
+
131
+ return this._decodeObject(0, parsedLines, 0);
132
+ }
133
+
134
+ // --- Directive parsing ---
135
+
136
+ _parseDirective(line) {
137
+ const directive = line.slice(2);
138
+ if (directive.startsWith('enforce(') && directive.endsWith(')')) {
139
+ this.directives.enforce = directive.slice(8, -1);
140
+ } else {
141
+ this.directives.mode = directive;
142
+ }
143
+ }
144
+
145
+ // --- Response group parsing ---
146
+
147
+ _parseResponseGroup(input) {
148
+ input = input.trim();
149
+ if (input[0] !== OBRACE) return {};
150
+
151
+ const inner = this._extractBraceContent(input);
152
+ const result = {};
153
+ const parts = this._splitTopLevel(inner, COMMA);
154
+
155
+ for (let part of parts) {
156
+ part = part.trim();
157
+ const m = part.match(/^(\d+):("(?:[^"\\]|\\.)*")\s*(?:@s:(\S+))?\s*(.*)$/);
158
+ if (m) {
159
+ const code = m[1];
160
+ const desc = this._parseStringLiteral(m[2]);
161
+ const entry = { description: desc };
162
+ if (m[3]) entry.schemaRef = m[3];
163
+ if (m[4]) {
164
+ const overridesStr = m[4].trim();
165
+ if (overridesStr !== '' && overridesStr[0] === OBRACE) {
166
+ entry.overrides = this._parseInlineValue(overridesStr);
167
+ }
168
+ }
169
+ result[code] = entry;
170
+ }
171
+ }
172
+
173
+ return result;
174
+ }
175
+
176
+ // --- Inline value parsing ---
177
+
178
+ _parseInlineValue(input) {
179
+ input = input.trim();
180
+ if (input === '') return '';
181
+
182
+ // Object
183
+ if (input[0] === OBRACE) {
184
+ const inner = this._extractBraceContent(input);
185
+ return this._parseInlineObject(inner);
186
+ }
187
+
188
+ // Array
189
+ if (input[0] === OBRACK) {
190
+ const close = this._findMatchingBracket(input, 0);
191
+ if (close !== -1) {
192
+ const inner = input.slice(1, close);
193
+ const items = this._splitTopLevel(inner, COMMA);
194
+ return items.map(i => this._parseInlineValue(i.trim()));
195
+ }
196
+ }
197
+
198
+ // Reference
199
+ if (input.startsWith('@s:') || input.startsWith('@r:') || input.startsWith('@sec:')) {
200
+ return this._resolveReference(input);
201
+ }
202
+
203
+ return this._parsePrimitive(input);
204
+ }
205
+
206
+ _parseInlineObject(inner) {
207
+ const result = {};
208
+ const parts = this._splitTopLevel(inner, COMMA);
209
+
210
+ for (let part of parts) {
211
+ part = part.trim();
212
+ if (part === '') continue;
213
+
214
+ const colonPos = this._findKeyColon(part);
215
+ if (colonPos === -1) continue;
216
+
217
+ let key = part.slice(0, colonPos).trim();
218
+ const val = part.slice(colonPos + 1).trim();
219
+
220
+ key = this._parseStringLiteral(key);
221
+
222
+ if (key.includes('.')) {
223
+ this._setDotPath(result, key, this._parseInlineValue(val));
224
+ } else {
225
+ result[key] = this._parseInlineValue(val);
226
+ }
227
+ }
228
+
229
+ return result;
230
+ }
231
+
232
+ // --- Reference resolution ---
233
+
234
+ _resolveReference(refStr) {
235
+ // Polymorphic: @s:a | @s:b
236
+ if (refStr.includes(' | ')) {
237
+ const refs = [];
238
+ for (let r of refStr.split(' | ')) {
239
+ r = r.trim();
240
+ const m = r.match(/^@(s|r|sec):(\S+)/);
241
+ if (m) refs.push({ type: m[1], name: m[2] });
242
+ }
243
+ return this.registry.resolvePolymorphic(refs);
244
+ }
245
+
246
+ const m = refStr.match(/^@(s|r|sec):(\S+)\s*(.*)$/);
247
+ if (m) {
248
+ const type = m[1];
249
+ const name = m[2];
250
+ const rest = (m[3] || '').trim();
251
+
252
+ if (rest !== '' && rest[0] === OBRACE) {
253
+ const overrides = this._parseInlineValue(rest);
254
+ return this.registry.resolveWithOverride(type, name, typeof overrides === 'object' && overrides !== null ? overrides : {});
255
+ }
256
+
257
+ return this.registry.resolve(type, name);
258
+ }
259
+
260
+ return refStr;
261
+ }
262
+
263
+ // --- Minification detection ---
264
+
265
+ _isMinified(str) {
266
+ return !str.includes('\n') && str.includes(SEMICOLON);
267
+ }
268
+
269
+ // --- Body parsing ---
270
+
271
+ _calculateDepth(line) {
272
+ let spaces = 0;
273
+ for (let i = 0; i < line.length; i++) {
274
+ if (line[i] === ' ') spaces++;
275
+ else if (line[i] === '\t') throw new Error('Tabs not allowed for indentation');
276
+ else break;
277
+ }
278
+ if (this.indent > 0 && spaces % this.indent !== 0) {
279
+ throw new Error(`Invalid indentation: ${spaces} spaces (indent=${this.indent})`);
280
+ }
281
+ return this.indent > 0 ? spaces / this.indent : 0;
282
+ }
283
+
284
+ _decodeObject(baseDepth, parsedLines, startIndex) {
285
+ const result = {};
286
+ let i = startIndex;
287
+
288
+ while (i < parsedLines.length) {
289
+ const line = parsedLines[i];
290
+ if (line.depth < baseDepth) break;
291
+ if (line.depth > baseDepth) { i++; continue; }
292
+
293
+ const content = line.content;
294
+
295
+ // Array header
296
+ if (this._isArrayHeader(content)) {
297
+ const header = this._parseArrayHeader(content);
298
+ if (header.key !== null) {
299
+ result[header.key] = this._decodeArrayFromHeader(i, parsedLines);
300
+ i++;
301
+ while (i < parsedLines.length && parsedLines[i].depth > baseDepth) i++;
302
+ continue;
303
+ }
304
+ }
305
+
306
+ // Key-value
307
+ if (this._isKeyValueLine(content)) {
308
+ const [key, value, nextIndex] = this._decodeKeyValue(line, parsedLines, i, baseDepth);
309
+ result[key] = value;
310
+ i = nextIndex;
311
+ continue;
312
+ }
313
+
314
+ i++;
315
+ }
316
+
317
+ return result;
318
+ }
319
+
320
+ _decodeKeyValue(line, parsedLines, index, baseDepth) {
321
+ const content = line.content;
322
+ const keyData = this._parseKey(content);
323
+ const key = keyData.key;
324
+ const rest = content.slice(keyData.end).trim();
325
+
326
+ // Reference value
327
+ if (rest !== '' && rest.startsWith('@')) {
328
+ return [key, this._resolveReference(rest), index + 1];
329
+ }
330
+
331
+ if (rest !== '') {
332
+ return [key, this._parsePrimitive(rest), index + 1];
333
+ }
334
+
335
+ // Nested object
336
+ if (index + 1 < parsedLines.length && parsedLines[index + 1].depth > baseDepth) {
337
+ const value = this._decodeObject(baseDepth + 1, parsedLines, index + 1);
338
+ let nextIndex = index + 1;
339
+ while (nextIndex < parsedLines.length && parsedLines[nextIndex].depth > baseDepth) {
340
+ nextIndex++;
341
+ }
342
+ return [key, value, nextIndex];
343
+ }
344
+
345
+ return [key, [], index + 1];
346
+ }
347
+
348
+ _decodeArrayFromHeader(index, parsedLines) {
349
+ const line = parsedLines[index];
350
+ const header = this._parseArrayHeader(line.content);
351
+ const baseDepth = line.depth;
352
+
353
+ if (header.length === 0) return [];
354
+
355
+ if (header.inlineValues !== null && header.fields === null) {
356
+ return this._parseDelimitedValues(header.inlineValues, header.delimiter);
357
+ }
358
+
359
+ if (header.fields !== null) {
360
+ return this._decodeTabularArray(index, parsedLines, baseDepth, header.length, header.fields, header.delimiter);
361
+ }
362
+
363
+ return this._decodeExpandedArray(index, parsedLines, baseDepth, header.length);
364
+ }
365
+
366
+ _decodeTabularArray(headerIdx, parsedLines, baseDepth, expected, fields, delim) {
367
+ const result = [];
368
+ let i = headerIdx + 1;
369
+
370
+ while (i < parsedLines.length && result.length < expected) {
371
+ if (parsedLines[i].depth !== baseDepth + 1) break;
372
+ const values = this._parseDelimitedValues(parsedLines[i].content, delim);
373
+ const row = {};
374
+ for (let j = 0; j < fields.length; j++) {
375
+ row[fields[j]] = values[j] ?? null;
376
+ }
377
+ result.push(row);
378
+ i++;
379
+ }
380
+
381
+ return result;
382
+ }
383
+
384
+ _decodeExpandedArray(headerIdx, parsedLines, baseDepth, expected) {
385
+ const result = [];
386
+ let i = headerIdx + 1;
387
+
388
+ while (i < parsedLines.length && result.length < expected) {
389
+ const line = parsedLines[i];
390
+ if (line.depth !== baseDepth + 1) break;
391
+
392
+ if (line.content.startsWith(LIST_PREFIX)) {
393
+ const itemContent = line.content.slice(LIST_PREFIX.length);
394
+
395
+ // Schema/response/security reference as list item
396
+ if (itemContent.startsWith('@s:') || itemContent.startsWith('@r:') || itemContent.startsWith('@sec:')) {
397
+ result.push(this._resolveReference(itemContent));
398
+ i++;
399
+ continue;
400
+ }
401
+
402
+ if (this._isKeyValueLine(itemContent)) {
403
+ const obj = this._decodeListItemObject(line, parsedLines, i, baseDepth);
404
+ result.push(obj);
405
+ i++;
406
+ while (i < parsedLines.length && parsedLines[i].depth > baseDepth + 1) i++;
407
+ continue;
408
+ }
409
+
410
+ if (this._isArrayHeader(itemContent)) {
411
+ const itemHeader = this._parseArrayHeader(itemContent);
412
+ if (itemHeader.inlineValues !== null) {
413
+ result.push(this._parseDelimitedValues(itemHeader.inlineValues, itemHeader.delimiter));
414
+ }
415
+ } else {
416
+ result.push(this._parsePrimitive(itemContent));
417
+ }
418
+ }
419
+ i++;
420
+ }
421
+
422
+ return result;
423
+ }
424
+
425
+ _decodeListItemObject(line, parsedLines, index, baseDepth) {
426
+ const itemContent = line.content.slice(LIST_PREFIX.length);
427
+ const keyData = this._parseKey(itemContent);
428
+ const key = keyData.key;
429
+ const rest = itemContent.slice(keyData.end).trim();
430
+
431
+ const result = {};
432
+ const contDepth = baseDepth + 2;
433
+
434
+ if (rest !== '' && rest.startsWith('@')) {
435
+ result[key] = this._resolveReference(rest);
436
+ } else if (rest !== '') {
437
+ result[key] = this._parsePrimitive(rest);
438
+ } else if (index + 1 < parsedLines.length && parsedLines[index + 1].depth >= contDepth) {
439
+ result[key] = this._decodeObject(contDepth, parsedLines, index + 1);
440
+ } else {
441
+ result[key] = [];
442
+ }
443
+
444
+ // Parse continuation fields
445
+ let i = index + 1;
446
+ while (i < parsedLines.length) {
447
+ const nextLine = parsedLines[i];
448
+ if (nextLine.depth < contDepth) break;
449
+ if (nextLine.depth === contDepth) {
450
+ if (nextLine.content.startsWith(LIST_PREFIX)) break;
451
+
452
+ // Array header in continuation
453
+ if (this._isArrayHeader(nextLine.content)) {
454
+ const header = this._parseArrayHeader(nextLine.content);
455
+ if (header.key !== null) {
456
+ result[header.key] = this._decodeArrayFromHeader(i, parsedLines);
457
+ i++;
458
+ while (i < parsedLines.length && parsedLines[i].depth > contDepth) i++;
459
+ continue;
460
+ }
461
+ }
462
+ if (this._isKeyValueLine(nextLine.content)) {
463
+ const [k, v, nextIdx] = this._decodeKeyValue(nextLine, parsedLines, i, contDepth);
464
+ result[k] = v;
465
+ i = nextIdx;
466
+ continue;
467
+ }
468
+ }
469
+ i++;
470
+ }
471
+
472
+ return result;
473
+ }
474
+
475
+ // --- Parsing helpers ---
476
+
477
+ _parseArrayHeader(content) {
478
+ let key = null;
479
+ const bracketStart = content.indexOf(OBRACK);
480
+
481
+ if (bracketStart > 0) {
482
+ const rawKey = content.slice(0, bracketStart).trim();
483
+ key = this._parseStringLiteral(rawKey);
484
+ }
485
+
486
+ const bracketEnd = content.indexOf(CBRACK, bracketStart);
487
+ if (bracketEnd === -1) throw new Error('Invalid array header: missing ]');
488
+
489
+ let bracketContent = content.slice(bracketStart + 1, bracketEnd);
490
+
491
+ let delimiter = COMMA;
492
+ if (bracketContent.endsWith(TAB)) {
493
+ delimiter = TAB;
494
+ bracketContent = bracketContent.slice(0, -1);
495
+ } else if (bracketContent.endsWith(PIPE)) {
496
+ delimiter = PIPE;
497
+ bracketContent = bracketContent.slice(0, -1);
498
+ }
499
+
500
+ const length = parseInt(bracketContent, 10);
501
+ let fields = null;
502
+ let braceStart = content.indexOf(OBRACE, bracketEnd);
503
+ let colonIndex = content.indexOf(COLON, bracketEnd);
504
+
505
+ if (braceStart !== -1 && (colonIndex === -1 || braceStart < colonIndex)) {
506
+ const braceEnd = content.indexOf(CBRACE, braceStart);
507
+ if (braceEnd !== -1) {
508
+ const fieldsContent = content.slice(braceStart + 1, braceEnd);
509
+ fields = this._parseDelimitedValues(fieldsContent, delimiter);
510
+ colonIndex = content.indexOf(COLON, braceEnd);
511
+ }
512
+ }
513
+
514
+ let inlineValues = null;
515
+ if (colonIndex !== -1) {
516
+ const afterColon = content.slice(colonIndex + 1).trim();
517
+ if (afterColon !== '') {
518
+ inlineValues = afterColon;
519
+ }
520
+ }
521
+
522
+ return { key, length, delimiter, fields, inlineValues };
523
+ }
524
+
525
+ _parseDelimitedValues(input, delimiter) {
526
+ const values = [];
527
+ let buffer = '';
528
+ let inQuotes = false;
529
+ let braceDepth = 0;
530
+
531
+ for (let i = 0; i < input.length; i++) {
532
+ const char = input[i];
533
+
534
+ if (char === BACKSLASH && inQuotes && i + 1 < input.length) {
535
+ buffer += char + input[i + 1];
536
+ i++;
537
+ continue;
538
+ }
539
+
540
+ if (char === DQUOTE) {
541
+ inQuotes = !inQuotes;
542
+ buffer += char;
543
+ continue;
544
+ }
545
+
546
+ if (!inQuotes) {
547
+ if (char === OBRACE) braceDepth++;
548
+ if (char === CBRACE) braceDepth--;
549
+ }
550
+
551
+ if (char === delimiter && !inQuotes && braceDepth === 0) {
552
+ values.push(this._parsePrimitive(buffer.trim()));
553
+ buffer = '';
554
+ continue;
555
+ }
556
+
557
+ buffer += char;
558
+ }
559
+
560
+ if (buffer !== '' || values.length > 0) {
561
+ values.push(this._parsePrimitive(buffer.trim()));
562
+ }
563
+
564
+ return values;
565
+ }
566
+
567
+ _parsePrimitive(token) {
568
+ const trimmed = token.trim();
569
+ if (trimmed === '') return '';
570
+ if (trimmed === '[]') return [];
571
+
572
+ if (trimmed[0] === DQUOTE) {
573
+ return this._parseStringLiteral(trimmed);
574
+ }
575
+
576
+ if (trimmed === 'true') return true;
577
+ if (trimmed === 'false') return false;
578
+ if (trimmed === 'null') return null;
579
+
580
+ // Strict decimal regex matching PHP is_numeric (no hex, no binary, no Infinity)
581
+ if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(trimmed)) {
582
+ if (trimmed.includes('.') || trimmed.includes('e') || trimmed.includes('E')) {
583
+ return parseFloat(trimmed);
584
+ }
585
+ return parseInt(trimmed, 10);
586
+ }
587
+
588
+ return trimmed;
589
+ }
590
+
591
+ _parseStringLiteral(token) {
592
+ const trimmed = token.trim();
593
+ if (trimmed === '' || trimmed[0] !== DQUOTE) return trimmed;
594
+
595
+ const closingQuote = this._findClosingQuote(trimmed, 0);
596
+ if (closingQuote === -1) throw new Error('Unterminated string');
597
+
598
+ return this._unescapeString(trimmed.slice(1, closingQuote));
599
+ }
600
+
601
+ _findClosingQuote(str, start) {
602
+ let i = start + 1;
603
+ while (i < str.length) {
604
+ if (str[i] === BACKSLASH && i + 1 < str.length) { i += 2; continue; }
605
+ if (str[i] === DQUOTE) return i;
606
+ i++;
607
+ }
608
+ return -1;
609
+ }
610
+
611
+ _unescapeString(str) {
612
+ let result = str.replace(/\\\\/g, '\x00BACKSLASH\x00');
613
+ result = result.replace(/\\"/g, '"');
614
+ result = result.replace(/\\n/g, '\n');
615
+ result = result.replace(/\\r/g, '\r');
616
+ result = result.replace(/\\t/g, '\t');
617
+ result = result.replace(/\\;/g, ';');
618
+ result = result.replace(/\x00BACKSLASH\x00/g, '\\');
619
+ return result;
620
+ }
621
+
622
+ _parseKey(content) {
623
+ if (content[0] === DQUOTE) {
624
+ const closingQuote = this._findClosingQuote(content, 0);
625
+ if (closingQuote === -1) throw new Error('Unterminated quoted key');
626
+ const key = this._unescapeString(content.slice(1, closingQuote));
627
+ const end = closingQuote + 1;
628
+ if (end >= content.length || content[end] !== COLON) {
629
+ throw new Error('Missing colon after key');
630
+ }
631
+ return { key, end: end + 1 };
632
+ }
633
+
634
+ const colonPos = content.indexOf(COLON);
635
+ if (colonPos === -1) throw new Error('Missing colon after key');
636
+
637
+ return { key: content.slice(0, colonPos).trim(), end: colonPos + 1 };
638
+ }
639
+
640
+ _findKeyColon(str) {
641
+ let inQuotes = false;
642
+ let braceDepth = 0;
643
+
644
+ for (let i = 0; i < str.length; i++) {
645
+ const char = str[i];
646
+ if (char === BACKSLASH && inQuotes && i + 1 < str.length) { i++; continue; }
647
+ if (char === DQUOTE) { inQuotes = !inQuotes; continue; }
648
+ if (!inQuotes) {
649
+ if (char === OBRACE) braceDepth++;
650
+ if (char === CBRACE) braceDepth--;
651
+ if (char === COLON && braceDepth === 0) return i;
652
+ }
653
+ }
654
+
655
+ return -1;
656
+ }
657
+
658
+ _splitTopLevel(input, delimiter) {
659
+ const parts = [];
660
+ let buffer = '';
661
+ let inQuotes = false;
662
+ let braceDepth = 0;
663
+ let bracketDepth = 0;
664
+
665
+ for (let i = 0; i < input.length; i++) {
666
+ const char = input[i];
667
+ if (char === BACKSLASH && inQuotes && i + 1 < input.length) {
668
+ buffer += char + input[i + 1];
669
+ i++;
670
+ continue;
671
+ }
672
+ if (char === DQUOTE) inQuotes = !inQuotes;
673
+ if (!inQuotes) {
674
+ if (char === OBRACE) braceDepth++;
675
+ if (char === CBRACE) braceDepth--;
676
+ if (char === OBRACK) bracketDepth++;
677
+ if (char === CBRACK) bracketDepth--;
678
+ }
679
+ if (char === delimiter && !inQuotes && braceDepth === 0 && bracketDepth === 0) {
680
+ parts.push(buffer);
681
+ buffer = '';
682
+ continue;
683
+ }
684
+ buffer += char;
685
+ }
686
+
687
+ if (buffer !== '') parts.push(buffer);
688
+ return parts;
689
+ }
690
+
691
+ _findMatchingBracket(str, start) {
692
+ let depth = 0;
693
+ let inQuotes = false;
694
+
695
+ for (let i = start; i < str.length; i++) {
696
+ const char = str[i];
697
+ if (char === BACKSLASH && inQuotes && i + 1 < str.length) { i++; continue; }
698
+ if (char === DQUOTE) { inQuotes = !inQuotes; continue; }
699
+ if (!inQuotes) {
700
+ if (char === OBRACK) depth++;
701
+ if (char === CBRACK) {
702
+ depth--;
703
+ if (depth === 0) return i;
704
+ }
705
+ }
706
+ }
707
+
708
+ return -1;
709
+ }
710
+
711
+ _extractBraceContent(input) {
712
+ let depth = 0;
713
+ let start = -1;
714
+ let inQuotes = false;
715
+
716
+ for (let i = 0; i < input.length; i++) {
717
+ const char = input[i];
718
+ if (char === BACKSLASH && inQuotes && i + 1 < input.length) { i++; continue; }
719
+ if (char === DQUOTE) { inQuotes = !inQuotes; continue; }
720
+ if (!inQuotes) {
721
+ if (char === OBRACE) {
722
+ if (depth === 0) start = i;
723
+ depth++;
724
+ }
725
+ if (char === CBRACE) {
726
+ depth--;
727
+ if (depth === 0) {
728
+ return input.slice(start + 1, i);
729
+ }
730
+ }
731
+ }
732
+ }
733
+
734
+ return '';
735
+ }
736
+
737
+ _isArrayHeader(content) {
738
+ const bracketPos = content.indexOf(OBRACK);
739
+ const colonPos = content.indexOf(COLON);
740
+ return bracketPos !== -1 && colonPos !== -1 && bracketPos < colonPos;
741
+ }
742
+
743
+ _isKeyValueLine(content) {
744
+ return content.includes(COLON);
745
+ }
746
+
747
+ _setDotPath(obj, path, val) {
748
+ const keys = path.split('.');
749
+ let ref = obj;
750
+ for (let i = 0; i < keys.length; i++) {
751
+ if (i === keys.length - 1) {
752
+ ref[keys[i]] = val;
753
+ } else {
754
+ if (!Object.hasOwn(ref, keys[i]) || typeof ref[keys[i]] !== 'object' || ref[keys[i]] === null) {
755
+ ref[keys[i]] = {};
756
+ }
757
+ ref = ref[keys[i]];
758
+ }
759
+ }
760
+ }
761
+ }