links-notation 0.11.0 → 0.12.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/bun.lock +1 -0
- package/dist/index.js +484 -143
- package/examples/debug_indentation.js +16 -0
- package/examples/test_leading_spaces.js +33 -0
- package/package.json +1 -1
- package/src/FormatConfig.js +19 -0
- package/src/FormatOptions.js +52 -0
- package/src/Link.js +287 -25
- package/src/Parser.js +43 -8
- package/src/grammar.pegjs +30 -10
- package/src/index.js +3 -1
- package/src/parser-generated.js +276 -123
- package/tests/ApiTests.test.js +104 -7
- package/tests/EdgeCaseParser.test.js +9 -9
- package/tests/FormatConfig.test.js +126 -0
- package/tests/IndentationConsistency.test.js +128 -0
- package/tests/IndentedIdSyntax.test.js +2 -2
- package/tests/Link.test.js +1 -1
- package/tests/LinksGroup.test.js +28 -1
- package/tests/MixedIndentationModes.test.js +5 -5
- package/tests/MultilineParser.test.js +5 -5
- package/tests/NestedParser.test.js +31 -4
- package/tests/NestedSelfReference.test.js +102 -0
- package/tests/SingleLineParser.test.js +86 -6
|
@@ -105,7 +105,7 @@ test('Parse nested structure with indentation', () => {
|
|
|
105
105
|
expect(result[2].values.length).toBe(2);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
test('
|
|
108
|
+
test('Indentation consistency', () => {
|
|
109
109
|
// Test that indentation must be consistent
|
|
110
110
|
const input = `parent
|
|
111
111
|
child1
|
|
@@ -135,7 +135,7 @@ test('Complex indentation', () => {
|
|
|
135
135
|
expect(result.length).toBe(6);
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
-
test('
|
|
138
|
+
test('Nested links', () => {
|
|
139
139
|
const input = '(1: (2: (3: 3)))';
|
|
140
140
|
const parsed = parser.parse(input);
|
|
141
141
|
expect(parsed.length).toBeGreaterThan(0);
|
|
@@ -148,7 +148,7 @@ test('Test nested links', () => {
|
|
|
148
148
|
expect(parsed.length).toBe(1);
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
test('
|
|
151
|
+
test('Indentation parser', () => {
|
|
152
152
|
const input = 'parent\n child1\n child2';
|
|
153
153
|
const result = parser.parse(input);
|
|
154
154
|
expect(result.length).toBeGreaterThan(0);
|
|
@@ -157,10 +157,37 @@ test('Test indentation (parser)', () => {
|
|
|
157
157
|
expect(hasParentLink).toBe(true);
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
test('
|
|
160
|
+
test('Nested indentation parser', () => {
|
|
161
161
|
const input = 'parent\n child\n grandchild';
|
|
162
162
|
const result = parser.parse(input);
|
|
163
163
|
expect(result.length).toBeGreaterThan(0);
|
|
164
164
|
// Should create nested structure with proper hierarchy
|
|
165
165
|
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('Three level nesting roundtrip', () => {
|
|
169
|
+
const input = '(1: (2: (3: 3)))';
|
|
170
|
+
const parsed = parser.parse(input);
|
|
171
|
+
|
|
172
|
+
// Validate round-trip
|
|
173
|
+
const output = formatLinks(parsed);
|
|
174
|
+
expect(output).toBe(input);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('Deep nested structure roundtrip', () => {
|
|
178
|
+
const input = '(a: (b: (c: (d: d))))';
|
|
179
|
+
const parsed = parser.parse(input);
|
|
180
|
+
|
|
181
|
+
// Validate round-trip
|
|
182
|
+
const output = formatLinks(parsed);
|
|
183
|
+
expect(output).toBe(input);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('Multiple nested links roundtrip', () => {
|
|
187
|
+
const input = '(parent: (child1: value1) (child2: value2))';
|
|
188
|
+
const parsed = parser.parse(input);
|
|
189
|
+
|
|
190
|
+
// Validate round-trip
|
|
191
|
+
const output = formatLinks(parsed);
|
|
192
|
+
expect(output).toBe(input);
|
|
166
193
|
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { Parser } from '../src/Parser.js';
|
|
3
|
+
|
|
4
|
+
test('Nested self-referenced object in pair value', () => {
|
|
5
|
+
// Test case from PARSER_BUG.md
|
|
6
|
+
// This should parse a dict with two pairs, where the second pair's value
|
|
7
|
+
// is itself a self-referenced dict definition (obj_1: dict ...)
|
|
8
|
+
const notation = '(obj_0: dict ((str bmFtZQ==) (str ZGljdDE=)) ((str b3RoZXI=) (obj_1: dict ((str bmFtZQ==) (str ZGljdDI=)) ((str b3RoZXI=) obj_0))))';
|
|
9
|
+
|
|
10
|
+
const parser = new Parser();
|
|
11
|
+
const links = parser.parse(notation);
|
|
12
|
+
|
|
13
|
+
// Should parse exactly one top-level link
|
|
14
|
+
expect(links.length).toBe(1);
|
|
15
|
+
|
|
16
|
+
const link = links[0];
|
|
17
|
+
|
|
18
|
+
// Top-level link should have ID "obj_0"
|
|
19
|
+
expect(link.id).toBe('obj_0');
|
|
20
|
+
|
|
21
|
+
// Should have: type marker + 2 pairs = 3 values
|
|
22
|
+
expect(link.values.length).toBe(3);
|
|
23
|
+
|
|
24
|
+
// First value is the type marker "dict"
|
|
25
|
+
expect(link.values[0].id).toBe('dict');
|
|
26
|
+
|
|
27
|
+
// Second and third values are the two pairs
|
|
28
|
+
const pair1 = link.values[1];
|
|
29
|
+
const pair2 = link.values[2];
|
|
30
|
+
|
|
31
|
+
// Pair 1: ((str bmFtZQ==) (str ZGljdDE=))
|
|
32
|
+
// This is a parenthesized expression containing two sub-expressions
|
|
33
|
+
expect(pair1.id).toBe(null);
|
|
34
|
+
expect(pair1.values.length).toBe(2);
|
|
35
|
+
|
|
36
|
+
// First element of pair1: (str bmFtZQ==)
|
|
37
|
+
expect(pair1.values[0].id).toBe(null);
|
|
38
|
+
expect(pair1.values[0].values.length).toBe(2); // "str" and "bmFtZQ=="
|
|
39
|
+
expect(pair1.values[0].values[0].id).toBe('str');
|
|
40
|
+
expect(pair1.values[0].values[1].id).toBe('bmFtZQ==');
|
|
41
|
+
|
|
42
|
+
// Second element of pair1: (str ZGljdDE=)
|
|
43
|
+
expect(pair1.values[1].id).toBe(null);
|
|
44
|
+
expect(pair1.values[1].values.length).toBe(2); // "str" and "ZGljdDE="
|
|
45
|
+
expect(pair1.values[1].values[0].id).toBe('str');
|
|
46
|
+
expect(pair1.values[1].values[1].id).toBe('ZGljdDE=');
|
|
47
|
+
|
|
48
|
+
// Pair 2: ((str b3RoZXI=) (obj_1: dict ...))
|
|
49
|
+
// This is the critical test - the second element should be a self-referenced dict
|
|
50
|
+
expect(pair2.id).toBe(null);
|
|
51
|
+
expect(pair2.values.length).toBe(2);
|
|
52
|
+
|
|
53
|
+
// First element of pair2: (str b3RoZXI=)
|
|
54
|
+
expect(pair2.values[0].id).toBe(null);
|
|
55
|
+
expect(pair2.values[0].values.length).toBe(2);
|
|
56
|
+
expect(pair2.values[0].values[0].id).toBe('str');
|
|
57
|
+
expect(pair2.values[0].values[1].id).toBe('b3RoZXI=');
|
|
58
|
+
|
|
59
|
+
// Second element of pair2: (obj_1: dict ((str bmFtZQ==) (str ZGljdDI=)) ((str b3RoZXI=) obj_0))
|
|
60
|
+
// THIS IS THE KEY TEST - obj_1 should have its ID preserved
|
|
61
|
+
const obj1 = pair2.values[1];
|
|
62
|
+
expect(obj1.id).toBe('obj_1'); // This is the self-reference ID
|
|
63
|
+
expect(obj1.values).toBeTruthy(); // Should have nested values (dict definition)
|
|
64
|
+
expect(obj1.values.length).toBe(3); // type marker + 2 pairs
|
|
65
|
+
|
|
66
|
+
// obj_1's type marker
|
|
67
|
+
expect(obj1.values[0].id).toBe('dict');
|
|
68
|
+
|
|
69
|
+
// obj_1's first pair: ((str bmFtZQ==) (str ZGljdDI=))
|
|
70
|
+
const obj1_pair1 = obj1.values[1];
|
|
71
|
+
expect(obj1_pair1.values.length).toBe(2);
|
|
72
|
+
expect(obj1_pair1.values[0].values[0].id).toBe('str');
|
|
73
|
+
expect(obj1_pair1.values[0].values[1].id).toBe('bmFtZQ==');
|
|
74
|
+
expect(obj1_pair1.values[1].values[0].id).toBe('str');
|
|
75
|
+
expect(obj1_pair1.values[1].values[1].id).toBe('ZGljdDI=');
|
|
76
|
+
|
|
77
|
+
// obj_1's second pair: ((str b3RoZXI=) obj_0) - reference back to obj_0
|
|
78
|
+
const obj1_pair2 = obj1.values[2];
|
|
79
|
+
expect(obj1_pair2.values.length).toBe(2);
|
|
80
|
+
expect(obj1_pair2.values[0].values[0].id).toBe('str');
|
|
81
|
+
expect(obj1_pair2.values[0].values[1].id).toBe('b3RoZXI=');
|
|
82
|
+
expect(obj1_pair2.values[1].id).toBe('obj_0'); // Reference to obj_0
|
|
83
|
+
expect(obj1_pair2.values[1].values).toEqual([]); // Just a reference, no nested values
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('Self-reference as direct child works correctly', () => {
|
|
87
|
+
// This pattern should work (and did work before)
|
|
88
|
+
const notation = '(obj_0: list (int 1) (int 2) (obj_1: list (int 3) (int 4) obj_0))';
|
|
89
|
+
|
|
90
|
+
const parser = new Parser();
|
|
91
|
+
const links = parser.parse(notation);
|
|
92
|
+
|
|
93
|
+
expect(links.length).toBe(1);
|
|
94
|
+
expect(links[0].id).toBe('obj_0');
|
|
95
|
+
expect(links[0].values.length).toBe(4); // list + 1 + 2 + obj_1
|
|
96
|
+
|
|
97
|
+
// The fourth value should be obj_1 with a self-reference
|
|
98
|
+
const obj1 = links[0].values[3];
|
|
99
|
+
expect(obj1.id).toBe('obj_1');
|
|
100
|
+
expect(obj1.values.length).toBe(4); // list + 3 + 4 + obj_0
|
|
101
|
+
expect(obj1.values[3].id).toBe('obj_0'); // Reference back to obj_0
|
|
102
|
+
});
|
|
@@ -4,28 +4,28 @@ import { formatLinks } from '../src/Link.js';
|
|
|
4
4
|
|
|
5
5
|
const parser = new Parser();
|
|
6
6
|
|
|
7
|
-
test('
|
|
7
|
+
test('Single link', () => {
|
|
8
8
|
const source = '(address: source target)';
|
|
9
9
|
const links = parser.parse(source);
|
|
10
10
|
const target = formatLinks(links);
|
|
11
11
|
expect(target).toBe(source);
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
test('
|
|
14
|
+
test('Triplet single link', () => {
|
|
15
15
|
const source = '(papa has car)';
|
|
16
16
|
const links = parser.parse(source);
|
|
17
17
|
const target = formatLinks(links);
|
|
18
18
|
expect(target).toBe(source);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
test('
|
|
21
|
+
test('Bug test 1', () => {
|
|
22
22
|
const source = '(ignore conan-center-index repository)';
|
|
23
23
|
const links = parser.parse(source);
|
|
24
24
|
const target = formatLinks(links);
|
|
25
25
|
expect(target).toBe(source);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
test('
|
|
28
|
+
test('Quoted references', () => {
|
|
29
29
|
const source = `(a: 'b' "c")`;
|
|
30
30
|
const target = `(a: b c)`;
|
|
31
31
|
const links = parser.parse(source);
|
|
@@ -33,7 +33,7 @@ test('QuotedReferencesTest', () => {
|
|
|
33
33
|
expect(formattedLinks).toBe(target);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
test('
|
|
36
|
+
test('Quoted references with spaces', () => {
|
|
37
37
|
const source = `('a a': 'b b' "c c")`;
|
|
38
38
|
const target = `('a a': 'b b' 'c c')`;
|
|
39
39
|
const links = parser.parse(source);
|
|
@@ -127,7 +127,7 @@ test('Test value link', () => {
|
|
|
127
127
|
expect(result.length).toBeGreaterThan(0);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
test('
|
|
130
|
+
test('Parse quoted references values only', () => {
|
|
131
131
|
const source = `"has space" 'has:colon'`;
|
|
132
132
|
const parser = new Parser();
|
|
133
133
|
const links = parser.parse(source);
|
|
@@ -226,4 +226,84 @@ test('Test value link (parser)', () => {
|
|
|
226
226
|
expect(result.length).toBe(1);
|
|
227
227
|
expect(result[0].id).toBe(null);
|
|
228
228
|
expect(result[0].values.length).toBe(3);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('Multiline without id', () => {
|
|
232
|
+
// Test that '(:)' syntax is forbidden and should throw an error
|
|
233
|
+
const input = '(: value1 value2)';
|
|
234
|
+
// JS parser forbids this syntax
|
|
235
|
+
expect(() => parser.parse(input)).toThrow();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('Parse values only standalone colon', () => {
|
|
239
|
+
// Test that standalone ':' is forbidden and should throw an error
|
|
240
|
+
const input = ': value1 value2';
|
|
241
|
+
// JS parser forbids this syntax
|
|
242
|
+
expect(() => parser.parse(input)).toThrow();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('Quoted references with spaces in link', () => {
|
|
246
|
+
const input = '(id: "value with spaces")';
|
|
247
|
+
const result = parser.parse(input);
|
|
248
|
+
expect(result.length).toBe(1);
|
|
249
|
+
expect(result[0].id).toBe('id');
|
|
250
|
+
expect(result[0].values.length).toBe(1);
|
|
251
|
+
expect(result[0].values[0].id).toBe('value with spaces');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('Single line without id', () => {
|
|
255
|
+
// Test parsing a single line without ID (just values)
|
|
256
|
+
const input = '(value1 value2)';
|
|
257
|
+
const result = parser.parse(input);
|
|
258
|
+
expect(result.length).toBe(1);
|
|
259
|
+
expect(result[0].id).toBe(null);
|
|
260
|
+
expect(result[0].values.length).toBe(2);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('Link with id', () => {
|
|
264
|
+
const input = '(id: a b c)';
|
|
265
|
+
const result = parser.parse(input);
|
|
266
|
+
expect(result.length).toBe(1);
|
|
267
|
+
expect(result[0].id).toBe('id');
|
|
268
|
+
expect(result[0].values.length).toBe(3);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('Quoted reference', () => {
|
|
272
|
+
const input = '"quoted value"';
|
|
273
|
+
const result = parser.parse(input);
|
|
274
|
+
expect(result.length).toBe(1);
|
|
275
|
+
expect(result[0].id).toBe(null);
|
|
276
|
+
expect(result[0].values.length).toBe(1);
|
|
277
|
+
expect(result[0].values[0].id).toBe('quoted value');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('Quoted references with special chars', () => {
|
|
281
|
+
const input = '("special:char" "another@char")';
|
|
282
|
+
const result = parser.parse(input);
|
|
283
|
+
expect(result.length).toBe(1);
|
|
284
|
+
expect(result[0].id).toBe(null);
|
|
285
|
+
expect(result[0].values.length).toBe(2);
|
|
286
|
+
expect(result[0].values[0].id).toBe('special:char');
|
|
287
|
+
expect(result[0].values[1].id).toBe('another@char');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('Simple reference', () => {
|
|
291
|
+
const input = 'simplereference';
|
|
292
|
+
const result = parser.parse(input);
|
|
293
|
+
expect(result.length).toBeGreaterThan(0);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('Single line link', () => {
|
|
297
|
+
const input = 'id: value1 value2';
|
|
298
|
+
const result = parser.parse(input);
|
|
299
|
+
expect(result.length).toBe(1);
|
|
300
|
+
expect(result[0].id).toBe('id');
|
|
301
|
+
expect(result[0].values.length).toBe(2);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('Single line with id', () => {
|
|
305
|
+
const input = 'myid: val1 val2';
|
|
306
|
+
const result = parser.parse(input);
|
|
307
|
+
expect(result.length).toBeGreaterThan(0);
|
|
308
|
+
expect(result[0].id).toBe('myid');
|
|
229
309
|
});
|