Compare commits

...
3 Commits
Author SHA1 Message Date
pierpaolo.mammi 15422cca1f fix unclosed block detection
add parser tests (codex)
2026-08-18 12:49:31 +02:00
pierpaolo.mammi c34d2864aa update installation instructions in readme 2026-08-18 12:48:55 +02:00
pierpaolo.mammi d43f6ac434 split samples for correct parsing 2026-08-18 12:48:20 +02:00
7 changed files with 72 additions and 27 deletions
+1 -1
View File
@@ -78,7 +78,7 @@ To make the extension available in your VS Code without packaging it as a VSIX:
```powershell
# PowerShell
New-Item -ItemType Junction -Path "$env:USERPROFILE\.vscode\extensions\eftl-language" -Value "D:\__Git\vscode-eftl-language"
New-Item -ItemType Junction -Path "$env:USERPROFILE\.vscode\extensions\eftl-language" -Value "$(pwd)"
```
*Note: Replace the value path with the actual absolute path to your project folder if it differs.*
+20
View File
@@ -0,0 +1,20 @@
[!-- Example EFTL file for testing syntax highlighting --]
[EFTL][HEADER name="trimDocument" value="true" type="boolean" /]
[VAR name="isRichiedenteInPartecipanti" type="string"][% isRichiedenteInPartecipanti = "Utente non ammesso!"; %][/VAR]
[VAR name="partecipanti" type="string"][TAG]GETVALUEBYTAG,CONTRATTO_PARTECIPANTI,REQUEST,IUQOID[/TAG][/VAR]
[VAR name="nominativoUtente" type="string"][TAG]GETVALUEBYTAG,RICHIEDENTE_NOMINATIVO,REQUEST,IUQOID[/TAG][/VAR]
[!-- Check if user is in participants list --]
[% nominativoUtente = " " + nominativoUtente + ", CF: "; %]
[IF]
[CONDITION][CONTAINS varname="partecipanti"][VALUE_OF varname="nominativoUtente" /][/CONTAINS][/CONDITION]
[THEN][% isRichiedenteInPartecipanti = ""; %][/THEN]
[ELSE IF]
[CONDITION][% partecipanti == "" %][/CONDITION]
[THEN][% isRichiedenteInPartecipanti = "Nessun partecipante trovato"; %][/THEN]
[/ELSE IF]
[/IF]
[%= isRichiedenteInPartecipanti %]
[/EFTL]
@@ -1,27 +1,4 @@
[!-- Example EFTL file for testing syntax highlighting --]
[EFTL][HEADER name="trimDocument" value="true" type="boolean" /]
[VAR name="isRichiedenteInPartecipanti" type="string"][% isRichiedenteInPartecipanti = "Utente non ammesso!"; %][/VAR]
[VAR name="partecipanti" type="string"][TAG]GETVALUEBYTAG,CONTRATTO_PARTECIPANTI,REQUEST,IUQOID[/TAG][/VAR]
[VAR name="nominativoUtente" type="string"][TAG]GETVALUEBYTAG,RICHIEDENTE_NOMINATIVO,REQUEST,IUQOID[/TAG][/VAR]
[!-- Check if user is in participants list --]
[% nominativoUtente = " " + nominativoUtente + ", CF: "; %]
[IF]
[CONDITION][CONTAINS varname="partecipanti"][VALUE_OF varname="nominativoUtente" /][/CONTAINS][/CONDITION]
[THEN][% isRichiedenteInPartecipanti = ""; %][/THEN]
[ELSE IF]
[CONDITION][% partecipanti == "" %][/CONDITION]
[THEN][% isRichiedenteInPartecipanti = "Nessun partecipante trovato"; %][/THEN]
[/ELSE IF]
[/IF]
[%= isRichiedenteInPartecipanti %]
[/EFTL]
[!-- Another example with loops and calculations --]
[EFTL][HEADER name="trimDocument" value="true" type="boolean" /]
[VAR name="IDX_NOME" type="number"][% IDX_NOME = 0; %][/VAR]
[VAR name="IDX_SEDE" type="number"][% IDX_SEDE = 2; %][/VAR]
-2
View File
@@ -303,7 +303,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -489,7 +488,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
+1
View File
@@ -57,6 +57,7 @@
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"test": "npm run compile && node --test test/*.test.js",
"watch": "tsc -watch -p ./",
"lint": "eslint src --ext ts"
},
+2 -1
View File
@@ -41,8 +41,9 @@ export class EftlParser {
// Check for unclosed blocks
for (const block of this.blockStack) {
const expectedClose = this.getMatchingClose(block.type);
this.errors.push({
message: `Unclosed block: ${block.token.value}`,
message: `Unclosed tag: ${block.token.value} (expected ${expectedClose})`,
line: block.token.line,
column: block.token.column,
length: block.token.length
+48
View File
@@ -0,0 +1,48 @@
const assert = require('node:assert/strict');
const test = require('node:test');
const { EftlParser } = require('../out/parser');
const { EftlTokenizer } = require('../out/tokenizer');
function parse(source) {
const { tokens, errors: tokenizerErrors } = new EftlTokenizer(source).tokenize();
assert.deepEqual(tokenizerErrors, []);
return new EftlParser(tokens).parse().errors;
}
test('accepts a correctly closed VAR tag', () => {
assert.deepEqual(parse('[VAR][/VAR]'), []);
});
test('reports a VAR tag without its closing tag', () => {
assert.deepEqual(parse('[VAR]'), [{
message: 'Unclosed tag: [VAR] (expected [/VAR])',
line: 1,
column: 1,
length: 5
}]);
});
test('reports an unexpected closing tag', () => {
const errors = parse('[/VAR]');
assert.equal(errors.length, 1);
assert.match(errors[0].message, /Unexpected closing tag/);
});
test('reports every unclosed nested tag at its opening token', () => {
assert.deepEqual(parse('[EFTL]\n[VAR]'), [
{
message: 'Unclosed tag: [EFTL] (expected [/EFTL])',
line: 1,
column: 1,
length: 6
},
{
message: 'Unclosed tag: [VAR] (expected [/VAR])',
line: 2,
column: 1,
length: 5
}
]);
});