mason-parser 1.0.1 → 1.0.2

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/dist/index.js CHANGED
@@ -20,99 +20,573 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ fastParseWithTrace: () => fastParseWithTrace,
23
24
  parse: () => parse,
24
25
  parseMSON: () => parse,
26
+ parseMaSON: () => parseWithTrace,
25
27
  parsePrimitiveValue: () => parsePrimitiveValue,
28
+ parseValueWithMultiline: () => parseValueWithMultiline,
26
29
  parseWithTrace: () => parseWithTrace,
27
30
  stringify: () => stringify,
28
31
  stringifyMSON: () => stringify,
32
+ stringifyMaSON: () => stringify,
29
33
  stringifyPrimitiveValue: () => stringifyPrimitiveValue
30
34
  });
31
35
  module.exports = __toCommonJS(index_exports);
36
+ var BACKTICK_REGEX = /`+/g;
37
+ var DOUBLE_QUOTE_REGEX = /"/g;
38
+ var LANGUAGE_TAG_REGEX = /^[a-zA-Z0-9+#-]+$/;
39
+ var NUMBER_REGEX = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
40
+ function findColonIndex(line, start) {
41
+ const len = line.length;
42
+ let inDouble = false;
43
+ let inSingle = false;
44
+ let inBacktick = false;
45
+ for (let i = start; i < len; i++) {
46
+ const code = line.charCodeAt(i);
47
+ if (code === 92) {
48
+ i++;
49
+ continue;
50
+ }
51
+ if (code === 34 && !inSingle && !inBacktick) {
52
+ inDouble = !inDouble;
53
+ } else if (code === 39 && !inDouble && !inBacktick) {
54
+ inSingle = !inSingle;
55
+ } else if (code === 96 && !inDouble && !inSingle) {
56
+ inBacktick = !inBacktick;
57
+ } else if (code === 58 && !inDouble && !inSingle && !inBacktick) {
58
+ return i;
59
+ }
60
+ }
61
+ return -1;
62
+ }
32
63
  function parsePrimitiveValue(val) {
33
- const trimmed = val.trim();
34
- if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
35
- return trimmed.slice(1, -1);
64
+ const len = val.length;
65
+ if (len === 0) return val;
66
+ const first = val.charCodeAt(0);
67
+ const last = val.charCodeAt(len - 1);
68
+ if (len >= 2) {
69
+ if (first === 34 && last === 34 || first === 39 && last === 39 || first === 96 && last === 96) {
70
+ const content = val.slice(1, -1);
71
+ if (content.indexOf("\\") !== -1) {
72
+ if (first === 34) {
73
+ return content.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
74
+ } else if (first === 39) {
75
+ return content.replace(/\\'/g, "'").replace(/\\\\/g, "\\");
76
+ } else if (first === 96) {
77
+ return content.replace(/\\`/g, "`").replace(/\\\\/g, "\\");
78
+ }
79
+ }
80
+ return content;
81
+ }
36
82
  }
