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.
Files changed (54) hide show
  1. {lispython-0.4.0 → lispython-0.4.2}/.gitignore +12 -1
  2. lispython-0.4.2/PKG-INFO +108 -0
  3. lispython-0.4.2/README.md +86 -0
  4. lispython-0.4.2/docs/macros.md +114 -0
  5. lispython-0.4.2/docs/usage/getting-started.md +20 -0
  6. {lispython-0.4.0 → lispython-0.4.2}/pyproject.toml +1 -1
  7. lispython-0.4.2/src/lispy/core/builtins.py +19 -0
  8. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/meta_functions.py +1 -0
  9. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core_meta_functions.lpy +2 -1
  10. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/lsp/server.lpy +50 -6
  11. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/macros/sugar.lpy +10 -0
  12. lispython-0.4.2/tests/test_as_thread.py +36 -0
  13. lispython-0.4.2/tests/test_gensym.py +78 -0
  14. {lispython-0.4.0 → lispython-0.4.2}/uv.lock +1 -1
  15. lispython-0.4.0/PKG-INFO +0 -79
  16. lispython-0.4.0/README.md +0 -57
  17. lispython-0.4.0/docs/macros.md +0 -57
  18. lispython-0.4.0/docs/usage/getting-started.md +0 -20
  19. {lispython-0.4.0 → lispython-0.4.2}/.claude/settings.local.json +0 -0
  20. {lispython-0.4.0 → lispython-0.4.2}/.pre-commit-config.yaml +0 -0
  21. {lispython-0.4.0 → lispython-0.4.2}/.vscode/settings.json +0 -0
  22. {lispython-0.4.0 → lispython-0.4.2}/LICENSE.md +0 -0
  23. {lispython-0.4.0 → lispython-0.4.2}/docs/index.md +0 -0
  24. {lispython-0.4.0 → lispython-0.4.2}/docs/syntax/expressions.md +0 -0
  25. {lispython-0.4.0 → lispython-0.4.2}/docs/syntax/overview.md +0 -0
  26. {lispython-0.4.0 → lispython-0.4.2}/docs/syntax/statements.md +0 -0
  27. {lispython-0.4.0 → lispython-0.4.2}/docs/usage/cli.md +0 -0
  28. {lispython-0.4.0 → lispython-0.4.2}/docs/version_macro.py +0 -0
  29. {lispython-0.4.0 → lispython-0.4.2}/docs/why-lispy.md +0 -0
  30. {lispython-0.4.0 → lispython-0.4.2}/mkdocs.yml +0 -0
  31. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/__init__.py +0 -0
  32. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/__init__.py +0 -0
  33. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/expr.py +0 -0
  34. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/literal.py +0 -0
  35. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/stmt.py +0 -0
  36. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/compiler/utils.py +0 -0
  37. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/importer.py +0 -0
  38. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/macro.py +0 -0
  39. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/nodes.py +0 -0
  40. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/parser.py +0 -0
  41. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/core/utils.py +0 -0
  42. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/lsp/__init__.py +0 -0
  43. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/lsp/__main__.py +0 -0
  44. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/macros/__init__.py +0 -0
  45. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/macros/init.lpy +0 -0
  46. {lispython-0.4.0 → lispython-0.4.2}/src/lispy/tools.lpy +0 -0
  47. {lispython-0.4.0 → lispython-0.4.2}/tests/__init__.py +0 -0
  48. {lispython-0.4.0 → lispython-0.4.2}/tests/test_expr.py +0 -0
  49. {lispython-0.4.0 → lispython-0.4.2}/tests/test_include_meta.py +0 -0
  50. {lispython-0.4.0 → lispython-0.4.2}/tests/test_literal.py +0 -0
  51. {lispython-0.4.0 → lispython-0.4.2}/tests/test_meta_functions.py +0 -0
  52. {lispython-0.4.0 → lispython-0.4.2}/tests/test_parser.py +0 -0
  53. {lispython-0.4.0 → lispython-0.4.2}/tests/test_stmt.py +0 -0
  54. {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/
@@ -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
+ [![PyPI version](https://badge.fury.io/py/lispython.svg)](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
+ [![PyPI version](https://badge.fury.io/py/lispython.svg)](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
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lispython"
3
- version = "0.4.0"
3
+ version = "0.4.2"
4
4
  description = "Lisp-like Syntax for Python with Lisp-like Macros"
5
5
  authors = [{ name = "Jetack", email = "jetack23@gmail.com" }]
6
6
  license = { text = "MIT" }
@@ -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
- ;; Search in workspace folders and sys.path-like locations
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.Error
394
+ :severity lsp.DiagnosticSeverity.Warning
351
395
  :source "lpy-lsp"
352
- :message f"Compile error: {exc}"))))
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
@@ -260,7 +260,7 @@ wheels = [
260
260
 
261
261
  [[package]]
262
262
  name = "lispython"
263
- version = "0.3.3"
263
+ version = "0.4.2"
264
264
  source = { editable = "." }
265
265
  dependencies = [
266
266
  { name = "pygls" },
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
- [![PyPI version](https://badge.fury.io/py/lispython.svg)](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
- [![PyPI version](https://badge.fury.io/py/lispython.svg)](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+
@@ -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