jlex 1.1.3 → 1.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/README.md +78 -39
- package/examples/grammar.jison +3 -0
- package/examples/manual-lexer.js +5 -5
- package/jlex.js +113 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,30 +17,30 @@ npx jlex <package name>
|
|
|
17
17
|
`jlex` is a tiny wrapper around `jison-lex` that allows you to use the script
|
|
18
18
|
`jlex` as standalone (`flex` like) processor.
|
|
19
19
|
|
|
20
|
-
Assuming the following lexer in file
|
|
20
|
+
Assuming the following lexer in file [examples/example.l](examples/example.l):
|
|
21
21
|
|
|
22
22
|
```
|
|
23
|
+
comment [/][*](.|[\r\n])*?[*][/]
|
|
23
24
|
%%
|
|
24
|
-
\s
|
|
25
|
+
\s+|{comment} /* skip whitespace */
|
|
25
26
|
[0-9]+ return 'NUMBER';
|
|
26
|
-
|
|
27
|
+
[-+*/] return 'OPERATOR';
|
|
27
28
|
<<EOF>> return 'EOF';
|
|
28
|
-
. return 'INVALID'
|
|
29
|
+
. return 'INVALID';
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
Compile it with:
|
|
32
33
|
|
|
33
34
|
```
|
|
34
|
-
|
|
35
|
-
Processing file: example
|
|
36
|
-
Writing file: example.js
|
|
35
|
+
npx jlex examples/example.l
|
|
37
36
|
```
|
|
38
37
|
|
|
39
|
-
This produces a Common.JS module `example.js` you can use with a simple `require` like in the file
|
|
38
|
+
This produces a Common.JS module `examples/example.js` you can use with a simple `require` like in the file [main.js](examples/main.js) below:
|
|
40
39
|
|
|
41
40
|
```js
|
|
42
41
|
const lex = require("./example");
|
|
43
|
-
|
|
42
|
+
const input = process.argv[2] || "2\n-/* a comment*/\n3";
|
|
43
|
+
lex.setInput(input);
|
|
44
44
|
|
|
45
45
|
const results = [];
|
|
46
46
|
|
|
@@ -49,38 +49,13 @@ results.push({ type: lex.lex(), lexeme: lex.yytext, loc: lex.yylloc });
|
|
|
49
49
|
results.push({ type: lex.lex(), lexeme: lex.yytext, loc: lex.yylloc });
|
|
50
50
|
results.push({ type: lex.lex(), lexeme: lex.yytext, loc: lex.yylloc });
|
|
51
51
|
|
|
52
|
-
console.log(results);
|
|
53
|
-
/*
|
|
54
|
-
➜ examples git:(main) ✗ node main.js
|
|
55
|
-
[
|
|
56
|
-
{
|
|
57
|
-
type: 'NUMBER',
|
|
58
|
-
lexeme: '2',
|
|
59
|
-
loc: { first_line: 1, last_line: 1, first_column: 0, last_column: 1 }
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
type: '-',
|
|
63
|
-
lexeme: '-',
|
|
64
|
-
loc: { first_line: 2, last_line: 2, first_column: 0, last_column: 1 }
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
type: 'NUMBER',
|
|
68
|
-
lexeme: '3',
|
|
69
|
-
loc: { first_line: 3, last_line: 3, first_column: 0, last_column: 1 }
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
type: 'EOF',
|
|
73
|
-
lexeme: '',
|
|
74
|
-
loc: { first_line: 3, last_line: 3, first_column: 1, last_column: 1 }
|
|
75
|
-
}
|
|
76
|
-
]
|
|
77
|
-
*/
|
|
52
|
+
console.log(results);
|
|
78
53
|
```
|
|
79
54
|
When you execute the former program, you get:
|
|
80
55
|
|
|
81
56
|
|
|
82
57
|
```js
|
|
83
|
-
➜ jlex
|
|
58
|
+
➜ jlex git:(main) ✗ node examples/main.js
|
|
84
59
|
[
|
|
85
60
|
{
|
|
86
61
|
type: 'NUMBER',
|
|
@@ -88,7 +63,7 @@ When you execute the former program, you get:
|
|
|
88
63
|
loc: { first_line: 1, last_line: 1, first_column: 0, last_column: 1 }
|
|
89
64
|
},
|
|
90
65
|
{
|
|
91
|
-
type: '
|
|
66
|
+
type: 'OPERATOR',
|
|
92
67
|
lexeme: '-',
|
|
93
68
|
loc: { first_line: 2, last_line: 2, first_column: 0, last_column: 1 }
|
|
94
69
|
},
|
|
@@ -124,7 +99,7 @@ Compile the grammar with:
|
|
|
124
99
|
|
|
125
100
|
And use the parser:
|
|
126
101
|
|
|
127
|
-
```
|
|
102
|
+
```js
|
|
128
103
|
➜ jlex git:(main) ✗ node
|
|
129
104
|
Welcome to Node.js v25.6.0.
|
|
130
105
|
Type ".help" for more information.
|
|
@@ -191,4 +166,68 @@ Here is a description of the attributes of the lexer object:
|
|
|
191
166
|
## Writing a Jison compatible lexer by hand
|
|
192
167
|
|
|
193
168
|
See file [examples/manual-lexer.js](examples/manual-lexer.js) to see an example that
|
|
194
|
-
illustrates how to write a Jison compatible lexer by hand.
|
|
169
|
+
illustrates how to write a Jison compatible lexer by hand.
|
|
170
|
+
|
|
171
|
+
To use with the [grammar](examples/grammar.jison#L33-L34) in the `examples` folder, set the `parser.lexer` to the hand-written one:
|
|
172
|
+
|
|
173
|
+
```diff
|
|
174
|
+
➜ jlex git:(main) ✗ git -P diff examples/grammar.jison
|
|
175
|
+
diff --git a/examples/grammar.jison b/examples/grammar.jison
|
|
176
|
+
index 0b99d40..c9e974d 100644
|
|
177
|
+
--- a/examples/grammar.jison
|
|
178
|
+
+++ b/examples/grammar.jison
|
|
179
|
+
@@ -12,6 +12,7 @@ expr
|
|
180
|
+
{
|
|
181
|
+
$$ = {
|
|
182
|
+
type: "OPERATOR",
|
|
183
|
+
+ lexeme: $2,
|
|
184
|
+
left: $1,
|
|
185
|
+
right: $3,
|
|
186
|
+
loc: @2
|
|
187
|
+
@@ -29,5 +30,7 @@ expr
|
|
188
|
+
|
|
189
|
+
%%
|
|
190
|
+
|
|
191
|
+
-const lexer = require("./example.js");
|
|
192
|
+
+//const lexer = require("./example.js");
|
|
193
|
+
+const lexer = require("./manual-lexer.js");
|
|
194
|
+
+
|
|
195
|
+
parser.lexer = lexer;
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Then compile the grammar:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
➜ jlex git:(main) ✗ npx jison examples/grammar.jison -o examples/parser.js
|
|
203
|
+
```
|
|
204
|
+
and use it like this:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
➜ jlex git:(main) ✗ node
|
|
208
|
+
Welcome to Node.js v25.6.0.
|
|
209
|
+
Type ".help" for more information.
|
|
210
|
+
> p = require("./examples/parser.js")
|
|
211
|
+
{
|
|
212
|
+
parser: { yy: {} },
|
|
213
|
+
Parser: [Function: Parser],
|
|
214
|
+
parse: [Function (anonymous)],
|
|
215
|
+
main: [Function: commonjsMain]
|
|
216
|
+
}
|
|
217
|
+
> p.parse("2*3")
|
|
218
|
+
{
|
|
219
|
+
type: 'OPERATOR',
|
|
220
|
+
lexeme: '*',
|
|
221
|
+
left: {
|
|
222
|
+
type: 'number',
|
|
223
|
+
value: 2,
|
|
224
|
+
loc: { first_line: 1, last_line: 1, first_column: 0, last_column: 1 }
|
|
225
|
+
},
|
|
226
|
+
right: {
|
|
227
|
+
type: 'number',
|
|
228
|
+
value: 3,
|
|
229
|
+
loc: { first_line: 1, last_line: 1, first_column: 2, last_column: 3 }
|
|
230
|
+
},
|
|
231
|
+
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 2 }
|
|
232
|
+
}
|
|
233
|
+
```
|
package/examples/grammar.jison
CHANGED
package/examples/manual-lexer.js
CHANGED
|
@@ -16,7 +16,7 @@ const lexer = {
|
|
|
16
16
|
|
|
17
17
|
lex() {
|
|
18
18
|
if (this.index >= this.input.length) {
|
|
19
|
-
return this.EOF
|
|
19
|
+
return 'EOF' /* this.EOF */;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Saltar espacios
|
|
@@ -33,9 +33,9 @@ const lexer = {
|
|
|
33
33
|
|
|
34
34
|
const char = this.input[this.index];
|
|
35
35
|
|
|
36
|
-
//
|
|
37
|
-
if (char
|
|
38
|
-
this.yytext =
|
|
36
|
+
// OPERATOR
|
|
37
|
+
if (/[-+*\/]/.test(char)) {
|
|
38
|
+
this.yytext = char;
|
|
39
39
|
this.advance(char);
|
|
40
40
|
|
|
41
41
|
this.yylloc = {
|
|
@@ -45,7 +45,7 @@ const lexer = {
|
|
|
45
45
|
last_column: this.column
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
return "
|
|
48
|
+
return "OPERATOR";
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// NUMBER (soporta múltiples dígitos)
|
package/jlex.js
CHANGED
|
@@ -1,35 +1,133 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// replace example by the name of the generated module
|
|
3
3
|
const fs = require("fs");
|
|
4
|
-
const
|
|
4
|
+
const jisonLex = require('jison-lex');
|
|
5
|
+
|
|
5
6
|
const { Command } = require('commander')
|
|
6
7
|
const packageJson = require('./package.json')
|
|
7
8
|
const path = require('path');
|
|
8
9
|
const program = new Command();
|
|
9
10
|
|
|
10
11
|
program
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
.version(packageJson.version)
|
|
13
|
+
.description('A tiny wrapper around jison-lex that allows you to use jison-lex as a standalone (flex like) processor.')
|
|
14
|
+
.addHelpText('after', `See https://github.com/ULL-ESIT-PL/jlex/blob/main/README.md for more help`)
|
|
15
|
+
.option("-o, --output <fileName>", "Output file name")
|
|
16
|
+
.option("-v, --verbose", "Enable verbose output")
|
|
17
|
+
.usage("[options] <filename>");
|
|
16
18
|
|
|
17
19
|
program.parse(process.argv);
|
|
18
20
|
const options = program.opts();
|
|
19
21
|
|
|
22
|
+
// Logging helper
|
|
23
|
+
function log(message, isVerbose = false) {
|
|
24
|
+
if (!isVerbose || options.verbose) {
|
|
25
|
+
console.log(message);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function logError(message) {
|
|
30
|
+
console.error(`❌ Error: ${message}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function logSuccess(message) {
|
|
34
|
+
console.log(`✅ ${message}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (program.args.length === 0) {
|
|
38
|
+
logError("No input file specified");
|
|
39
|
+
program.help();
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
20
42
|
const fileName = program.args[0];
|
|
21
43
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
44
|
+
// Validate input file extension
|
|
45
|
+
if (!/[.](l|lex|flex)$/.test(fileName)) {
|
|
46
|
+
console.warn(`⚠️ Warning: Expected .l or .lex extension for lexer file, got: ${path.extname(fileName)}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { dir, name } = path.parse(fileName);
|
|
50
|
+
const outputFileName = options.output || path.join(dir, `${name}.js`);
|
|
25
51
|
|
|
26
|
-
const shellCommand = `npx jison-lex ${fileName} -o ${outputFileName}`;
|
|
27
52
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
53
|
+
if (!fs.existsSync(fileName)) {
|
|
54
|
+
logError(`File '${fileName}' does not exist`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const stats = fs.statSync(fileName);
|
|
59
|
+
if (!stats.isFile()) {
|
|
60
|
+
logError(`'${fileName}' is not a regular file`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
log(`📖 Reading lexer grammar from: ${fileName}`, true);
|
|
65
|
+
let lexerStr = fs.readFileSync(fileName, "utf8");
|
|
66
|
+
|
|
67
|
+
let generatedCode = jisonLex.generate(lexerStr, { moduleType: 'commonjs' });
|
|
68
|
+
|
|
69
|
+
if (!generatedCode || generatedCode.trim().length === 0) {
|
|
70
|
+
logError(`jison-lex failed to generate code from '${fileName}'`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
log(`📝 Generated ${generatedCode.length} characters of lexer code`, true);
|
|
75
|
+
|
|
76
|
+
// More robust transformation with multiple patterns
|
|
77
|
+
const patterns = [
|
|
78
|
+
/var\s+lexer\s*=/,
|
|
79
|
+
/let\s+lexer\s*=/,
|
|
80
|
+
/const\s+lexer\s*=/,
|
|
81
|
+
new RegExp(`var\\s+${name}\\s*=`)
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
let lexerModule = generatedCode;
|
|
85
|
+
let transformApplied = false;
|
|
86
|
+
|
|
87
|
+
for (const pattern of patterns) {
|
|
88
|
+
if (pattern.test(generatedCode)) {
|
|
89
|
+
lexerModule = generatedCode.replace(pattern, `module.exports =`);
|
|
90
|
+
transformApplied = true;
|
|
91
|
+
log(`🔄 Applied transformation pattern: ${pattern}`, true);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//transformApplied = false;
|
|
97
|
+
if (!transformApplied) {
|
|
98
|
+
logError(
|
|
99
|
+
`No standard pattern found in the generated code!.
|
|
100
|
+
Applied patterns: ${patterns.map(p => p.toString()).join(' or ')}.
|
|
101
|
+
Output may not be a valid CommonJS module.
|
|
102
|
+
Consider adding an issue: https://github.com/ULL-ESIT-PL/jlex/issues.
|
|
103
|
+
`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Ensure output directory exists
|
|
107
|
+
const outputDir = path.dirname(outputFileName);
|
|
108
|
+
if (!fs.existsSync(outputDir)) {
|
|
109
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
110
|
+
log(`📁 Created directory: ${outputDir}`, true);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
log(`📝 Writing file: ${outputFileName}`);
|
|
32
114
|
fs.writeFileSync(outputFileName, lexerModule);
|
|
115
|
+
|
|
116
|
+
logSuccess(`Successfully processed ${fileName} → ${outputFileName}`);
|
|
117
|
+
log(`📊 Output size: ${(fs.statSync(outputFileName).size / 1024).toFixed(1)}KB`, true);
|
|
118
|
+
|
|
33
119
|
} catch (error) {
|
|
34
|
-
|
|
120
|
+
if (error.message.includes('Lexical error') || error.message.includes('Parse error')) {
|
|
121
|
+
logError(`Invalid lexer grammar in '${fileName}': ${error.message}`);
|
|
122
|
+
} else if (error.code === 'EACCES') {
|
|
123
|
+
logError(`Permission denied accessing '${error.path}'`);
|
|
124
|
+
} else if (error.code === 'ENOSPC') {
|
|
125
|
+
logError(`No space left on device when writing '${outputFileName}'`);
|
|
126
|
+
} else {
|
|
127
|
+
logError(error.message);
|
|
128
|
+
if (options.verbose) {
|
|
129
|
+
console.error('Stack trace:', error.stack);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
process.exit(1);
|
|
35
133
|
}
|