37
- if (trimmed === "true") return true;
38
- if (trimmed === "false") return false;
39
- if (trimmed === "null") return null;
40
- if (trimmed !== "" && !isNaN(Number(trimmed))) {
41
- return Number(trimmed);
83
+ if (first === 116 && val === "true") return true;
84
+ if (first === 102 && val === "false") return false;
85
+ if (first === 110 && val === "null") return null;
86
+ if (first >= 48 && first <= 57 || first === 45 || first === 46 || first === 43) {
87
+ if (NUMBER_REGEX.test(val)) {
88
+ const num = Number(val);
89
+ if (!isNaN(num)) {
90
+ return num;
91
+ }
92
+ }
42
93
  }
43
- return trimmed;
94
+ return val;
95
+ }
96
+ function parseValueWithMultiline(initialValStr, lines, currentLineIndex) {
97
+ const valStr = initialValStr;
98
+ let nextLineIndex = currentLineIndex;
99
+ let backtickCount = 0;
100
+ while (backtickCount < valStr.length && valStr.charCodeAt(backtickCount) === 96) {
101
+ backtickCount++;
102
+ }
103
+ if (backtickCount > 0) {
104
+ const termSeq = "`".repeat(backtickCount);
105
+ if (valStr.endsWith(termSeq) && valStr.length >= backtickCount * 2) {
106
+ let content = valStr.slice(backtickCount, -backtickCount);
107
+ if (backtickCount > 1 && content.startsWith(" ") && content.endsWith(" ") && content.trim() !== "") {
108
+ content = content.slice(1, -1);
109
+ }
110
+ const trimmedVal = content.trim();
111
+ if (trimmedVal === "true" || trimmedVal === "false" || trimmedVal === "null" || trimmedVal !== "" && !isNaN(Number(trimmedVal))) {
112
+ return { value: parsePrimitiveValue(trimmedVal), nextLineIndex };
113
+ } else {
114
+ return { value: content, nextLineIndex };
115
+ }
116
+ } else {
117
+ let firstLineContent = valStr.slice(backtickCount).trim();
118
+ if (backtickCount >= 3 && LANGUAGE_TAG_REGEX.test(firstLineContent)) {
119
+ firstLineContent = "";
120
+ }
121
+ let multilineContent = firstLineContent;
122
+ if (multilineContent) {
123
+ multilineContent += "\n";
124
+ }
125
+ let j = currentLineIndex + 1;
126
+ for (; j < lines.length; j++) {
127
+ const nextRawLine = lines[j];
128
+ let endIdx = nextRawLine.length - 1;
129
+ while (endIdx >= 0) {
130
+ const code = nextRawLine.charCodeAt(endIdx);
131
+ if (code === 32 || code === 9 || code === 13) {
132
+ endIdx--;
133
+ } else {
134
+ break;
135
+ }
136
+ }
137
+ let endsWithTerm = false;
138
+ if (endIdx + 1 >= backtickCount) {
139
+ endsWithTerm = true;
140
+ for (let k = 0; k < backtickCount; k++) {
141
+ if (nextRawLine.charCodeAt(endIdx - k) !== 96) {
142
+ endsWithTerm = false;
143
+ break;
144
+ }
145
+ }
146
+ }
147
+ if (endsWithTerm) {
148
+ const lastIndex = endIdx - backtickCount + 1;
149
+ const lineContent = nextRawLine.slice(0, lastIndex);
150
+ multilineContent += lineContent;
151
+ nextLineIndex = j;
152
+ break;
153
+ } else {
154
+ multilineContent += nextRawLine + "\n";
155
+ }
156
+ }
157
+ const trimmedVal = multilineContent.trim();
158
+ if (trimmedVal === "true" || trimmedVal === "false" || trimmedVal === "null" || trimmedVal !== "" && !isNaN(Number(trimmedVal))) {
159
+ return { value: parsePrimitiveValue(trimmedVal), nextLineIndex };
160
+ } else {
161
+ let cleanStr = multilineContent;
162
+ if (cleanStr.startsWith("\n")) {
163
+ cleanStr = cleanStr.slice(1);
164
+ } else if (cleanStr.startsWith("\r\n")) {
165
+ cleanStr = cleanStr.slice(2);
166
+ }
167
+ if (cleanStr.endsWith("\n")) {
168
+ cleanStr = cleanStr.slice(0, -1);
169
+ }
170
+ if (cleanStr.endsWith("\r")) {
171
+ cleanStr = cleanStr.slice(0, -1);
172
+ }
173
+ if (cleanStr.startsWith('"') && cleanStr.endsWith('"') || cleanStr.startsWith("'") && cleanStr.endsWith("'")) {
174
+ cleanStr = cleanStr.slice(1, -1);
175
+ }
176
+ return { value: cleanStr, nextLineIndex };
177
+ }
178
+ }
179
+ }
180
+ return { value: parsePrimitiveValue(valStr), nextLineIndex };
44
181
  }
45
182
  function stringifyPrimitiveValue(val) {
46
183
  if (val === null) return "null";
47
184
  if (typeof val === "boolean") return val ? "true" : "false";
48
185
  if (typeof val === "number") return String(val);
49
186
  const str = String(val);
50
- const needsQuotes = str.trim() !== str || str.includes(":") || str === "true" || str === "false" || str === "null" || !isNaN(Number(str)) && str !== "";
187
+ if (str.includes("\n")) {
188
+ let maxConsecutive = 0;
189
+ let match;
190
+ BACKTICK_REGEX.lastIndex = 0;
191
+ while ((match = BACKTICK_REGEX.exec(str)) !== null) {
192
+ if (match[0].length > maxConsecutive) {
193
+ maxConsecutive = match[0].length;
194
+ }
195
+ }
196
+ const wrapCount = maxConsecutive + 1;
197
+ const wrapSeq = "`".repeat(wrapCount);
198
+ return `${wrapSeq}
199
+ ${str}
200
+ ${wrapSeq}`;
201
+ }
202
+ if (str.includes("`")) {
203
+ let maxConsecutive = 0;
204
+ let match;
205
+ BACKTICK_REGEX.lastIndex = 0;
206
+ while ((match = BACKTICK_REGEX.exec(str)) !== null) {
207
+ if (match[0].length > maxConsecutive) {
208
+ maxConsecutive = match[0].length;
209
+ }
210
+ }
211
+ const wrapCount = maxConsecutive + 1;
212
+ const wrapSeq = "`".repeat(wrapCount);
213
+ return `${wrapSeq}${str}${wrapSeq}`;
214
+ }
215
+ const needsQuotes = str.trim() !== str || str.includes(":") || str === "true" || str === "false" || str === "null" || NUMBER_REGEX.test(str) && str !== "";
51
216
  if (needsQuotes) {
52
- return `"${str.replace(/"/g, '\\"')}"`;
217
+ return `"${str.replace(DOUBLE_QUOTE_REGEX, '\\"')}"`;
53
218
  }
54
219
  return str;
55
220
  }
