pybrace 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.
- pybrace-0.1.0/.gitignore +9 -0
- pybrace-0.1.0/LICENSE +21 -0
- pybrace-0.1.0/PKG-INFO +143 -0
- pybrace-0.1.0/README.md +118 -0
- pybrace-0.1.0/README.zh-CN.md +117 -0
- pybrace-0.1.0/examples/demo.pyj +35 -0
- pybrace-0.1.0/pyj.py +720 -0
- pybrace-0.1.0/pyj_autoload.pth +1 -0
- pybrace-0.1.0/pyproject.toml +48 -0
- pybrace-0.1.0/tests/test_pyj.py +169 -0
pybrace-0.1.0/.gitignore
ADDED
pybrace-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yyszh01
|
|
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.
|
pybrace-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pybrace
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Write Python with braces and semicolons; .pyj files run transparently and tracebacks point back to the source
|
|
5
|
+
Project-URL: Homepage, https://github.com/yyszh01/pyj
|
|
6
|
+
Project-URL: Issues, https://github.com/yyszh01/pyj/issues
|
|
7
|
+
Project-URL: Source, https://github.com/yyszh01/pyj
|
|
8
|
+
Author: yyszh01
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: braces,curly-braces,import-hook,python,syntax,transpiler,whitespace
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# pyj — Python with braces
|
|
27
|
+
|
|
28
|
+
[](https://github.com/yyszh01/pyj/actions/workflows/ci.yml)
|
|
29
|
+
[](https://pypi.org/project/pybrace/)
|
|
30
|
+

|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
English | [简体中文](https://github.com/yyszh01/pyj/blob/main/README.zh-CN.md)
|
|
34
|
+
|
|
35
|
+
Write Python like JavaScript: blocks in `{ }`, statements separated by `;` or newlines, and no significant whitespace. `.pyj` files are converted to standard Python transparently at import and run time, and errors point back to the original `.pyj` lines and columns.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
def fib(n) { a, b = 0, 1; for _ in range(n) { a, b = b, a + b }; return a }
|
|
39
|
+
|
|
40
|
+
if n < 0 { print("negative") } else if n == 0 { print("zero") }
|
|
41
|
+
else { print("positive") }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- **Single file, zero dependencies**: the whole tool is one `pyj.py` that uses only the standard library.
|
|
47
|
+
- **Transparent execution**: once installed, `python app.pyj` just works, any program can `import` a `.pyj` module, and `.py` and `.pyj` files can be mixed freely.
|
|
48
|
+
- **Errors point to your source**: tracebacks, syntax errors and `inspect.getsource` all show `.pyj` lines and columns.
|
|
49
|
+
- **Dicts and sets work as usual**: pyj tells a block `{` apart from a literal `{`.
|
|
50
|
+
- **Cross-platform**: Python 3.10+, tested in CI on Linux, macOS and Windows.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
### With pip (recommended)
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install pybrace
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
After installing, every Python process in that environment understands `.pyj` files. `pip uninstall pybrace` removes it completely. The package is named `pybrace` on PyPI, but the module and the command are both `pyj`.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python app.pyj arg1 arg2 # run directly
|
|
64
|
+
python -c "import mymod" # import any .pyj module; packages can use __init__.pyj
|
|
65
|
+
pyj build app.pyj # convert to app.py
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- `.pyj` and `.py` modules can import each other. If both `foo.py` and `foo.pyj` exist, `foo.pyj` wins.
|
|
69
|
+
- Compiled code is cached in `__pycache__/<module>.pyj.cpython-XY.pyc`. The cache is invalidated when the source file or pyj itself changes.
|
|
70
|
+
- How `python app.pyj` works: at startup Python runs `pyj_autoload.pth` from site-packages. When it sees that the script is a `.pyj` file, it replaces the process with `python -m pyj app.pyj` (on Windows it starts a child process instead). Interpreter flags are preserved.
|
|
71
|
+
|
|
72
|
+
### Single file, no pip
|
|
73
|
+
|
|
74
|
+
Put `pyj.py` anywhere and run:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python pyj.py install # writes pyj_autoload.pth; same effect as pip install (inside a venv, only that venv)
|
|
78
|
+
python pyj.py uninstall # undo
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Without installing anything
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python pyj.py run app.pyj [args...] # or: python pyj.py app.pyj
|
|
85
|
+
python pyj.py build app.pyj # writes app.py
|
|
86
|
+
python pyj.py build app.pyj -o - # prints to stdout
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
From Python code:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import pyj
|
|
93
|
+
pyj.install_import_hook() # .pyj modules become importable
|
|
94
|
+
print(pyj.transpile("if x { y() }"))
|
|
95
|
+
code = pyj.compile_pyj(src, "file.pyj") # code object with positions mapped to the .pyj source
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Error locations
|
|
99
|
+
|
|
100
|
+
Runtime exceptions, syntax errors, `inspect.getsource` and `linecache` all point to the exact **line and column** in the `.pyj` source. This holds even when several statements share a line or one expression spans several lines:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
File "util.pyj", line 3, in divide
|
|
104
|
+
return a /
|
|
105
|
+
~~^
|
|
106
|
+
b }
|
|
107
|
+
~
|
|
108
|
+
ZeroDivisionError: division by zero
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
How it works: while converting, pyj records where each token came from in the source. It parses the generated code with `ast.parse`, rewrites every AST node's position back to the source position, and then compiles.
|
|
112
|
+
|
|
113
|
+
## Syntax
|
|
114
|
+
|
|
115
|
+
| Rule | Example |
|
|
116
|
+
|---|---|
|
|
117
|
+
| Blocks use `{ }`; indentation is free-form | `while x { x -= 1 }` |
|
|
118
|
+
| The colon is optional | `def f(): { ... }` is the same as `def f() { ... }` |
|
|
119
|
+
| Statements are separated by `;` | `a = 1; b = 2` |
|
|
120
|
+
| A newline ends a statement when the statement is clearly complete | One statement per line needs no `;` |
|
|
121
|
+
| Newlines inside brackets, or after a trailing operator or comma, are ignored | `total = 1 +`⏎`2` |
|
|
122
|
+
| A line starting with `.` `,` `and` `or` etc. continues the previous line | `s = text`⏎`.strip()` |
|
|
123
|
+
| `else if` is shorthand for `elif` | `} else if x {` |
|
|
124
|
+
| Empty blocks get `pass` automatically | `class E {}` |
|
|
125
|
+
| Dicts and sets are written as usual | `if k in {1, 2} { d = {} }` |
|
|
126
|
+
|
|
127
|
+
**How does pyj tell blocks from dicts?** A `{` opens a block only when it ends the header of an `if/for/while/def/class/try/with/match/case…` statement, that is, when it follows a name, number, string, closing bracket, `:`, or `else/try/finally/except`. In Python an expression followed by `{` is never valid, so this is unambiguous.
|
|
128
|
+
|
|
129
|
+
## Caveats
|
|
130
|
+
|
|
131
|
+
- A line starting with `(`, `[`, `-` or `*` does **not** continue the previous line. This avoids the JavaScript pitfall where `a`⏎`(b)` becomes a function call. To continue a line, put the operator at the end of the line or wrap the expression in parentheses.
|
|
132
|
+
- In a compound statement header written as `if x: {...}`, the `{` always opens a block. To put a dict expression after a one-line `if`, wrap it in a normal `{ }` block.
|
|
133
|
+
- Comments are preserved. A comment in the middle of an expression is moved to the end of that statement's line.
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
python -m unittest discover -s tests -v
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
[MIT](https://github.com/yyszh01/pyj/blob/main/LICENSE)
|
pybrace-0.1.0/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# pyj — Python with braces
|
|
2
|
+
|
|
3
|
+
[](https://github.com/yyszh01/pyj/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/pybrace/)
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
English | [简体中文](https://github.com/yyszh01/pyj/blob/main/README.zh-CN.md)
|
|
9
|
+
|
|
10
|
+
Write Python like JavaScript: blocks in `{ }`, statements separated by `;` or newlines, and no significant whitespace. `.pyj` files are converted to standard Python transparently at import and run time, and errors point back to the original `.pyj` lines and columns.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
def fib(n) { a, b = 0, 1; for _ in range(n) { a, b = b, a + b }; return a }
|
|
14
|
+
|
|
15
|
+
if n < 0 { print("negative") } else if n == 0 { print("zero") }
|
|
16
|
+
else { print("positive") }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Single file, zero dependencies**: the whole tool is one `pyj.py` that uses only the standard library.
|
|
22
|
+
- **Transparent execution**: once installed, `python app.pyj` just works, any program can `import` a `.pyj` module, and `.py` and `.pyj` files can be mixed freely.
|
|
23
|
+
- **Errors point to your source**: tracebacks, syntax errors and `inspect.getsource` all show `.pyj` lines and columns.
|
|
24
|
+
- **Dicts and sets work as usual**: pyj tells a block `{` apart from a literal `{`.
|
|
25
|
+
- **Cross-platform**: Python 3.10+, tested in CI on Linux, macOS and Windows.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### With pip (recommended)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install pybrace
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
After installing, every Python process in that environment understands `.pyj` files. `pip uninstall pybrace` removes it completely. The package is named `pybrace` on PyPI, but the module and the command are both `pyj`.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python app.pyj arg1 arg2 # run directly
|
|
39
|
+
python -c "import mymod" # import any .pyj module; packages can use __init__.pyj
|
|
40
|
+
pyj build app.pyj # convert to app.py
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- `.pyj` and `.py` modules can import each other. If both `foo.py` and `foo.pyj` exist, `foo.pyj` wins.
|
|
44
|
+
- Compiled code is cached in `__pycache__/<module>.pyj.cpython-XY.pyc`. The cache is invalidated when the source file or pyj itself changes.
|
|
45
|
+
- How `python app.pyj` works: at startup Python runs `pyj_autoload.pth` from site-packages. When it sees that the script is a `.pyj` file, it replaces the process with `python -m pyj app.pyj` (on Windows it starts a child process instead). Interpreter flags are preserved.
|
|
46
|
+
|
|
47
|
+
### Single file, no pip
|
|
48
|
+
|
|
49
|
+
Put `pyj.py` anywhere and run:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python pyj.py install # writes pyj_autoload.pth; same effect as pip install (inside a venv, only that venv)
|
|
53
|
+
python pyj.py uninstall # undo
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Without installing anything
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
python pyj.py run app.pyj [args...] # or: python pyj.py app.pyj
|
|
60
|
+
python pyj.py build app.pyj # writes app.py
|
|
61
|
+
python pyj.py build app.pyj -o - # prints to stdout
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
From Python code:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import pyj
|
|
68
|
+
pyj.install_import_hook() # .pyj modules become importable
|
|
69
|
+
print(pyj.transpile("if x { y() }"))
|
|
70
|
+
code = pyj.compile_pyj(src, "file.pyj") # code object with positions mapped to the .pyj source
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Error locations
|
|
74
|
+
|
|
75
|
+
Runtime exceptions, syntax errors, `inspect.getsource` and `linecache` all point to the exact **line and column** in the `.pyj` source. This holds even when several statements share a line or one expression spans several lines:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
File "util.pyj", line 3, in divide
|
|
79
|
+
return a /
|
|
80
|
+
~~^
|
|
81
|
+
b }
|
|
82
|
+
~
|
|
83
|
+
ZeroDivisionError: division by zero
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
How it works: while converting, pyj records where each token came from in the source. It parses the generated code with `ast.parse`, rewrites every AST node's position back to the source position, and then compiles.
|
|
87
|
+
|
|
88
|
+
## Syntax
|
|
89
|
+
|
|
90
|
+
| Rule | Example |
|
|
91
|
+
|---|---|
|
|
92
|
+
| Blocks use `{ }`; indentation is free-form | `while x { x -= 1 }` |
|
|
93
|
+
| The colon is optional | `def f(): { ... }` is the same as `def f() { ... }` |
|
|
94
|
+
| Statements are separated by `;` | `a = 1; b = 2` |
|
|
95
|
+
| A newline ends a statement when the statement is clearly complete | One statement per line needs no `;` |
|
|
96
|
+
| Newlines inside brackets, or after a trailing operator or comma, are ignored | `total = 1 +`⏎`2` |
|
|
97
|
+
| A line starting with `.` `,` `and` `or` etc. continues the previous line | `s = text`⏎`.strip()` |
|
|
98
|
+
| `else if` is shorthand for `elif` | `} else if x {` |
|
|
99
|
+
| Empty blocks get `pass` automatically | `class E {}` |
|
|
100
|
+
| Dicts and sets are written as usual | `if k in {1, 2} { d = {} }` |
|
|
101
|
+
|
|
102
|
+
**How does pyj tell blocks from dicts?** A `{` opens a block only when it ends the header of an `if/for/while/def/class/try/with/match/case…` statement, that is, when it follows a name, number, string, closing bracket, `:`, or `else/try/finally/except`. In Python an expression followed by `{` is never valid, so this is unambiguous.
|
|
103
|
+
|
|
104
|
+
## Caveats
|
|
105
|
+
|
|
106
|
+
- A line starting with `(`, `[`, `-` or `*` does **not** continue the previous line. This avoids the JavaScript pitfall where `a`⏎`(b)` becomes a function call. To continue a line, put the operator at the end of the line or wrap the expression in parentheses.
|
|
107
|
+
- In a compound statement header written as `if x: {...}`, the `{` always opens a block. To put a dict expression after a one-line `if`, wrap it in a normal `{ }` block.
|
|
108
|
+
- Comments are preserved. A comment in the middle of an expression is moved to the end of that statement's line.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python -m unittest discover -s tests -v
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
[MIT](https://github.com/yyszh01/pyj/blob/main/LICENSE)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# pyj — 用花括号写 Python
|
|
2
|
+
|
|
3
|
+
[](https://github.com/yyszh01/pyj/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/pybrace/)
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
[English](https://github.com/yyszh01/pyj/blob/main/README.md) | 简体中文
|
|
9
|
+
|
|
10
|
+
不用管换行和缩进,像 JS 一样写 Python。`.pyj` 文件在运行时透明地转换成标准 Python,报错直接指回 `.pyj` 源码的行号和列号。
|
|
11
|
+
```python
|
|
12
|
+
def fib(n) { a, b = 0, 1; for _ in range(n) { a, b = b, a + b }; return a }
|
|
13
|
+
|
|
14
|
+
if n < 0 { print("负数") } else if n == 0 { print("零") }
|
|
15
|
+
else { print("正数") }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 特点
|
|
19
|
+
|
|
20
|
+
- **单文件、零依赖**:整个工具就是一个 `pyj.py`,只用标准库。
|
|
21
|
+
- **透明运行**:安装后 `python app.pyj` 直接跑,任何程序都能 `import` `.pyj` 模块,`.py` 和 `.pyj` 可以混用。
|
|
22
|
+
- **报错指回源码**:traceback、语法错误、`inspect.getsource` 都显示 `.pyj` 源码的行和列。
|
|
23
|
+
- **字典、集合照常写**:能自动区分代码块的 `{` 和字面量的 `{`。
|
|
24
|
+
- **跨平台**:Python 3.10+,在 Linux / macOS / Windows 上由 CI 测试。
|
|
25
|
+
|
|
26
|
+
## 安装
|
|
27
|
+
|
|
28
|
+
### 用 pip 安装(推荐)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install pybrace
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
装好后当前 Python 环境里的所有进程都自动支持 `.pyj`,`pip uninstall pybrace` 即可完全移除。PyPI 上的包名是 `pybrace`,模块名和命令仍然是 `pyj`。
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
python app.pyj arg1 arg2 # 直接运行
|
|
38
|
+
python -c "import mymod" # import 任意 .pyj 模块,包可以用 __init__.pyj
|
|
39
|
+
pyj build app.pyj # 转成 app.py
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `.pyj` 和 `.py` 可以互相 import;同名时 `.pyj` 优先。
|
|
43
|
+
- 编译结果缓存在 `__pycache__/<模块名>.pyj.cpython-XY.pyc`;源文件或 pyj 本身改动后会自动失效。
|
|
44
|
+
- `python app.pyj` 的原理:Python 启动时会执行 site-packages 中的 `pyj_autoload.pth`,它发现启动的是 `.pyj` 文件,就把进程替换成 `python -m pyj app.pyj`(Windows 上是启动子进程),解释器参数原样保留。
|
|
45
|
+
|
|
46
|
+
### 只下载单个文件
|
|
47
|
+
|
|
48
|
+
把 `pyj.py` 放到任意位置即可:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python pyj.py install # 写入 pyj_autoload.pth,效果同 pip 安装(在 venv 中运行则只装进该 venv)
|
|
52
|
+
python pyj.py uninstall # 卸载
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 不安装也能用
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
python3 pyj.py run app.pyj [参数...] # 或 python3 pyj.py app.pyj
|
|
59
|
+
python3 pyj.py build app.pyj # 生成 app.py
|
|
60
|
+
python3 pyj.py build app.pyj -o - # 输出到终端
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
在 Python 代码里使用:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import pyj
|
|
67
|
+
pyj.install_import_hook() # 之后可以 import .pyj 模块
|
|
68
|
+
print(pyj.transpile("if x { y() }"))
|
|
69
|
+
code = pyj.compile_pyj(src, "file.pyj") # 位置已映射回源码的 code 对象
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 报错定位
|
|
73
|
+
|
|
74
|
+
运行时异常、语法错误、`inspect.getsource`、`linecache` 都直接指向 `.pyj` 源码的**行号和列号**,即使多条语句写在同一行、一个表达式跨了多行也一样准确:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
File "util.pyj", line 3, in 除法
|
|
78
|
+
return a /
|
|
79
|
+
~~^
|
|
80
|
+
b }
|
|
81
|
+
~
|
|
82
|
+
ZeroDivisionError: division by zero
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
原理:转译时记录每个记号在生成代码和源码中的位置,用 `ast.parse` 解析生成的代码后,把每个 AST 节点的位置改写回源码位置,再编译。
|
|
86
|
+
|
|
87
|
+
## 语法规则
|
|
88
|
+
|
|
89
|
+
| 规则 | 示例 |
|
|
90
|
+
|---|---|
|
|
91
|
+
| 代码块用 `{ }`,缩进随意 | `while x { x -= 1 }` |
|
|
92
|
+
| 冒号可写可不写 | `def f(): { ... }` 等同 `def f() { ... }` |
|
|
93
|
+
| 语句用 `;` 分隔 | `a = 1; b = 2` |
|
|
94
|
+
| 换行时语句明显结束会自动断句 | 每行一条语句可以省略 `;` |
|
|
95
|
+
| 括号里、或行尾是运算符/逗号时,换行被忽略 | `total = 1 +`⏎`2` |
|
|
96
|
+
| 下一行以 `.` `,` `and` `or` 等开头时接上一行 | `s = text`⏎`.strip()` |
|
|
97
|
+
| `else if` 是 `elif` 的语法糖 | `} else if x {` |
|
|
98
|
+
| 空代码块自动补 `pass` | `class E {}` |
|
|
99
|
+
| 字典/集合照常写 | `if k in {1, 2} { d = {} }` |
|
|
100
|
+
|
|
101
|
+
**如何区分代码块和字典?** 只有当 `{` 出现在 `if/for/while/def/class/try/with/match/case…` 语句头部末尾(前面是名字、数字、字符串、右括号、`:`,或 `else/try/finally/except`)时才是代码块。在 Python 里 `表达式 {` 本来就不合法,所以不会产生歧义。
|
|
102
|
+
|
|
103
|
+
## 注意事项
|
|
104
|
+
|
|
105
|
+
- 下一行以 `(`、`[`、`-`、`*` 开头时**不会**接到上一行(避免 JS 那种 `a`⏎`(b)` 被当成函数调用的坑)。需要时把运算符放在行尾,或用括号包起来。
|
|
106
|
+
- 复合语句头部写成 `if x: {...}` 时,`{` 总被当成代码块;如果真想在单行 `if` 后写一个字典表达式语句,请用普通的 `{ }` 块包一层。
|
|
107
|
+
- 注释会保留;表达式中间的注释会被移到该语句行尾。
|
|
108
|
+
|
|
109
|
+
## 开发
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
python -m unittest discover -s tests -v
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 许可证
|
|
116
|
+
|
|
117
|
+
[MIT](https://github.com/yyszh01/pyj/blob/main/LICENSE)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# pyj 示例:随便换行、随便缩进
|
|
2
|
+
import math; from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Point { x: float; y: float
|
|
6
|
+
def dist(self, other) -> float { return math.hypot(self.x - other.x,
|
|
7
|
+
self.y - other.y) } }
|
|
8
|
+
|
|
9
|
+
def fib(n) { a, b = 0, 1; for _ in range(n) { a, b = b, a + b }; return a }
|
|
10
|
+
|
|
11
|
+
def classify(n) {
|
|
12
|
+
if n < 0 { return "负数" } else if n == 0 { return "零" }
|
|
13
|
+
else { return "正数" }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
config = {"name": "pyj", "tags": {"a", "b"}} # 字典和集合照常用
|
|
17
|
+
if "pyj" in {config["name"]} { print(f"hello {config['name']}! {{literal}} {len({1, 2})}") }
|
|
18
|
+
|
|
19
|
+
total = 1 +
|
|
20
|
+
2 +
|
|
21
|
+
3
|
|
22
|
+
print(total)
|
|
23
|
+
|
|
24
|
+
p = Point(0, 0); q = Point(3, 4)
|
|
25
|
+
print(p.dist(q), [fib(i) for i in range(10)], classify(-5), classify(0), classify(7))
|
|
26
|
+
|
|
27
|
+
try { 1 / 0 } except ZeroDivisionError as e { print("捕获:", e) } finally { print("finally") }
|
|
28
|
+
|
|
29
|
+
def http(code) { match code { case 200 | 201 { return "ok" } case _ { return "err" } } }
|
|
30
|
+
print(http(200), http(500))
|
|
31
|
+
|
|
32
|
+
class Empty {}
|
|
33
|
+
while False {}
|
|
34
|
+
squares = {k: k * k for k in range(4)}
|
|
35
|
+
print(squares)
|