larkita 0.1.0__tar.gz

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.
larkita-0.1.0/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright © 2017 Erez Shinan
2
+ Copyright © 2026 Karl Stroetmann
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ this software and associated documentation files (the "Software"), to deal in
6
+ the Software without restriction, including without limitation the rights to
7
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ the Software, and to permit persons to whom the Software is furnished to do so,
9
+ subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
larkita-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,301 @@
1
+ Metadata-Version: 2.4
2
+ Name: larkita
3
+ Version: 0.1.0
4
+ Summary: A light-weight canonical LR(1) parser generator that returns abstract syntax trees as nested tuples
5
+ Author: Karl Stroetmann
6
+ Author-email: "Erez Shinan (Lark)" <erezshin@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/karlstroetmann/Larkita
9
+ Project-URL: Documentation, https://github.com/karlstroetmann/Larkita/blob/master/tutorial/larkita-tutorial.pdf
10
+ Keywords: LR(1),parser,parsing,ast
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Text Processing :: General
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Provides-Extra: regex
21
+ Requires-Dist: regex; extra == "regex"
22
+ Provides-Extra: interegular
23
+ Requires-Dist: interegular<0.4.0,>=0.3.1; extra == "interegular"
24
+ Dynamic: license-file
25
+
26
+ # Larkita
27
+
28
+ Larkita is a light-weight version of the Python parser generator
29
+ [Lark](https://github.com/lark-parser/lark). It differs from Lark in three ways:
30
+
31
+ * It generates a **canonical LR(1) parser** (not Earley or CYK). On request, it
32
+ merges states with the same core; for an LALR(1) grammar and the basic lexer,
33
+ the result is the LALR(1) parser.
34
+ * The parser returns the **abstract syntax tree as a nested tuple**, not as a tree
35
+ of objects. How the tuple is built follows from the grammar itself; you never
36
+ have to write tree-building code.
37
+ * Every time a parser is generated, Larkita writes an **output file** with all
38
+ the LR(1) states, every item annotated with its lookahead tokens. This makes
39
+ conflicts easy to debug.
40
+
41
+ Grammars are written in Lark's EBNF notation, with a few additions for building
42
+ the abstract syntax tree.
43
+
44
+ ## Installation
45
+
46
+ Larkita needs Python 3.12 or newer and has no dependencies. Install it with
47
+
48
+ pip install larkita
49
+
50
+ The source code is on GitHub at <https://github.com/karlstroetmann/Larkita>. To
51
+ install the latest version from there, run
52
+
53
+ pip install git+https://github.com/karlstroetmann/Larkita.git
54
+
55
+ If the optional package `interegular` is installed, Larkita checks whether the
56
+ regular expressions of different terminals can match the same text. It reports
57
+ such collisions via `larkita.logger`, which is silent by default; call
58
+ `larkita.logger.setLevel(logging.WARNING)` to see them.
59
+
60
+ ## Quick start
61
+
62
+ ```python
63
+ from larkita import Larkita
64
+
65
+ grammar = r'''
66
+ stmnt: IDENTIFIER ":=" expr ";"
67
+ | expr ";"
68
+
69
+ expr: expr "+" product
70
+ | expr "-" product
71
+ | product
72
+
73
+ product: product "*" factor
74
+ | product "/" factor
75
+ | factor
76
+
77
+ factor: "(" expr ")" %ignore "(", ")"
78
+ | NUMBER
79
+ | IDENTIFIER
80
+
81
+ %import common.CNAME -> IDENTIFIER
82
+ %import common.NUMBER
83
+ %import common.WS
84
+ %ignore WS
85
+ '''
86
+
87
+ parser = Larkita(grammar)
88
+ print(parser.parse('x := (a + 3) * b;'))
89
+ ```
90
+
91
+ prints
92
+
93
+ ```python
94
+ ((':=', ';'), ('IDENTIFIER', 'x'), ('*', ('+', ('IDENTIFIER', 'a'), ('NUMBER', '3')), ('IDENTIFIER', 'b')))
95
+ ```
96
+
97
+ and writes the description of the automaton to `grammar.out`. To read the
98
+ grammar from a file, use
99
+
100
+ ```python
101
+ parser = Larkita.open('new-syntax/expr.g') # writes new-syntax/expr.out
102
+ ```
103
+
104
+ The start symbol is the first rule of the grammar (here `stmnt`).
105
+
106
+ ## Tutorial
107
+
108
+ The tutorial
109
+ [`tutorial/larkita-tutorial.pdf`](https://github.com/karlstroetmann/Larkita/blob/master/tutorial/larkita-tutorial.pdf)
110
+ explains how to write grammars for Larkita, how the abstract syntax trees are
111
+ built, how to process them with `match`, and how to deal with syntax errors and
112
+ conflicts. Its grammars and programs are in the directory `tutorial/examples` of
113
+ the repository; running `make` in the directory `tutorial` runs them and rebuilds
114
+ the PDF.
115
+
116
+ ## How the abstract syntax tree is built
117
+
118
+ Consider an alternative `a: X1 X2 ... Xn`. The literal tokens written in double
119
+ quotes form the **head** of the tuple; all other symbols become its **children**:
120
+
121
+ (head, ast(child_1), ..., ast(child_k))
122
+
123
+ | Grammar | Abstract syntax tree |
124
+ |---|---|
125
+ | `expr: expr "+" product` | `("+", ast(expr), ast(product))` |
126
+ | `stmnt: expr ";"` | `(";", ast(expr))` |
127
+ | `stmnt: IDENTIFIER ":=" expr ";"` | `((":=", ";"), ("IDENTIFIER", name), ast(expr))` |
128
+ | `atom: IDENTIFIER` | `("IDENTIFIER", name)` |
129
+ | `expr: product` | `ast(product)` |
130
+ | `factor: "(" expr ")" %ignore "(", ")"` | `ast(expr)` |
131
+ | `atom: IDENTIFIER "(" args ")" -> "call"` | `("call", ("IDENTIFIER", name), ast(args))` |
132
+ | `args: expr ("," expr)*` | `(",", ast(expr_1), ..., ast(expr_n))` |
133
+ | `set: "{" expr ("," expr)* "}"` | `(("{", ",", "}"), ast(expr_1), ..., ast(expr_n))` |
134
+ | `program: statement*` | `("program", ast(statement_1), ..., ast(statement_n))` |
135
+ | `stmnt: "return" [expr] ";"` | `(("return", ";"),)` or `(("return", ";"), ast(expr))` |
136
+
137
+ In detail:
138
+
139
+ * **Head.** The head consists of all literals of the alternative that are not
140
+ ignored, in the order in which they are written, one entry per occurrence. If
141
+ there is exactly one literal, the head is that string; otherwise it is a tuple
142
+ of strings. The head is fixed by the grammar, not by the input: `f(a)` with the
143
+ rule for `args` above yields `(",", ast(a))`, even though no comma was read.
144
+ Literals inside repetitions, optional parts and groups belong to the head, too.
145
+ * **Named tokens** become `(NAME, text)`, e.g. `("NUMBER", "42")`. The text is
146
+ not converted. Inline regular expressions `/.../` are treated like named tokens.
147
+ * **Pass-through.** If an alternative has no head and consists of exactly one
148
+ symbol (with no `*`, `+`, `?`, `[...]` or `|`), it returns the abstract syntax
149
+ tree of that symbol unchanged.
150
+ * **Rule name as head.** If an alternative has no head and is not a pass-through,
151
+ the name of the rule is used as head, e.g. `("program", ...)`. An empty
152
+ alternative yields `("rule_name",)`.
153
+ * **Repetitions and groups are flattened** into the tuple of the alternative.
154
+ * **Missing optional parts are left out**; there is no `None` placeholder.
155
+ * **Aliases** replace the whole head: `-> "call"` and `-> call` are equivalent.
156
+
157
+ ### Ignoring literals
158
+
159
+ `%ignore` has three forms:
160
+
161
+ ```
162
+ %ignore WS // the lexer skips the terminal WS
163
+ factor: "(" expr ")" %ignore "(", ")"
164
+ // "(" and ")" are left out of the head of this alternative
165
+ %ignore: "(", ")", "{", "}" // these literals are left out of the heads of all rules
166
+ ```
167
+
168
+ With `%ignore: "(", ")"`, the rule
169
+ `statement: "while" "(" expr ")" statement` yields
170
+ `("while", ast(expr), ast(statement))`.
171
+
172
+ ## Grammar syntax
173
+
174
+ Larkita reads Lark grammars:
175
+
176
+ * Rules have lowercase names, terminals have uppercase names.
177
+ * `|` separates alternatives, `( ... )` groups, `[ ... ]` and `?` mark optional
178
+ parts, `*` and `+` repeat, and `~ n` or `~ n..m` repeat a fixed number of times.
179
+ * String literals are written as `"..."` (append `i` for case-insensitive
180
+ matching), regular expressions as `/.../flags`.
181
+ * Terminals are defined with `NAME: ...`; `NAME.p: ...` gives a terminal the
182
+ priority `p`, and `rule.p: ...` gives a rule the priority `p` (used to resolve
183
+ reduce/reduce conflicts).
184
+ * `%import common.NUMBER` imports terminals from Lark's standard library
185
+ (`larkita/grammars/common.lark`), and `%import .file.rule` imports from a
186
+ grammar file next to the current one. `-> NAME` renames an import.
187
+ * `%declare`, `%extend` and `%override` work as in Lark.
188
+ * `//` starts a comment.
189
+
190
+ Not supported (compared with Lark): the rule modifiers `?rule` and `!rule`,
191
+ templates, post-lexers (such as Lark's `Indenter`), lexer callbacks, and
192
+ transformers. Rules and terminals whose names start with `_` are not inlined
193
+ or filtered.
194
+
195
+ ## The output file
196
+
197
+ If the grammar is read from a file, the output file has the same name with the
198
+ extension `.out` (`expr.g` produces `expr.out`). Otherwise it is `grammar.out` in
199
+ the current directory. The parameter `out_file` sets another path.
200
+
201
+ The file contains
202
+
203
+ 1. the rules after the EBNF operators have been expanded, each followed by the
204
+ abstract syntax tree that a reduction produces, e.g.
205
+ `(3) expr → expr "+" product ⇒ ("+", ast(expr), ast(product))`;
206
+ 2. a list of all conflicts and how they were resolved;
207
+ 3. all states. Each state lists its items with their lookahead tokens (closure
208
+ items are marked with `+`), followed by its actions and gotos:
209
+
210
+ ```
211
+ State 2
212
+
213
+ (1) stmnt → IDENTIFIER • ":=" expr ";" { $END }
214
+ (11) factor → IDENTIFIER • { "*", "+", "-", "/", ";" }
215
+
216
+ ":=" shift and go to state 8
217
+ "*" reduce using rule (11) factor → IDENTIFIER
218
+ "+" reduce using rule (11) factor → IDENTIFIER
219
+ "-" reduce using rule (11) factor → IDENTIFIER
220
+ "/" reduce using rule (11) factor → IDENTIFIER
221
+ ";" reduce using rule (11) factor → IDENTIFIER
222
+ ```
223
+
224
+ ## Conflicts
225
+
226
+ * **Shift/reduce conflicts** are resolved in favour of shift (so the dangling
227
+ `else` belongs to the innermost `if`). Larkita issues a `ConflictWarning`.
228
+ * **Reduce/reduce conflicts** are resolved in favour of the rule with the highest
229
+ priority. If no rule has a higher priority than the others, `Larkita` raises a
230
+ `GrammarError`; the output file is written anyway.
231
+
232
+ ## Merging states
233
+
234
+ Canonical LR(1) automata can be large. With `merge_states=True`, Larkita merges
235
+ states with the same core (i.e. the same items without lookaheads), as long as
236
+ this is safe:
237
+
238
+ * Merging must not introduce a new conflict, neither in the merged states nor in
239
+ their successors.
240
+ * With the contextual lexer, merging must not change how the input is split into
241
+ tokens. For example, if a keyword like `"else"` is only acceptable in one of
242
+ two states, merging them could make the lexer read `else` as a keyword where
243
+ the other state reads an identifier.
244
+
245
+ The resulting parser accepts the same strings, builds the same abstract syntax
246
+ trees, and detects syntax errors at the same tokens as the canonical parser,
247
+ although it may perform a few more reductions before it reports an error. With
248
+ `lexer='basic'`, the lexer does not depend on the state of the parser, so for an
249
+ LALR(1) grammar the result is the LALR(1) automaton. The output file then
250
+ describes the merged states and explains why the remaining states with the same
251
+ core have been kept apart.
252
+
253
+ ## API
254
+
255
+ ```python
256
+ Larkita(grammar, *, start=None, lexer='contextual', import_paths=(),
257
+ source_path=None, out_file=None, regex=False, g_regex_flags=0,
258
+ merge_states=False)
259
+ ```
260
+
261
+ * `grammar`: the grammar as a string or an open file.
262
+ * `start`: the start symbol; defaults to the first rule.
263
+ * `lexer`: `'contextual'` (only matches the terminals that the parser can accept
264
+ in its current state) or `'basic'`.
265
+ * `import_paths`: additional directories to search for imported grammars.
266
+ * `source_path`: the file name of the grammar, used for relative imports and to
267
+ name the output file.
268
+ * `out_file`: the path of the output file.
269
+ * `regex`: use the `regex` module instead of `re`.
270
+ * `g_regex_flags`: flags applied to all terminals.
271
+ * `merge_states`: merge states with the same core, as long as this is safe
272
+ (see above).
273
+
274
+ `Larkita.open(filename, rel_to=None, **options)` reads the grammar from a file.
275
+
276
+ `parser.parse(text)` returns the abstract syntax tree. It raises
277
+ `UnexpectedCharacters` if the lexer cannot match the input, and `UnexpectedToken`
278
+ if the parser receives a token it cannot accept (at the end of the input, the
279
+ token has the type `$END`). All exceptions derive from `LarkitaError`.
280
+
281
+ `parser.lex(text)` runs only the scanner and returns an iterator over the tokens.
282
+ Every token `t` provides `t.type`, `t.value`, `t.line`, and `t.column`; ignored
283
+ tokens are skipped.
284
+
285
+ After construction, `parser.conflicts`, `parser.rules`, `parser.terminals`,
286
+ `parser.parse_table` and `parser.out_file` describe the generated parser.
287
+
288
+ ## Examples and tests
289
+
290
+ The directory `new-syntax` of the repository contains two example grammars:
291
+ `expr.g` (arithmetic expressions) and `interpreter.g` (a small imperative language
292
+ with sets).
293
+
294
+ In a clone of the repository, run the tests with
295
+
296
+ python3 -m tests
297
+
298
+ ## License
299
+
300
+ MIT. Larkita is derived from [Lark](https://github.com/lark-parser/lark) by
301
+ Erez Shinan; see `LICENSE`.
@@ -0,0 +1,276 @@
1
+ # Larkita
2
+
3
+ Larkita is a light-weight version of the Python parser generator
4
+ [Lark](https://github.com/lark-parser/lark). It differs from Lark in three ways:
5
+
6
+ * It generates a **canonical LR(1) parser** (not Earley or CYK). On request, it
7
+ merges states with the same core; for an LALR(1) grammar and the basic lexer,
8
+ the result is the LALR(1) parser.
9
+ * The parser returns the **abstract syntax tree as a nested tuple**, not as a tree
10
+ of objects. How the tuple is built follows from the grammar itself; you never
11
+ have to write tree-building code.
12
+ * Every time a parser is generated, Larkita writes an **output file** with all
13
+ the LR(1) states, every item annotated with its lookahead tokens. This makes
14
+ conflicts easy to debug.
15
+
16
+ Grammars are written in Lark's EBNF notation, with a few additions for building
17
+ the abstract syntax tree.
18
+
19
+ ## Installation
20
+
21
+ Larkita needs Python 3.12 or newer and has no dependencies. Install it with
22
+
23
+ pip install larkita
24
+
25
+ The source code is on GitHub at <https://github.com/karlstroetmann/Larkita>. To
26
+ install the latest version from there, run
27
+
28
+ pip install git+https://github.com/karlstroetmann/Larkita.git
29
+
30
+ If the optional package `interegular` is installed, Larkita checks whether the
31
+ regular expressions of different terminals can match the same text. It reports
32
+ such collisions via `larkita.logger`, which is silent by default; call
33
+ `larkita.logger.setLevel(logging.WARNING)` to see them.
34
+
35
+ ## Quick start
36
+
37
+ ```python
38
+ from larkita import Larkita
39
+
40
+ grammar = r'''
41
+ stmnt: IDENTIFIER ":=" expr ";"
42
+ | expr ";"
43
+
44
+ expr: expr "+" product
45
+ | expr "-" product
46
+ | product
47
+
48
+ product: product "*" factor
49
+ | product "/" factor
50
+ | factor
51
+
52
+ factor: "(" expr ")" %ignore "(", ")"
53
+ | NUMBER
54
+ | IDENTIFIER
55
+
56
+ %import common.CNAME -> IDENTIFIER
57
+ %import common.NUMBER
58
+ %import common.WS
59
+ %ignore WS
60
+ '''
61
+
62
+ parser = Larkita(grammar)
63
+ print(parser.parse('x := (a + 3) * b;'))
64
+ ```
65
+
66
+ prints
67
+
68
+ ```python
69
+ ((':=', ';'), ('IDENTIFIER', 'x'), ('*', ('+', ('IDENTIFIER', 'a'), ('NUMBER', '3')), ('IDENTIFIER', 'b')))
70
+ ```
71
+
72
+ and writes the description of the automaton to `grammar.out`. To read the
73
+ grammar from a file, use
74
+
75
+ ```python
76
+ parser = Larkita.open('new-syntax/expr.g') # writes new-syntax/expr.out
77
+ ```
78
+
79
+ The start symbol is the first rule of the grammar (here `stmnt`).
80
+
81
+ ## Tutorial
82
+
83
+ The tutorial
84
+ [`tutorial/larkita-tutorial.pdf`](https://github.com/karlstroetmann/Larkita/blob/master/tutorial/larkita-tutorial.pdf)
85
+ explains how to write grammars for Larkita, how the abstract syntax trees are
86
+ built, how to process them with `match`, and how to deal with syntax errors and
87
+ conflicts. Its grammars and programs are in the directory `tutorial/examples` of
88
+ the repository; running `make` in the directory `tutorial` runs them and rebuilds
89
+ the PDF.
90
+
91
+ ## How the abstract syntax tree is built
92
+
93
+ Consider an alternative `a: X1 X2 ... Xn`. The literal tokens written in double
94
+ quotes form the **head** of the tuple; all other symbols become its **children**:
95
+
96
+ (head, ast(child_1), ..., ast(child_k))
97
+
98
+ | Grammar | Abstract syntax tree |
99
+ |---|---|
100
+ | `expr: expr "+" product` | `("+", ast(expr), ast(product))` |
101
+ | `stmnt: expr ";"` | `(";", ast(expr))` |
102
+ | `stmnt: IDENTIFIER ":=" expr ";"` | `((":=", ";"), ("IDENTIFIER", name), ast(expr))` |
103
+ | `atom: IDENTIFIER` | `("IDENTIFIER", name)` |
104
+ | `expr: product` | `ast(product)` |
105
+ | `factor: "(" expr ")" %ignore "(", ")"` | `ast(expr)` |
106
+ | `atom: IDENTIFIER "(" args ")" -> "call"` | `("call", ("IDENTIFIER", name), ast(args))` |
107
+ | `args: expr ("," expr)*` | `(",", ast(expr_1), ..., ast(expr_n))` |
108
+ | `set: "{" expr ("," expr)* "}"` | `(("{", ",", "}"), ast(expr_1), ..., ast(expr_n))` |
109
+ | `program: statement*` | `("program", ast(statement_1), ..., ast(statement_n))` |
110
+ | `stmnt: "return" [expr] ";"` | `(("return", ";"),)` or `(("return", ";"), ast(expr))` |
111
+
112
+ In detail:
113
+
114
+ * **Head.** The head consists of all literals of the alternative that are not
115
+ ignored, in the order in which they are written, one entry per occurrence. If
116
+ there is exactly one literal, the head is that string; otherwise it is a tuple
117
+ of strings. The head is fixed by the grammar, not by the input: `f(a)` with the
118
+ rule for `args` above yields `(",", ast(a))`, even though no comma was read.
119
+ Literals inside repetitions, optional parts and groups belong to the head, too.
120
+ * **Named tokens** become `(NAME, text)`, e.g. `("NUMBER", "42")`. The text is
121
+ not converted. Inline regular expressions `/.../` are treated like named tokens.
122
+ * **Pass-through.** If an alternative has no head and consists of exactly one
123
+ symbol (with no `*`, `+`, `?`, `[...]` or `|`), it returns the abstract syntax
124
+ tree of that symbol unchanged.
125
+ * **Rule name as head.** If an alternative has no head and is not a pass-through,
126
+ the name of the rule is used as head, e.g. `("program", ...)`. An empty
127
+ alternative yields `("rule_name",)`.
128
+ * **Repetitions and groups are flattened** into the tuple of the alternative.
129
+ * **Missing optional parts are left out**; there is no `None` placeholder.
130
+ * **Aliases** replace the whole head: `-> "call"` and `-> call` are equivalent.
131
+
132
+ ### Ignoring literals
133
+
134
+ `%ignore` has three forms:
135
+
136
+ ```
137
+ %ignore WS // the lexer skips the terminal WS
138
+ factor: "(" expr ")" %ignore "(", ")"
139
+ // "(" and ")" are left out of the head of this alternative
140
+ %ignore: "(", ")", "{", "}" // these literals are left out of the heads of all rules
141
+ ```
142
+
143
+ With `%ignore: "(", ")"`, the rule
144
+ `statement: "while" "(" expr ")" statement` yields
145
+ `("while", ast(expr), ast(statement))`.
146
+
147
+ ## Grammar syntax
148
+
149
+ Larkita reads Lark grammars:
150
+
151
+ * Rules have lowercase names, terminals have uppercase names.
152
+ * `|` separates alternatives, `( ... )` groups, `[ ... ]` and `?` mark optional
153
+ parts, `*` and `+` repeat, and `~ n` or `~ n..m` repeat a fixed number of times.
154
+ * String literals are written as `"..."` (append `i` for case-insensitive
155
+ matching), regular expressions as `/.../flags`.
156
+ * Terminals are defined with `NAME: ...`; `NAME.p: ...` gives a terminal the
157
+ priority `p`, and `rule.p: ...` gives a rule the priority `p` (used to resolve
158
+ reduce/reduce conflicts).
159
+ * `%import common.NUMBER` imports terminals from Lark's standard library
160
+ (`larkita/grammars/common.lark`), and `%import .file.rule` imports from a
161
+ grammar file next to the current one. `-> NAME` renames an import.
162
+ * `%declare`, `%extend` and `%override` work as in Lark.
163
+ * `//` starts a comment.
164
+
165
+ Not supported (compared with Lark): the rule modifiers `?rule` and `!rule`,
166
+ templates, post-lexers (such as Lark's `Indenter`), lexer callbacks, and
167
+ transformers. Rules and terminals whose names start with `_` are not inlined
168
+ or filtered.
169
+
170
+ ## The output file
171
+
172
+ If the grammar is read from a file, the output file has the same name with the
173
+ extension `.out` (`expr.g` produces `expr.out`). Otherwise it is `grammar.out` in
174
+ the current directory. The parameter `out_file` sets another path.
175
+
176
+ The file contains
177
+
178
+ 1. the rules after the EBNF operators have been expanded, each followed by the
179
+ abstract syntax tree that a reduction produces, e.g.
180
+ `(3) expr → expr "+" product ⇒ ("+", ast(expr), ast(product))`;
181
+ 2. a list of all conflicts and how they were resolved;
182
+ 3. all states. Each state lists its items with their lookahead tokens (closure
183
+ items are marked with `+`), followed by its actions and gotos:
184
+
185
+ ```
186
+ State 2
187
+
188
+ (1) stmnt → IDENTIFIER • ":=" expr ";" { $END }
189
+ (11) factor → IDENTIFIER • { "*", "+", "-", "/", ";" }
190
+
191
+ ":=" shift and go to state 8
192
+ "*" reduce using rule (11) factor → IDENTIFIER
193
+ "+" reduce using rule (11) factor → IDENTIFIER
194
+ "-" reduce using rule (11) factor → IDENTIFIER
195
+ "/" reduce using rule (11) factor → IDENTIFIER
196
+ ";" reduce using rule (11) factor → IDENTIFIER
197
+ ```
198
+
199
+ ## Conflicts
200
+
201
+ * **Shift/reduce conflicts** are resolved in favour of shift (so the dangling
202
+ `else` belongs to the innermost `if`). Larkita issues a `ConflictWarning`.
203
+ * **Reduce/reduce conflicts** are resolved in favour of the rule with the highest
204
+ priority. If no rule has a higher priority than the others, `Larkita` raises a
205
+ `GrammarError`; the output file is written anyway.
206
+
207
+ ## Merging states
208
+
209
+ Canonical LR(1) automata can be large. With `merge_states=True`, Larkita merges
210
+ states with the same core (i.e. the same items without lookaheads), as long as
211
+ this is safe:
212
+
213
+ * Merging must not introduce a new conflict, neither in the merged states nor in
214
+ their successors.
215
+ * With the contextual lexer, merging must not change how the input is split into
216
+ tokens. For example, if a keyword like `"else"` is only acceptable in one of
217
+ two states, merging them could make the lexer read `else` as a keyword where
218
+ the other state reads an identifier.
219
+
220
+ The resulting parser accepts the same strings, builds the same abstract syntax
221
+ trees, and detects syntax errors at the same tokens as the canonical parser,
222
+ although it may perform a few more reductions before it reports an error. With
223
+ `lexer='basic'`, the lexer does not depend on the state of the parser, so for an
224
+ LALR(1) grammar the result is the LALR(1) automaton. The output file then
225
+ describes the merged states and explains why the remaining states with the same
226
+ core have been kept apart.
227
+
228
+ ## API
229
+
230
+ ```python
231
+ Larkita(grammar, *, start=None, lexer='contextual', import_paths=(),
232
+ source_path=None, out_file=None, regex=False, g_regex_flags=0,
233
+ merge_states=False)
234
+ ```
235
+
236
+ * `grammar`: the grammar as a string or an open file.
237
+ * `start`: the start symbol; defaults to the first rule.
238
+ * `lexer`: `'contextual'` (only matches the terminals that the parser can accept
239
+ in its current state) or `'basic'`.
240
+ * `import_paths`: additional directories to search for imported grammars.
241
+ * `source_path`: the file name of the grammar, used for relative imports and to
242
+ name the output file.
243
+ * `out_file`: the path of the output file.
244
+ * `regex`: use the `regex` module instead of `re`.
245
+ * `g_regex_flags`: flags applied to all terminals.
246
+ * `merge_states`: merge states with the same core, as long as this is safe
247
+ (see above).
248
+
249
+ `Larkita.open(filename, rel_to=None, **options)` reads the grammar from a file.
250
+
251
+ `parser.parse(text)` returns the abstract syntax tree. It raises
252
+ `UnexpectedCharacters` if the lexer cannot match the input, and `UnexpectedToken`
253
+ if the parser receives a token it cannot accept (at the end of the input, the
254
+ token has the type `$END`). All exceptions derive from `LarkitaError`.
255
+
256
+ `parser.lex(text)` runs only the scanner and returns an iterator over the tokens.
257
+ Every token `t` provides `t.type`, `t.value`, `t.line`, and `t.column`; ignored
258
+ tokens are skipped.
259
+
260
+ After construction, `parser.conflicts`, `parser.rules`, `parser.terminals`,
261
+ `parser.parse_table` and `parser.out_file` describe the generated parser.
262
+
263
+ ## Examples and tests
264
+
265
+ The directory `new-syntax` of the repository contains two example grammars:
266
+ `expr.g` (arithmetic expressions) and `interpreter.g` (a small imperative language
267
+ with sets).
268
+
269
+ In a clone of the repository, run the tests with
270
+
271
+ python3 -m tests
272
+
273
+ ## License
274
+
275
+ MIT. Larkita is derived from [Lark](https://github.com/lark-parser/lark) by
276
+ Erez Shinan; see `LICENSE`.
@@ -0,0 +1,29 @@
1
+ from .exceptions import (
2
+ ConflictWarning,
3
+ GrammarError,
4
+ LarkitaError,
5
+ LexError,
6
+ ParseError,
7
+ UnexpectedCharacters,
8
+ UnexpectedInput,
9
+ UnexpectedToken,
10
+ )
11
+ from .larkita import Larkita
12
+ from .lexer import Token
13
+ from .utils import logger
14
+
15
+ __version__: str = "0.1.0"
16
+
17
+ __all__ = (
18
+ "ConflictWarning",
19
+ "GrammarError",
20
+ "LarkitaError",
21
+ "LexError",
22
+ "ParseError",
23
+ "UnexpectedCharacters",
24
+ "UnexpectedInput",
25
+ "UnexpectedToken",
26
+ "Larkita",
27
+ "Token",
28
+ "logger",
29
+ )