56
- function parse(text) {
57
- return parseWithTrace(text).data;
221
+ function parse(text, options) {
222
+ return parseWithTrace(text, options).data;
58
223
  }
59
- function parseWithTrace(text) {
224
+ function fastParseWithTrace(text) {
60
225
  const startTime = typeof performance !== "undefined" ? performance.now() : Date.now();
61
226
  const lines = text.split(/\r?\n/);
62
- const trace = [];
63
227
  let root = {};
64
228
  let rootConvertedToArray = false;
65
229
  const stack = [];
66
- const getStackNames = () => stack.map((s) => `${"#".repeat(s.level)} ${s.key}`);
67
230
  for (let i = 0; i < lines.length; i++) {
68
231
  const rawLine = lines[i];
69
- const line = rawLine.trim();
70
- const lineNumber = i + 1;
71
- if (!line || line.startsWith("<!--")) {
72
- trace.push({
73
- lineNumber,
74
- lineText: rawLine,
75
- action: "Skipped empty line or comment",
76
- stackDepth: stack.length,
77
- currentStack: getStackNames(),
78
- status: "info"
79
- });
232
+ const len = rawLine.length;
233
+ let startIdx = 0;
234
+ while (startIdx < len) {
235
+ const code = rawLine.charCodeAt(startIdx);
236
+ if (code === 32 || code === 9 || code === 13) {
237
+ startIdx++;
238
+ } else {
239
+ break;
240
+ }
241
+ }
242
+ if (startIdx === len) {
243
+ continue;
244
+ }
245
+ const firstChar = rawLine.charCodeAt(startIdx);
246
+ if (firstChar === 60 && rawLine.startsWith("<!--", startIdx)) {
80
247
  continue;
81
248
  }
82
- if (line === "[]") {
83
- const isRootEmpty = Array.isArray(root) ? root.length === 0 : Object.keys(root).length === 0;
249
+ let endIdx = len - 1;
250
+ while (endIdx > startIdx) {
251
+ const code = rawLine.charCodeAt(endIdx);
252
+ if (code === 32 || code === 9 || code === 13) {
253
+ endIdx--;
254
+ } else {
255
+ break;
256
+ }
257
+ }
258
+ const trimmedLen = endIdx - startIdx + 1;
259
+ if (trimmedLen === 2 && rawLine.charCodeAt(startIdx) === 91 && rawLine.charCodeAt(startIdx + 1) === 93) {
260
+ let isRootEmpty = false;
261
+ if (Array.isArray(root)) {
262
+ isRootEmpty = root.length === 0;
263
+ } else {
264
+ let hasKeys = false;
265
+ for (const _ in root) {
266
+ hasKeys = true;
267
+ break;
268
+ }
269
+ isRootEmpty = !hasKeys;
270
+ }
84
271
  if (stack.length === 0 && !rootConvertedToArray && isRootEmpty) {
85
272
  root = [];
86
273
  rootConvertedToArray = true;
274
+ continue;
275
+ }
276
+ }
277
+ if (firstChar === 35) {
278
+ let hashCount = 0;
279
+ while (startIdx + hashCount <= endIdx && rawLine.charCodeAt(startIdx + hashCount) === 35) {
280
+ hashCount++;
281
+ }
282
+ let hStart = startIdx + hashCount;
283
+ while (hStart <= endIdx) {
284
+ const code = rawLine.charCodeAt(hStart);
285
+ if (code === 32 || code === 9) {
286
+ hStart++;
287
+ } else {
288
+ break;
289
+ }
290
+ }
291
+ let actualHeadingName = "";
292
+ let isExplicitArray = false;
293
+ if (hStart <= endIdx) {
294
+ if (endIdx - hStart >= 1 && rawLine.charCodeAt(endIdx - 1) === 91 && rawLine.charCodeAt(endIdx) === 93) {
295
+ isExplicitArray = true;
296
+ let hEnd = endIdx - 2;
297
+ while (hEnd >= hStart) {
298
+ const code = rawLine.charCodeAt(hEnd);
299
+ if (code === 32 || code === 9) {
300
+ hEnd--;
301
+ } else {
302
+ break;
303
+ }
304
+ }
305
+ actualHeadingName = hStart <= hEnd ? rawLine.slice(hStart, hEnd + 1) : "";
306
+ } else {
307
+ actualHeadingName = rawLine.slice(hStart, endIdx + 1);
308
+ }
309
+ }
310
+ while (stack.length > 0 && stack[stack.length - 1].level >= hashCount) {
311
+ stack.pop();
312
+ }
313
+ let activeParent = root;
314
+ let activeParentItem = null;
315
+ if (stack.length > 0) {
316
+ activeParentItem = stack[stack.length - 1];
317
+ activeParent = activeParentItem.value;
318
+ }
319
+ if (!actualHeadingName) {
320
+ if (activeParentItem && activeParentItem.isExplicitArray || Array.isArray(activeParent)) {
321
+ actualHeadingName = "";
322
+ } else {
323
+ continue;
324
+ }
325
+ }
326
+ const newNode = isExplicitArray ? [] : {};
327
+ if (activeParentItem && activeParentItem.isExplicitArray) {
328
+ activeParent.push(newNode);
329
+ } else if (Array.isArray(activeParent)) {
330
+ if (actualHeadingName === "") {
331
+ activeParent.push(newNode);
332
+ } else {
333
+ activeParent.push({ [actualHeadingName]: newNode });
334
+ }
335
+ } else {
336
+ activeParent[actualHeadingName] = newNode;
337
+ }
338
+ stack.push({
339
+ level: hashCount,
340
+ key: actualHeadingName,
341
+ parent: activeParent,
342
+ value: newNode,
343
+ type: isExplicitArray ? "array" : "object",
344
+ isExplicitArray
345
+ });
346
+ continue;
347
+ }
348
+ if (firstChar === 42 || firstChar === 45 || firstChar === 43) {
349
+ let bulletValIdx = startIdx + 1;
350
+ while (bulletValIdx <= endIdx) {
351
+ const code = rawLine.charCodeAt(bulletValIdx);
352
+ if (code === 32 || code === 9) {
353
+ bulletValIdx++;
354
+ } else {
355
+ break;
356
+ }
357
+ }
358
+ const bulletValStr = bulletValIdx <= endIdx ? rawLine.slice(bulletValIdx, endIdx + 1) : "";
359
+ let value;
360
+ if (bulletValStr.charCodeAt(0) === 96) {
361
+ const multiline = parseValueWithMultiline(bulletValStr, lines, i);
362
+ value = multiline.value;
363
+ i = multiline.nextLineIndex;
364
+ } else {
365
+ value = parsePrimitiveValue(bulletValStr);
366
+ }
367
+ if (stack.length === 0) {
368
+ if (!root._items) {
369
+ root._items = [];
370
+ }
371
+ root._items.push(value);
372
+ continue;
373
+ }
374
+ const activeItem = stack[stack.length - 1];
375
+ if (activeItem.type !== "array") {
376
+ let hasKeys = false;
377
+ for (const k in activeItem.value) {
378
+ if (Object.prototype.hasOwnProperty.call(activeItem.value, k)) {
379
+ hasKeys = true;
380
+ break;
381
+ }
382
+ }
383
+ if (!hasKeys) {
384
+ const parent = activeItem.parent;
385
+ const key = activeItem.key;
386
+ activeItem.type = "array";
387
+ activeItem.value = [];
388
+ if (Array.isArray(parent)) {
389
+ parent[parent.length - 1] = { [key]: activeItem.value };
390
+ } else {
391
+ parent[key] = activeItem.value;
392
+ }
393
+ activeItem.value.push(value);
394
+ } else {
395
+ if (!activeItem.value._items) {
396
+ activeItem.value._items = [];
397
+ }
398
+ activeItem.value._items.push(value);
399
+ }
400
+ } else {
401
+ activeItem.value.push(value);
402
+ }
403
+ continue;
404
+ }
405
+ const colonIndex = findColonIndex(rawLine, startIdx);
406
+ if (colonIndex !== -1 && colonIndex <= endIdx) {
407
+ let kEnd = colonIndex - 1;
408
+ while (kEnd >= startIdx) {
409
+ const code = rawLine.charCodeAt(kEnd);
410
+ if (code === 32 || code === 9) {
411
+ kEnd--;
412
+ } else {
413
+ break;
414
+ }
415
+ }
416
+ const key = startIdx <= kEnd ? rawLine.slice(startIdx, kEnd + 1) : "";
417
+ let vStart = colonIndex + 1;
418
+ while (vStart <= endIdx) {
419
+ const code = rawLine.charCodeAt(vStart);
420
+ if (code === 32 || code === 9) {
421
+ vStart++;
422
+ } else {
423
+ break;
424
+ }
425
+ }
426
+ const valStr = vStart <= endIdx ? rawLine.slice(vStart, endIdx + 1) : "";
427
+ let parsedVal;
428
+ if (valStr.charCodeAt(0) === 96) {
429
+ const multiline = parseValueWithMultiline(valStr, lines, i);
430
+ parsedVal = multiline.value;
431
+ i = multiline.nextLineIndex;
432
+ } else {
433
+ parsedVal = parsePrimitiveValue(valStr);
434
+ }
435
+ if (stack.length === 0) {
436
+ root[key] = parsedVal;
437
+ continue;
438
+ }
439
+ const activeItem = stack[stack.length - 1];
440
+ if (activeItem.type === "array") {
441
+ continue;
442
+ }
443
+ activeItem.value[key] = parsedVal;
444
+ continue;
445
+ }
446
+ }
447
+ const duration = (typeof performance !== "undefined" ? performance.now() : Date.now()) - startTime;
448
+ return {
449
+ data: root,
450
+ trace: [],
451
+ stats: {
452
+ parseTimeMs: duration,
453
+ linesProcessed: lines.length,
454
+ charCount: text.length,
455
+ estimatedTokens: Math.ceil(text.length / 3.8),
456
+ jsonCharCount: 0,
457
+ jsonEstimatedTokens: 0,
458
+ tokenSavingsPercent: 0
459
+ }
460
+ };
461
+ }
462
+ function parseWithTrace(text, options) {
463
+ if (options?.noTrace) {
464
+ return fastParseWithTrace(text);
465
+ }
466
+ const startTime = typeof performance !== "undefined" ? performance.now() : Date.now();
467
+ const lines = text.split(/\r?\n/);
468
+ const trace = [];
469
+ const noTrace = options?.noTrace ?? false;
470
+ let root = {};
471
+ let rootConvertedToArray = false;
472
+ const stack = [];
473
+ const getStackNames = () => {
474
+ if (noTrace) return [];
475
+ return stack.map((s) => `${"#".repeat(s.level)} ${s.key}`);
476
+ };
477
+ for (let i = 0; i < lines.length; i++) {
478
+ const rawLine = lines[i];
479
+ const lineNumber = i + 1;
480
+ let startIdx = 0;
481
+ const len = rawLine.length;
482
+ while (startIdx < len) {
483
+ const code = rawLine.charCodeAt(startIdx);
484
+ if (code === 32 || code === 9 || code === 13) {
485
+ startIdx++;
486
+ } else {
487
+ break;
488
+ }
489
+ }
490
+ if (startIdx === len) {
491
+ if (!noTrace) {
87
492
  trace.push({
88
493
  lineNumber,
89
494
  lineText: rawLine,
90
- action: 'Converted root container to an Array via top-level "[]"',
495
+ action: "Skipped empty line or comment",
91
496
  stackDepth: stack.length,
92
497
  currentStack: getStackNames(),
93
- status: "success"
498
+ status: "info"
94
499
  });
95
- continue;
500
+ }
501
+ continue;
502
+ }
503
+ const firstChar = rawLine.charCodeAt(startIdx);
504
+ if (firstChar === 60 && rawLine.startsWith("<!--", startIdx)) {
505
+ if (!noTrace) {
506
+ trace.push({
507
+ lineNumber,
508
+ lineText: rawLine,
509
+ action: "Skipped empty line or comment",
510
+ stackDepth: stack.length,
511
+ currentStack: getStackNames(),
512
+ status: "info"
513
+ });
514
+ }
515
+ continue;
516
+ }
517
+ let endIdx = len - 1;
518
+ while (endIdx > startIdx) {
519
+ const code = rawLine.charCodeAt(endIdx);
520
+ if (code === 32 || code === 9 || code === 13) {
521
+ endIdx--;
522
+ } else {
523
+ break;
96
524
  }
97
525
  }
98
- if (line.startsWith("#")) {
99
- const headingMatch = line.match(/^(#+)\s*(.*)$/);
100
- if (headingMatch) {
101
- const hashCount = headingMatch[1].length;
102
- let headingName = headingMatch[2].trim();
103
- while (stack.length > 0 && stack[stack.length - 1].level >= hashCount) {
104
- stack.pop();
526
+ const trimmedLen = endIdx - startIdx + 1;
527
+ if (trimmedLen === 2 && rawLine.charCodeAt(startIdx) === 91 && rawLine.charCodeAt(startIdx + 1) === 93) {
528
+ let isRootEmpty = false;
529
+ if (Array.isArray(root)) {
530
+ isRootEmpty = root.length === 0;
531
+ } else {
532
+ let hasKeys = false;
533
+ for (const _ in root) {
534
+ hasKeys = true;
535
+ break;
105
536
  }
106
- let activeParent = root;
107
- let activeParentItem = null;
108
- if (stack.length > 0) {
109
- activeParentItem = stack[stack.length - 1];
110
- activeParent = activeParentItem.value;
537
+ isRootEmpty = !hasKeys;
538
+ }
539
+ if (stack.length === 0 && !rootConvertedToArray && isRootEmpty) {
540
+ root = [];
541
+ rootConvertedToArray = true;
542
+ if (!noTrace) {
543
+ trace.push({
544
+ lineNumber,
545
+ lineText: rawLine,
546
+ action: 'Converted root container to an Array via top-level "[]"',
547
+ stackDepth: stack.length,
548
+ currentStack: getStackNames(),
549
+ status: "success"
550
+ });
111
551
  }
112
- if (!headingName) {
113
- if (activeParentItem && activeParentItem.isExplicitArray || Array.isArray(activeParent)) {
114
- headingName = "";
115
- } else {
552
+ continue;
553
+ }
554
+ }
555
+ if (firstChar === 35) {
556
+ let hashCount = 0;
557
+ while (startIdx + hashCount <= endIdx && rawLine.charCodeAt(startIdx + hashCount) === 35) {
558
+ hashCount++;
559
+ }
560
+ let hStart = startIdx + hashCount;
561
+ while (hStart <= endIdx) {
562
+ const code = rawLine.charCodeAt(hStart);
563
+ if (code === 32 || code === 9) {
564
+ hStart++;
565
+ } else {
566
+ break;
567
+ }
568
+ }
569
+ const headingName = hStart <= endIdx ? rawLine.slice(hStart, endIdx + 1) : "";
570
+ while (stack.length > 0 && stack[stack.length - 1].level >= hashCount) {
571
+ stack.pop();
572
+ }
573
+ let activeParent = root;
574
+ let activeParentItem = null;
575
+ if (stack.length > 0) {
576
+ activeParentItem = stack[stack.length - 1];
577
+ activeParent = activeParentItem.value;
578
+ }
579
+ let actualHeadingName = headingName;
580
+ let isExplicitArray = false;
581
+ if (headingName.endsWith("[]")) {
582
+ actualHeadingName = headingName.slice(0, -2).trim();
583
+ isExplicitArray = true;
584
+ }
585
+ if (!actualHeadingName) {
586
+ if (activeParentItem && activeParentItem.isExplicitArray || Array.isArray(activeParent)) {
587
+ actualHeadingName = "";
588
+ } else {
589
+ if (!noTrace) {
116
590
  trace.push({
117
591
  lineNumber,
118
592
  lineText: rawLine,
@@ -121,28 +595,27 @@ function parseWithTrace(text) {
121
595
  currentStack: getStackNames(),
122
596
  status: "warning"
123
597
  });
124
- continue;
125
598
  }
599
+ continue;
126
600
  }
127
- let isExplicitArray = false;
128
- if (headingName.endsWith("[]")) {
129
- headingName = headingName.slice(0, -2).trim();
130
- isExplicitArray = true;
131
- }
132
- const newNode = isExplicitArray ? [] : {};
133
- if (activeParentItem && activeParentItem.isExplicitArray) {
134
- activeParent.push(newNode);
601
+ }
602
+ const newNode = isExplicitArray ? [] : {};
603
+ if (activeParentItem && activeParentItem.isExplicitArray) {
604
+ activeParent.push(newNode);
605
+ if (!noTrace) {
135
606
  trace.push({
136
607
  lineNumber,
137
608
  lineText: rawLine,
138
- action: `Pushed new item into explicit array "${activeParentItem.key}" via heading "${headingName}"`,
609
+ action: `Pushed new item into explicit array "${activeParentItem.key}" via heading "${actualHeadingName}"`,
139
610
  stackDepth: stack.length,
140
611
  currentStack: getStackNames(),
141
612
  status: "success"
142
613
  });
143
- } else if (Array.isArray(activeParent)) {
144
- if (headingName === "") {
145
- activeParent.push(newNode);
614
+ }
615
+ } else if (Array.isArray(activeParent)) {
616
+ if (actualHeadingName === "") {
617
+ activeParent.push(newNode);
618
+ if (!noTrace) {
146
619
  trace.push({
147
620
  lineNumber,
148
621
  lineText: rawLine,
@@ -151,146 +624,233 @@ function parseWithTrace(text) {
151
624
  currentStack: getStackNames(),
152
625
  status: "success"
153
626
  });
154
- } else {
155
- activeParent.push({ [headingName]: newNode });
627
+ }
628
+ } else {
629
+ activeParent.push({ [actualHeadingName]: newNode });
630
+ if (!noTrace) {
156
631
  trace.push({
157
632
  lineNumber,
158
633
  lineText: rawLine,
159
- action: `Created heading "${headingName}" at level ${hashCount} and pushed inside parent Array`,
634
+ action: `Created heading "${actualHeadingName}" at level ${hashCount} and pushed inside parent Array`,
160
635
  stackDepth: stack.length,
161
636
  currentStack: getStackNames(),
162
637
  status: "success"
163
638
  });
164
639
  }
165
- } else {
166
- activeParent[headingName] = newNode;
640
+ }
641
+ } else {
642
+ activeParent[actualHeadingName] = newNode;
643
+ if (!noTrace) {
167
644
  trace.push({
168
645
  lineNumber,
169
646
  lineText: rawLine,
170
- action: `Created heading "${headingName}" at level ${hashCount} in parent Object${isExplicitArray ? " as Array" : ""}`,
647
+ action: `Created heading "${actualHeadingName}" at level ${hashCount} in parent Object${isExplicitArray ? " as Array" : ""}`,
171
648
  stackDepth: stack.length,
172
649
  currentStack: getStackNames(),
173
650
  status: "success"
174
651
  });
175
652
  }
176
- stack.push({
177
- level: hashCount,
178
- key: headingName,
179
- parent: activeParent,
180
- value: newNode,
181
- type: isExplicitArray ? "array" : "object",
182
- isExplicitArray
183
- });
184
- continue;
185
653
  }
654
+ stack.push({
655
+ level: hashCount,
656
+ key: actualHeadingName,
657
+ parent: activeParent,
658
+ value: newNode,
659
+ type: isExplicitArray ? "array" : "object",
660
+ isExplicitArray
661
+ });
662
+ continue;
186
663
  }
187
- const bulletMatch = line.match(/^([\*\-\+])\s*(.*)$/);
188
- if (bulletMatch) {
189
- const bulletValStr = bulletMatch[2].trim();
190
- const value = parsePrimitiveValue(bulletValStr);
664
+ if (firstChar === 42 || firstChar === 45 || firstChar === 43) {
665
+ let bulletValIdx = startIdx + 1;
666
+ while (bulletValIdx <= endIdx) {
667
+ const code = rawLine.charCodeAt(bulletValIdx);
668
+ if (code === 32 || code === 9) {
669
+ bulletValIdx++;
670
+ } else {
671
+ break;
672
+ }
673
+ }
674
+ const bulletValStr = bulletValIdx <= endIdx ? rawLine.slice(bulletValIdx, endIdx + 1) : "";
675
+ const { value, nextLineIndex } = parseValueWithMultiline(bulletValStr, lines, i);
676
+ i = nextLineIndex;
191
677
  if (stack.length === 0) {
192
678
  if (!root._items) {
193
679
  root._items = [];
194
680
  }
195
681
  root._items.push(value);
196
- trace.push({
197
- lineNumber,
198
- lineText: rawLine,
199
- action: `No active heading. Pushed bullet item to root implicit list "_items"`,
200
- stackDepth: stack.length,
201
- currentStack: getStackNames(),
202
- status: "info"
203
- });
682
+ if (!noTrace) {
683
+ trace.push({
684
+ lineNumber,
685
+ lineText: rawLine,
686
+ action: `No active heading. Pushed bullet item to root implicit list "_items"`,
687
+ stackDepth: stack.length,
688
+ currentStack: getStackNames(),
689
+ status: "info"
690
+ });
691
+ }
204
692
  continue;
205
693
  }
206
694
  const activeItem = stack[stack.length - 1];
207
695
  if (activeItem.type !== "array") {
208
- const parent = activeItem.parent;
209
- const key = activeItem.key;
210
- activeItem.type = "array";
211
- activeItem.value = [];
212
- if (Array.isArray(parent)) {
213
- parent[parent.length - 1] = { [key]: activeItem.value };
696
+ let hasKeys = false;
697
+ for (const k in activeItem.value) {
698
+ if (Object.prototype.hasOwnProperty.call(activeItem.value, k)) {
699
+ hasKeys = true;
700
+ break;
701
+ }
702
+ }
703
+ if (!hasKeys) {
704
+ const parent = activeItem.parent;
705
+ const key = activeItem.key;
706
+ activeItem.type = "array";
707
+ activeItem.value = [];
708
+ if (Array.isArray(parent)) {
709
+ parent[parent.length - 1] = { [key]: activeItem.value };
710
+ } else {
711
+ parent[key] = activeItem.value;
712
+ }
713
+ if (!noTrace) {
714
+ trace.push({
715
+ lineNumber,
716
+ lineText: rawLine,
717
+ action: `Array trigger: Converted heading "${key}" to an Array because it was an empty Object`,
718
+ stackDepth: stack.length,
719
+ currentStack: getStackNames(),
720
+ status: "info"
721
+ });
722
+ }
723
+ activeItem.value.push(value);
724
+ if (!noTrace) {
725
+ trace.push({
726
+ lineNumber,
727
+ lineText: rawLine,
728
+ action: `Pushed item "${value}" (type: ${typeof value}) into array "${activeItem.key}"`,
729
+ stackDepth: stack.length,
730
+ currentStack: getStackNames(),
731
+ status: "success"
732
+ });
733
+ }
214
734
  } else {
215
- parent[key] = activeItem.value;
735
+ if (!activeItem.value._items) {
736
+ activeItem.value._items = [];
737
+ }
738
+ activeItem.value._items.push(value);
739
+ if (!noTrace) {
740
+ trace.push({
741
+ lineNumber,
742
+ lineText: rawLine,
743
+ action: `Pushed bullet item "${value}" to implicit list "_items" inside non-empty Object under heading "${activeItem.key}"`,
744
+ stackDepth: stack.length,
745
+ currentStack: getStackNames(),
746
+ status: "info"
747
+ });
748
+ }
749
+ }
750
+ } else {
751
+ activeItem.value.push(value);
752
+ if (!noTrace) {
753
+ trace.push({
754
+ lineNumber,
755
+ lineText: rawLine,
756
+ action: `Pushed item "${value}" (type: ${typeof value}) into array "${activeItem.key}"`,
757
+ stackDepth: stack.length,
758
+ currentStack: getStackNames(),
759
+ status: "success"
760
+ });
216
761
  }
217
- trace.push({
218
- lineNumber,
219
- lineText: rawLine,
220
- action: `Array trigger: Converted heading "${key}" to an Array`,
221
- stackDepth: stack.length,
222
- currentStack: getStackNames(),
223
- status: "info"
224
- });
225
762
  }
226
- activeItem.value.push(value);
227
- trace.push({
228
- lineNumber,
229
- lineText: rawLine,
230
- action: `Pushed item "${value}" (type: ${typeof value}) into array "${activeItem.key}"`,
231
- stackDepth: stack.length,
232
- currentStack: getStackNames(),
233
- status: "success"
234
- });
235
763
  continue;
236
764
  }
237
- if (line.includes(":")) {
238
- const colonIndex = line.indexOf(":");
239
- const key = line.slice(0, colonIndex).trim();
240
- const valStr = line.slice(colonIndex + 1).trim();
241
- const parsedVal = parsePrimitiveValue(valStr);
765
+ const colonIndex = findColonIndex(rawLine, startIdx);
766
+ if (colonIndex !== -1 && colonIndex <= endIdx) {
767
+ let kEnd = colonIndex - 1;
768
+ while (kEnd >= startIdx) {
769
+ const code = rawLine.charCodeAt(kEnd);
770
+ if (code === 32 || code === 9) {
771
+ kEnd--;
772
+ } else {
773
+ break;
774
+ }
775
+ }
776
+ const key = startIdx <= kEnd ? rawLine.slice(startIdx, kEnd + 1) : "";
777
+ let vStart = colonIndex + 1;
778
+ while (vStart <= endIdx) {
779
+ const code = rawLine.charCodeAt(vStart);
780
+ if (code === 32 || code === 9) {
781
+ vStart++;
782
+ } else {
783
+ break;
784
+ }
785
+ }
786
+ const valStr = vStart <= endIdx ? rawLine.slice(vStart, endIdx + 1) : "";
787
+ const { value: parsedVal, nextLineIndex } = parseValueWithMultiline(valStr, lines, i);
788
+ i = nextLineIndex;
242
789
  if (stack.length === 0) {
243
790
  root[key] = parsedVal;
244
- trace.push({
245
- lineNumber,
246
- lineText: rawLine,
247
- action: `Set root key "${key}" to "${parsedVal}"`,
248
- stackDepth: stack.length,
249
- currentStack: getStackNames(),
250
- status: "success"
251
- });
791
+ if (!noTrace) {
792
+ trace.push({
793
+ lineNumber,
794
+ lineText: rawLine,
795
+ action: `Set root key "${key}" to "${parsedVal}"`,
796
+ stackDepth: stack.length,
797
+ currentStack: getStackNames(),
798
+ status: "success"
799
+ });
800
+ }
252
801
  continue;
253
802
  }
254
803
  const activeItem = stack[stack.length - 1];
255
804
  if (activeItem.type === "array") {
805
+ if (!noTrace) {
806
+ trace.push({
807
+ lineNumber,
808
+ lineText: rawLine,
809
+ action: `Error: Key-value "${key}" encountered inside array "${activeItem.key}"`,
810
+ stackDepth: stack.length,
811
+ currentStack: getStackNames(),
812
+ status: "error"
813
+ });
814
+ }
815
+ continue;
816
+ }
817
+ activeItem.value[key] = parsedVal;
818
+ if (!noTrace) {
256
819
  trace.push({
257
820
  lineNumber,
258
821
  lineText: rawLine,
259
- action: `Error: Key-value "${key}" encountered inside array "${activeItem.key}"`,
822
+ action: `Set key "${key}" to "${parsedVal}" in active heading "${activeItem.key}"`,
260
823
  stackDepth: stack.length,
261
824
  currentStack: getStackNames(),
262
- status: "error"
825
+ status: "success"
263
826
  });
264
- continue;
265
827
  }
266
- activeItem.value[key] = parsedVal;
828
+ continue;
829
+ }
830
+ if (!noTrace) {
267
831
  trace.push({
268
832
  lineNumber,
269
833
  lineText: rawLine,
270
- action: `Set key "${key}" to "${parsedVal}" in active heading "${activeItem.key}"`,
834
+ action: `Unparsed content: Line skipped or treated as plain text`,
271
835
  stackDepth: stack.length,
272
836
  currentStack: getStackNames(),
273
- status: "success"
837
+ status: "warning"
274
838
  });
275
- continue;
276
839
  }
277
- trace.push({
278
- lineNumber,
279
- lineText: rawLine,
280
- action: `Unparsed content: Line skipped or treated as plain text`,
281
- stackDepth: stack.length,
282
- currentStack: getStackNames(),
283
- status: "warning"
284
- });
285
840
  }
286
841
  const endTime = typeof performance !== "undefined" ? performance.now() : Date.now();
287
842
  const parseTimeMs = Number((endTime - startTime).toFixed(3));
288
843
  const charCount = text.length;
289
844
  const estimatedTokens = Math.ceil(charCount / 4.1);
290
- const jsonString = JSON.stringify(root, null, 2);
291
- const jsonCharCount = jsonString.length;
292
- const jsonEstimatedTokens = Math.ceil(jsonCharCount / 3.7);
293
- const tokenSavingsPercent = Number(((jsonEstimatedTokens - estimatedTokens) / jsonEstimatedTokens * 100).toFixed(1));
845
+ let jsonCharCount = 0;
846
+ let jsonEstimatedTokens = 0;
847
+ let tokenSavingsPercent = 0;
848
+ if (!noTrace) {
849
+ const jsonString = JSON.stringify(root, null, 2);
850
+ jsonCharCount = jsonString.length;
851
+ jsonEstimatedTokens = Math.ceil(jsonCharCount / 3.7);
852
+ tokenSavingsPercent = Number(((jsonEstimatedTokens - estimatedTokens) / jsonEstimatedTokens * 100).toFixed(1));
853
+ }
294
854
  return {
295
855
  data: root,
296
856
  trace,
@@ -368,11 +928,15 @@ function stringify(obj, level = 0, parentKey) {
368
928
  }
369
929
  // Annotate the CommonJS export names for ESM import in node:
370
930
  0 && (module.exports = {
931
+ fastParseWithTrace,
371
932
  parse,
372
933
  parseMSON,
934
+ parseMaSON,
373
935
  parsePrimitiveValue,
936
+ parseValueWithMultiline,
374
937
  parseWithTrace,
375
938
  stringify,
376
939
  stringifyMSON,
940
+ stringifyMaSON,
377
941
  stringifyPrimitiveValue
378
942
  });