zsuite-py 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.
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ .pytest_cache/
6
+ dist/
7
+ build/
8
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniele Linguaglossa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.5
2
+ Name: zsuite-py
3
+ Version: 0.1.0
4
+ Summary: A toolkit for building programming languages in Python: zgram (syntax), zrules (checks), zrun (execution), zlsp (editors), at versions that work together.
5
+ Project-URL: Homepage, https://github.com/dzonerzy/zsuite
6
+ Project-URL: Documentation, https://github.com/dzonerzy/zsuite/tree/main/docs
7
+ Author: Daniele Linguaglossa
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Software Development :: Compilers
17
+ Classifier: Topic :: Software Development :: Interpreters
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: zgram-py<0.6,>=0.5.1
20
+ Requires-Dist: zlsp-py<0.2,>=0.1.3
21
+ Requires-Dist: zrules-py<0.3,>=0.2.0
22
+ Requires-Dist: zrun-py<0.5,>=0.4.0
23
+ Provides-Extra: exe
24
+ Requires-Dist: zrun-py[exe]<0.5,>=0.4.0; extra == 'exe'
25
+ Description-Content-Type: text/markdown
26
+
27
+ <div align="center">
28
+
29
+ <img src="https://raw.githubusercontent.com/dzonerzy/zsuite/main/docs/assets/logo.svg" alt="zsuite Logo" width="150">
30
+
31
+ # zsuite
32
+
33
+ **A toolkit for building programming languages in Python: syntax, checks, execution and editor support.**
34
+
35
+ Programming languages, DSLs and data formats, from a grammar to a fast implementation with an editor. Each stage is its own package, usable alone; together they cover the whole way.
36
+
37
+ [![GitHub Stars](https://img.shields.io/github/stars/dzonerzy/zsuite?style=flat)](https://github.com/dzonerzy/zsuite)
38
+ [![Python](https://img.shields.io/badge/python-3.10+-blue)](https://www.python.org/)
39
+ [![Zig](https://img.shields.io/badge/zig-0.16+-orange)](https://ziglang.org/)
40
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/dzonerzy/zsuite/blob/main/LICENSE)
41
+
42
+ </div>
43
+
44
+ ---
45
+
46
+ | Package | Stage | What it does | PyPI |
47
+ |---|---|---|---|
48
+ | [zgram](https://github.com/dzonerzy/zgram) | Syntax | PEG grammars compiled to native parsers with LLVM: parse trees, ASTs, error messages, error recovery | `zgram-py` |
49
+ | [zrules](https://github.com/dzonerzy/zrules) | Static semantics | Rules over zgram's trees: scopes and names, context rules, types (generics, unions, subtyping), control flow, diagnostics | `zrules-py` |
50
+ | [zrun](https://github.com/dzonerzy/zrun) | Execution | Semantics written as Python functions, compiled to native code by partial evaluation; executables of a program | `zrun-py` |
51
+ | [zlsp](https://github.com/dzonerzy/zlsp) | Editors | A Language Server for any language defined with zgram and checked with zrules: diagnostics, navigation, completion, hover | `zlsp-py` |
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install zsuite-py # the four packages, at versions that work together
57
+ pip install "zsuite-py[exe]" # and what building executables needs
58
+ ```
59
+
60
+ The import names are `zgram`, `zrules`, `zrun` and `zlsp` (each can also be installed alone as `<name>-py`). Wheels cover CPython 3.10+ on x86_64 Linux and Windows.
61
+
62
+ ## Quick start
63
+
64
+ ```bash
65
+ zsuite new calc # a language project: grammar, rules, semantics, language server, tests
66
+ cd calc
67
+ python calc.py run fib.calc # run a program, compiled to native code
68
+ python calc.py check fib.calc # its errors and warnings
69
+ python calc.py lsp # a language server for your editor
70
+ python -m pytest test_calc.py # its tests
71
+ ```
72
+
73
+ The project is the [tutorial](docs/tutorial.md)'s language under your name: change the grammar in `syntax.py`, the rules in `checks.py`, what each construct does in `semantics.py`, and the editor's view in `server.py`.
74
+
75
+ ## How they fit
76
+
77
+ ```
78
+ source ──zgram──▶ parse tree ──zrules──▶ checked tree + symbols + types ──zrun──▶ running program
79
+ │
80
+ └──zlsp──▶ editor (diagnostics, go to definition, hover, ...)
81
+ ```
82
+
83
+ - **zgram** turns text into a tree: a grammar in, a parser out (`zgram.compile(grammar)`), its nodes labelled with what the grammar names them.
84
+ - **zrules** checks the tree: rules written as selectors over node kinds (`"break_stmt"` inside a loop, each name defined once in its scope, the types of expressions), reported as diagnostics with source positions.
85
+ - **zrun** runs it: a semantic per node kind, plain Python taking the node and the runtime; zrun compiles the semantics for the program's tree to native code, so the language runs at a fraction of a hand-written interpreter's speed.
86
+ - **zlsp** serves it to editors: the grammar and the rules are all it needs.
87
+
88
+ ## Versions
89
+
90
+ Each package is released on its own; these are the current ones, which work together:
91
+
92
+ | zgram | zrules | zrun | zlsp |
93
+ |---|---|---|---|
94
+ | 0.5.1 | 0.2.0 | 0.4.0 | 0.1.3 |
95
+
96
+ ## Documentation
97
+
98
+ - [Tutorial](docs/tutorial.md): building a language through all four stages, from grammar to editor and executable.
99
+ - [Best practices](docs/best-practices.md): grammars that recover well, rules, semantics that compile fast, testing, shipping.
100
+ - [Design](docs/design.md): how the suite fits together, its decisions and its roadmap.
101
+ - Each package's README is its reference.
102
+
103
+ ## Project Structure
104
+
105
+ ```
106
+ src/zsuite/ # the zsuite package: versions(), `zsuite new`
107
+ examples/tiny/ # the tutorial's language: one file per stage, and its tests
108
+ # (also `zsuite new`'s template)
109
+ docs/ # the tutorial, best practices, the design
110
+ tests/ # the package's tests
111
+ ```
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,89 @@
1
+ <div align="center">
2
+
3
+ <img src="https://raw.githubusercontent.com/dzonerzy/zsuite/main/docs/assets/logo.svg" alt="zsuite Logo" width="150">
4
+
5
+ # zsuite
6
+
7
+ **A toolkit for building programming languages in Python: syntax, checks, execution and editor support.**
8
+
9
+ Programming languages, DSLs and data formats, from a grammar to a fast implementation with an editor. Each stage is its own package, usable alone; together they cover the whole way.
10
+
11
+ [![GitHub Stars](https://img.shields.io/github/stars/dzonerzy/zsuite?style=flat)](https://github.com/dzonerzy/zsuite)
12
+ [![Python](https://img.shields.io/badge/python-3.10+-blue)](https://www.python.org/)
13
+ [![Zig](https://img.shields.io/badge/zig-0.16+-orange)](https://ziglang.org/)
14
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/dzonerzy/zsuite/blob/main/LICENSE)
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ | Package | Stage | What it does | PyPI |
21
+ |---|---|---|---|
22
+ | [zgram](https://github.com/dzonerzy/zgram) | Syntax | PEG grammars compiled to native parsers with LLVM: parse trees, ASTs, error messages, error recovery | `zgram-py` |
23
+ | [zrules](https://github.com/dzonerzy/zrules) | Static semantics | Rules over zgram's trees: scopes and names, context rules, types (generics, unions, subtyping), control flow, diagnostics | `zrules-py` |
24
+ | [zrun](https://github.com/dzonerzy/zrun) | Execution | Semantics written as Python functions, compiled to native code by partial evaluation; executables of a program | `zrun-py` |
25
+ | [zlsp](https://github.com/dzonerzy/zlsp) | Editors | A Language Server for any language defined with zgram and checked with zrules: diagnostics, navigation, completion, hover | `zlsp-py` |
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install zsuite-py # the four packages, at versions that work together
31
+ pip install "zsuite-py[exe]" # and what building executables needs
32
+ ```
33
+
34
+ The import names are `zgram`, `zrules`, `zrun` and `zlsp` (each can also be installed alone as `<name>-py`). Wheels cover CPython 3.10+ on x86_64 Linux and Windows.
35
+
36
+ ## Quick start
37
+
38
+ ```bash
39
+ zsuite new calc # a language project: grammar, rules, semantics, language server, tests
40
+ cd calc
41
+ python calc.py run fib.calc # run a program, compiled to native code
42
+ python calc.py check fib.calc # its errors and warnings
43
+ python calc.py lsp # a language server for your editor
44
+ python -m pytest test_calc.py # its tests
45
+ ```
46
+
47
+ The project is the [tutorial](docs/tutorial.md)'s language under your name: change the grammar in `syntax.py`, the rules in `checks.py`, what each construct does in `semantics.py`, and the editor's view in `server.py`.
48
+
49
+ ## How they fit
50
+
51
+ ```
52
+ source ──zgram──▶ parse tree ──zrules──▶ checked tree + symbols + types ──zrun──▶ running program
53
+ │
54
+ └──zlsp──▶ editor (diagnostics, go to definition, hover, ...)
55
+ ```
56
+
57
+ - **zgram** turns text into a tree: a grammar in, a parser out (`zgram.compile(grammar)`), its nodes labelled with what the grammar names them.
58
+ - **zrules** checks the tree: rules written as selectors over node kinds (`"break_stmt"` inside a loop, each name defined once in its scope, the types of expressions), reported as diagnostics with source positions.
59
+ - **zrun** runs it: a semantic per node kind, plain Python taking the node and the runtime; zrun compiles the semantics for the program's tree to native code, so the language runs at a fraction of a hand-written interpreter's speed.
60
+ - **zlsp** serves it to editors: the grammar and the rules are all it needs.
61
+
62
+ ## Versions
63
+
64
+ Each package is released on its own; these are the current ones, which work together:
65
+
66
+ | zgram | zrules | zrun | zlsp |
67
+ |---|---|---|---|
68
+ | 0.5.1 | 0.2.0 | 0.4.0 | 0.1.3 |
69
+
70
+ ## Documentation
71
+
72
+ - [Tutorial](docs/tutorial.md): building a language through all four stages, from grammar to editor and executable.
73
+ - [Best practices](docs/best-practices.md): grammars that recover well, rules, semantics that compile fast, testing, shipping.
74
+ - [Design](docs/design.md): how the suite fits together, its decisions and its roadmap.
75
+ - Each package's README is its reference.
76
+
77
+ ## Project Structure
78
+
79
+ ```
80
+ src/zsuite/ # the zsuite package: versions(), `zsuite new`
81
+ examples/tiny/ # the tutorial's language: one file per stage, and its tests
82
+ # (also `zsuite new`'s template)
83
+ docs/ # the tutorial, best practices, the design
84
+ tests/ # the package's tests
85
+ ```
86
+
87
+ ## License
88
+
89
+ MIT
@@ -0,0 +1,167 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
2
+ <defs>
3
+ <!-- Background gradient: the zsuite tile -->
4
+ <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
5
+ <stop offset="0%" stop-color="#0d1117"/>
6
+ <stop offset="100%" stop-color="#161b22"/>
7
+ </linearGradient>
8
+
9
+ <!-- Zig orange: the zsuite Z -->
10
+ <linearGradient id="zig" x1="0%" y1="0%" x2="100%" y2="100%">
11
+ <stop offset="0%" stop-color="#f7a41d"/>
12
+ <stop offset="100%" stop-color="#f97316"/>
13
+ </linearGradient>
14
+
15
+ <!-- The four tools' accents -->
16
+ <linearGradient id="gram" x1="0%" y1="0%" x2="0%" y2="100%">
17
+ <stop offset="0%" stop-color="#34d399"/>
18
+ <stop offset="100%" stop-color="#10b981"/>
19
+ </linearGradient>
20
+ <linearGradient id="rules" x1="0%" y1="0%" x2="100%" y2="100%">
21
+ <stop offset="0%" stop-color="#2dd4bf"/>
22
+ <stop offset="100%" stop-color="#14b8a6"/>
23
+ </linearGradient>
24
+ <linearGradient id="run" x1="0%" y1="0%" x2="100%" y2="100%">
25
+ <stop offset="0%" stop-color="#67e8f9"/>
26
+ <stop offset="100%" stop-color="#0ea5e9"/>
27
+ </linearGradient>
28
+ <linearGradient id="lsp" x1="0%" y1="0%" x2="100%" y2="100%">
29
+ <stop offset="0%" stop-color="#c4b5fd"/>
30
+ <stop offset="100%" stop-color="#8b5cf6"/>
31
+ </linearGradient>
32
+
33
+ <!-- The pipeline through them: green, teal, cyan, violet -->
34
+ <linearGradient id="flow" gradientUnits="userSpaceOnUse" x1="112" y1="108" x2="400" y2="340">
35
+ <stop offset="0%" stop-color="#34d399"/>
36
+ <stop offset="35%" stop-color="#2dd4bf"/>
37
+ <stop offset="65%" stop-color="#0ea5e9"/>
38
+ <stop offset="100%" stop-color="#8b5cf6"/>
39
+ </linearGradient>
40
+
41
+ <!-- The word, across the four -->
42
+ <linearGradient id="word" gradientUnits="userSpaceOnUse" x1="150" y1="0" x2="362" y2="0">
43
+ <stop offset="0%" stop-color="#34d399"/>
44
+ <stop offset="33%" stop-color="#2dd4bf"/>
45
+ <stop offset="66%" stop-color="#22d3ee"/>
46
+ <stop offset="100%" stop-color="#a78bfa"/>
47
+ </linearGradient>
48
+
49
+ <filter id="glow">
50
+ <feGaussianBlur stdDeviation="3" result="blur"/>
51
+ <feMerge>
52
+ <feMergeNode in="blur"/>
53
+ <feMergeNode in="SourceGraphic"/>
54
+ </feMerge>
55
+ </filter>
56
+
57
+ <filter id="outerGlow">
58
+ <feGaussianBlur stdDeviation="8" result="blur"/>
59
+ <feComposite in="blur" in2="SourceGraphic" operator="out" result="glowOut"/>
60
+ <feMerge>
61
+ <feMergeNode in="glowOut"/>
62
+ <feMergeNode in="SourceGraphic"/>
63
+ </feMerge>
64
+ </filter>
65
+
66
+ <filter id="shadow">
67
+ <feGaussianBlur stdDeviation="4"/>
68
+ </filter>
69
+ </defs>
70
+
71
+ <!-- Background -->
72
+ <rect width="512" height="512" rx="64" fill="url(#bg)"/>
73
+
74
+ <!-- The pipeline: syntax, checks, running, the editor, and round again -->
75
+ <rect x="112" y="108" width="288" height="232" rx="44"
76
+ fill="none" stroke="url(#flow)" stroke-width="2.5" stroke-opacity="0.5"
77
+ filter="url(#glow)"/>
78
+ <!-- direction marks on the loop -->
79
+ <g fill="url(#flow)" opacity="0.7">
80
+ <path d="M 252 100 l 12 8 l -12 8 z"/>
81
+ <path d="M 392 220 l 8 12 l 8 -12 z"/>
82
+ <path d="M 260 332 l -12 8 l 12 8 z"/>
83
+ <path d="M 104 228 l 8 -12 l 8 12 z"/>
84
+ </g>
85
+
86
+ <!-- zgram: a parse tree -->
87
+ <g filter="url(#glow)">
88
+ <circle cx="112" cy="108" r="32" fill="#161b22" stroke="url(#gram)" stroke-width="3.5"/>
89
+ </g>
90
+ <g stroke="url(#gram)" stroke-width="3" stroke-linecap="round">
91
+ <line x1="112" y1="96" x2="100" y2="118"/>
92
+ <line x1="112" y1="96" x2="124" y2="118"/>
93
+ </g>
94
+ <g fill="url(#gram)">
95
+ <circle cx="112" cy="94" r="5.5"/>
96
+ <circle cx="100" cy="120" r="5"/>
97
+ <circle cx="124" cy="120" r="5"/>
98
+ </g>
99
+
100
+ <!-- zrules: a check -->
101
+ <g filter="url(#glow)">
102
+ <circle cx="400" cy="108" r="32" fill="#161b22" stroke="url(#rules)" stroke-width="3.5"/>
103
+ </g>
104
+ <path d="M 386 109 l 9 9 l 19 -20" fill="none" stroke="url(#rules)" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
105
+
106
+ <!-- zrun: running -->
107
+ <g filter="url(#glow)">
108
+ <circle cx="400" cy="340" r="32" fill="#161b22" stroke="url(#run)" stroke-width="3.5"/>
109
+ </g>
110
+ <path d="M 391 325 L 416 340 L 391 355 Z" fill="url(#run)"/>
111
+
112
+ <!-- zlsp: an editor -->
113
+ <g filter="url(#glow)">
114
+ <circle cx="112" cy="340" r="32" fill="#161b22" stroke="url(#lsp)" stroke-width="3.5"/>
115
+ </g>
116
+ <rect x="94" y="326" width="36" height="28" rx="5" fill="none" stroke="url(#lsp)" stroke-width="3"/>
117
+ <!-- (solid: a gradient on a straight line has no box to span) -->
118
+ <g stroke="#a78bfa" stroke-width="3" stroke-linecap="round">
119
+ <line x1="101" y1="336" x2="115" y2="336"/>
120
+ <line x1="101" y1="344" x2="110" y2="344"/>
121
+ <line x1="121" y1="333" x2="121" y2="347"/>
122
+ </g>
123
+
124
+ <!-- The Z's shadow, for depth -->
125
+ <path transform="translate(38 4)" d="
126
+ M 130 152
127
+ L 318 152
128
+ L 318 178
129
+ L 192 288
130
+ L 318 288
131
+ L 318 314
132
+ L 130 314
133
+ L 130 288
134
+ L 256 178
135
+ L 130 178
136
+ Z
137
+ " fill="#000000" opacity="0.45" filter="url(#shadow)"/>
138
+
139
+ <!-- Main "Z" letterform: the zsuite mark, at the center of the four -->
140
+ <g filter="url(#outerGlow)">
141
+ <path transform="translate(32 -9)" d="
142
+ M 130 152
143
+ L 318 152
144
+ L 318 178
145
+ L 192 288
146
+ L 318 288
147
+ L 318 314
148
+ L 130 314
149
+ L 130 288
150
+ L 256 178
151
+ L 130 178
152
+ Z
153
+ " fill="url(#zig)"/>
154
+ </g>
155
+
156
+ <!-- "suite" text -->
157
+ <text x="256" y="474" text-anchor="middle"
158
+ font-family="'SF Mono', 'Fira Code', 'JetBrains Mono', monospace"
159
+ font-size="58" font-weight="700" letter-spacing="8"
160
+ fill="url(#word)">suite</text>
161
+
162
+ <!-- The pipeline, as a watermark -->
163
+ <text x="256" y="58" text-anchor="middle"
164
+ font-family="'SF Mono', 'Fira Code', monospace"
165
+ font-size="11" fill="#ffffff" opacity="0.14"
166
+ letter-spacing="1">grammar → rules → run → editor</text>
167
+ </svg>
@@ -0,0 +1,192 @@
1
+ # Best practices
2
+
3
+ What makes a zsuite language pleasant to use and fast to run, stage by stage.
4
+ The [tutorial](tutorial.md) shows the basics; this is what to do as the
5
+ language grows.
6
+
7
+ ## Grammars
8
+
9
+ **Label what anything downstream reads.** Rules, semantics and the editor
10
+ reach a node's parts by label (`node.cond`, `"FuncDef > .name"`), never by
11
+ position. A label that may repeat (`params:ident (ws ',' ws params:ident)*`)
12
+ is a list; under `?` it's the node or `None`.
13
+
14
+ **Give each construct a kind.** `-> While`, `-> FuncDef`: rules and semantics
15
+ select on kinds, and several rules can share one (`expr`, `sum` and `term` are
16
+ all `BinOp`). Make helper rules `@silent` so they don't add levels to the
17
+ tree.
18
+
19
+ **Keywords end at a word boundary.** Follow each keyword with a silent
20
+ lookahead, and keep identifiers from being keywords:
21
+
22
+ ```
23
+ while_stmt = 'while' kw ws cond:expr ws body:block
24
+ @silent keyword = ('fn' | 'while' | 'if' | 'else' | 'return') kw
25
+ @silent kw = ![a-zA-Z0-9_]
26
+ ident = !keyword [a-zA-Z_] [a-zA-Z0-9_]*
27
+ ```
28
+
29
+ Word literals are also what zlsp highlights and completes as keywords.
30
+
31
+ **One whitespace rule, comments included.** `@silent ws = ([ \t\n\r] | '#'
32
+ [^\n]*)*`, written wherever whitespace may appear. Lists of statements as
33
+ `(stmt ws)*`.
34
+
35
+ **Fold operators, don't nest by hand.** `@left` for left-associative
36
+ operators, `@right` for right-associative ones, `@postfix` for calls, indexing
37
+ and member access (`a.b(c)[d]`). Each level gets exactly `left`, `op`,
38
+ `right` (or `target`), and a lone operand isn't wrapped.
39
+
40
+ **Name rules for error messages.** `ident "name" = ...`, `expr "expression" =
41
+ ...`: errors read "expected expression", not "expected sum".
42
+
43
+ **Avoid exponential backtracking.** Two alternatives that share a long prefix
44
+ re-parse it: `type = union | single` where a union begins with a single. Write
45
+ it as one rule with a suffix chain (`@postfix type = members:single
46
+ union_tail*`), or factor the prefix out. `@memo` helps a rule that's re-tried
47
+ at the same position, at the cost of a table lookup per call: add it where
48
+ profiling shows retries, not everywhere.
49
+
50
+ **Shape the grammar for error recovery.** zgram recovers without grammar
51
+ changes, best when the grammar has the usual shapes:
52
+
53
+ - statements and items as repetitions (`(stmt ws)*`, `(item (ws ',' ws item)*)?`),
54
+ so a broken one is skipped and the rest kept;
55
+ - closing brackets and words (`}`, `end`) as literals, so a missing one is
56
+ inserted and a stray one recognized;
57
+ - separators as literals or small punctuation classes (`','`, `[,;]`), so a
58
+ missing one is inserted;
59
+ - the word that decides a construct first (`'let' kw ...`): it's never made up.
60
+
61
+ For statements that end at a terminator, `@recover(';')` makes a broken one
62
+ end there. Try broken input as you write the grammar:
63
+ `parser.parse_tree(src, recover=True).errors`.
64
+
65
+ ## Rules
66
+
67
+ **Give every rule a code.** `code="break-outside-loop"`: diagnostics carry it,
68
+ tests match on it, editor quick fixes key on it, users search for it. Write
69
+ messages for the user, with the node's text where it helps (`"duplicate
70
+ parameter '{text}'"`).
71
+
72
+ **Model the names exactly.** Most of a language's checks are its scoping, and
73
+ `scopes()` has an option for each common rule: `define_outer` (a function's
74
+ name lives outside it), `hoist` (usable before its definition), `after`
75
+ (`let a = a` sees the outer `a`), `ordered` (visible only after its
76
+ definition), `members` (`a.b`), imports. Separate namespaces (types and
77
+ values, labels) are separate `scopes()` rules. Report unused names
78
+ (`on_unused="warning"`) once the scoping is right.
79
+
80
+ **Add types when the language has them.** `types()` is gradual: start with
81
+ literals and declarations, add operators, calls and structs; anything unknown
82
+ is compatible, so a partial setup never reports false errors. A typed language
83
+ gets more than errors: zrun compiles typed code to unboxed values, and zlsp
84
+ shows types in hover, signature help and inlay hints.
85
+
86
+ **Add `flow()` for languages with returns and loops**: unreachable code,
87
+ functions that may end without returning, variables read before they have a
88
+ value, `goto` to missing labels.
89
+
90
+ **Write Python rules last.** `@rules.rule(selector)` can check anything, but
91
+ it runs in Python, per node, with the GIL held. Prefer the declarative rules;
92
+ use Python for what only the language knows.
93
+
94
+ **Check real code.** Run the rules over every file of the language you can
95
+ find: zrules' Lua checker runs over 830 real-world files, with no false error
96
+ allowed.
97
+
98
+ ## Semantics
99
+
100
+ **Branch on the node, not on values.** Anything computed from the node (its
101
+ kind, its operator text, the number of its children) is decided while
102
+ compiling and costs nothing. `if node.op == "+"` is free; looking an operator
103
+ up in a dict of Python functions and calling one isn't.
104
+
105
+ **Use the values zrun knows.** Ints, floats, bools, strs, lists, dicts, tuples
106
+ and records are native in compiled code. Make the language's objects records:
107
+ dataclasses or classes with `__slots__`, whose methods compile too. An
108
+ instance of an ordinary class, a set or `bytes` is a Python object: every
109
+ operation on it goes through Python.
110
+
111
+ **Don't allocate on the common path.** Each list, dict or record made is
112
+ allocation, reference counting and collection. Return one value as itself,
113
+ not a one-item list (Lua's single results); create a part of an object only
114
+ when it's used (a Lua table's hash part); avoid temporary lists for things
115
+ known in advance.
116
+
117
+ **Wrap integers with `rt.wrapping_*`.** Ints are 64-bit and checked: an
118
+ overflow is the program's error. For a language whose ints wrap (Lua, hashes,
119
+ bit tricks), use `rt.wrapping_add`, `_sub`, `_mul`, `_shl`, `_shr`, `_ushr`.
120
+ Wrapping by hand (`(a + b) & 0xFFFFFFFFFFFFFFFF`, then the sign) makes 128-bit
121
+ arithmetic first.
122
+
123
+ **Keep lists of one kind.** A list whose items are all ints, or all floats, is
124
+ marked so, and reads from it need no checks.
125
+
126
+ **Use `rt.tail_call` where the language guarantees tail calls.** `return
127
+ f(x)` in Lua or Scheme: the frame is given up first, so tail recursion runs in
128
+ constant stack, in every mode.
129
+
130
+ **Make everything compile.** `lang.python_semantics()` should be empty: each
131
+ entry says which semantic runs as Python, and why. Then run with
132
+ `report=True` and read `program.report()["python_crossings"]`: each line is a
133
+ place compiled code went through Python, with a count. Fix the biggest.
134
+
135
+ **Host functions: small, or native.** A small Python host function compiles
136
+ inline. One that does real work in Python is a call into Python each time; for
137
+ a hot one, write it in Zig or C and register it with `lang.native_host`.
138
+
139
+ zrun's [guide to writing fast
140
+ semantics](https://github.com/dzonerzy/zrun/blob/main/docs/writing-fast-semantics.md)
141
+ has the full list of what compiles.
142
+
143
+ ## Testing
144
+
145
+ **Run every program in both modes and compare.** The reference
146
+ (`mode="python"`) is the definition; compiled code must print the same and
147
+ fail the same way: the same message, at the same node, with the same call
148
+ stack. A difference is a zrun bug worth reporting.
149
+
150
+ **Test each stage on its own.** The grammar on valid and broken input (and
151
+ the errors recovery reports), the rules on programs that should and shouldn't
152
+ pass (match on codes), the semantics on programs and their output, the server
153
+ through `server.handle(message)` without an editor.
154
+
155
+ **Test what users will write wrong.** Undefined names, misplaced statements,
156
+ unclosed brackets, a missing separator: the error messages are part of the
157
+ language.
158
+
159
+ ## Editor support
160
+
161
+ **Say what each definition is.** `symbols={"FuncDef > .name": "function",
162
+ ...}` drives the outline, highlighting and completion; names without a kind
163
+ are guessed from their type and use.
164
+
165
+ **Highlight with selectors.** `tokens={"number": "number", "string":
166
+ "string"}`; keywords come from the grammar's word literals, comments from
167
+ `comments=`.
168
+
169
+ **Use hooks for what only the language knows**: documentation in hover,
170
+ snippets in completion, quick fixes for its own diagnostics, a formatter, the
171
+ editor's settings for rules that need them.
172
+
173
+ **Generate the TextMate grammar** (`server.textmate()`) for VS Code, so files
174
+ are colored before the server answers.
175
+
176
+ ## Shipping and running
177
+
178
+ **Compiled code is cached.** zgram keeps compiled grammars and zrun compiled
179
+ programs in the platform's cache directory, so only the first run of a
180
+ program pays for compiling. Where the home directory isn't writable, point
181
+ them elsewhere: `zgram.configure(cache=...)`, `zrun.configure(cache=...)`.
182
+
183
+ **Programs or engines.** A script runs once from its start: `program.run()`.
184
+ An engine (rules, filters, queries) is loaded once and called many times:
185
+ `program.call()` from Python, about 0.15 µs a call, and `program.map()` over
186
+ many inputs on native threads, with data passed as `zrun.Bytes`, not copied.
187
+ Calls that never touch Python run in parallel; `report()["gil_taken"]` says
188
+ whether they do.
189
+
190
+ **Ship programs as executables** (`zrun.build_executable`) to machines
191
+ without Python, and engines' rule sets as **compiled modules**
192
+ (`lang.compile`, `lang.load_compiled`) to skip compiling on every start.