@sormy/lemon-js 1.0.1

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/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ lemon-js is in the public domain.
2
+
3
+ The lemon parser generator and its parser template are taken from the SQLite
4
+ distribution, whose author disclaims copyright to them. The translation of the
5
+ template to JavaScript, the changes to the generator and everything else here
6
+ are released on the same terms.
7
+
8
+ In place of a legal notice, here is a blessing:
9
+
10
+ May you do good and not evil.
11
+ May you find forgiveness for yourself and forgive others.
12
+ May you share freely, never taking more than you give.
package/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # LEMON.JS - LALR(1) Parser Generator for JavaScript
2
+
3
+ [![CI](https://github.com/sormy/lemon-js/actions/workflows/ci.yml/badge.svg)](https://github.com/sormy/lemon-js/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/@sormy/lemon-js)](https://www.npmjs.com/package/@sormy/lemon-js)
5
+
6
+ Lemon.JS is an LALR(1) parser generator for JavaScript based on Lemon parser generator for C included in SQLite package distribution.
7
+
8
+ Looking for a lexer to go with it? [FLEX.JS](https://github.com/sormy/flex-js).
9
+
10
+ ## Parser Code Base
11
+
12
+ Files `lemon.c`, `lempar.c`, `lemon.html` are extracted from SQLite v3.17.0. Original parser generator code is slightly fixed to produce JavaScript compatible statements. Parser template translated from C to JavaScript. Source comments mostly not touched to keep it easy diff against original file.
13
+
14
+ Both original C version and patched JS version are included for side by side comparison for reference.
15
+
16
+ ## Installation
17
+
18
+ From npm, which carries a prebuilt generator for macOS, Linux and Windows on
19
+ both x86_64 and arm64:
20
+
21
+ ```bash
22
+ npm install --save-dev @sormy/lemon-js
23
+ npx lemon-js grammar.y
24
+ ```
25
+
26
+ The package is `@sormy/lemon-js` and the command it installs is `lemon-js`.
27
+
28
+ Or from source, which is the way on any platform the package does not carry:
29
+
30
+ ```bash
31
+ cc -o lemon-js -O2 lemon-js.c
32
+ ./lemon-js grammar.y
33
+ ```
34
+
35
+ Built this way the generator looks for `lempar.js` beside its own executable,
36
+ so keep the two together, or name the template with `-T`.
37
+
38
+ ## Usage
39
+
40
+ ```bash
41
+ lemon-js <filename>.y
42
+ ```
43
+
44
+ The parser is written beside the grammar as `<filename>.js`, along with a
45
+ TypeScript declaration, `<filename>.d.ts`, and a report of the grammar and the
46
+ states it produced, `<filename>.out`. Pass `-q` if the report is not wanted.
47
+
48
+ - `-q` - do not write the report file.
49
+ - `-l` - leave out the `// line ...` comments pointing back at the grammar.
50
+ - `-T<file>` - read another parser template. Installed from npm this already
51
+ points at the bundled `lempar.js`, so it is only needed for a template of
52
+ your own.
53
+ - `-s` - print statistics about the parser tables.
54
+ - `-g` - print the grammar without actions.
55
+ - `-b` - report only the basis of each state, rather than every configuration.
56
+ - `-c` - do not compress the action table.
57
+ - `-p` - show the conflicts that precedence rules resolved.
58
+ - `-r` - do not sort or renumber the states.
59
+ - `-x` - print the version.
60
+
61
+ A grammar the generator will not accept is reported and exits non-zero.
62
+
63
+ See `lemon.html`, included here, or https://sqlite.org/lemon.html for more details.
64
+
65
+ ## Special Directives
66
+
67
+ - %name - Set parser class name (default is "Parse")
68
+ - %include - Include code in the beginning of file (useful for imports)
69
+ - %code - Include code in the end of file (useful for exports or main code)
70
+ - %token_destructor - Define code which will be executed on token destruction.
71
+ - %default_destructor - Destructor for the non-terminals that have none of their own.
72
+ - %token_prefix - Define token name prefix.
73
+ - %syntax_error - Define custom error handler for syntax errors.
74
+ - %parse_accept - Code to run when the parser accepts its input, meaning every token was processed without error.
75
+ - %parse_failure - Code to run once error recovery has failed and the parse cannot go on.
76
+ - %stack_overflow - Define handler for stack overflow.
77
+ - %extra_argument - Declare a name visible in every action, given to the constructor: `%extra_argument { ctx }` in the grammar, `new Parser(ctx)` in the code.
78
+ - %token_type - Type of the value a token carries, used in the generated declaration: `%token_type { number }` makes `parse` take a `number`.
79
+ - %default_type - **NOT SUPPORTED**, nothing to declare while the output is JavaScript
80
+ - %stack_size - Depth of the parser stack, 100 by default.
81
+ - %start_symbol - Symbol the grammar starts at, the left side of the first rule by default.
82
+ - %left - Set left associative tokens.
83
+ - %right - Set right associative tokens.
84
+ - %nonassoc - Set non associative tokens.
85
+ - %destructor - Destructor for one non-terminal symbol, as %token_destructor is for terminals.
86
+ - %type - **NOT SUPPORTED**, nothing to declare while the output is JavaScript
87
+ - %fallback - Give tokens an alternative meaning, tried when the original would be a syntax error.
88
+ - %wildcard - Name the token that matches any input token.
89
+ - %token_class - Define a class of tokens usable as one symbol in rules, `%token_class number INTEGER|FLOAT.`
90
+
91
+ Notes:
92
+
93
+ - a regular expression holding `/*`, such as `/\/*/`, ends a `%code` or
94
+ `%include` section early: the generator reads `/*` as the start of a comment
95
+ and swallows the rest of the file. It says so and stops rather than producing
96
+ anything, and writing the star as a character class, `/\/[*]/`, avoids it.
97
+ - the best place to put something like `module.exports = ParserName;` or `export default ParserName;` is in `%code` section.
98
+
99
+ ## Generated Parser
100
+
101
+ The generator writes a `.js` beside the grammar holding one class, named by
102
+ `%name`. Nothing is exported unless you say so, which is what the `%code`
103
+ section is for:
104
+
105
+ ```javascript
106
+ %code { module.exports = Parser; }
107
+ ```
108
+
109
+ The class carries a constant for every terminal, named with `%token_prefix`,
110
+ and these methods:
111
+
112
+ - `new Parser(argument)` - the argument is the one `%extra_argument` names, and
113
+ is left out by a grammar that declares none.
114
+ - `parse(major, minor)` - hand over one token: its constant, and the value the
115
+ actions receive. Call `parse()` with nothing to say the input has ended.
116
+ - `setTraceCallback(callback, prompt)` - report every shift, reduce and accept.
117
+ The callback is given the text, and the prompt goes in front of each line.
118
+ - `trace(message)` - write one line through that callback.
119
+ - `getStackPeak()` - how deep the parser stack has been, to compare against
120
+ `%stack_size`.
121
+ - `init()` - return the parser to its starting state, ready for another input.
122
+ The constructor calls it, so it is only needed to reuse a parser.
123
+ - `finalize()` - run the destructors over whatever the stack still holds, for a
124
+ parse given up partway. It is worth calling only for a grammar that declares
125
+ destructors: they may hold something JavaScript will not reclaim on its own,
126
+ where the stack itself it will. The parser needs `init()` again afterwards,
127
+ so this is not a cleanup to reach for out of habit.
128
+
129
+ A declaration is written beside the parser, so TypeScript callers get the token
130
+ constants as literal types and are told when one does not exist. It assumes the
131
+ parser is exported as `export =`, which is what `module.exports = Parser` in a
132
+ `%code` section comes to.
133
+
134
+ ```javascript
135
+ var parser = new Parser(context);
136
+
137
+ parser.parse(parser.TOKEN_INT, 42);
138
+ parser.parse(parser.TOKEN_PLUS);
139
+ parser.parse(parser.TOKEN_INT, 8);
140
+ parser.parse();
141
+ ```
142
+
143
+ ## Lexer
144
+
145
+ A primitive single-state lexer is bundled in `lexer/`. It has no dependencies,
146
+ takes the first rule that matches, and is enough for the calculator example or
147
+ as a starting point for one of your own.
148
+
149
+ Past that, [FLEX.JS](https://github.com/sormy/flex-js) is the lexer this project
150
+ is meant to be used with: FLEX semantics, longest match whatever order the rules
151
+ were added in, start conditions, and actions that run while scanning. Lemon.JS is
152
+ the parser generator that goes with it.
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ npm test # against a generator compiled from this tree
158
+ ./build-dev.sh # both generators, and the C example, to compare them
159
+ ./build-dist.sh # the binaries the package ships
160
+ npm run test:dist # the same tests, against those binaries
161
+ ```
162
+
163
+ `npm test` compiles `lemon-js.c` first, so it never runs against a stale
164
+ binary. Publishing builds the shipped binaries and then runs the tests against
165
+ them, reaching them the way an install does.
166
+
167
+ The original `lemon.c` and `lempar.c` are kept beside the ported ones so the
168
+ changes stay easy to read as a diff. Nothing should reformat them, which is
169
+ why trailing whitespace is left alone in C files.
170
+
171
+ ## Alternative Lexers
172
+
173
+ Maintained:
174
+
175
+ - [moo](https://github.com/no-context/moo) - all rules compiled into one sticky expression, first match wins
176
+ - [chevrotain](https://chevrotain.io/) - lexer and parser toolkit, token modes, first match wins
177
+
178
+ Unmaintained:
179
+
180
+ - [lexed.js](https://github.com/tantaman/lexed.js)
181
+ - [lexer](https://github.com/aaditmshah/lexer)
182
+ - [jslex](https://github.com/YuhangGe/jslex)
183
+
184
+ ## Alternative Parsers
185
+
186
+ - [jison](https://github.com/zaach/jison) - parser generator, `jison-lex` accepts FLEX-style lex files
187
+ - [peggy](https://peggyjs.org/) - PEG parser generator, successor to PEG.js, no separate scanner
188
+ - [jscc](http://jscc.brobston.com) - LALR(1) parser generator
189
+
190
+ ## License
191
+
192
+ Public domain. The generator and its template come from SQLite, whose author
193
+ disclaims copyright to them, and the translation to JavaScript and the changes
194
+ to the generator are released on the same terms. See [LICENSE](LICENSE).
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ /*
4
+ ** Runs the prebuilt generator for the platform in use.
5
+ **
6
+ ** The generator looks for lempar.js beside its own executable, which is not
7
+ ** where it lives here, so the template is named explicitly unless the caller
8
+ ** already named one.
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ var path = require('path');
14
+ var childProcess = require('child_process');
15
+
16
+ var BINARIES = {
17
+ 'darwin-x64': 'lemon-js-darwin-universal',
18
+ 'darwin-arm64': 'lemon-js-darwin-universal',
19
+ 'linux-x64': 'lemon-js-linux-x64',
20
+ 'linux-arm64': 'lemon-js-linux-arm64',
21
+ 'win32-x64': 'lemon-js-win32-x64.exe',
22
+ 'win32-arm64': 'lemon-js-win32-arm64.exe'
23
+ };
24
+
25
+ var platform = process.platform + '-' + process.arch;
26
+ var binary = BINARIES[platform];
27
+
28
+ if (!binary) {
29
+ process.stderr.write(
30
+ '@sormy/lemon-js ships no generator for ' + platform + '.\n' +
31
+ 'It can be built from source: https://github.com/sormy/lemon-js#installation\n'
32
+ );
33
+ process.exit(1);
34
+ }
35
+
36
+ var args = process.argv.slice(2);
37
+ var named = args.some(function (arg) { return arg.indexOf('-T') === 0; });
38
+
39
+ if (!named) {
40
+ args.unshift('-T' + path.join(__dirname, '..', 'lempar.js'));
41
+ }
42
+
43
+ var run = childProcess.spawnSync(path.join(__dirname, '..', 'dist', binary), args, { stdio: 'inherit' });
44
+
45
+ if (run.error) {
46
+ process.stderr.write(run.error.message + '\n');
47
+ process.exit(1);
48
+ }
49
+
50
+ process.exit(run.status === null ? 1 : run.status);
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,74 @@
1
+ %name Parser
2
+
3
+ %token_prefix TOKEN_
4
+
5
+ %left PLUS MINUS.
6
+ %left DIVIDE TIMES.
7
+
8
+ %include {
9
+ // include something
10
+ }
11
+
12
+ %code {
13
+ var Lexer = require('../lexer/lexer');
14
+
15
+ var parser = new Parser();
16
+
17
+ parser.setTraceCallback(function (value) {
18
+ process.stdout.write(value);
19
+ }, '> ');
20
+
21
+ var lexer = new Lexer();
22
+
23
+ lexer.addRule(/\d+/, function (value) {
24
+ return { major: parser.TOKEN_INTEGER, minor: parseInt(value, 10) };
25
+ });
26
+ lexer.addRule('+', function (value) {
27
+ return { major: parser.TOKEN_PLUS, minor: null };
28
+ });
29
+ lexer.addRule('-', function (value) {
30
+ return { major: parser.TOKEN_MINUS, minor: null };
31
+ });
32
+ lexer.addRule('/', function (value) {
33
+ return { major: parser.TOKEN_DIVIDE, minor: null };
34
+ });
35
+ lexer.addRule('*', function (value) {
36
+ return { major: parser.TOKEN_TIMES, minor: null };
37
+ });
38
+ lexer.addRule(/\s+/, function () {});
39
+
40
+ var data = '';
41
+
42
+ process.stdin.on('data', function (chunk) {
43
+ data += chunk;
44
+ });
45
+
46
+ process.stdin.on('end', function () {
47
+ var token;
48
+
49
+ lexer.setInput(data);
50
+
51
+ while (token = lexer.lex()) {
52
+ parser.parse(token.major, token.minor);
53
+ }
54
+
55
+ parser.parse();
56
+ });
57
+ }
58
+
59
+ %syntax_error {
60
+ console.log("Syntax error");
61
+ }
62
+
63
+ program ::= expr(A). { console.log("Result=" + A); }
64
+ expr(A) ::= expr(B) MINUS expr(C). { A = B - C; }
65
+ expr(A) ::= expr(B) PLUS expr(C). { A = B + C; }
66
+ expr(A) ::= expr(B) TIMES expr(C). { A = B * C; }
67
+ expr(A) ::= expr(B) DIVIDE expr(C). {
68
+ if (C != 0) {
69
+ A = B / C;
70
+ } else {
71
+ throw new Error("Divide by zero");
72
+ }
73
+ }
74
+ expr(A) ::= INTEGER(B). { A = B; }