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.mjs CHANGED
@@ -1,88 +1,558 @@
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
+ }
12
59
  }
13
- return trimmed;
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
+ }
145
+ }
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;
28
189
  }
29
- function parseWithTrace(text) {
190
+ function fastParseWithTrace(text) {
30
191
  const startTime = typeof performance !== "undefined" ? performance.now() : Date.now();
31
192
  const lines = text.split(/\r?\n/);
32
- const trace = [];
33
193
  let root = {};
34
194
  let rootConvertedToArray = false;
35
195
  const stack = [];
36
- const getStackNames = () => stack.map((s) => `${"#".repeat(s.level)} ${s.key}`);
37
196
  for (let i = 0; i < lines.length; i++) {
38
197
  const rawLine = lines[i];
39
- const line = rawLine.trim();
40
- const lineNumber = i + 1;
41
- if (!line || line.startsWith("<!--")) {
42
- trace.push({
43
- lineNumber,
44
- lineText: rawLine,
45
- action: "Skipped empty line or comment",
46
- stackDepth: stack.length,
47
- currentStack: getStackNames(),
48
- status: "info"
49
- });
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)) {
50
213
  continue;
51
214
  }
52
- if (line === "[]") {
53
- const isRootEmpty = Array.isArray(root) ? root.length === 0 : Object.keys(root).length === 0;
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
+ }
54
237
  if (stack.length === 0 && !rootConvertedToArray && isRootEmpty) {
55
238
  root = [];
56
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
+ };
427
+ }
428
+ function parseWithTrace(text, options) {
429
+ if (options?.noTrace) {
430
+ return fastParseWithTrace(text);
431
+ }
432
+ const startTime = typeof performance !== "undefined" ? performance.now() : Date.now();
433
+ const lines = text.split(/\r?\n/);
434
+ const trace = [];
435
+ const noTrace = options?.noTrace ?? false;
436
+ let root = {};
437
+ let rootConvertedToArray = false;
438
+ const stack = [];
439
+ const getStackNames = () => {
440
+ if (noTrace) return [];
441
+ return stack.map((s) => `${"#".repeat(s.level)} ${s.key}`);
442
+ };
443
+ for (let i = 0; i < lines.length; i++) {
444
+ const rawLine = lines[i];
445
+ const lineNumber = i + 1;
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) {
57
458
  trace.push({
58
459
  lineNumber,
59
460
  lineText: rawLine,
60
- action: 'Converted root container to an Array via top-level "[]"',
461
+ action: "Skipped empty line or comment",
61
462
  stackDepth: stack.length,
62
463
  currentStack: getStackNames(),
63
- status: "success"
464
+ status: "info"
64
465
  });
65
- continue;
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
+ }
481
+ continue;
482
+ }
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;
66
490
  }
67
491
  }
