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