mason-parser 1.0.0 → 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,227 +20,837 @@ 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
+ }
93
+ }
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
+ }
42
179
  }
43
- return trimmed;
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;
223
+ }
224
+ function fastParseWithTrace(text) {
225
+ const startTime = typeof performance !== "undefined" ? performance.now() : Date.now();
226
+ const lines = text.split(/\r?\n/);
227
+ let root = {};
228
+ let rootConvertedToArray = false;
229
+ const stack = [];
230
+ for (let i = 0; i < lines.length; i++) {
231
+ const rawLine = lines[i];
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)) {
247
+ continue;
248
+ }
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
+ }
271
+ if (stack.length === 0 && !rootConvertedToArray && isRootEmpty) {
272
+ root = [];
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
+ };
58
461
  }
59
- function parseWithTrace(text) {
462
+ function parseWithTrace(text, options) {
463
+ if (options?.noTrace) {
464
+ return fastParseWithTrace(text);
465
+ }
60
466
  const startTime = typeof performance !== "undefined" ? performance.now() : Date.now();
61
467
  const lines = text.split(/\r?\n/);
62
468
  const trace = [];
63
- const root = {};
469
+ const noTrace = options?.noTrace ?? false;
470
+ let root = {};
471
+ let rootConvertedToArray = false;
64
472
  const stack = [];
65
- const getStackNames = () => stack.map((s) => `${"#".repeat(s.level)} ${s.key}`);
473
+ const getStackNames = () => {
474
+ if (noTrace) return [];
475
+ return stack.map((s) => `${"#".repeat(s.level)} ${s.key}`);
476
+ };
66
477
  for (let i = 0; i < lines.length; i++) {
67
478
  const rawLine = lines[i];
68
- const line = rawLine.trim();
69
479
  const lineNumber = i + 1;
70
- if (!line || line.startsWith("<!--")) {
71
- trace.push({
72
- lineNumber,
73
- lineText: rawLine,
74
- action: "Skipped empty line or comment",
75
- stackDepth: stack.length,
76
- currentStack: getStackNames(),
77
- status: "info"
78
- });
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) {
492
+ trace.push({
493
+ lineNumber,
494
+ lineText: rawLine,
495
+ action: "Skipped empty line or comment",
496
+ stackDepth: stack.length,
497
+ currentStack: getStackNames(),
498
+ status: "info"
499
+ });
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
+ }
79
515
  continue;
80
516
  }
81
- if (line.startsWith("#")) {
82
- const headingMatch = line.match(/^(#+)\s*(.*)$/);
83
- if (headingMatch) {
84
- const hashCount = headingMatch[1].length;
85
- const headingName = headingMatch[2].trim();
86
- if (!headingName) {
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;
524
+ }
525
+ }
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;
536
+ }
537
+ isRootEmpty = !hasKeys;
538
+ }
539
+ if (stack.length === 0 && !rootConvertedToArray && isRootEmpty) {
540
+ root = [];
541
+ rootConvertedToArray = true;
542
+ if (!noTrace) {
87
543
  trace.push({
88
544
  lineNumber,
89
545
  lineText: rawLine,
90
- action: `Warning: Empty heading name at level ${hashCount}`,
546
+ action: 'Converted root container to an Array via top-level "[]"',
91
547
  stackDepth: stack.length,
92
548
  currentStack: getStackNames(),
93
- status: "warning"
549
+ status: "success"
94
550
  });
95
- continue;
96
551
  }
97
- while (stack.length > 0 && stack[stack.length - 1].level >= hashCount) {
98
- stack.pop();
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;
99
567
  }
100
- let activeParent = root;
101
- if (stack.length > 0) {
102
- const topItem = stack[stack.length - 1];
103
- activeParent = topItem.value;
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) {
590
+ trace.push({
591
+ lineNumber,
592
+ lineText: rawLine,
593
+ action: `Warning: Empty heading name at level ${hashCount}`,
594
+ stackDepth: stack.length,
595
+ currentStack: getStackNames(),
596
+ status: "warning"
597
+ });
598
+ }
599
+ continue;
104
600
  }
105
- const newNode = {};
106
- if (Array.isArray(activeParent)) {
107
- activeParent.push({ [headingName]: newNode });
601
+ }
602
+ const newNode = isExplicitArray ? [] : {};
603
+ if (activeParentItem && activeParentItem.isExplicitArray) {
604
+ activeParent.push(newNode);
605
+ if (!noTrace) {
108
606
  trace.push({
109
607
  lineNumber,
110
608
  lineText: rawLine,
111
- action: `Created heading "${headingName}" at level ${hashCount} and pushed inside parent Array`,
609
+ action: `Pushed new item into explicit array "${activeParentItem.key}" via heading "${actualHeadingName}"`,
112
610
  stackDepth: stack.length,
113
611
  currentStack: getStackNames(),
114
612
  status: "success"
115
613
  });
614
+ }
615
+ } else if (Array.isArray(activeParent)) {
616
+ if (actualHeadingName === "") {
617
+ activeParent.push(newNode);
618
+ if (!noTrace) {
619
+ trace.push({
620
+ lineNumber,
621
+ lineText: rawLine,
622
+ action: `Pushed anonymous object directly into parent Array`,
623
+ stackDepth: stack.length,
624
+ currentStack: getStackNames(),
625
+ status: "success"
626
+ });
627
+ }
116
628
  } else {
117
- activeParent[headingName] = newNode;
629
+ activeParent.push({ [actualHeadingName]: newNode });
630
+ if (!noTrace) {
631
+ trace.push({
632
+ lineNumber,
633
+ lineText: rawLine,
634
+ action: `Created heading "${actualHeadingName}" at level ${hashCount} and pushed inside parent Array`,
635
+ stackDepth: stack.length,
636
+ currentStack: getStackNames(),
637
+ status: "success"
638
+ });
639
+ }
640
+ }
641
+ } else {
642
+ activeParent[actualHeadingName] = newNode;
643
+ if (!noTrace) {
118
644
  trace.push({
119
645
  lineNumber,
120
646
  lineText: rawLine,
121
- action: `Created heading "${headingName}" at level ${hashCount} in parent Object`,
647
+ action: `Created heading "${actualHeadingName}" at level ${hashCount} in parent Object${isExplicitArray ? " as Array" : ""}`,
122
648
  stackDepth: stack.length,
123
649
  currentStack: getStackNames(),
124
650
  status: "success"
125
651
  });
126
652
  }
127
- stack.push({
128
- level: hashCount,
129
- key: headingName,
130
- parent: activeParent,
131
- value: newNode,
132
- type: "object"
133
- });
134
- continue;
135
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;
136
663
  }
137
- const bulletMatch = line.match(/^([\*\-\+])\s*(.*)$/);
138
- if (bulletMatch) {
139
- const bulletValStr = bulletMatch[2].trim();
140
- 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;
141
677
  if (stack.length === 0) {
142
678
  if (!root._items) {
143
679
  root._items = [];
144
680
  }
145
681
  root._items.push(value);
146
- trace.push({
147
- lineNumber,
148
- lineText: rawLine,
149
- action: `No active heading. Pushed bullet item to root implicit list "_items"`,
150
- stackDepth: stack.length,
151
- currentStack: getStackNames(),
152
- status: "info"
153
- });
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
+ }
154
692
  continue;
155
693
  }
156
694
  const activeItem = stack[stack.length - 1];
157
695
  if (activeItem.type !== "array") {
158
- const parent = activeItem.parent;
159
- const key = activeItem.key;
160
- activeItem.type = "array";
161
- activeItem.value = [];
162
- if (Array.isArray(parent)) {
163
- 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
+ }
164
734
  } else {
165
- 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
+ });
166
761
  }
167
- trace.push({
168
- lineNumber,
169
- lineText: rawLine,
170
- action: `Array trigger: Converted heading "${key}" to an Array`,
171
- stackDepth: stack.length,
172
- currentStack: getStackNames(),
173
- status: "info"
174
- });
175
762
  }
