@yr-lang/yr 0.1.1 → 0.2.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/BUGS.md +3 -21
- package/CHANGELOG.md +45 -0
- package/package.json +2 -2
- package/yr.js +70 -43
package/BUGS.md
CHANGED
|
@@ -3,21 +3,10 @@
|
|
|
3
3
|
This file documents known bugs and limitations in yr.
|
|
4
4
|
The project is under active development.
|
|
5
5
|
|
|
6
|
-
### Wrapper-only section closure
|
|
6
|
+
### Wrapper-only section closure ✓ Fixed in 0.2.0
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
For example, the following may result in an error:
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
++
|
|
14
|
-
_ .teste
|
|
15
|
-
_wrapper/test
|
|
16
|
-
_ .teste2
|
|
17
|
-
_wrapper/test2
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
However, explicitly closing the section with elements works as expected:
|
|
8
|
+
Previously, a section could not always be closed using only a wrapper and required an explicit
|
|
9
|
+
`_` element. This has been resolved — the following now works as expected:
|
|
21
10
|
|
|
22
11
|
```
|
|
23
12
|
++
|
|
@@ -25,11 +14,4 @@ _ .teste
|
|
|
25
14
|
_wrapper/test
|
|
26
15
|
_ .teste2
|
|
27
16
|
_wrapper/test2
|
|
28
|
-
_
|
|
29
|
-
_
|
|
30
17
|
```
|
|
31
|
-
|
|
32
|
-
#### Notes
|
|
33
|
-
|
|
34
|
-
* This appears to be related to how section termination is detected when the last node is a wrapper.
|
|
35
|
-
* The parser may not recognize wrappers as valid closing tokens in some nesting scenarios.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0] - 2026-03-01
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Wrapper-only section closure** — Sections containing nested wrappers no longer require an
|
|
8
|
+
explicit `_` to close. The parser now correctly closes open layers when a wrapper is terminated
|
|
9
|
+
by indentation, producing balanced HTML in all nesting scenarios. Resolves the issue documented
|
|
10
|
+
in `BUGS.md`.
|
|
11
|
+
|
|
12
|
+
- **Multiple wrapper closure on a single line** — When an element's indentation requires closing
|
|
13
|
+
more than one wrapper at once, the parser now closes all of them (changed `if` to `while` in
|
|
14
|
+
the html parser wrapper check). Previously only the innermost wrapper was closed, causing
|
|
15
|
+
sibling elements to be rendered inside the wrong wrapper.
|
|
16
|
+
|
|
17
|
+
- **Section change leaves wrappers open** — When a namespace token (e.g. `><` → `##`) was
|
|
18
|
+
encountered while one or more wrappers were active, only the outermost wrapper was cleaned up
|
|
19
|
+
via `shift()`. Now all open wrappers are properly closed innermost-first.
|
|
20
|
+
|
|
21
|
+
- **Closing tag indentation** — `getWhiteSpace()` was designed to receive a hierarchy level but
|
|
22
|
+
was called with a raw character-count indentation, producing increasingly wrong whitespace for
|
|
23
|
+
deeper elements. Closing tags now mirror the indentation of their opening tags.
|
|
24
|
+
|
|
25
|
+
- **`throw -1` loses file/project context** — A stray semicolon caused the error message for
|
|
26
|
+
odd-indentation errors to drop the filename and project name. The `-1` error message now
|
|
27
|
+
matches the `-2` error message in completeness.
|
|
28
|
+
|
|
29
|
+
- **`getElementAttributes` spurious empty key** — When an `att={{}}` block ended with a trailing
|
|
30
|
+
comma, an empty string key was unconditionally added to the parsed attributes object. Now only
|
|
31
|
+
added if `parseKey` is non-empty.
|
|
32
|
+
|
|
33
|
+
- **Self-importing wrapper causes infinite recursion** — A wrapper that listed itself via `!!`
|
|
34
|
+
in its own `++` section would loop indefinitely. The extension is now registered in
|
|
35
|
+
`sections.extensions` before its content is parsed, so the guard fires correctly on
|
|
36
|
+
re-entry.
|
|
37
|
+
|
|
38
|
+
- **Wrapper without `++` section does not invoke JS function** — A wrapper that had a
|
|
39
|
+
`_@wrapper(Category, Option)` function in `@>` but no `++` body would have its div created
|
|
40
|
+
but the JS function never called. The `wrapperjs` entry is now added independently of whether
|
|
41
|
+
a `++` section exists.
|
|
42
|
+
|
|
43
|
+
## [0.1.1] - prior
|
|
44
|
+
|
|
45
|
+
Initial public releases.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yr-lang/yr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Yr is a modern structural language and parser",
|
|
5
5
|
"main": "./node.js",
|
|
6
6
|
"exports": {
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
"license": "SEE LICENSE IN LICENSE",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/yr-lang/yr"
|
|
12
|
+
"url": "git+https://github.com/yr-lang/yr.git"
|
|
13
13
|
}
|
|
14
14
|
}
|
package/yr.js
CHANGED
|
@@ -17,10 +17,11 @@ const utils = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const common = {
|
|
20
|
+
// Bug fix: was using new Array((i*2)-1).join which was designed for 'level' units,
|
|
21
|
+
// but is called with raw indentation (char count). Use simple repeat instead.
|
|
20
22
|
getWhiteSpace(indentation) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} catch(error) { return ''; }
|
|
23
|
+
if (!indentation || indentation < 0) return '';
|
|
24
|
+
return ' '.repeat(indentation);
|
|
24
25
|
}
|
|
25
26
|
};
|
|
26
27
|
|
|
@@ -140,7 +141,9 @@ function getElementAttributes(line) {
|
|
|
140
141
|
lastChar = value;
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
|
|
144
|
+
// Bug fix: only add the last key-value if parseKey is non-empty
|
|
145
|
+
// (avoids adding a spurious empty key when attrs end with a trailing comma)
|
|
146
|
+
if (parseKey.trim()) parse[parseKey.trim()] = parseValue.trim();
|
|
144
147
|
parseKey = '', parseValue = '';
|
|
145
148
|
return { ...attributes, ...parse };
|
|
146
149
|
}
|
|
@@ -234,16 +237,32 @@ const parserFns = {
|
|
|
234
237
|
}
|
|
235
238
|
yrParsedLine = addIdToElement(yrParsedLine, config, elementId);
|
|
236
239
|
let yrIndentation = 0, ignoreYr, wildCard = '', replaceYr;
|
|
237
|
-
|
|
240
|
+
// Bug fix: use while loop to close ALL wrappers that need closing (not just one)
|
|
241
|
+
// A single element may need to close multiple nested wrappers at once
|
|
242
|
+
while (state.wrapper.length > 0) {
|
|
238
243
|
let wrapper = state.wrapper[state.wrapper.length - 1];
|
|
239
244
|
if (parsedLine.indentation >= wrapper.reference) {
|
|
240
245
|
yrIndentation = wrapper.new - wrapper.reference + 2;
|
|
241
246
|
replaceYr = sections.yr[section].includes('#@#\n');
|
|
247
|
+
break; // Still inside this wrapper, stop closing
|
|
242
248
|
} else {
|
|
243
|
-
wrapper = state.wrapper[state.wrapper.length - 1];
|
|
244
249
|
sections.yr[section] = sections.yr[section].replace(/#@#\n/, '');
|
|
250
|
+
// Close any layers that were opened inside this wrapper before popping it
|
|
251
|
+
for (let j = state.layers.length - 1; j >= 0; j--) {
|
|
252
|
+
const layer = state.layers[j];
|
|
253
|
+
if (!layer) continue;
|
|
254
|
+
// Only close layers that belong inside this wrapper (indentation >= wrapper.reference)
|
|
255
|
+
if (layer.indentation >= wrapper.reference) {
|
|
256
|
+
sections[layer.section] +=
|
|
257
|
+
layer.whiteSpace + `</${layer.tag}>\n`;
|
|
258
|
+
state.layers.pop();
|
|
259
|
+
} else {
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
245
263
|
if (wrapper.wildCard) sections[section] += wrapper.wildCard + '\n';
|
|
246
264
|
state.wrapper.pop();
|
|
265
|
+
// After popping, loop again to check if we need to close more wrappers
|
|
247
266
|
}
|
|
248
267
|
}
|
|
249
268
|
if (yrIndentation < 0) yrIndentation = 0;
|
|
@@ -257,7 +276,7 @@ const parserFns = {
|
|
|
257
276
|
}
|
|
258
277
|
if (parsedLine.indentation !== -1) {
|
|
259
278
|
if (parsedLine.indentation % 2 !== 0)
|
|
260
|
-
throw `-1: indentation error at line ${lineNumber}` + ''
|
|
279
|
+
throw `-1: indentation error at line ${lineNumber}` + '' +
|
|
261
280
|
` (${config.name}.yr, ${config.project}):\n\n"""\n${line}\n"""`;
|
|
262
281
|
if (parsedLine.level > state.level + 1)
|
|
263
282
|
throw `-2: indentation error at line ${lineNumber}` + '' +
|
|
@@ -333,33 +352,28 @@ const parserFns = {
|
|
|
333
352
|
state.wrapper.push(newWrapper);
|
|
334
353
|
wrapperIndentation = parsedLine.indentation;
|
|
335
354
|
}
|
|
355
|
+
// Always set up element attributes for wrapperjs call,
|
|
356
|
+
// even if wrapper has no ++ section (no wrapperbody)
|
|
357
|
+
elementAttributes.id = elementId;
|
|
358
|
+
elementAttributes.category = wrapper[0];
|
|
359
|
+
elementAttributes.option = wrapper[1];
|
|
360
|
+
let attributes = [];
|
|
361
|
+
try {
|
|
362
|
+
attributes = sections.wrappers[wrapperName]
|
|
363
|
+
? sections.wrappers[wrapperName].vars.attributes
|
|
364
|
+
: sections.vars.attributes;
|
|
365
|
+
} catch(error) {/* pass */}
|
|
366
|
+
if (!attributes) attributes = sections.vars.attributes;
|
|
367
|
+
elementAttributes.attributes = attributes;
|
|
368
|
+
// Call the JS wrapper function if it exists, regardless of whether ++ is present
|
|
369
|
+
if (sections.jsheader
|
|
370
|
+
.includes(`function __${wrapperName.replace(/\//, '_')}(`)
|
|
371
|
+
&& !sections.wrapperjs.includes(`"id":"${elementId}"`)) {
|
|
372
|
+
const wrapperjs =
|
|
373
|
+
`__${wrapperName.replace(/\//, '_')}(${JSON.stringify(elementAttributes)});\n`;
|
|
374
|
+
sections.wrapperjs += wrapperjs;
|
|
375
|
+
}
|
|
336
376
|
if (sections.wrappers[wrapperName]) {
|
|
337
|
-
elementAttributes.id = elementId;
|
|
338
|
-
elementAttributes.category = wrapper[0];
|
|
339
|
-
elementAttributes.option = wrapper[1];
|
|
340
|
-
let attributes = [];
|
|
341
|
-
try {
|
|
342
|
-
attributes = sections.wrappers[wrapperName].vars.attributes;
|
|
343
|
-
} catch(error) {
|
|
344
|
-
console.log(wrapperName);
|
|
345
|
-
console.log(sections.wrappers[wrapperName]);
|
|
346
|
-
console.log(parsedLine);
|
|
347
|
-
console.log(error);
|
|
348
|
-
throw error;
|
|
349
|
-
}
|
|
350
|
-
if (!attributes) attributes = sections.vars.attributes;
|
|
351
|
-
elementAttributes.attributes = attributes;
|
|
352
|
-
let redone;
|
|
353
|
-
try {
|
|
354
|
-
redone = !sections.wrappers[wrapperName].redone;
|
|
355
|
-
} catch(error) {/* pass */}
|
|
356
|
-
if (sections.jsheader
|
|
357
|
-
.includes(`function __${wrapperName.replace(/\//, '_')}(`)
|
|
358
|
-
&& !sections.wrapperjs.includes(`"id":"${elementId}"`)) {
|
|
359
|
-
const wrapperjs =
|
|
360
|
-
`__${wrapperName.replace(/\//, '_')}(${JSON.stringify(elementAttributes)});\n`;
|
|
361
|
-
sections.wrapperjs += wrapperjs;
|
|
362
|
-
}
|
|
363
377
|
if (!tag.includes('!')) {
|
|
364
378
|
if (sections.wrappers[wrapperName].yrwrapperbody) {
|
|
365
379
|
for (let value of sections.wrappers[wrapperName]
|
|
@@ -627,6 +641,10 @@ const core = {
|
|
|
627
641
|
return;
|
|
628
642
|
}
|
|
629
643
|
|
|
644
|
+
// Bug fix: register extension BEFORE parsing its content
|
|
645
|
+
// to prevent infinite recursion when a wrapper imports itself via !!
|
|
646
|
+
sections.extensions += '!! ' + wrapperName + '\n';
|
|
647
|
+
|
|
630
648
|
const _yr = sections.yr;
|
|
631
649
|
sections.yr = {};
|
|
632
650
|
|
|
@@ -640,8 +658,6 @@ const core = {
|
|
|
640
658
|
sections, wrapper: `${wrapper.join('/')}`, ...config
|
|
641
659
|
});
|
|
642
660
|
|
|
643
|
-
sections.extensions += '!! ' + wrapperName + '\n';
|
|
644
|
-
|
|
645
661
|
if (!sections.wrappers[wrapperName])
|
|
646
662
|
sections.wrappers[wrapperName] = {};
|
|
647
663
|
|
|
@@ -715,16 +731,25 @@ const core = {
|
|
|
715
731
|
section = parsers.namespaces[line].name;
|
|
716
732
|
state.sectionChanged = true;
|
|
717
733
|
|
|
718
|
-
|
|
719
|
-
|
|
734
|
+
// Bug fix: close ALL open wrappers when section changes, innermost first (pop, not shift)
|
|
735
|
+
while (state.wrapper.length > 0) {
|
|
736
|
+
const wrapper = state.wrapper[state.wrapper.length - 1];
|
|
720
737
|
|
|
721
738
|
sections.yr[wrapper.section] =
|
|
722
739
|
sections.yr[wrapper.section].replace(/#@#\n/, '');
|
|
723
740
|
|
|
741
|
+
// Close layers created inside this wrapper
|
|
742
|
+
for (let j = state.layers.length - 1; j >= 0; j--) {
|
|
743
|
+
const layer = state.layers[j];
|
|
744
|
+
if (!layer || layer.indentation < wrapper.reference) break;
|
|
745
|
+
sections[layer.section] += layer.whiteSpace + `</${layer.tag}>\n`;
|
|
746
|
+
state.layers.pop();
|
|
747
|
+
}
|
|
748
|
+
|
|
724
749
|
if (wrapper.wildCard)
|
|
725
750
|
sections[wrapper.section] += wrapper.wildCard + '\n';
|
|
726
751
|
|
|
727
|
-
state.wrapper.
|
|
752
|
+
state.wrapper.pop();
|
|
728
753
|
}
|
|
729
754
|
|
|
730
755
|
continue;
|
|
@@ -861,15 +886,17 @@ const core = {
|
|
|
861
886
|
sections[wrapper.section] += wrapper.wildCard + '\n';
|
|
862
887
|
|
|
863
888
|
|
|
864
|
-
|
|
865
|
-
|
|
889
|
+
// Only close layers that were created INSIDE this wrapper (indentation >= wrapper.reference)
|
|
890
|
+
// External layers (pre-existing before the wrapper) should NOT be closed here
|
|
891
|
+
for (let j = state.layers.length - 1; j >= 0; j--) {
|
|
892
|
+
const layer = state.layers[j];
|
|
866
893
|
if (!layer) continue;
|
|
894
|
+
if (layer.indentation < wrapper.reference) break;
|
|
867
895
|
|
|
868
896
|
sections[layer.section] +=
|
|
869
|
-
|
|
897
|
+
layer.whiteSpace + `</${layer.tag}>\n`;
|
|
870
898
|
|
|
871
|
-
|
|
872
|
-
state.layers = state.layers.filter(e => e !== layer);
|
|
899
|
+
state.layers.pop();
|
|
873
900
|
}
|
|
874
901
|
|
|
875
902
|
state.wrapper.pop();
|