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.
- zsuite_py-0.1.0/.gitignore +8 -0
- zsuite_py-0.1.0/LICENSE +21 -0
- zsuite_py-0.1.0/PKG-INFO +115 -0
- zsuite_py-0.1.0/README.md +89 -0
- zsuite_py-0.1.0/docs/assets/logo.svg +167 -0
- zsuite_py-0.1.0/docs/best-practices.md +192 -0
- zsuite_py-0.1.0/docs/design.md +632 -0
- zsuite_py-0.1.0/docs/tutorial.md +343 -0
- zsuite_py-0.1.0/examples/tiny/checks.py +32 -0
- zsuite_py-0.1.0/examples/tiny/fib.tiny +11 -0
- zsuite_py-0.1.0/examples/tiny/semantics.py +93 -0
- zsuite_py-0.1.0/examples/tiny/server.py +36 -0
- zsuite_py-0.1.0/examples/tiny/syntax.py +44 -0
- zsuite_py-0.1.0/examples/tiny/test_tiny.py +84 -0
- zsuite_py-0.1.0/examples/tiny/tiny.py +74 -0
- zsuite_py-0.1.0/pyproject.toml +66 -0
- zsuite_py-0.1.0/src/zsuite/__init__.py +39 -0
- zsuite_py-0.1.0/src/zsuite/__main__.py +41 -0
- zsuite_py-0.1.0/src/zsuite/_new.py +50 -0
- zsuite_py-0.1.0/tests/test_zsuite.py +45 -0
zsuite_py-0.1.0/LICENSE
ADDED
|
@@ -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.
|
zsuite_py-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/dzonerzy/zsuite)
|
|
38
|
+
[](https://www.python.org/)
|
|
39
|
+
[](https://ziglang.org/)
|
|
40
|
+
[](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
|
+
[](https://github.com/dzonerzy/zsuite)
|
|
12
|
+
[](https://www.python.org/)
|
|
13
|
+
[](https://ziglang.org/)
|
|
14
|
+
[](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.
|