176
- activeItem.value.push(value);
177
- trace.push({
178
- lineNumber,
179
- lineText: rawLine,
180
- action: `Pushed item "${value}" (type: ${typeof value}) into array "${activeItem.key}"`,
181
- stackDepth: stack.length,
182
- currentStack: getStackNames(),
183
- status: "success"
184
- });
185
763
  continue;
186
764
  }
187
- if (line.includes(":")) {
188
- const colonIndex = line.indexOf(":");
189
- const key = line.slice(0, colonIndex).trim();
190
- const valStr = line.slice(colonIndex + 1).trim();
191
- 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;
192
789
  if (stack.length === 0) {
193
790
  root[key] = parsedVal;
194
- trace.push({
195
- lineNumber,
196
- lineText: rawLine,
197
- action: `Set root key "${key}" to "${parsedVal}"`,
198
- stackDepth: stack.length,
199
- currentStack: getStackNames(),
200
- status: "success"
201
- });
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
+ }
202
801
  continue;
203
802
  }
204
803
  const activeItem = stack[stack.length - 1];
205
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) {
206
819
  trace.push({
207
820
  lineNumber,
208
821
  lineText: rawLine,
209
- action: `Error: Key-value "${key}" encountered inside array "${activeItem.key}"`,
822
+ action: `Set key "${key}" to "${parsedVal}" in active heading "${activeItem.key}"`,
210
823
  stackDepth: stack.length,
211
824
  currentStack: getStackNames(),
212
- status: "error"
825
+ status: "success"
213
826
  });
214
- continue;
215
827
  }
