links-notation 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +28 -0
- package/.prettierignore +4 -0
- package/.prettierrc +4 -0
- package/README.md +52 -7
- package/README.ru.md +3 -6
- package/bun.lock +199 -0
- package/dist/index.js +391 -98
- package/package.json +6 -3
- package/src/FormatOptions.js +1 -1
- package/src/Link.js +71 -33
- package/src/LinksGroup.js +2 -2
- package/src/Parser.js +15 -15
- package/src/grammar.pegjs +105 -4
- package/src/index.js +1 -1
- package/src/parser-generated.js +401 -88
- package/tests/ApiTests.test.js +2 -2
- package/tests/EdgeCaseParser.test.js +3 -3
- package/tests/FormatConfig.test.js +32 -10
- package/tests/IndentedIdSyntax.test.js +39 -39
- package/tests/Link.test.js +2 -2
- package/tests/LinksGroup.test.js +8 -5
- package/tests/MixedIndentationModes.test.js +35 -35
- package/tests/MultiQuoteParser.test.js +463 -0
- package/tests/MultilineParser.test.js +12 -12
- package/tests/MultilineQuotedString.test.js +9 -9
- package/tests/NestedParser.test.js +5 -3
- package/tests/NestedSelfReference.test.js +4 -2
- package/tests/SingleLineParser.test.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "links-notation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Links Notation parser for JavaScript",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"build": "bun run build:grammar && bun build src/index.js --outdir=dist --target=node",
|
|
9
9
|
"build:grammar": "peggy --format es -o src/parser-generated.js src/grammar.pegjs",
|
|
10
10
|
"test": "bun test",
|
|
11
|
-
"test:watch": "bun test --watch"
|
|
11
|
+
"test:watch": "bun test --watch",
|
|
12
|
+
"lint": "eslint src/ tests/",
|
|
13
|
+
"lint:fix": "eslint src/ tests/ --fix"
|
|
12
14
|
},
|
|
13
15
|
"keywords": [
|
|
14
16
|
"lino",
|
|
@@ -20,7 +22,8 @@
|
|
|
20
22
|
"license": "Unlicense",
|
|
21
23
|
"devDependencies": {
|
|
22
24
|
"bun-types": "^1.2.19",
|
|
25
|
+
"eslint": "^8.56.0",
|
|
23
26
|
"peggy": "^5.0.6"
|
|
24
27
|
},
|
|
25
28
|
"dependencies": {}
|
|
26
|
-
}
|
|
29
|
+
}
|
package/src/FormatOptions.js
CHANGED
|
@@ -21,7 +21,7 @@ export class FormatOptions {
|
|
|
21
21
|
this.indentLongLines = options.indentLongLines ?? false;
|
|
22
22
|
this.maxInlineRefs = options.maxInlineRefs ?? null;
|
|
23
23
|
this.groupConsecutive = options.groupConsecutive ?? false;
|
|
24
|
-
this.indentString = options.indentString ??
|
|
24
|
+
this.indentString = options.indentString ?? ' ';
|
|
25
25
|
this.preferInline = options.preferInline ?? true;
|
|
26
26
|
}
|
|
27
27
|
|
package/src/Link.js
CHANGED
|
@@ -32,8 +32,9 @@ export class Link {
|
|
|
32
32
|
* @returns {string} Space-separated string of values
|
|
33
33
|
*/
|
|
34
34
|
getValuesString() {
|
|
35
|
-
return
|
|
36
|
-
|
|
35
|
+
return !this.values || this.values.length === 0
|
|
36
|
+
? ''
|
|
37
|
+
: this.values.map((v) => Link.getValueString(v)).join(' ');
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -46,7 +47,7 @@ export class Link {
|
|
|
46
47
|
} else if (this.values.length === 1) {
|
|
47
48
|
return this.values[0];
|
|
48
49
|
} else {
|
|
49
|
-
const newValues = this.values.map(v => {
|
|
50
|
+
const newValues = this.values.map((v) => {
|
|
50
51
|
// Check if value has simplify method (defensive programming)
|
|
51
52
|
return v && typeof v.simplify === 'function' ? v.simplify() : v;
|
|
52
53
|
});
|
|
@@ -70,7 +71,9 @@ export class Link {
|
|
|
70
71
|
*/
|
|
71
72
|
static getValueString(value) {
|
|
72
73
|
// Defensive check for method existence
|
|
73
|
-
return value && typeof value.toLinkOrIdString === 'function'
|
|
74
|
+
return value && typeof value.toLinkOrIdString === 'function'
|
|
75
|
+
? value.toLinkOrIdString()
|
|
76
|
+
: String(value);
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
/**
|
|
@@ -164,7 +167,6 @@ export class Link {
|
|
|
164
167
|
return true;
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
|
|
168
170
|
/**
|
|
169
171
|
* Format the link as a string
|
|
170
172
|
* @param {boolean|FormatOptions} lessParentheses - If true, omit parentheses where safe; or a FormatOptions object
|
|
@@ -174,8 +176,12 @@ export class Link {
|
|
|
174
176
|
format(lessParentheses = false, isCompoundValue = false) {
|
|
175
177
|
// Support FormatOptions/FormatConfig as first parameter
|
|
176
178
|
// Check if it's an object with formatting properties (FormatOptions or FormatConfig)
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
+
if (
|
|
180
|
+
lessParentheses &&
|
|
181
|
+
typeof lessParentheses === 'object' &&
|
|
182
|
+
(lessParentheses.constructor.name === 'FormatOptions' ||
|
|
183
|
+
lessParentheses.constructor.name === 'FormatConfig')
|
|
184
|
+
) {
|
|
179
185
|
return this._formatWithOptions(lessParentheses, isCompoundValue);
|
|
180
186
|
}
|
|
181
187
|
|
|
@@ -184,7 +190,7 @@ export class Link {
|
|
|
184
190
|
if (this.id === null && (!this.values || this.values.length === 0)) {
|
|
185
191
|
return lessParentheses ? '' : '()';
|
|
186
192
|
}
|
|
187
|
-
|
|
193
|
+
|
|
188
194
|
// Link with only ID, no values
|
|
189
195
|
if (!this.values || this.values.length === 0) {
|
|
190
196
|
const escapedId = Link.escapeReference(this.id);
|
|
@@ -192,21 +198,27 @@ export class Link {
|
|
|
192
198
|
if (isCompoundValue) {
|
|
193
199
|
return `(${escapedId})`;
|
|
194
200
|
}
|
|
195
|
-
return lessParentheses && !this.needsParentheses(this.id)
|
|
201
|
+
return lessParentheses && !this.needsParentheses(this.id)
|
|
202
|
+
? escapedId
|
|
203
|
+
: `(${escapedId})`;
|
|
196
204
|
}
|
|
197
|
-
|
|
198
|
-
// Format values recursively
|
|
199
|
-
const valuesStr = this.values.map(v => this.formatValue(v)).join(' ');
|
|
200
|
-
|
|
205
|
+
|
|
206
|
+
// Format values recursively
|
|
207
|
+
const valuesStr = this.values.map((v) => this.formatValue(v)).join(' ');
|
|
208
|
+
|
|
201
209
|
// Link with values only (null id)
|
|
202
210
|
if (this.id === null) {
|
|
203
211
|
// For lessParentheses mode with simple values, don't wrap the whole thing
|
|
204
212
|
if (lessParentheses) {
|
|
205
213
|
// Check if all values are simple (no nested values)
|
|
206
|
-
const allSimple = this.values.every(
|
|
214
|
+
const allSimple = this.values.every(
|
|
215
|
+
(v) => !v.values || v.values.length === 0
|
|
216
|
+
);
|
|
207
217
|
if (allSimple) {
|
|
208
218
|
// Format each value without extra wrapping
|
|
209
|
-
const simpleValuesStr = this.values
|
|
219
|
+
const simpleValuesStr = this.values
|
|
220
|
+
.map((v) => Link.escapeReference(v.id))
|
|
221
|
+
.join(' ');
|
|
210
222
|
return simpleValuesStr;
|
|
211
223
|
}
|
|
212
224
|
// For mixed or complex values in lessParentheses mode, still avoid outer wrapper
|
|
@@ -216,13 +228,15 @@ export class Link {
|
|
|
216
228
|
// For normal mode, wrap in parentheses
|
|
217
229
|
return `(${valuesStr})`;
|
|
218
230
|
}
|
|
219
|
-
|
|
231
|
+
|
|
220
232
|
// Link with ID and values
|
|
221
233
|
const idStr = Link.escapeReference(this.id);
|
|
222
234
|
const withColon = `${idStr}: ${valuesStr}`;
|
|
223
|
-
return lessParentheses && !this.needsParentheses(this.id)
|
|
235
|
+
return lessParentheses && !this.needsParentheses(this.id)
|
|
236
|
+
? withColon
|
|
237
|
+
: `(${withColon})`;
|
|
224
238
|
}
|
|
225
|
-
|
|
239
|
+
|
|
226
240
|
/**
|
|
227
241
|
* Format a value within this link
|
|
228
242
|
* @param {Link} value - The value to format
|
|
@@ -257,7 +271,13 @@ export class Link {
|
|
|
257
271
|
* @returns {boolean} True if parentheses are needed
|
|
258
272
|
*/
|
|
259
273
|
needsParentheses(str) {
|
|
260
|
-
return
|
|
274
|
+
return (
|
|
275
|
+
str &&
|
|
276
|
+
(str.includes(' ') ||
|
|
277
|
+
str.includes(':') ||
|
|
278
|
+
str.includes('(') ||
|
|
279
|
+
str.includes(')'))
|
|
280
|
+
);
|
|
261
281
|
}
|
|
262
282
|
|
|
263
283
|
/**
|
|
@@ -278,7 +298,9 @@ export class Link {
|
|
|
278
298
|
if (isCompoundValue) {
|
|
279
299
|
return `(${escapedId})`;
|
|
280
300
|
}
|
|
281
|
-
return options.lessParentheses && !this.needsParentheses(this.id)
|
|
301
|
+
return options.lessParentheses && !this.needsParentheses(this.id)
|
|
302
|
+
? escapedId
|
|
303
|
+
: `(${escapedId})`;
|
|
282
304
|
}
|
|
283
305
|
|
|
284
306
|
// Check if we should use indented format
|
|
@@ -287,11 +309,13 @@ export class Link {
|
|
|
287
309
|
shouldIndent = true;
|
|
288
310
|
} else {
|
|
289
311
|
// Try inline format first
|
|
290
|
-
const valuesStr = this.values.map(v => this.formatValue(v)).join(' ');
|
|
312
|
+
const valuesStr = this.values.map((v) => this.formatValue(v)).join(' ');
|
|
291
313
|
let testLine;
|
|
292
314
|
if (this.id !== null) {
|
|
293
315
|
const idStr = Link.escapeReference(this.id);
|
|
294
|
-
testLine = options.lessParentheses
|
|
316
|
+
testLine = options.lessParentheses
|
|
317
|
+
? `${idStr}: ${valuesStr}`
|
|
318
|
+
: `(${idStr}: ${valuesStr})`;
|
|
295
319
|
} else {
|
|
296
320
|
testLine = options.lessParentheses ? valuesStr : `(${valuesStr})`;
|
|
297
321
|
}
|
|
@@ -307,14 +331,16 @@ export class Link {
|
|
|
307
331
|
}
|
|
308
332
|
|
|
309
333
|
// Standard inline formatting
|
|
310
|
-
const valuesStr = this.values.map(v => this.formatValue(v)).join(' ');
|
|
334
|
+
const valuesStr = this.values.map((v) => this.formatValue(v)).join(' ');
|
|
311
335
|
|
|
312
336
|
// Link with values only (null id)
|
|
313
337
|
if (this.id === null) {
|
|
314
338
|
if (options.lessParentheses) {
|
|
315
|
-
const allSimple = this.values.every(
|
|
339
|
+
const allSimple = this.values.every(
|
|
340
|
+
(v) => !v.values || v.values.length === 0
|
|
341
|
+
);
|
|
316
342
|
if (allSimple) {
|
|
317
|
-
return this.values.map(v => Link.escapeReference(v.id)).join(' ');
|
|
343
|
+
return this.values.map((v) => Link.escapeReference(v.id)).join(' ');
|
|
318
344
|
}
|
|
319
345
|
return valuesStr;
|
|
320
346
|
}
|
|
@@ -324,7 +350,9 @@ export class Link {
|
|
|
324
350
|
// Link with ID and values
|
|
325
351
|
const idStr = Link.escapeReference(this.id);
|
|
326
352
|
const withColon = `${idStr}: ${valuesStr}`;
|
|
327
|
-
return options.lessParentheses && !this.needsParentheses(this.id)
|
|
353
|
+
return options.lessParentheses && !this.needsParentheses(this.id)
|
|
354
|
+
? withColon
|
|
355
|
+
: `(${withColon})`;
|
|
328
356
|
}
|
|
329
357
|
|
|
330
358
|
/**
|
|
@@ -335,7 +363,9 @@ export class Link {
|
|
|
335
363
|
_formatIndented(options) {
|
|
336
364
|
if (this.id === null) {
|
|
337
365
|
// Values only - format each on separate line
|
|
338
|
-
const lines = this.values.map(
|
|
366
|
+
const lines = this.values.map(
|
|
367
|
+
(v) => options.indentString + this.formatValue(v)
|
|
368
|
+
);
|
|
339
369
|
return lines.join('\n');
|
|
340
370
|
}
|
|
341
371
|
|
|
@@ -373,7 +403,11 @@ function _groupConsecutiveLinks(links) {
|
|
|
373
403
|
|
|
374
404
|
while (j < links.length) {
|
|
375
405
|
const nextLink = links[j];
|
|
376
|
-
if (
|
|
406
|
+
if (
|
|
407
|
+
nextLink.id === current.id &&
|
|
408
|
+
nextLink.values &&
|
|
409
|
+
nextLink.values.length > 0
|
|
410
|
+
) {
|
|
377
411
|
sameIdValues.push(...nextLink.values);
|
|
378
412
|
j++;
|
|
379
413
|
} else {
|
|
@@ -401,17 +435,21 @@ export function formatLinks(links, lessParentheses = false) {
|
|
|
401
435
|
if (!links || links.length === 0) return '';
|
|
402
436
|
|
|
403
437
|
// Support FormatOptions/FormatConfig as parameter
|
|
404
|
-
if (
|
|
405
|
-
|
|
438
|
+
if (
|
|
439
|
+
lessParentheses &&
|
|
440
|
+
typeof lessParentheses === 'object' &&
|
|
441
|
+
(lessParentheses.constructor.name === 'FormatOptions' ||
|
|
442
|
+
lessParentheses.constructor.name === 'FormatConfig')
|
|
443
|
+
) {
|
|
406
444
|
const options = lessParentheses;
|
|
407
445
|
// Apply consecutive link grouping if enabled
|
|
408
446
|
let linksToFormat = links;
|
|
409
447
|
if (options.groupConsecutive) {
|
|
410
448
|
linksToFormat = _groupConsecutiveLinks(links);
|
|
411
449
|
}
|
|
412
|
-
return linksToFormat.map(link => link.format(options)).join('\n');
|
|
450
|
+
return linksToFormat.map((link) => link.format(options)).join('\n');
|
|
413
451
|
}
|
|
414
452
|
|
|
415
453
|
// Backward compatibility with boolean parameter
|
|
416
|
-
return links.map(link => link.format(lessParentheses)).join('\n');
|
|
417
|
-
}
|
|
454
|
+
return links.map((link) => link.format(lessParentheses)).join('\n');
|
|
455
|
+
}
|
package/src/LinksGroup.js
CHANGED
package/src/Parser.js
CHANGED
|
@@ -26,7 +26,9 @@ export class Parser {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
if (input.length > this.maxInputSize) {
|
|
29
|
-
throw new Error(
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Input size exceeds maximum allowed size of ${this.maxInputSize} bytes`
|
|
31
|
+
);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
try {
|
|
@@ -72,10 +74,10 @@ export class Parser {
|
|
|
72
74
|
});
|
|
73
75
|
const linkWithChildren = {
|
|
74
76
|
id: item.id,
|
|
75
|
-
values: childValues
|
|
77
|
+
values: childValues,
|
|
76
78
|
};
|
|
77
79
|
const currentLink = this.transformLink(linkWithChildren);
|
|
78
|
-
|
|
80
|
+
|
|
79
81
|
if (parentPath.length === 0) {
|
|
80
82
|
result.push(currentLink);
|
|
81
83
|
} else {
|
|
@@ -84,17 +86,17 @@ export class Parser {
|
|
|
84
86
|
} else {
|
|
85
87
|
// Regular indented structure - process as before
|
|
86
88
|
const currentLink = this.transformLink(item);
|
|
87
|
-
|
|
89
|
+
|
|
88
90
|
// Add the link combined with parent path
|
|
89
91
|
if (parentPath.length === 0) {
|
|
90
92
|
result.push(currentLink);
|
|
91
93
|
} else {
|
|
92
94
|
result.push(this.combinePathElements(parentPath, currentLink));
|
|
93
95
|
}
|
|
94
|
-
|
|
96
|
+
|
|
95
97
|
// Process each child with this item in the path
|
|
96
98
|
const newPath = [...parentPath, currentLink];
|
|
97
|
-
|
|
99
|
+
|
|
98
100
|
for (const child of item.children) {
|
|
99
101
|
this.collectLinks(child, newPath, result);
|
|
100
102
|
}
|
|
@@ -102,7 +104,7 @@ export class Parser {
|
|
|
102
104
|
} else {
|
|
103
105
|
// Leaf item or item with inline values
|
|
104
106
|
const currentLink = this.transformLink(item);
|
|
105
|
-
|
|
107
|
+
|
|
106
108
|
if (parentPath.length === 0) {
|
|
107
109
|
result.push(currentLink);
|
|
108
110
|
} else {
|
|
@@ -110,7 +112,7 @@ export class Parser {
|
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
|
-
|
|
115
|
+
|
|
114
116
|
combinePathElements(pathElements, current) {
|
|
115
117
|
if (pathElements.length === 0) return current;
|
|
116
118
|
if (pathElements.length === 1) {
|
|
@@ -118,21 +120,20 @@ export class Parser {
|
|
|
118
120
|
combined._isFromPathCombination = true;
|
|
119
121
|
return combined;
|
|
120
122
|
}
|
|
121
|
-
|
|
123
|
+
|
|
122
124
|
// For multiple path elements, we need to build proper nesting
|
|
123
125
|
// The last element in the path should be combined with its parent
|
|
124
126
|
const parentPath = pathElements.slice(0, -1);
|
|
125
127
|
const lastElement = pathElements[pathElements.length - 1];
|
|
126
|
-
|
|
128
|
+
|
|
127
129
|
// Build the parent structure
|
|
128
130
|
let parent = this.combinePathElements(parentPath, lastElement);
|
|
129
|
-
|
|
131
|
+
|
|
130
132
|
// Add current element to the built structure
|
|
131
133
|
const combined = new Link(null, [parent, current]);
|
|
132
134
|
combined._isFromPathCombination = true;
|
|
133
135
|
return combined;
|
|
134
136
|
}
|
|
135
|
-
|
|
136
137
|
|
|
137
138
|
/**
|
|
138
139
|
* Transform a parsed item into a Link object
|
|
@@ -156,12 +157,11 @@ export class Parser {
|
|
|
156
157
|
if (item.values && Array.isArray(item.values)) {
|
|
157
158
|
// Create a link with id (if present) and transformed values
|
|
158
159
|
const link = new Link(item.id || null, []);
|
|
159
|
-
link.values = item.values.map(v => this.transformLink(v));
|
|
160
|
+
link.values = item.values.map((v) => this.transformLink(v));
|
|
160
161
|
return link;
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
// Default case
|
|
164
165
|
return new Link(item.id || null, []);
|
|
165
166
|
}
|
|
166
|
-
|
|
167
|
-
}
|
|
167
|
+
}
|
package/src/grammar.pegjs
CHANGED
|
@@ -34,6 +34,59 @@
|
|
|
34
34
|
function getCurrentIndentation() {
|
|
35
35
|
return indentationStack[indentationStack.length - 1];
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
// Universal procedural parser for N-quote strings (any N >= 1)
|
|
39
|
+
// Parses from the given position in the input string
|
|
40
|
+
// Returns { value, length } or null
|
|
41
|
+
function parseQuotedStringAt(inputStr, startPos, quoteChar) {
|
|
42
|
+
if (startPos >= inputStr.length || inputStr[startPos] !== quoteChar) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Count opening quotes
|
|
47
|
+
let quoteCount = 0;
|
|
48
|
+
let pos = startPos;
|
|
49
|
+
while (pos < inputStr.length && inputStr[pos] === quoteChar) {
|
|
50
|
+
quoteCount++;
|
|
51
|
+
pos++;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const closeSeq = quoteChar.repeat(quoteCount);
|
|
55
|
+
const escapeSeq = quoteChar.repeat(quoteCount * 2);
|
|
56
|
+
|
|
57
|
+
let content = '';
|
|
58
|
+
while (pos < inputStr.length) {
|
|
59
|
+
// Check for escape sequence (2*N quotes)
|
|
60
|
+
if (inputStr.substr(pos, escapeSeq.length) === escapeSeq) {
|
|
61
|
+
content += closeSeq; // 2*N quotes become N quotes
|
|
62
|
+
pos += escapeSeq.length;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check for closing sequence (exactly N quotes)
|
|
67
|
+
if (inputStr.substr(pos, quoteCount) === closeSeq) {
|
|
68
|
+
// Verify it's exactly N quotes (not followed by more of same char)
|
|
69
|
+
const afterClose = pos + quoteCount;
|
|
70
|
+
if (afterClose >= inputStr.length || inputStr[afterClose] !== quoteChar) {
|
|
71
|
+
// Found valid closing
|
|
72
|
+
return {
|
|
73
|
+
value: content,
|
|
74
|
+
length: afterClose - startPos
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Add character to content
|
|
80
|
+
content += inputStr[pos];
|
|
81
|
+
pos++;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null; // No valid closing found
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Global state for passing parsed values between predicate and action
|
|
88
|
+
let parsedValue = null;
|
|
89
|
+
let parsedLength = 0;
|
|
37
90
|
}
|
|
38
91
|
|
|
39
92
|
document = &{ indentationStack = [0]; baseIndentation = null; return true; } skipEmptyLines links:links _ eof { return links; }
|
|
@@ -79,13 +132,61 @@ multiLineValueLink = "(" v:multiLineValues _ ")" { return { values: v }; }
|
|
|
79
132
|
|
|
80
133
|
indentedIdLink = id:reference __ ":" eol { return { id: id, values: [] }; }
|
|
81
134
|
|
|
82
|
-
|
|
135
|
+
// Reference can be quoted (with any number of quotes N >= 1) or simple unquoted
|
|
136
|
+
// Universal approach: use procedural parsing for all quote types and counts
|
|
137
|
+
reference = quotedReference / simpleReference
|
|
83
138
|
|
|
84
139
|
simpleReference = chars:referenceSymbol+ { return chars.join(''); }
|
|
85
140
|
|
|
86
|
-
|
|
141
|
+
// Universal quoted reference - handles any N quotes for all quote types
|
|
142
|
+
// Uses procedural parsing with input/offset() for clean, simple logic
|
|
143
|
+
quotedReference = doubleQuotedUniversal / singleQuotedUniversal / backtickQuotedUniversal
|
|
144
|
+
|
|
145
|
+
// Double quotes: peek at input, parse procedurally, consume exact chars
|
|
146
|
+
doubleQuotedUniversal = &'"' &{
|
|
147
|
+
const pos = offset();
|
|
148
|
+
const result = parseQuotedStringAt(input, pos, '"');
|
|
149
|
+
if (result) {
|
|
150
|
+
parsedValue = result.value;
|
|
151
|
+
parsedLength = result.length;
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
} chars:consumeDouble { return parsedValue; }
|
|
156
|
+
|
|
157
|
+
// Consume exactly parsedLength characters for double quotes
|
|
158
|
+
consumeDouble = c:. cs:consumeDoubleMore* { return [c].concat(cs).join(''); }
|
|
159
|
+
consumeDoubleMore = &{ return parsedLength > 1 && (parsedLength--, true); } c:. { return c; }
|
|
160
|
+
|
|
161
|
+
// Single quotes
|
|
162
|
+
singleQuotedUniversal = &"'" &{
|
|
163
|
+
const pos = offset();
|
|
164
|
+
const result = parseQuotedStringAt(input, pos, "'");
|
|
165
|
+
if (result) {
|
|
166
|
+
parsedValue = result.value;
|
|
167
|
+
parsedLength = result.length;
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
} chars:consumeSingle { return parsedValue; }
|
|
172
|
+
|
|
173
|
+
consumeSingle = c:. cs:consumeSingleMore* { return [c].concat(cs).join(''); }
|
|
174
|
+
consumeSingleMore = &{ return parsedLength > 1 && (parsedLength--, true); } c:. { return c; }
|
|
175
|
+
|
|
176
|
+
// Backticks
|
|
177
|
+
backtickQuotedUniversal = &'`' &{
|
|
178
|
+
const pos = offset();
|
|
179
|
+
const result = parseQuotedStringAt(input, pos, '`');
|
|
180
|
+
if (result) {
|
|
181
|
+
parsedValue = result.value;
|
|
182
|
+
parsedLength = result.length;
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
} chars:consumeBacktick { return parsedValue; }
|
|
87
187
|
|
|
88
|
-
|
|
188
|
+
consumeBacktick = c:. cs:consumeBacktickMore* { return [c].concat(cs).join(''); }
|
|
189
|
+
consumeBacktickMore = &{ return parsedLength > 1 && (parsedLength--, true); } c:. { return c; }
|
|
89
190
|
|
|
90
191
|
SET_BASE_INDENTATION = spaces:" "* { setBaseIndentation(spaces); }
|
|
91
192
|
|
|
@@ -103,4 +204,4 @@ _ = whiteSpaceSymbol*
|
|
|
103
204
|
|
|
104
205
|
whiteSpaceSymbol = [ \t\n\r]
|
|
105
206
|
|
|
106
|
-
referenceSymbol = [^ \t\n\r(:)]
|
|
207
|
+
referenceSymbol = [^ \t\n\r(:)]
|
package/src/index.js
CHANGED
|
@@ -2,4 +2,4 @@ export { Link, formatLinks } from './Link.js';
|
|
|
2
2
|
export { LinksGroup } from './LinksGroup.js';
|
|
3
3
|
export { Parser } from './Parser.js';
|
|
4
4
|
export { FormatConfig } from './FormatConfig.js';
|
|
5
|
-
export { FormatOptions } from './FormatOptions.js';
|
|
5
|
+
export { FormatOptions } from './FormatOptions.js';
|