Skip to content

Commit 65f53ca

Browse files
committed
Refactor parser and link formatting for improved handling of compound values
- Updated the grammar build command in package.json to use ES format. - Enhanced the Link class to support formatting for compound values, allowing for better representation of nested structures. - Modified the Parser class to streamline link collection and transformation, ensuring proper handling of single and nested links. - Adjusted grammar rules to improve indentation checks and link parsing logic. - Updated tests to reflect changes in expected output for single and nested links, ensuring consistency with the new formatting logic. (part of #64)
1 parent 9c549d2 commit 65f53ca

File tree

9 files changed

+1687
-219
lines changed

9 files changed

+1687
-219
lines changed

js/dist/index.js

Lines changed: 1414 additions & 0 deletions
Large diffs are not rendered by default.

js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"type": "module",
77
"scripts": {
88
"build": "bun run build:grammar && bun build src/index.js --outdir=dist --target=node",
9-
"build:grammar": "peggy -o src/parser-generated.js src/grammar.pegjs",
9+
"build:grammar": "peggy --format es -o src/parser-generated.js src/grammar.pegjs",
1010
"test": "bun test",
1111
"test:watch": "bun test --watch"
1212
},

js/src/Link.js

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class Link {
7777
}
7878

7979

80-
format(lessParentheses = false) {
80+
format(lessParentheses = false, isCompoundValue = false) {
8181
// Empty link
8282
if (this.id === null && (!this.values || this.values.length === 0)) {
8383
return lessParentheses ? '' : '()';
@@ -86,15 +86,33 @@ export class Link {
8686
// Link with only ID, no values
8787
if (!this.values || this.values.length === 0) {
8888
const escapedId = Link.escapeReference(this.id);
89+
// When used as a value in a compound link (created from combining links), wrap in parentheses
90+
if (isCompoundValue) {
91+
return `(${escapedId})`;
92+
}
8993
return lessParentheses && !this.needsParentheses(this.id) ? escapedId : `(${escapedId})`;
9094
}
9195

9296
// Format values recursively
9397
const valuesStr = this.values.map(v => this.formatValue(v)).join(' ');
9498

95-
// Link with values only
99+
// Link with values only (null id)
96100
if (this.id === null) {
97-
return lessParentheses ? valuesStr : `(${valuesStr})`;
101+
// For lessParentheses mode with simple values, don't wrap the whole thing
102+
if (lessParentheses) {
103+
// Check if all values are simple (no nested values)
104+
const allSimple = this.values.every(v => !v.values || v.values.length === 0);
105+
if (allSimple) {
106+
// Format each value without extra wrapping
107+
const simpleValuesStr = this.values.map(v => Link.escapeReference(v.id)).join(' ');
108+
return simpleValuesStr;
109+
}
110+
// For mixed or complex values in lessParentheses mode, still avoid outer wrapper
111+
// but keep the inner formatting
112+
return valuesStr;
113+
}
114+
// For normal mode, wrap in parentheses
115+
return `(${valuesStr})`;
98116
}
99117

100118
// Link with ID and values
@@ -108,13 +126,22 @@ export class Link {
108126
return Link.escapeReference(value.id || '');
109127
}
110128

111-
// Simple value (just ID)
129+
// Check if we're in a compound link that was created from path combinations
130+
// This is indicated by having a parent context passed through
131+
const isCompoundFromPaths = this._isFromPathCombination === true;
132+
133+
// For compound links from paths, format values with parentheses
134+
if (isCompoundFromPaths) {
135+
return value.format(false, true);
136+
}
137+
138+
// Simple link with just an ID - don't wrap in parentheses when used as a value
112139
if (!value.values || value.values.length === 0) {
113140
return Link.escapeReference(value.id);
114141
}
115142

116-
// Nested structure - always use parentheses for nested values to preserve structure
117-
return value.format(false);
143+
// Complex value with its own structure - format it normally with parentheses
144+
return value.format(false, false);
118145
}
119146

120147
needsParentheses(str) {

js/src/Parser.js

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,20 @@ export class Parser {
2929
collectLinks(item, parentPath, result) {
3030
if (!item) return;
3131

32-
// Handle singlet value links by promoting the single value to id
33-
if (item.id == null && item.values && item.values.length === 1 && item.values[0].id !== null && (!item.values[0].values || item.values[0].values.length === 0)) {
34-
item.id = item.values[0].id;
35-
item.values = [];
36-
}
37-
3832
// For items with children (indented structure)
3933
if (item.children && item.children.length > 0) {
40-
// Add the parent item alone first
41-
if (item.id !== undefined) {
42-
if (parentPath.length === 0) {
43-
result.push(new Link(item.id));
44-
} else {
45-
result.push(this.combinePathElements(parentPath, new Link(item.id)));
46-
}
34+
// First create the link for the current item
35+
const currentLink = this.transformLink(item);
36+
37+
// Add the link combined with parent path
38+
if (parentPath.length === 0) {
39+
result.push(currentLink);
40+
} else {
41+
result.push(this.combinePathElements(parentPath, currentLink));
4742
}
4843

49-
// Process each child with parent in the path
50-
const currentElement = item.id !== undefined ? new Link(item.id) : null;
51-
const newPath = currentElement ? [...parentPath, currentElement] : parentPath;
44+
// Process each child with this item in the path
45+
const newPath = [...parentPath, currentLink];
5246

5347
for (const child of item.children) {
5448
this.collectLinks(child, newPath, result);
@@ -68,7 +62,9 @@ export class Parser {
6862
combinePathElements(pathElements, current) {
6963
if (pathElements.length === 0) return current;
7064
if (pathElements.length === 1) {
71-
return new Link(null, [pathElements[0], current]);
65+
const combined = new Link(null, [pathElements[0], current]);
66+
combined._isFromPathCombination = true;
67+
return combined;
7268
}
7369

7470
// For multiple path elements, we need to build proper nesting
@@ -80,7 +76,9 @@ export class Parser {
8076
let parent = this.combinePathElements(parentPath, lastElement);
8177

8278
// Add current element to the built structure
83-
return new Link(null, [parent, current]);
79+
const combined = new Link(null, [parent, current]);
80+
combined._isFromPathCombination = true;
81+
return combined;
8482
}
8583

8684

@@ -91,22 +89,21 @@ export class Parser {
9189
return item;
9290
}
9391

94-
const link = new Link(item.id || null, []);
95-
92+
// Handle simple reference objects like {id: 'a'}
93+
if (item.id !== undefined && !item.values && !item.children) {
94+
return new Link(item.id);
95+
}
96+
97+
// For items with values, create a link with those values
9698
if (item.values && Array.isArray(item.values)) {
99+
// Create a link with id (if present) and transformed values
100+
const link = new Link(item.id || null, []);
97101
link.values = item.values.map(v => this.transformLink(v));
102+
return link;
98103
}
99104

100-
if (item.children && Array.isArray(item.children)) {
101-
for (const child of item.children) {
102-
const childLink = this.transformLink(child);
103-
if (childLink) {
104-
link.values.push(childLink);
105-
}
106-
}
107-
}
108-
109-
return link;
105+
// Default case
106+
return new Link(item.id || null, []);
110107
}
111108

112109
}

js/src/grammar.pegjs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,27 @@
1111
}
1212
}
1313

14-
function checkIndentation(spaces) {
15-
return spaces.length >= indentationStack[indentationStack.length - 1];
16-
}
14+
function checkIndentation(spaces) {
15+
return spaces.length >= indentationStack[indentationStack.length - 1];
16+
}
1717

1818
function getCurrentIndentation() {
1919
return indentationStack[indentationStack.length - 1];
2020
}
2121
}
2222

23-
document = _ links:links eof { return links.flat(); }
23+
document = _ links:links eof { return links; }
2424
/ _ eof { return []; }
2525

2626
links = fl:firstLine list:line* { popIndentation(); return [fl].concat(list || []); }
2727

2828
firstLine = l:element { return l; }
2929

30-
line = spaces:" "* &{ return checkIndentation(spaces); } l:element { return l; }
30+
line = CHECK_INDENTATION l:element { return l; }
3131

32-
element = e:anyLink PUSH_INDENTATION l:links {
33-
return { id: e.id, values: e.values, children: l };
34-
}
32+
element = e:anyLink PUSH_INDENTATION l:links {
33+
return { id: e.id, values: e.values, children: l };
34+
}
3535
/ e:anyLink { return e; }
3636

3737
referenceOrLink = l:multiLineAnyLink { return l; } / i:reference { return { id: i }; }
@@ -59,8 +59,6 @@ singleLineValueLink = v:singleLineValues { return { values: v }; }
5959

6060
multiLineValueLink = "(" v:multiLineValues _ ")" { return { values: v }; }
6161

62-
63-
6462
reference = doubleQuotedReference / singleQuotedReference / simpleReference
6563

6664
simpleReference = chars:referenceSymbol+ { return chars.join(''); }
@@ -71,6 +69,8 @@ singleQuotedReference = "'" r:[^']+ "'" { return r.join(''); }
7169

7270
PUSH_INDENTATION = spaces:" "* &{ return spaces.length > getCurrentIndentation(); } { pushIndentation(spaces); }
7371

72+
CHECK_INDENTATION = spaces:" "* &{ return checkIndentation(spaces); }
73+
7474
eol = __ ([\r\n]+ / eof)
7575

7676
eof = !.

0 commit comments

Comments
 (0)