216
- activeItem.value[key] = parsedVal;
828
+ continue;
829
+ }
830
+ if (!noTrace) {
217
831
  trace.push({
218
832
  lineNumber,
219
833
  lineText: rawLine,
220
- action: `Set key "${key}" to "${parsedVal}" in active heading "${activeItem.key}"`,
834
+ action: `Unparsed content: Line skipped or treated as plain text`,
221
835
  stackDepth: stack.length,
222
836
  currentStack: getStackNames(),
223
- status: "success"
837
+ status: "warning"
224
838
  });
225
- continue;
226
839
  }
227
- trace.push({
228
- lineNumber,
229
- lineText: rawLine,
230
- action: `Unparsed content: Line skipped or treated as plain text`,
231
- stackDepth: stack.length,
232
- currentStack: getStackNames(),
233
- status: "warning"
234
- });
235
840
  }
236
841
  const endTime = typeof performance !== "undefined" ? performance.now() : Date.now();
237
842
  const parseTimeMs = Number((endTime - startTime).toFixed(3));
238
843
  const charCount = text.length;
239
844
  const estimatedTokens = Math.ceil(charCount / 4.1);
240
- const jsonString = JSON.stringify(root, null, 2);
241
- const jsonCharCount = jsonString.length;
242
- const jsonEstimatedTokens = Math.ceil(jsonCharCount / 3.7);
243
- 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
+ }
244
854
  return {
245
855
  data: root,
246
856
  trace,
@@ -255,14 +865,35 @@ function parseWithTrace(text) {
255
865
  }
256
866
  };
257
867
  }
258
- function stringify(obj, level = 0) {
868
+ function stringify(obj, level = 0, parentKey) {
259
869
  if (obj === null || obj === void 0) return "";
260
870
  let output = "";
261
871
  const hashes = (lvl) => "#".repeat(lvl);
262
872
  if (Array.isArray(obj)) {
873
+ if (level === 0) {
874
+ output += "[]\n\n";
875
+ }
876
+ let itemName = "Item";
877
+ if (parentKey) {
878
+ if (parentKey.endsWith("ies")) {
879
+ itemName = parentKey.slice(0, -3) + "y";
880
+ } else if (parentKey.endsWith("s") && !parentKey.endsWith("ss")) {
881
+ itemName = parentKey.slice(0, -1);
882
+ } else {
883
+ itemName = parentKey;
884
+ }
885
+ itemName = itemName.charAt(0).toUpperCase() + itemName.slice(1);
886
+ }
263
887
  for (const item of obj) {
264
888
  if (typeof item === "object" && item !== null) {
265
- output += stringify(item, level);
889
+ const nextLevel = level + 1;
890
+ output += `${hashes(nextLevel)} ${itemName}
891
+ `;
892
+ const nestedString = stringify(item, nextLevel);
893
+ if (nestedString) {
894
+ output += nestedString;
895
+ }
896
+ output += "\n";
266
897
  } else {
267
898
  output += `* ${stringifyPrimitiveValue(item)}
268
899
  `;
@@ -281,10 +912,12 @@ function stringify(obj, level = 0) {
281
912
  }
282
913
  for (const key of complex) {
283
914
  const nextLevel = level + 1;
284
- output += `${hashes(nextLevel)} ${key}
285
- `;
286
915
  const val = obj[key];
287
- const nestedString = stringify(val, nextLevel);
916
+ const isArrayOfObjects = Array.isArray(val) && val.some((item) => typeof item === "object" && item !== null);
917
+ const headingNameSuffix = isArrayOfObjects ? "[]" : "";
918
+ output += `${hashes(nextLevel)} ${key}${headingNameSuffix}
919
+ `;
920
+ const nestedString = stringify(val, nextLevel, key);
288
921
  if (nestedString) {
289
922
  output += nestedString;
290
923
  }
@@ -295,11 +928,15 @@ function stringify(obj, level = 0) {
295
928
  }
296
929
  // Annotate the CommonJS export names for ESM import in node:
297
930
  0 && (module.exports = {
931
+ fastParseWithTrace,
298
932
  parse,
299
933
  parseMSON,
934
+ parseMaSON,
300
935
  parsePrimitiveValue,
936
+ parseValueWithMultiline,
301
937
  parseWithTrace,
302
938
  stringify,
303
939
  stringifyMSON,
940
+ stringifyMaSON,
304
941
  stringifyPrimitiveValue
305
942
  });