lispython 0.4.0__tar.gz → 0.4.2__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.
- {lispython-0.4.0 → lispython-0.4.2}/.gitignore +12 -1
- lispython-0.4.2/PKG-INFO +108 -0
- lispython-0.4.2/README.md +86 -0
- lispython-0.4.2/docs/macros.md +114 -0
- lispython-0.4.2/docs/usage/getting-started.md +20 -0
- {lispython-0.4.0 → lispython-0.4.2}/pyproject.toml +1 -1
- lispython-0.4.2/src/lispy/core/builtins.py +19 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/meta_functions.py +1 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core_meta_functions.lpy +2 -1
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/lsp/server.lpy +50 -6
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/macros/sugar.lpy +10 -0
- lispython-0.4.2/tests/test_as_thread.py +36 -0
- lispython-0.4.2/tests/test_gensym.py +78 -0
- {lispython-0.4.0 → lispython-0.4.2}/uv.lock +1 -1
- lispython-0.4.0/PKG-INFO +0 -79
- lispython-0.4.0/README.md +0 -57
- lispython-0.4.0/docs/macros.md +0 -57
- lispython-0.4.0/docs/usage/getting-started.md +0 -20
- {lispython-0.4.0 → lispython-0.4.2}/.claude/settings.local.json +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/.pre-commit-config.yaml +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/.vscode/settings.json +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/LICENSE.md +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/docs/index.md +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/docs/syntax/expressions.md +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/docs/syntax/overview.md +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/docs/syntax/statements.md +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/docs/usage/cli.md +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/docs/version_macro.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/docs/why-lispy.md +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/mkdocs.yml +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/__init__.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/__init__.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/expr.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/literal.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/stmt.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/utils.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/importer.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/macro.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/nodes.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/parser.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/utils.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/lsp/__init__.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/lsp/__main__.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/macros/__init__.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/macros/init.lpy +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/src/lispy/tools.lpy +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/tests/__init__.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/tests/test_expr.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/tests/test_include_meta.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/tests/test_literal.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/tests/test_meta_functions.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/tests/test_parser.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/tests/test_stmt.py +0 -0
- {lispython-0.4.0 → lispython-0.4.2}/tests/utils.py +0 -0
|
@@ -321,4 +321,15 @@ tags
|
|
|
321
321
|
|
|
322
322
|
poetry.lock
|
|
323
323
|
**test.hy
|
|
324
|
-
**temp*
|
|
324
|
+
**temp*
|
|
325
|
+
|
|
326
|
+
# AI agent instructions (per-user, not committed)
|
|
327
|
+
AGENTS.md
|
|
328
|
+
CLAUDE.md
|
|
329
|
+
GEMINI.md
|
|
330
|
+
|
|
331
|
+
# Git worktrees
|
|
332
|
+
.worktrees/
|
|
333
|
+
|
|
334
|
+
# Agent-generated docs (not mkdocs content)
|
|
335
|
+
docs/superpowers/
|
lispython-0.4.2/PKG-INFO
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lispython
|
|
3
|
+
Version: 0.4.2
|
|
4
|
+
Summary: Lisp-like Syntax for Python with Lisp-like Macros
|
|
5
|
+
Project-URL: Homepage, https://jetack.github.io/lispython
|
|
6
|
+
Project-URL: Repository, https://github.com/jetack/lispython
|
|
7
|
+
Author-email: Jetack <jetack23@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE.md
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: pygls>=1.0.0
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: toml>=0.10.2; extra == 'dev'
|
|
17
|
+
Provides-Extra: docs
|
|
18
|
+
Requires-Dist: mike>=2.1.3; extra == 'docs'
|
|
19
|
+
Requires-Dist: mkdocs-macros-plugin>=1.3.7; extra == 'docs'
|
|
20
|
+
Requires-Dist: mkdocs-material>=9.6.14; extra == 'docs'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# LisPython
|
|
24
|
+
[](https://badge.fury.io/py/lispython)
|
|
25
|
+
|
|
26
|
+
LisPython is a Lisp-flavored syntax for Python with Lisp-style macros. Source files (`.lpy`) are transpiled to Python and executed on the standard CPython runtime.
|
|
27
|
+
|
|
28
|
+
## Documentation
|
|
29
|
+
Full documentation lives at [https://jetack.github.io/lispython/](https://jetack.github.io/lispython/).
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
```lisp
|
|
33
|
+
(import math)
|
|
34
|
+
|
|
35
|
+
(def area [r]
|
|
36
|
+
(return (* math.pi (** r 2))))
|
|
37
|
+
|
|
38
|
+
(print (area 3))
|
|
39
|
+
```
|
|
40
|
+
```bash
|
|
41
|
+
lpy example.lpy
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
### Using pip
|
|
46
|
+
```bash
|
|
47
|
+
pip install lispython
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Manual Installation (for development)
|
|
51
|
+
```bash
|
|
52
|
+
uv sync # install dependencies
|
|
53
|
+
uv pip install -e . # for development
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## How to Run LisPython code
|
|
57
|
+
### Run from source
|
|
58
|
+
```bash
|
|
59
|
+
lpy {filename}.lpy
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Run REPL
|
|
63
|
+
```bash
|
|
64
|
+
lpy
|
|
65
|
+
# or
|
|
66
|
+
lpy -t # if you want to print python translation.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Show translation
|
|
70
|
+
```bash
|
|
71
|
+
l2py {filename}.lpy
|
|
72
|
+
```
|
|
73
|
+
Prints the translated Python to stdout. It does not execute the code.
|
|
74
|
+
|
|
75
|
+
### Run Tests
|
|
76
|
+
```bash
|
|
77
|
+
# in project root directory
|
|
78
|
+
pytest
|
|
79
|
+
# or
|
|
80
|
+
lpy -m pytest
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## LSP Server
|
|
84
|
+
LisPython ships with a language server (`lpy-lsp`) that speaks LSP over stdio. It provides:
|
|
85
|
+
|
|
86
|
+
- Diagnostics (parse / compile errors)
|
|
87
|
+
- Hover documentation for special forms
|
|
88
|
+
- Document symbols
|
|
89
|
+
- Go-to-definition, including across `.lpy` files in the workspace
|
|
90
|
+
|
|
91
|
+
### Editor setup
|
|
92
|
+
Point your editor's LSP client at the `lpy-lsp` command for files with the `.lpy` extension. The server speaks LSP over stdio.
|
|
93
|
+
|
|
94
|
+
#### VSCode
|
|
95
|
+
Install the [LisPython](https://marketplace.visualstudio.com/items?itemName=jetack.vscode-lispython) extension from the VSCode Marketplace.
|
|
96
|
+
|
|
97
|
+
#### Emacs
|
|
98
|
+
Use [`lpy-mode`](https://github.com/jetack/lpy-mode) for syntax highlighting and LSP integration. For completion, install [`lpy-autocomplete`](https://github.com/jetack/lpy-autocomplete).
|
|
99
|
+
|
|
100
|
+
## Todo
|
|
101
|
+
### Environment
|
|
102
|
+
- [ ] Test on more python versions
|
|
103
|
+
- [ ] REPL should track history and arrow key navigation
|
|
104
|
+
- [ ] REPL multi-line input support
|
|
105
|
+
- [ ] Better compilation error messages
|
|
106
|
+
### Python AST
|
|
107
|
+
- [ ] `type_comment` never considered. Later, it should be covered
|
|
108
|
+
- [ ] Any missing AST nodes in the version 3.12+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# LisPython
|
|
2
|
+
[](https://badge.fury.io/py/lispython)
|
|
3
|
+
|
|
4
|
+
LisPython is a Lisp-flavored syntax for Python with Lisp-style macros. Source files (`.lpy`) are transpiled to Python and executed on the standard CPython runtime.
|
|
5
|
+
|
|
6
|
+
## Documentation
|
|
7
|
+
Full documentation lives at [https://jetack.github.io/lispython/](https://jetack.github.io/lispython/).
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
```lisp
|
|
11
|
+
(import math)
|
|
12
|
+
|
|
13
|
+
(def area [r]
|
|
14
|
+
(return (* math.pi (** r 2))))
|
|
15
|
+
|
|
16
|
+
(print (area 3))
|
|
17
|
+
```
|
|
18
|
+
```bash
|
|
19
|
+
lpy example.lpy
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
### Using pip
|
|
24
|
+
```bash
|
|
25
|
+
pip install lispython
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Manual Installation (for development)
|
|
29
|
+
```bash
|
|
30
|
+
uv sync # install dependencies
|
|
31
|
+
uv pip install -e . # for development
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## How to Run LisPython code
|
|
35
|
+
### Run from source
|
|
36
|
+
```bash
|
|
37
|
+
lpy {filename}.lpy
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Run REPL
|
|
41
|
+
```bash
|
|
42
|
+
lpy
|
|
43
|
+
# or
|
|
44
|
+
lpy -t # if you want to print python translation.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Show translation
|
|
48
|
+
```bash
|
|
49
|
+
l2py {filename}.lpy
|
|
50
|
+
```
|
|
51
|
+
Prints the translated Python to stdout. It does not execute the code.
|
|
52
|
+
|
|
53
|
+
### Run Tests
|
|
54
|
+
```bash
|
|
55
|
+
# in project root directory
|
|
56
|
+
pytest
|
|
57
|
+
# or
|
|
58
|
+
lpy -m pytest
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## LSP Server
|
|
62
|
+
LisPython ships with a language server (`lpy-lsp`) that speaks LSP over stdio. It provides:
|
|
63
|
+
|
|
64
|
+
- Diagnostics (parse / compile errors)
|
|
65
|
+
- Hover documentation for special forms
|
|
66
|
+
- Document symbols
|
|
67
|
+
- Go-to-definition, including across `.lpy` files in the workspace
|
|
68
|
+
|
|
69
|
+
### Editor setup
|
|
70
|
+
Point your editor's LSP client at the `lpy-lsp` command for files with the `.lpy` extension. The server speaks LSP over stdio.
|
|
71
|
+
|
|
72
|
+
#### VSCode
|
|
73
|
+
Install the [LisPython](https://marketplace.visualstudio.com/items?itemName=jetack.vscode-lispython) extension from the VSCode Marketplace.
|
|
74
|
+
|
|
75
|
+
#### Emacs
|
|
76
|
+
Use [`lpy-mode`](https://github.com/jetack/lpy-mode) for syntax highlighting and LSP integration. For completion, install [`lpy-autocomplete`](https://github.com/jetack/lpy-autocomplete).
|
|
77
|
+
|
|
78
|
+
## Todo
|
|
79
|
+
### Environment
|
|
80
|
+
- [ ] Test on more python versions
|
|
81
|
+
- [ ] REPL should track history and arrow key navigation
|
|
82
|
+
- [ ] REPL multi-line input support
|
|
83
|
+
- [ ] Better compilation error messages
|
|
84
|
+
### Python AST
|
|
85
|
+
- [ ] `type_comment` never considered. Later, it should be covered
|
|
86
|
+
- [ ] Any missing AST nodes in the version 3.12+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Macros
|
|
2
|
+
LisPy's macro system is highly inspired by Clojure's macro system.
|
|
3
|
+
You can check out the [Chapter 7](https://www.braveclojure.com/read-and-eval/) and [Chapter 8](https://www.braveclojure.com/writing-macros/) of "Brave Clojure" for more information about Clojure's macro system.
|
|
4
|
+
## Expression Nodes
|
|
5
|
+
These nodes are defined in `src/lispython/core/nodes.py`.
|
|
6
|
+
|
|
7
|
+
You can check out how to manipulate these nodes in
|
|
8
|
+
|
|
9
|
+
- the definitions of nodes themselves
|
|
10
|
+
- the definitions of built-in macros in `src/lispython/macros/sugar.lpy`.
|
|
11
|
+
|
|
12
|
+
## quote
|
|
13
|
+
```python
|
|
14
|
+
'expr
|
|
15
|
+
```
|
|
16
|
+
## syntax-quote
|
|
17
|
+
```python
|
|
18
|
+
`expr
|
|
19
|
+
```
|
|
20
|
+
## unquote
|
|
21
|
+
```python
|
|
22
|
+
~expr
|
|
23
|
+
```
|
|
24
|
+
## unquote-splicing
|
|
25
|
+
```python
|
|
26
|
+
~@expr
|
|
27
|
+
```
|
|
28
|
+
## Macro Definition
|
|
29
|
+
Just change `def` in function definition to `defmacro`. And macros usually return a quoted expression.
|
|
30
|
+
```python
|
|
31
|
+
(defmacro name [args*]
|
|
32
|
+
body*)
|
|
33
|
+
```
|
|
34
|
+
### Macro Example
|
|
35
|
+
```python
|
|
36
|
+
(defmacro when [pred *body]
|
|
37
|
+
(return `(if ~pred
|
|
38
|
+
(do ~@body))))
|
|
39
|
+
|
|
40
|
+
(defmacro cond [*body]
|
|
41
|
+
(def recur [*body]
|
|
42
|
+
(if (< (len body) 4)
|
|
43
|
+
(return `(if ~@body))
|
|
44
|
+
(do (= [test then *orelse] body)
|
|
45
|
+
(return `(if ~test ~then ~(recur *orelse))))))
|
|
46
|
+
(return (recur *body)))
|
|
47
|
+
|
|
48
|
+
(defmacro -> [x *fs]
|
|
49
|
+
(if (== 0 (len fs))
|
|
50
|
+
(return x))
|
|
51
|
+
(= [f *rest] fs)
|
|
52
|
+
(if (isinstance f Paren)
|
|
53
|
+
(do (f.list.insert 1 x)
|
|
54
|
+
(return `(-> ~f ~@rest)))
|
|
55
|
+
(return `(-> (~f ~x) ~@rest))))
|
|
56
|
+
```
|
|
57
|
+
You can find more in `src/lispython/macros/sugar.lpy`.
|
|
58
|
+
|
|
59
|
+
### `as->`
|
|
60
|
+
Unlike `->` (thread first) and `->>` (thread last), `as->` lets you name the threaded value and place it anywhere in each form:
|
|
61
|
+
```python
|
|
62
|
+
(as-> 0 x
|
|
63
|
+
(+ x 10) ;; x = 10
|
|
64
|
+
(* 2 x) ;; x = 20
|
|
65
|
+
(str x)) ;; x = "20"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This expands to:
|
|
69
|
+
```python
|
|
70
|
+
x = 0
|
|
71
|
+
x = x + 10
|
|
72
|
+
x = 2 * x
|
|
73
|
+
x = str(x)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Useful when the threaded value doesn't always go in the first or last position.
|
|
77
|
+
|
|
78
|
+
## gensym
|
|
79
|
+
`gensym` generates unique symbols to avoid variable name collisions in macros. It is automatically available inside `defmacro` bodies.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
(gensym) ;; => Symbol("__gensym_0")
|
|
83
|
+
(gensym "tmp") ;; => Symbol("__tmp_1")
|
|
84
|
+
(gensym 'tmp) ;; => Symbol("__tmp_2") (also accepts a quoted Symbol)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The prefix can be a string or a symbol. Each call increments a global counter, so every generated symbol is unique.
|
|
88
|
+
|
|
89
|
+
### Why gensym?
|
|
90
|
+
Without `gensym`, a macro that introduces a local variable can accidentally shadow a variable in the caller's scope:
|
|
91
|
+
```python
|
|
92
|
+
;; BAD: if the caller has a variable named `tmp`, this breaks
|
|
93
|
+
(defmacro broken-swap [a b]
|
|
94
|
+
(return `(do (= tmp ~a)
|
|
95
|
+
(= ~a ~b)
|
|
96
|
+
(= ~b tmp))))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Using `gensym` prevents this:
|
|
100
|
+
```python
|
|
101
|
+
(defmacro swap [a b]
|
|
102
|
+
(= tmp (gensym "tmp"))
|
|
103
|
+
(return `(do (= ~tmp ~a)
|
|
104
|
+
(= ~a ~b)
|
|
105
|
+
(= ~b ~tmp))))
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Now `(swap x y)` expands to something like:
|
|
109
|
+
```python
|
|
110
|
+
__tmp_0 = x
|
|
111
|
+
x = y
|
|
112
|
+
y = __tmp_0
|
|
113
|
+
```
|
|
114
|
+
The generated name `__tmp_0` won't collide with user variables.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
## Simple Web REPL
|
|
3
|
+
You can try LisPy without installing at
|
|
4
|
+
[https://jetack.github.io/lispy-web/](https://jetack.github.io/lispy-web/).
|
|
5
|
+
## Installation
|
|
6
|
+
### Using pip
|
|
7
|
+
```bash
|
|
8
|
+
pip install lispython
|
|
9
|
+
```
|
|
10
|
+
### Manual Installation (for development)
|
|
11
|
+
```bash
|
|
12
|
+
uv sync # install dependencies
|
|
13
|
+
uv pip install -e . # for development
|
|
14
|
+
```
|
|
15
|
+
#### uv
|
|
16
|
+
I recommend using [uv](https://docs.astral.sh/uv/) for development.
|
|
17
|
+
It manages the virtual environment and dependencies from `pyproject.toml` / `uv.lock` automatically.
|
|
18
|
+
```bash
|
|
19
|
+
uv run lpy # run commands inside the project environment
|
|
20
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from lispy.core.nodes import Symbol
|
|
2
|
+
|
|
3
|
+
_gensym_counter = 0
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def gensym(prefix="gensym"):
|
|
7
|
+
global _gensym_counter
|
|
8
|
+
if isinstance(prefix, Symbol):
|
|
9
|
+
prefix = prefix.name
|
|
10
|
+
elif not isinstance(prefix, str):
|
|
11
|
+
raise TypeError(f"gensym prefix must be a Symbol or string, got {type(prefix).__name__}")
|
|
12
|
+
sym = Symbol("__" + prefix + "_" + str(_gensym_counter))
|
|
13
|
+
_gensym_counter += 1
|
|
14
|
+
return sym
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def reset_gensym_counter():
|
|
18
|
+
global _gensym_counter
|
|
19
|
+
_gensym_counter = 0
|
|
@@ -9,6 +9,7 @@ def defmacro_transform(sexp):
|
|
|
9
9
|
return Paren(
|
|
10
10
|
Symbol("do"),
|
|
11
11
|
Paren(Symbol("from"), Symbol("lispy.core.nodes"), Symbol("*")),
|
|
12
|
+
Paren(Symbol("from"), Symbol("lispy.core.builtins"), Symbol("*")),
|
|
12
13
|
Paren(Symbol("def"), newname, *body),
|
|
13
14
|
Paren(
|
|
14
15
|
Symbol("="),
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
(from lispy.core.nodes *)
|
|
7
7
|
|
|
8
8
|
(def defmacro-transform [sexp]
|
|
9
|
-
(= [op macroname *body] sexp.list)
|
|
9
|
+
(= [op macroname *body] sexp.list)
|
|
10
10
|
(= macroname (str macroname))
|
|
11
11
|
(= newname (Symbol (+ "___defmacro_temp___" macroname)))
|
|
12
12
|
(return `(do (from lispy.core.nodes *)
|
|
13
|
+
(from lispy.core.builtins *)
|
|
13
14
|
(def ~newname ~@body)
|
|
14
15
|
(= (sub (.setdefault (globals) "__macro_namespace" {})
|
|
15
16
|
~macroname)
|
|
@@ -162,6 +162,17 @@
|
|
|
162
162
|
"Convert a filesystem path to a file:// URI."
|
|
163
163
|
(return (.as-uri (pathlib.Path path))))
|
|
164
164
|
|
|
165
|
+
(def index-required-modules [tree]
|
|
166
|
+
"Find all (require module-name ...) forms in tree and index those packages."
|
|
167
|
+
(for node in tree
|
|
168
|
+
(if (and (isinstance node Paren)
|
|
169
|
+
(> (len node) 1)
|
|
170
|
+
(isinstance node.op Symbol)
|
|
171
|
+
(== node.op.value "require")
|
|
172
|
+
(isinstance (sub node 1) Symbol))
|
|
173
|
+
(do (= module-name (. (sub node 1) value))
|
|
174
|
+
(index-installed-package module-name)))))
|
|
175
|
+
|
|
165
176
|
(def index-file [uri]
|
|
166
177
|
"Parse a .lpy file and add its definitions to the workspace index.
|
|
167
178
|
Removes old entries for this URI before re-indexing."
|
|
@@ -185,6 +196,9 @@
|
|
|
185
196
|
(= tree (parse src))
|
|
186
197
|
(= (sub FILE-TREES uri) tree)
|
|
187
198
|
(collect-definitions tree uri :defs WORKSPACE-INDEX)
|
|
199
|
+
;; Also eagerly index any packages this file requires so defmacros
|
|
200
|
+
;; from required modules are discoverable for go-to-definition.
|
|
201
|
+
(index-required-modules tree)
|
|
188
202
|
(except [Exception as exc]
|
|
189
203
|
(logger.debug f"Failed to index {fpath}: {exc}"))))
|
|
190
204
|
|
|
@@ -201,19 +215,44 @@
|
|
|
201
215
|
e.g., 'lispy.tools' -> '/path/to/src/lispy/tools.lpy'"
|
|
202
216
|
(= parts (.split (.replace module-name "-" "_") "."))
|
|
203
217
|
(= rel-path (+ (osp.join *parts) ".lpy"))
|
|
204
|
-
;;
|
|
218
|
+
;; 1. Workspace folders (direct or in src/)
|
|
205
219
|
(for folder in workspace-folders
|
|
206
220
|
(= root (uri-to-path folder.uri))
|
|
207
|
-
;; Direct match
|
|
208
221
|
(= candidate (osp.join root rel-path))
|
|
209
222
|
(if (osp.isfile candidate)
|
|
210
223
|
(return candidate))
|
|
211
|
-
;; Check in src/ subdirectory
|
|
212
224
|
(= candidate (osp.join root "src" rel-path))
|
|
213
225
|
(if (osp.isfile candidate)
|
|
214
226
|
(return candidate)))
|
|
227
|
+
;; 2. Python's sys.path (finds installed packages, including lispython itself)
|
|
228
|
+
(try
|
|
229
|
+
(do (import importlib.util)
|
|
230
|
+
(= spec (importlib.util.find-spec (.replace module-name "-" "_")))
|
|
231
|
+
(if (and spec spec.origin (.endswith spec.origin ".lpy"))
|
|
232
|
+
(return spec.origin))
|
|
233
|
+
;; Package without __init__.lpy but has submodule files
|
|
234
|
+
(if (and spec spec.submodule-search-locations)
|
|
235
|
+
(for search-path in spec.submodule-search-locations
|
|
236
|
+
(= candidate (osp.join search-path "init.lpy"))
|
|
237
|
+
(if (osp.isfile candidate)
|
|
238
|
+
(return candidate)))))
|
|
239
|
+
(except [Exception]
|
|
240
|
+
(pass)))
|
|
215
241
|
(return None))
|
|
216
242
|
|
|
243
|
+
(def index-installed-package [module-name]
|
|
244
|
+
"Index all .lpy files in an installed Python package (e.g., lispython).
|
|
245
|
+
Used to make defmacros in required modules discoverable."
|
|
246
|
+
(try
|
|
247
|
+
(do (import importlib.util)
|
|
248
|
+
(= spec (importlib.util.find-spec (.replace module-name "-" "_")))
|
|
249
|
+
(if (and spec spec.submodule-search-locations)
|
|
250
|
+
(for search-path in spec.submodule-search-locations
|
|
251
|
+
(for fpath in (glob.glob (osp.join search-path "**" "*.lpy") :recursive True)
|
|
252
|
+
(index-file (path-to-uri fpath))))))
|
|
253
|
+
(except [Exception]
|
|
254
|
+
(pass))))
|
|
255
|
+
|
|
217
256
|
(def find-import-target [node workspace-folders]
|
|
218
257
|
"Given an import/from/require node, resolve where the imported name is defined."
|
|
219
258
|
(= form (. node op value))
|
|
@@ -336,10 +375,15 @@
|
|
|
336
375
|
:source "lpy-lsp"
|
|
337
376
|
:message f"Parse error: {exc}"))
|
|
338
377
|
(return diagnostics)))
|
|
339
|
-
;; 2. Compile (best-effort)
|
|
378
|
+
;; 2. Compile (best-effort) — reported as warnings, since compile-time
|
|
379
|
+
;; macro expansion depends on the runtime environment (imports must
|
|
380
|
+
;; actually resolve). Import errors here are often not real problems.
|
|
340
381
|
(try
|
|
341
382
|
(do (from lispy.core.macro [macroexpand-then-compile])
|
|
342
383
|
(macroexpand-then-compile tree))
|
|
384
|
+
(except [ModuleNotFoundError]
|
|
385
|
+
;; Silently ignore — the file may be using modules the LSP can't resolve
|
|
386
|
+
(pass))
|
|
343
387
|
(except [Exception as exc]
|
|
344
388
|
(= pos (extract-error-position exc source))
|
|
345
389
|
(if (is pos None)
|
|
@@ -347,9 +391,9 @@
|
|
|
347
391
|
(.append diagnostics
|
|
348
392
|
(lsp.Diagnostic
|
|
349
393
|
:range (make-range *pos)
|
|
350
|
-
:severity lsp.DiagnosticSeverity.
|
|
394
|
+
:severity lsp.DiagnosticSeverity.Warning
|
|
351
395
|
:source "lpy-lsp"
|
|
352
|
-
:message f"Compile
|
|
396
|
+
:message f"Compile warning: {exc}"))))
|
|
353
397
|
(return diagnostics))
|
|
354
398
|
|
|
355
399
|
(def publish-diagnostics [ls uri]
|
|
@@ -28,6 +28,16 @@
|
|
|
28
28
|
(return `(->> ~f ~@rest)))
|
|
29
29
|
(return `(-> (~f ~x) ~@rest))))
|
|
30
30
|
|
|
31
|
+
(defmacro as-> [expr name *forms]
|
|
32
|
+
(if (== 0 (len forms))
|
|
33
|
+
(return expr))
|
|
34
|
+
(= stmts [])
|
|
35
|
+
(stmts.append `(= ~name ~expr))
|
|
36
|
+
(for f in forms
|
|
37
|
+
(stmts.append `(= ~name ~f)))
|
|
38
|
+
(stmts.append name)
|
|
39
|
+
(return `(do ~@stmts)))
|
|
40
|
+
|
|
31
41
|
(defmacro fn [*body]
|
|
32
42
|
(return `(lambda ~@body)))
|
|
33
43
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from lispy.tools import src_to_python_org
|
|
2
|
+
|
|
3
|
+
PREAMBLE = "(require lispy.macros *)\n"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestAsThread:
|
|
7
|
+
def test_no_forms(self):
|
|
8
|
+
assert src_to_python_org(PREAMBLE + "(as-> 42 x)") == "42"
|
|
9
|
+
|
|
10
|
+
def test_single_form(self):
|
|
11
|
+
result = src_to_python_org(PREAMBLE + "(as-> 0 x (+ x 1))")
|
|
12
|
+
assert "x = 0" in result
|
|
13
|
+
assert "x = x + 1" in result
|
|
14
|
+
|
|
15
|
+
def test_multiple_forms(self):
|
|
16
|
+
result = src_to_python_org(
|
|
17
|
+
PREAMBLE + "(as-> 0 x (+ x 10) (* 2 x) (str x))"
|
|
18
|
+
)
|
|
19
|
+
assert "x = 0" in result
|
|
20
|
+
assert "x = x + 10" in result
|
|
21
|
+
assert "x = 2 * x" in result
|
|
22
|
+
assert "x = str(x)" in result
|
|
23
|
+
|
|
24
|
+
def test_method_call(self):
|
|
25
|
+
result = src_to_python_org(
|
|
26
|
+
PREAMBLE + '(as-> "hello" s (.upper s))'
|
|
27
|
+
)
|
|
28
|
+
assert "s = 'hello'" in result
|
|
29
|
+
assert "s = s.upper()" in result
|
|
30
|
+
|
|
31
|
+
def test_flexible_placement(self):
|
|
32
|
+
result = src_to_python_org(
|
|
33
|
+
PREAMBLE + "(as-> 1 x (+ 10 x) (- x 5))"
|
|
34
|
+
)
|
|
35
|
+
assert "x = 10 + x" in result
|
|
36
|
+
assert "x = x - 5" in result
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from lispy.core.builtins import gensym, reset_gensym_counter
|
|
2
|
+
from lispy.core.nodes import Symbol
|
|
3
|
+
from lispy.tools import src_to_python_org
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestGensym:
|
|
7
|
+
def setup_method(self):
|
|
8
|
+
reset_gensym_counter()
|
|
9
|
+
|
|
10
|
+
def test_returns_symbol(self):
|
|
11
|
+
result = gensym()
|
|
12
|
+
assert isinstance(result, Symbol)
|
|
13
|
+
|
|
14
|
+
def test_default_prefix(self):
|
|
15
|
+
result = gensym()
|
|
16
|
+
assert result.name == "__gensym_0"
|
|
17
|
+
|
|
18
|
+
def test_increments_counter(self):
|
|
19
|
+
a = gensym()
|
|
20
|
+
b = gensym()
|
|
21
|
+
assert a.name == "__gensym_0"
|
|
22
|
+
assert b.name == "__gensym_1"
|
|
23
|
+
|
|
24
|
+
def test_custom_prefix(self):
|
|
25
|
+
result = gensym("tmp")
|
|
26
|
+
assert result.name == "__tmp_0"
|
|
27
|
+
|
|
28
|
+
def test_custom_prefix_increments(self):
|
|
29
|
+
a = gensym("x")
|
|
30
|
+
b = gensym("y")
|
|
31
|
+
assert a.name == "__x_0"
|
|
32
|
+
assert b.name == "__y_1"
|
|
33
|
+
|
|
34
|
+
def test_unique_across_prefixes(self):
|
|
35
|
+
a = gensym("a")
|
|
36
|
+
b = gensym("b")
|
|
37
|
+
assert a.name != b.name
|
|
38
|
+
|
|
39
|
+
def test_symbol_prefix(self):
|
|
40
|
+
result = gensym(Symbol("tmp"))
|
|
41
|
+
assert result.name == "__tmp_0"
|
|
42
|
+
|
|
43
|
+
def test_rejects_invalid_prefix(self):
|
|
44
|
+
import pytest
|
|
45
|
+
|
|
46
|
+
with pytest.raises(TypeError):
|
|
47
|
+
gensym(123)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestGensymInMacro:
|
|
51
|
+
def setup_method(self):
|
|
52
|
+
reset_gensym_counter()
|
|
53
|
+
|
|
54
|
+
def test_gensym_with_quoted_symbol(self):
|
|
55
|
+
src = """
|
|
56
|
+
(defmacro test-quoted []
|
|
57
|
+
(= tmp (gensym 'tmp))
|
|
58
|
+
(return `(= ~tmp 1)))
|
|
59
|
+
|
|
60
|
+
(test-quoted)
|
|
61
|
+
"""
|
|
62
|
+
result = src_to_python_org(src)
|
|
63
|
+
assert "__tmp_0 = 1" in result
|
|
64
|
+
|
|
65
|
+
def test_swap_macro_uses_gensym(self):
|
|
66
|
+
src = """
|
|
67
|
+
(defmacro swap [a b]
|
|
68
|
+
(= tmp (gensym "tmp"))
|
|
69
|
+
(return `(do (= ~tmp ~a)
|
|
70
|
+
(= ~a ~b)
|
|
71
|
+
(= ~b ~tmp))))
|
|
72
|
+
|
|
73
|
+
(swap x y)
|
|
74
|
+
"""
|
|
75
|
+
result = src_to_python_org(src)
|
|
76
|
+
assert "__tmp_0 = x" in result
|
|
77
|
+
assert "x = y" in result
|
|
78
|
+
assert "y = __tmp_0" in result
|
lispython-0.4.0/PKG-INFO
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: lispython
|
|
3
|
-
Version: 0.4.0
|
|
4
|
-
Summary: Lisp-like Syntax for Python with Lisp-like Macros
|
|
5
|
-
Project-URL: Homepage, https://jetack.github.io/lispython
|
|
6
|
-
Project-URL: Repository, https://github.com/jetack/lispython
|
|
7
|
-
Author-email: Jetack <jetack23@gmail.com>
|
|
8
|
-
License: MIT
|
|
9
|
-
License-File: LICENSE.md
|
|
10
|
-
Requires-Python: >=3.11
|
|
11
|
-
Requires-Dist: pygls>=1.0.0
|
|
12
|
-
Provides-Extra: dev
|
|
13
|
-
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
|
|
14
|
-
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
15
|
-
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
16
|
-
Requires-Dist: toml>=0.10.2; extra == 'dev'
|
|
17
|
-
Provides-Extra: docs
|
|
18
|
-
Requires-Dist: mike>=2.1.3; extra == 'docs'
|
|
19
|
-
Requires-Dist: mkdocs-macros-plugin>=1.3.7; extra == 'docs'
|
|
20
|
-
Requires-Dist: mkdocs-material>=9.6.14; extra == 'docs'
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
|
|
23
|
-
# LisPython
|
|
24
|
-
[](https://badge.fury.io/py/lispython)
|
|
25
|
-
|
|
26
|
-
# Documentation
|
|
27
|
-
You can find the documentation at [https://jetack.github.io/lispython/](https://jetack.github.io/lispython/).
|
|
28
|
-
|
|
29
|
-
# Installation
|
|
30
|
-
## Manual Installation (for development)
|
|
31
|
-
```bash
|
|
32
|
-
poetry install --no-root # for dependency
|
|
33
|
-
pip install -e . # for development
|
|
34
|
-
```
|
|
35
|
-
## Using pip
|
|
36
|
-
```bash
|
|
37
|
-
pip install lispython
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
# How to Run lispy code
|
|
41
|
-
## Run from source
|
|
42
|
-
```bash
|
|
43
|
-
lpy {filename}.lpy
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Run REPL
|
|
47
|
-
```bash
|
|
48
|
-
lpy
|
|
49
|
-
#or
|
|
50
|
-
lpy -t #if you want to print python translation.
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Run translation
|
|
54
|
-
```bash
|
|
55
|
-
l2py {filename}.lpy
|
|
56
|
-
```
|
|
57
|
-
It just displays translation. (don't run it)
|
|
58
|
-
|
|
59
|
-
## Run Tests
|
|
60
|
-
```bash
|
|
61
|
-
# in project root directory
|
|
62
|
-
python -m unittest
|
|
63
|
-
#or
|
|
64
|
-
lpy -m unittest
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# Todo
|
|
69
|
-
## Environment
|
|
70
|
-
- [ ] Test on more python versions
|
|
71
|
-
- [ ] REPL should track history and arrow key navigation
|
|
72
|
-
- [ ] REPL multi-line input support
|
|
73
|
-
- [ ] Better compileation error messages
|
|
74
|
-
## Macro System
|
|
75
|
-
- [ ] `as->` macro for syntactic sugar
|
|
76
|
-
- [ ] `gensym` for avoiding name collision
|
|
77
|
-
## Python AST
|
|
78
|
-
- [ ] `type_comment` never considered. Later, it should be covered
|
|
79
|
-
- [ ] Any missing AST nodes in the version 3.12+
|
lispython-0.4.0/README.md
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# LisPython
|
|
2
|
-
[](https://badge.fury.io/py/lispython)
|
|
3
|
-
|
|
4
|
-
# Documentation
|
|
5
|
-
You can find the documentation at [https://jetack.github.io/lispython/](https://jetack.github.io/lispython/).
|
|
6
|
-
|
|
7
|
-
# Installation
|
|
8
|
-
## Manual Installation (for development)
|
|
9
|
-
```bash
|
|
10
|
-
poetry install --no-root # for dependency
|
|
11
|
-
pip install -e . # for development
|
|
12
|
-
```
|
|
13
|
-
## Using pip
|
|
14
|
-
```bash
|
|
15
|
-
pip install lispython
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
# How to Run lispy code
|
|
19
|
-
## Run from source
|
|
20
|
-
```bash
|
|
21
|
-
lpy {filename}.lpy
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Run REPL
|
|
25
|
-
```bash
|
|
26
|
-
lpy
|
|
27
|
-
#or
|
|
28
|
-
lpy -t #if you want to print python translation.
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Run translation
|
|
32
|
-
```bash
|
|
33
|
-
l2py {filename}.lpy
|
|
34
|
-
```
|
|
35
|
-
It just displays translation. (don't run it)
|
|
36
|
-
|
|
37
|
-
## Run Tests
|
|
38
|
-
```bash
|
|
39
|
-
# in project root directory
|
|
40
|
-
python -m unittest
|
|
41
|
-
#or
|
|
42
|
-
lpy -m unittest
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# Todo
|
|
47
|
-
## Environment
|
|
48
|
-
- [ ] Test on more python versions
|
|
49
|
-
- [ ] REPL should track history and arrow key navigation
|
|
50
|
-
- [ ] REPL multi-line input support
|
|
51
|
-
- [ ] Better compileation error messages
|
|
52
|
-
## Macro System
|
|
53
|
-
- [ ] `as->` macro for syntactic sugar
|
|
54
|
-
- [ ] `gensym` for avoiding name collision
|
|
55
|
-
## Python AST
|
|
56
|
-
- [ ] `type_comment` never considered. Later, it should be covered
|
|
57
|
-
- [ ] Any missing AST nodes in the version 3.12+
|
lispython-0.4.0/docs/macros.md
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# Macros
|
|
2
|
-
LisPy's macro system is highly inspired by Clojure's macro system.
|
|
3
|
-
You can check out the [Chapter 7](https://www.braveclojure.com/read-and-eval/) and [Chapter 8](https://www.braveclojure.com/writing-macros/) of "Brave Clojure" for more information about Clojure's macro system.
|
|
4
|
-
## Expression Nodes
|
|
5
|
-
These nodes are defined in `src/lispython/core/nodes.py`.
|
|
6
|
-
|
|
7
|
-
You can check out how to manipulate these nodes in
|
|
8
|
-
|
|
9
|
-
- the definitions of nodes themselves
|
|
10
|
-
- the definitions of built-in macros in `src/lispython/macros/sugar.lpy`.
|
|
11
|
-
|
|
12
|
-
## quote
|
|
13
|
-
```python
|
|
14
|
-
'expr
|
|
15
|
-
```
|
|
16
|
-
## syntax-quote
|
|
17
|
-
```python
|
|
18
|
-
`expr
|
|
19
|
-
```
|
|
20
|
-
## unquote
|
|
21
|
-
```python
|
|
22
|
-
~expr
|
|
23
|
-
```
|
|
24
|
-
## unquote-splicing
|
|
25
|
-
```python
|
|
26
|
-
~@expr
|
|
27
|
-
```
|
|
28
|
-
## Macro Definition
|
|
29
|
-
Just change `def` in function definition to `defmacro`. And macros usually return a quoted expression.
|
|
30
|
-
```python
|
|
31
|
-
(defmacro name [args*]
|
|
32
|
-
body*)
|
|
33
|
-
```
|
|
34
|
-
### Macro Example
|
|
35
|
-
```python
|
|
36
|
-
(defmacro when [pred *body]
|
|
37
|
-
(return `(if ~pred
|
|
38
|
-
(do ~@body))))
|
|
39
|
-
|
|
40
|
-
(defmacro cond [*body]
|
|
41
|
-
(def recur [*body]
|
|
42
|
-
(if (< (len body) 4)
|
|
43
|
-
(return `(if ~@body))
|
|
44
|
-
(do (= [test then *orelse] body)
|
|
45
|
-
(return `(if ~test ~then ~(recur *orelse))))))
|
|
46
|
-
(return (recur *body)))
|
|
47
|
-
|
|
48
|
-
(defmacro -> [x *fs]
|
|
49
|
-
(if (== 0 (len fs))
|
|
50
|
-
(return x))
|
|
51
|
-
(= [f *rest] fs)
|
|
52
|
-
(if (isinstance f Paren)
|
|
53
|
-
(do (f.list.insert 1 x)
|
|
54
|
-
(return `(-> ~f ~@rest)))
|
|
55
|
-
(return `(-> (~f ~x) ~@rest))))
|
|
56
|
-
```
|
|
57
|
-
You can find more in `src/lispython/macros/sugar.lpy`.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Getting Started
|
|
2
|
-
## Simple Web REPL
|
|
3
|
-
You can try LisPy without installing at
|
|
4
|
-
[https://jetack.github.io/lispy-web/](https://jetack.github.io/lispy-web/).
|
|
5
|
-
## Installation
|
|
6
|
-
### Using pip
|
|
7
|
-
```bash
|
|
8
|
-
pip install lispython
|
|
9
|
-
```
|
|
10
|
-
### Manual Installation (for development)
|
|
11
|
-
```bash
|
|
12
|
-
poetry install --no-root # for dependency
|
|
13
|
-
pip install -e . # for development
|
|
14
|
-
```
|
|
15
|
-
#### Poetry
|
|
16
|
-
I recommend using [Poetry](https://python-poetry.org/) for development.
|
|
17
|
-
And turn off virtual environment creation in Poetry settings.
|
|
18
|
-
```bash
|
|
19
|
-
poetry config virtualenvs.create false
|
|
20
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|