68
- if (line.startsWith("#")) {
69
- const headingMatch = line.match(/^(#+)\s*(.*)$/);
70
- if (headingMatch) {
71
- const hashCount = headingMatch[1].length;
72
- let headingName = headingMatch[2].trim();
73
- while (stack.length > 0 && stack[stack.length - 1].level >= hashCount) {
74
- stack.pop();
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;
75
502
  }
76
- let activeParent = root;
77
- let activeParentItem = null;
78
- if (stack.length > 0) {
79
- activeParentItem = stack[stack.length - 1];
80
- activeParent = activeParentItem.value;
503
+ isRootEmpty = !hasKeys;
504
+ }
505
+ if (stack.length === 0 && !rootConvertedToArray && isRootEmpty) {
506
+ root = [];
507
+ rootConvertedToArray = true;
508
+ if (!noTrace) {
509
+ trace.push({
510
+ lineNumber,
511
+ lineText: rawLine,
512
+ action: 'Converted root container to an Array via top-level "[]"',
513
+ stackDepth: stack.length,
514
+ currentStack: getStackNames(),
515
+ status: "success"
516
+ });
81
517
  }
82
- if (!headingName) {
83
- if (activeParentItem && activeParentItem.isExplicitArray || Array.isArray(activeParent)) {
84
- headingName = "";
85
- } else {
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;
533
+ }
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) {
86
556
  trace.push({
87
557
  lineNumber,
88
558
  lineText: rawLine,
@@ -91,28 +561,27 @@ function parseWithTrace(text) {
91
561
  currentStack: getStackNames(),
92
562
  status: "warning"
93
563
  });
94
- continue;
95
564
  }
565
+ continue;
96
566
  }
97
- let isExplicitArray = false;
98
- if (headingName.endsWith("[]")) {
99
- headingName = headingName.slice(0, -2).trim();
100
- isExplicitArray = true;
101
- }
102
- const newNode = isExplicitArray ? [] : {};
103
- if (activeParentItem && activeParentItem.isExplicitArray) {
104
- activeParent.push(newNode);
567
+ }
568
+ const newNode = isExplicitArray ? [] : {};
569
+ if (activeParentItem && activeParentItem.isExplicitArray) {
570
+ activeParent.push(newNode);
571
+ if (!noTrace) {
105
572
  trace.push({
106
573
  lineNumber,
107
574
  lineText: rawLine,
108
- action: `Pushed new item into explicit array "${activeParentItem.key}" via heading "${headingName}"`,
575
+ action: `Pushed new item into explicit array "${activeParentItem.key}" via heading "${actualHeadingName}"`,
109
576
  stackDepth: stack.length,
110
577
  currentStack: getStackNames(),
111
578
  status: "success"
112
579
  });
113
- } else if (Array.isArray(activeParent)) {
114
- if (headingName === "") {
115
- activeParent.push(newNode);
580
+ }
581
+ } else if (Array.isArray(activeParent)) {
582
+ if (actualHeadingName === "") {
583
+ activeParent.push(newNode);
584
+ if (!noTrace) {
116
585
  trace.push({
117
586
  lineNumber,
118
587
  lineText: rawLine,
@@ -121,146 +590,233 @@ function parseWithTrace(text) {
121
590
  currentStack: getStackNames(),
122
591
  status: "success"
123
592
  });
124
- } else {
125
- activeParent.push({ [headingName]: newNode });
593
+ }
594
+ } else {
595
+ activeParent.push({ [actualHeadingName]: newNode });
596
+ if (!noTrace) {
126
597
  trace.push({
127
598
  lineNumber,
128
599
  lineText: rawLine,
129
- action: `Created heading "${headingName}" at level ${hashCount} and pushed inside parent Array`,
600
+ action: `Created heading "${actualHeadingName}" at level ${hashCount} and pushed inside parent Array`,
130
601
  stackDepth: stack.length,
131
602
  currentStack: getStackNames(),
132
603
  status: "success"
133
604
  });
134
605
  }
135
- } else {
136
- activeParent[headingName] = newNode;
606
+ }
607
+ } else {
608
+ activeParent[actualHeadingName] = newNode;
609
+ if (!noTrace) {
137
610
  trace.push({
138
611
  lineNumber,
139
612
  lineText: rawLine,
140
- action: `Created heading "${headingName}" at level ${hashCount} in parent Object${isExplicitArray ? " as Array" : ""}`,
613
+ action: `Created heading "${actualHeadingName}" at level ${hashCount} in parent Object${isExplicitArray ? " as Array" : ""}`,
141
614
  stackDepth: stack.length,
142
615
  currentStack: getStackNames(),
143
616
  status: "success"
144
617
  });
145
618
  }
146
- stack.push({
147
- level: hashCount,
148
- key: headingName,
149
- parent: activeParent,
150
- value: newNode,
151
- type: isExplicitArray ? "array" : "object",
152
- isExplicitArray
153
- });
154
- continue;
155
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;
156
629
  }
157
- const bulletMatch = line.match(/^([\*\-\+])\s*(.*)$/);
158
- if (bulletMatch) {
159
- const bulletValStr = bulletMatch[2].trim();
160
- 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;
161
643
  if (stack.length === 0) {
162
644
  if (!root._items) {
163
645
  root._items = [];
164
646
  }
165
647
  root._items.push(value);
166
- trace.push({
167
- lineNumber,
168
- lineText: rawLine,
169
- action: `No active heading. Pushed bullet item to root implicit list "_items"`,
170
- stackDepth: stack.length,
171
- currentStack: getStackNames(),
172
- status: "info"
173
- });
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
+ }
174
658
  continue;
175
659
  }
176
660
  const activeItem = stack[stack.length - 1];
177
661
  if (activeItem.type !== "array") {
178
- const parent = activeItem.parent;
179
- const key = activeItem.key;
180
- activeItem.type = "array";
181
- activeItem.value = [];
182
- if (Array.isArray(parent)) {
183
- 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
+ }
184
700
  } else {
185
- 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
+ });
186
727
  }
187
- trace.push({
188
- lineNumber,
189
- lineText: rawLine,
190
- action: `Array trigger: Converted heading "${key}" to an Array`,
191
- stackDepth: stack.length,
192
- currentStack: getStackNames(),
193
- status: "info"
194
- });
195
728
  }
196
- activeItem.value.push(value);
197
- trace.push({
198
- lineNumber,
199
- lineText: rawLine,
200
- action: `Pushed item "${value}" (type: ${typeof value}) into array "${activeItem.key}"`,
201
- stackDepth: stack.length,
202
- currentStack: getStackNames(),
203
- status: "success"
204
- });
205
729
  continue;
206
730
  }
207
- if (line.includes(":")) {
208
- const colonIndex = line.indexOf(":");
209
- const key = line.slice(0, colonIndex).trim();
210
- const valStr = line.slice(colonIndex + 1).trim();
211
- 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;
212
755
  if (stack.length === 0) {
213
756
  root[key] = parsedVal;
214
- trace.push({
215
- lineNumber,
216
- lineText: rawLine,
217
- action: `Set root key "${key}" to "${parsedVal}"`,
218
- stackDepth: stack.length,
219
- currentStack: getStackNames(),
220
- status: "success"
221
- });
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
+ }
222
767
  continue;
223
768
  }
224
769
  const activeItem = stack[stack.length - 1];
225
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) {
226
785
  trace.push({
227
786
  lineNumber,
228
787
  lineText: rawLine,
229
- action: `Error: Key-value "${key}" encountered inside array "${activeItem.key}"`,
788
+ action: `Set key "${key}" to "${parsedVal}" in active heading "${activeItem.key}"`,
230
789
  stackDepth: stack.length,
231
790
  currentStack: getStackNames(),
232
- status: "error"
791
+ status: "success"
233
792
  });
234
- continue;
235
793
  }
236
- activeItem.value[key] = parsedVal;
794
+ continue;
795
+ }
796
+ if (!noTrace) {
237
797
  trace.push({
238
798
  lineNumber,
239
799
  lineText: rawLine,
240
- action: `Set key "${key}" to "${parsedVal}" in active heading "${activeItem.key}"`,
800
+ action: `Unparsed content: Line skipped or treated as plain text`,
241
801
  stackDepth: stack.length,
242
802
  currentStack: getStackNames(),
243
- status: "success"
803
+ status: "warning"
244
804
  });
245
- continue;
246
805
  }
247
- trace.push({
248
- lineNumber,
249
- lineText: rawLine,
250
- action: `Unparsed content: Line skipped or treated as plain text`,
251
- stackDepth: stack.length,
252
- currentStack: getStackNames(),
253
- status: "warning"
254
- });
255
806
  }
256
807
  const endTime = typeof performance !== "undefined" ? performance.now() : Date.now();
257
808
  const parseTimeMs = Number((endTime - startTime).toFixed(3));
258
809
  const charCount = text.length;
259
810
  const estimatedTokens = Math.ceil(charCount / 4.1);
260
- const jsonString = JSON.stringify(root, null, 2);
261
- const jsonCharCount = jsonString.length;
262
- const jsonEstimatedTokens = Math.ceil(jsonCharCount / 3.7);
263
- 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
+ }
264
820
  return {
265
821
  data: root,
266
822
  trace,
@@ -337,11 +893,15 @@ function stringify(obj, level = 0, parentKey) {
337
893
  return output.trim() + "\n";
338
894
  }
339
895
  export {
896
+ fastParseWithTrace,
340
897
  parse,
341
898
  parse as parseMSON,
899
+ parseWithTrace as parseMaSON,
342
900
  parsePrimitiveValue,
901
+ parseValueWithMultiline,
343
902
  parseWithTrace,
344
903
  stringify,
345
904
  stringify as stringifyMSON,
905
+ stringify as stringifyMaSON,
346
906
  stringifyPrimitiveValue
347
907
  };