links-notation 0.11.2 → 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/src/Parser.js CHANGED
@@ -2,24 +2,54 @@ import { Link } from './Link.js';
2
2
  import * as parserModule from './parser-generated.js';
3
3
 
4
4
  export class Parser {
5
- constructor() {
5
+ /**
6
+ * Create a new Parser instance
7
+ * @param {Object} options - Parser options
8
+ * @param {number} options.maxInputSize - Maximum input size in bytes (default: 10MB)
9
+ * @param {number} options.maxDepth - Maximum nesting depth (default: 1000)
10
+ */
11
+ constructor(options = {}) {
12
+ this.maxInputSize = options.maxInputSize || 10 * 1024 * 1024; // 10MB default
13
+ this.maxDepth = options.maxDepth || 1000;
6
14
  }
7
15
 
16
+ /**
17
+ * Parse Lino notation text into Link objects
18
+ * @param {string} input - The Lino notation text to parse
19
+ * @returns {Link[]} Array of parsed Link objects
20
+ * @throws {Error} If parsing fails
21
+ */
8
22
  parse(input) {
23
+ // Validate input
24
+ if (typeof input !== 'string') {
25
+ throw new TypeError('Input must be a string');
26
+ }
27
+
28
+ if (input.length > this.maxInputSize) {
29
+ throw new Error(
30
+ `Input size exceeds maximum allowed size of ${this.maxInputSize} bytes`
31
+ );
32
+ }
33
+
9
34
  try {
10
35
  const rawResult = parserModule.parse(input);
11
36
  return this.transformResult(rawResult);
12
37
  } catch (error) {
13
- throw new Error(`Parse error: ${error.message}`);
38
+ // Preserve original error information
39
+ const parseError = new Error(`Parse error: ${error.message}`);
40
+ parseError.cause = error;
41
+ parseError.location = error.location;
42
+ throw parseError;
14
43
  }
15
44
  }
16
45
 
17
46
  transformResult(rawResult) {
18
47
  const links = [];
19
48
  const items = Array.isArray(rawResult) ? rawResult : [rawResult];
20
-
49
+
21
50
  for (const item of items) {
22
- if (item) {
51
+ // Use explicit null/undefined check
52
+ if (item !== null && item !== undefined) {
23
53
  this.collectLinks(item, [], links);
24
54
  }
25
55
  }
@@ -27,7 +57,8 @@ export class Parser {
27
57
  }
28
58
 
29
59
  collectLinks(item, parentPath, result) {
30
- if (!item) return;
60
+ // Use explicit null/undefined check
61
+ if (item === null || item === undefined) return;
31
62
 
32
63
  // For items with children (indented structure)
33
64
  if (item.children && item.children.length > 0) {
@@ -43,10 +74,10 @@ export class Parser {
43
74
  });
44
75
  const linkWithChildren = {
45
76
  id: item.id,
46
- values: childValues
77
+ values: childValues,
47
78
  };
48
79
  const currentLink = this.transformLink(linkWithChildren);
49
-
80
+
50
81
  if (parentPath.length === 0) {
51
82
  result.push(currentLink);
52
83
  } else {
@@ -55,17 +86,17 @@ export class Parser {
55
86
  } else {
56
87
  // Regular indented structure - process as before
57
88
  const currentLink = this.transformLink(item);
58
-
89
+
59
90
  // Add the link combined with parent path
60
91
  if (parentPath.length === 0) {
61
92
  result.push(currentLink);
62
93
  } else {
63
94
  result.push(this.combinePathElements(parentPath, currentLink));
64
95
  }
65
-
96
+
66
97
  // Process each child with this item in the path
67
98
  const newPath = [...parentPath, currentLink];
68
-
99
+
69
100
  for (const child of item.children) {
70
101
  this.collectLinks(child, newPath, result);
71
102
  }
@@ -73,7 +104,7 @@ export class Parser {
73
104
  } else {
74
105
  // Leaf item or item with inline values
75
106
  const currentLink = this.transformLink(item);
76
-
107
+
77
108
  if (parentPath.length === 0) {
78
109
  result.push(currentLink);
79
110
  } else {
@@ -81,7 +112,7 @@ export class Parser {
81
112
  }
82
113
  }
83
114
  }
84
-
115
+
85
116
  combinePathElements(pathElements, current) {
86
117
  if (pathElements.length === 0) return current;
87
118
  if (pathElements.length === 1) {
@@ -89,25 +120,30 @@ export class Parser {
89
120
  combined._isFromPathCombination = true;
90
121
  return combined;
91
122
  }
92
-
123
+
93
124
  // For multiple path elements, we need to build proper nesting
94
125
  // The last element in the path should be combined with its parent
95
126
  const parentPath = pathElements.slice(0, -1);
96
127
  const lastElement = pathElements[pathElements.length - 1];
97
-
128
+
98
129
  // Build the parent structure
99
130
  let parent = this.combinePathElements(parentPath, lastElement);
100
-
131
+
101
132
  // Add current element to the built structure
102
133
  const combined = new Link(null, [parent, current]);
103
134
  combined._isFromPathCombination = true;
104
135
  return combined;
105
136
  }
106
-
107
137
 
138
+ /**
139
+ * Transform a parsed item into a Link object
140
+ * @param {*} item - The item to transform
141
+ * @returns {Link|null} The transformed Link or null
142
+ */
108
143
  transformLink(item) {
109
- if (!item) return null;
110
-
144
+ // Use explicit null/undefined check
145
+ if (item === null || item === undefined) return null;
146
+
111
147
  if (item instanceof Link) {
112
148
  return item;
113
149
  }
@@ -121,12 +157,11 @@ export class Parser {
121
157
  if (item.values && Array.isArray(item.values)) {
122
158
  // Create a link with id (if present) and transformed values
123
159
  const link = new Link(item.id || null, []);
124
- link.values = item.values.map(v => this.transformLink(v));
160
+ link.values = item.values.map((v) => this.transformLink(v));
125
161
  return link;
126
162
  }
127
-
163
+
128
164
  // Default case
129
165
  return new Link(item.id || null, []);
130
166
  }
131
-
132
- }
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
- reference = doubleQuotedReference / singleQuotedReference / simpleReference
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
- doubleQuotedReference = '"' r:[^"]+ '"' { return r.join(''); }
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
- singleQuotedReference = "'" r:[^']+ "'" { return r.join(''); }
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
@@ -1,3 +1,5 @@
1
1
  export { Link, formatLinks } from './Link.js';
2
2
  export { LinksGroup } from './LinksGroup.js';
3
- export { Parser } from './Parser.js';
3
+ export { Parser } from './Parser.js';
4
+ export { FormatConfig } from './FormatConfig.js';
5
+ export { FormatOptions } from './FormatOptions.js';