lispython 0.4.7__tar.gz → 0.5.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.
Files changed (66) hide show
  1. lispython-0.5.0/.githooks/post-checkout +119 -0
  2. {lispython-0.4.7 → lispython-0.5.0}/.gitignore +4 -0
  3. lispython-0.5.0/CHANGELOG.md +83 -0
  4. {lispython-0.4.7 → lispython-0.5.0}/PKG-INFO +15 -10
  5. {lispython-0.4.7 → lispython-0.5.0}/README.md +5 -7
  6. {lispython-0.4.7 → lispython-0.5.0}/docs/syntax/expressions.md +48 -0
  7. {lispython-0.4.7 → lispython-0.5.0}/docs/syntax/statements.md +104 -0
  8. lispython-0.5.0/noxfile.py +13 -0
  9. {lispython-0.4.7 → lispython-0.5.0}/pyproject.toml +18 -2
  10. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/compiler/expr.py +94 -21
  11. lispython-0.5.0/src/lispy/core/compiler/features.py +85 -0
  12. lispython-0.5.0/src/lispy/core/compiler/literal.py +187 -0
  13. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/compiler/stmt.py +217 -22
  14. lispython-0.5.0/src/lispy/core/errors.py +109 -0
  15. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/importer.py +24 -9
  16. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/macro.py +15 -7
  17. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/meta_functions.py +22 -7
  18. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/nodes.py +107 -3
  19. lispython-0.5.0/src/lispy/core/parser.py +769 -0
  20. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core_meta_functions.lpy +22 -8
  21. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/lsp/server.lpy +289 -31
  22. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/tools.lpy +198 -58
  23. lispython-0.5.0/tests/test_ast_hygiene.py +89 -0
  24. lispython-0.5.0/tests/test_cli_module.py +54 -0
  25. lispython-0.5.0/tests/test_dx.py +561 -0
  26. {lispython-0.4.7 → lispython-0.5.0}/tests/test_expr.py +23 -0
  27. lispython-0.5.0/tests/test_glued_head.py +222 -0
  28. lispython-0.5.0/tests/test_literal.py +401 -0
  29. lispython-0.5.0/tests/test_lsp.py +218 -0
  30. lispython-0.5.0/tests/test_meta_functions.py +58 -0
  31. {lispython-0.4.7 → lispython-0.5.0}/tests/test_parser.py +33 -0
  32. lispython-0.5.0/tests/test_stmt.py +294 -0
  33. lispython-0.5.0/tests/test_version_features.py +1047 -0
  34. {lispython-0.4.7 → lispython-0.5.0}/uv.lock +2 -2
  35. lispython-0.4.7/src/lispy/core/compiler/literal.py +0 -24
  36. lispython-0.4.7/src/lispy/core/parser.py +0 -336
  37. lispython-0.4.7/tests/test_literal.py +0 -95
  38. lispython-0.4.7/tests/test_meta_functions.py +0 -26
  39. lispython-0.4.7/tests/test_stmt.py +0 -92
  40. {lispython-0.4.7 → lispython-0.5.0}/.pre-commit-config.yaml +0 -0
  41. {lispython-0.4.7 → lispython-0.5.0}/.vscode/settings.json +0 -0
  42. {lispython-0.4.7 → lispython-0.5.0}/LICENSE.md +0 -0
  43. {lispython-0.4.7 → lispython-0.5.0}/docs/index.md +0 -0
  44. {lispython-0.4.7 → lispython-0.5.0}/docs/macros.md +0 -0
  45. {lispython-0.4.7 → lispython-0.5.0}/docs/syntax/overview.md +0 -0
  46. {lispython-0.4.7 → lispython-0.5.0}/docs/usage/cli.md +0 -0
  47. {lispython-0.4.7 → lispython-0.5.0}/docs/usage/getting-started.md +0 -0
  48. {lispython-0.4.7 → lispython-0.5.0}/docs/version_macro.py +0 -0
  49. {lispython-0.4.7 → lispython-0.5.0}/docs/why-lispy.md +0 -0
  50. {lispython-0.4.7 → lispython-0.5.0}/mkdocs.yml +0 -0
  51. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/__init__.py +0 -0
  52. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/builtins.py +0 -0
  53. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/compiler/__init__.py +0 -0
  54. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/compiler/utils.py +0 -0
  55. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/utils.py +0 -0
  56. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/lsp/__init__.py +0 -0
  57. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/lsp/__main__.py +0 -0
  58. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/macros/__init__.py +0 -0
  59. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/macros/init.lpy +0 -0
  60. {lispython-0.4.7 → lispython-0.5.0}/src/lispy/macros/sugar.lpy +0 -0
  61. {lispython-0.4.7 → lispython-0.5.0}/tests/__init__.py +0 -0
  62. {lispython-0.4.7 → lispython-0.5.0}/tests/test_as_thread.py +0 -0
  63. {lispython-0.4.7 → lispython-0.5.0}/tests/test_gensym.py +0 -0
  64. {lispython-0.4.7 → lispython-0.5.0}/tests/test_include_meta.py +0 -0
  65. {lispython-0.4.7 → lispython-0.5.0}/tests/test_literal_unwrap.py +0 -0
  66. {lispython-0.4.7 → lispython-0.5.0}/tests/utils.py +0 -0
@@ -0,0 +1,119 @@
1
+ #!/bin/sh
2
+ #
3
+ # post-checkout — provision shared gitignored files into a fresh worktree.
4
+ #
5
+ # ⚠️ CANONICAL BODY — DO NOT EDIT BELOW THE CONFIG BLOCK.
6
+ # This file is copied verbatim from
7
+ # ~/vault/Vault/Projects/dotagents/guidelines/templates/post-checkout
8
+ # and the ONLY per-project variance is LINK_FILES / COPY_FILES. Nine hand-written
9
+ # copies had already drifted by 17-29 lines of logic — including two that lost the
10
+ # broken-symlink source check and the SQL escaping below — which is why the body
11
+ # is now one artifact. Fix a bug in the template, then re-copy.
12
+ #
13
+ # `git worktree add` runs a checkout in the new worktree, which fires this hook
14
+ # with the new worktree as cwd. That is what populates files git does not track,
15
+ # so a worktree is ready to build and run without manual copying.
16
+ #
17
+ # - env/secret files and agent docs are SYMLINKED back to the main checkout:
18
+ # they stay in sync, and symlinking a secret never reads its contents.
19
+ # ⚠️ NEVER copy an env file — a copy drifts and duplicates the secret.
20
+ # - the SQLite db is COPIED as an independent snapshot, so a schema migration
21
+ # run inside the worktree can never corrupt the main checkout's database.
22
+ #
23
+ # Idempotent: only provisions files that are missing, so it is safe to re-run
24
+ # and safe to fire on ordinary `git checkout` / `git switch`.
25
+ #
26
+ # Tracked in .githooks/ and activated per-clone (this pointer is local, not
27
+ # committed):
28
+ # git config core.hooksPath .githooks
29
+ # Run `git worktree add` from a checkout root so the relative path resolves.
30
+
31
+ # ==== PROJECT CONFIG — the only part that varies ==========================
32
+
33
+ # SYMLINK: files that should track the main checkout. Agent docs are themselves
34
+ # symlinks into the vault; linking the link is fine and keeps one source.
35
+ #
36
+ # ⚠️ Keep this list NARROW. Never link node_modules, build output or a whole data
37
+ # dir — those confuse build tools, watchers and cleanup. A large READ-ONLY data
38
+ # dir is the one defensible exception (vocadict links its dictionary shards);
39
+ # document it in that project's AGENTS.md.
40
+ LINK_FILES="
41
+ AGENTS.md
42
+ CLAUDE.md
43
+ GEMINI.md
44
+ "
45
+
46
+ # COPY: anything the worktree may mutate irreversibly — the dev/staging db above
47
+ # all, so a migration tried in a worktree cannot reach the database the main
48
+ # checkout serves. Empty is valid (a Supabase-backed project has no local db).
49
+ COPY_FILES="
50
+ "
51
+
52
+ # ==== CANONICAL BODY — do not edit per project ============================
53
+
54
+ # $3 is the checkout flag: 1 = branch checkout (incl. worktree add), 0 = file.
55
+ # Only act on branch checkouts.
56
+ [ "$3" = "1" ] || exit 0
57
+
58
+ # Resolve this worktree's gitdir and the shared common gitdir.
59
+ wt_gitdir=$(git rev-parse --absolute-git-dir 2>/dev/null) || exit 0
60
+ common_gitdir=$(cd "$(git rev-parse --git-common-dir 2>/dev/null)" 2>/dev/null && pwd) || exit 0
61
+
62
+ # In the MAIN checkout these are equal — nothing to provision, bail out.
63
+ [ "$wt_gitdir" = "$common_gitdir" ] && exit 0
64
+
65
+ # Main checkout top-level is the parent of the common gitdir (<main>/.git).
66
+ main_top=$(dirname "$common_gitdir")
67
+ wt_top=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
68
+
69
+ linked=""
70
+ for rel in $LINK_FILES; do
71
+ src="$main_top/$rel"
72
+ dst="$wt_top/$rel"
73
+ # Skip if the source is missing or the destination already exists.
74
+ [ -e "$src" ] || [ -L "$src" ] || continue
75
+ { [ -e "$dst" ] || [ -L "$dst" ]; } && continue
76
+ mkdir -p "$(dirname "$dst")"
77
+ ln -s "$src" "$dst" && linked="$linked $rel"
78
+ done
79
+
80
+ copied=""
81
+ for rel in $COPY_FILES; do
82
+ src="$main_top/$rel"
83
+ dst="$wt_top/$rel"
84
+ [ -e "$src" ] || continue
85
+ { [ -e "$dst" ] || [ -L "$dst" ]; } && continue
86
+ mkdir -p "$(dirname "$dst")"
87
+ case "$rel" in
88
+ *.db)
89
+ # WAL-mode dbs keep committed data in the -wal file until checkpoint,
90
+ # and a live `serve` process may be mid-write. VACUUM INTO writes a
91
+ # consistent standalone snapshot (WAL-committed data included) into a
92
+ # fresh file; fall back to cp if the sqlite3 CLI is unavailable. $dst is
93
+ # SQL-string-escaped so a worktree path with quotes/spaces stays safe.
94
+ # Safe because this branch only runs when $dst does not yet exist.
95
+ if command -v sqlite3 >/dev/null 2>&1; then
96
+ dst_sql=$(printf '%s' "$dst" | sed "s/'/''/g")
97
+ sqlite3 "$src" "VACUUM INTO '$dst_sql'" && copied="$copied $rel"
98
+ else
99
+ echo "post-checkout: sqlite3 missing; falling back to cp for $rel (may be inconsistent if db is being written)" >&2
100
+ cp -p "$src" "$dst" && copied="$copied $rel"
101
+ fi
102
+ ;;
103
+ *)
104
+ cp -p "$src" "$dst" && copied="$copied $rel"
105
+ ;;
106
+ esac
107
+ done
108
+
109
+ [ -n "$linked" ] && echo "post-checkout: linked shared files:$linked"
110
+ [ -n "$copied" ] && echo "post-checkout: copied isolated files:$copied"
111
+
112
+ # Optional per-project extension, so a genuine project-specific need never
113
+ # becomes a reason to fork the body above. Runs with $wt_top / $main_top set and
114
+ # provisioning already done — the place for a sanity check ON what was
115
+ # provisioned (fridge warns when the copied db has zero rounds, which is
116
+ # otherwise a silent empty-dev-db). Keep it to that; anything general belongs in
117
+ # the template.
118
+ [ -f "$wt_top/.githooks/post-checkout.project" ] && . "$wt_top/.githooks/post-checkout.project"
119
+ exit 0
@@ -328,6 +328,9 @@ AGENTS.md
328
328
  CLAUDE.md
329
329
  GEMINI.md
330
330
 
331
+ # nREPL port file (written by `lpy --nrepl`)
332
+ .nrepl-port
333
+
331
334
  # Git worktrees
332
335
  .worktrees/
333
336
 
@@ -335,3 +338,4 @@ GEMINI.md
335
338
  docs/superpowers/.stacks
336
339
  .agents
337
340
  .claude
341
+ .agent
@@ -0,0 +1,83 @@
1
+ # Changelog
2
+
3
+ All notable changes to lispython are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
5
+ follows [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.5.0]
8
+
9
+ ### Added
10
+
11
+ - Python 3.11 through 3.15 support, with each newer AST form gated on the
12
+ running interpreter and a clear compile error on older ones. A `nox` matrix
13
+ (`uvx nox -s tests`) runs the suite on every supported version.
14
+ - Type aliases: `(type NAME value)` compiles to a `type NAME = value`
15
+ statement (3.12+). The three-operand `(type name bases ns)` call is unchanged.
16
+ - Generic definitions: `def`, `async def` and `class` names accept glued type
17
+ parameters such as `f[T]`, including `ParamSpec`, `TypeVarTuple` and
18
+ defaults (parameters 3.12+, defaults 3.13+).
19
+ - t-strings compile to `TemplateStr` (3.14+).
20
+ - `lazy-import` and `lazy-from` (3.15+).
21
+ - Comprehension unpacking (3.15+), including `{**d for d dicts}`, which was
22
+ mistranslated as a dict display; unary plus is gated to 3.15+.
23
+ - Errors the CPython compiler raises on `.lpy` modules, and lispython's own
24
+ compile errors, are reported with the file, line and caret instead of a
25
+ traceback by `lpy` (file, `-m`, `-c`), `l2py`, the REPL and nREPL eval.
26
+ - LSP: `textDocument/signatureHelp` and `workspace/symbol`; `(type NAME ...)`
27
+ aliases are indexed as definitions.
28
+ - nREPL: completion responses carry type kinds, and the server writes
29
+ `.nrepl-port` in the working directory for editor auto-discovery.
30
+
31
+ ### Changed
32
+
33
+ - **The REPL and nREPL compile every statement of an input before running any
34
+ of it**, and `lpy -c` compiles its whole input first. A compile error,
35
+ lispython's or CPython's, now stops the input before any statement runs
36
+ (macro expansion still happens first); a `SyntaxError` the program raises
37
+ at run time keeps its own traceback.
38
+ - **Stricter parsing** rejects input that used to be accepted silently: an
39
+ unclosed final form or mismatched closing bracket, an unterminated string,
40
+ an f-string field holding more than one form, a lone `}` in an f-string,
41
+ non-symbol macro names (`(defmacro m[T] ...)`), `(import "x")`,
42
+ `(lazy-import "numpy")`, `(return a b)`, and `name[...]` glued under a
43
+ prefix such as `^list[int]` in a parameter list. Malformed literals such as
44
+ `09` or `\x4` are reported at their token.
45
+ - **Literal values**: `-1+2j` is now `(-1+2j)` rather than `-(1+2j)`; `\r\n`
46
+ and lone `\r` become `\n` in f-strings too; a multi-line raw string no
47
+ longer gains a literal `\n`; backslash-newline continues a string; a lone
48
+ `"` inside `"""..."""` no longer ends the token.
49
+ - **F-strings follow Python's rules.** An uppercase `F` prefix (and `rf`,
50
+ `fR`, ...) is a real f-string rather than being evaluated at compile time,
51
+ `{x=}` echoes the expression text and defaults to `!r`, and format specs
52
+ may nest replacement fields (`{x:>{w}.{p}}`). Unquotes expand inside
53
+ f-string and t-string fields.
54
+ - **`(require mod [])` registers no macros**, like an empty import list; only
55
+ an absent list (`(require mod)`) registers all of them. The LSP follows the
56
+ same rule in go-to-definition and hover.
57
+ - `lpy -m` puts the current directory on `sys.path` like `python -m`,
58
+ honouring `PYTHONSAFEPATH`.
59
+ - Requires `pygls` 2.0 or later.
60
+
61
+ ### Fixed
62
+
63
+ - `(return [])` compiled to a bare `return` (yielding `None`); it now returns
64
+ the empty list.
65
+ - `(case {})` accepts an empty mapping pattern.
66
+ - Signed literal match patterns: `(case -1 ...)` compiled to `case _(1):`.
67
+ - `(from . [a])` compiles; it raised `IndexError`.
68
+ - Quasiquoted f/t-string fields keep their conversion, spec and `=` text
69
+ (`f"{x!r:>6}"` used to come back as `{x}`), and `f"{~x}"` with a literal
70
+ macro argument no longer crashes.
71
+ - `l2py` emits valid Python for t-string interpolations.
72
+ - A lone starred subscript slice compiles to a one-element `Tuple`, like
73
+ Python's `t[*s]`.
74
+ - LSP: workspace symbols no longer include installed packages or function
75
+ locals, and report their real kinds; a buffer that is mid-edit keeps
76
+ working; signature help is not offered inside a quoted form.
77
+ - nREPL: completion kinds treat scope bindings as shadowing builtins, never
78
+ run user code to classify attributes, and report callables as functions;
79
+ an unwritable `.nrepl-port` no longer stops the server.
80
+
81
+ ## [0.4.7]
82
+
83
+ See the `v0.4.7` tag.
@@ -1,15 +1,22 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: lispython
3
- Version: 0.4.7
3
+ Version: 0.5.0
4
4
  Summary: Lisp-like Syntax for Python with Lisp-like Macros
5
5
  Project-URL: Homepage, https://jetack.github.io/lispython
6
6
  Project-URL: Repository, https://github.com/jetack/lispython
7
7
  Author-email: Jetack <jetack23@gmail.com>
8
8
  License: MIT
9
9
  License-File: LICENSE.md
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Programming Language :: Python :: 3.15
10
17
  Requires-Python: >=3.11
11
18
  Requires-Dist: prompt-toolkit>=3.0.52
12
- Requires-Dist: pygls>=1.0.0
19
+ Requires-Dist: pygls>=2.0
13
20
  Provides-Extra: dev
14
21
  Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
15
22
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
@@ -87,7 +94,8 @@ LisPython ships with a language server (`lpy-lsp`) that speaks LSP over stdio. I
87
94
  - Diagnostics (parse / compile errors)
88
95
  - Completions (special forms, builtins, file/workspace symbols)
89
96
  - Hover documentation for special forms and builtins
90
- - Document symbols
97
+ - Signature help for special forms and builtins
98
+ - Document symbols and workspace symbol search
91
99
  - Go-to-definition, including across `.lpy` files in the workspace
92
100
 
93
101
  ## nREPL Server
@@ -98,11 +106,13 @@ lpy --nrepl # start on a random port
98
106
  lpy --nrepl 7888 # start on a specific port
99
107
  ```
100
108
 
109
+ While running, the server writes its port to `.nrepl-port` in the working directory for editor auto-discovery.
110
+
101
111
  The server accepts newline-delimited JSON over TCP and supports:
102
112
  - `eval` — evaluate LisPython code, return value/stdout/error
103
113
  - `load-file` — load a `.lpy` file into the session
104
114
  - `macroexpand` — expand macros and return the result
105
- - `complete` — prefix and dot-completion from the live scope
115
+ - `complete` — prefix and dot-completion from the live scope, with a `kinds` map (class/function/variable/macro/keyword)
106
116
  - `docs` — signature and docstring for a symbol
107
117
  - `annotate` — type tag (function/class/module/macro)
108
118
 
@@ -113,8 +123,3 @@ Install the [LisPython](https://marketplace.visualstudio.com/items?itemName=jeta
113
123
 
114
124
  #### Emacs
115
125
  Use [`lpy-mode`](https://github.com/jetack/lpy-mode). It provides nREPL integration (eval, completion, eldoc, macroexpand) and LSP via eglot.
116
-
117
- ## Todo
118
- ### Python AST
119
- - [ ] `type_comment` never considered. Later, it should be covered
120
- - [ ] Any missing AST nodes in the version 3.12+
@@ -64,7 +64,8 @@ LisPython ships with a language server (`lpy-lsp`) that speaks LSP over stdio. I
64
64
  - Diagnostics (parse / compile errors)
65
65
  - Completions (special forms, builtins, file/workspace symbols)
66
66
  - Hover documentation for special forms and builtins
67
- - Document symbols
67
+ - Signature help for special forms and builtins
68
+ - Document symbols and workspace symbol search
68
69
  - Go-to-definition, including across `.lpy` files in the workspace
69
70
 
70
71
  ## nREPL Server
@@ -75,11 +76,13 @@ lpy --nrepl # start on a random port
75
76
  lpy --nrepl 7888 # start on a specific port
76
77
  ```
77
78
 
79
+ While running, the server writes its port to `.nrepl-port` in the working directory for editor auto-discovery.
80
+
78
81
  The server accepts newline-delimited JSON over TCP and supports:
79
82
  - `eval` — evaluate LisPython code, return value/stdout/error
80
83
  - `load-file` — load a `.lpy` file into the session
81
84
  - `macroexpand` — expand macros and return the result
82
- - `complete` — prefix and dot-completion from the live scope
85
+ - `complete` — prefix and dot-completion from the live scope, with a `kinds` map (class/function/variable/macro/keyword)
83
86
  - `docs` — signature and docstring for a symbol
84
87
  - `annotate` — type tag (function/class/module/macro)
85
88
 
@@ -90,8 +93,3 @@ Install the [LisPython](https://marketplace.visualstudio.com/items?itemName=jeta
90
93
 
91
94
  #### Emacs
92
95
  Use [`lpy-mode`](https://github.com/jetack/lpy-mode). It provides nREPL integration (eval, completion, eldoc, macroexpand) and LSP via eglot.
93
-
94
- ## Todo
95
- ### Python AST
96
- - [ ] `type_comment` never considered. Later, it should be covered
97
- - [ ] Any missing AST nodes in the version 3.12+
@@ -151,6 +151,26 @@ Python version
151
151
  ```python
152
152
  (2 * x for x in range(10) if x % 2 == 0)
153
153
  ```
154
+ ### Comprehension Unpacking
155
+ Python 3.15+ ([PEP 798](https://peps.python.org/pep-0798/)). On an older Python
156
+ these forms are a compile error naming the version they need.
157
+
158
+ LisPy version
159
+ ```python
160
+ [*L for L in lists]
161
+ (*L for L in lists)
162
+ {, *s for s in sets}
163
+ {**d for d in dicts}
164
+ [*L async-for L in lists]
165
+ ```
166
+ Python version
167
+ ```python
168
+ [*L for L in lists]
169
+ (*L for L in lists)
170
+ {*s for s in sets}
171
+ {**d for d in dicts}
172
+ [*L async for L in lists]
173
+ ```
154
174
 
155
175
  ## `await`
156
176
  LisPy version
@@ -199,6 +219,34 @@ Python version
199
219
  name = "World"
200
220
  f"Hello, {2 * name}!"
201
221
  ```
222
+ Replacement fields follow Python's rules: `{{`/`}}` are literal braces,
223
+ `!r`/`!s`/`!a` conversions, format specs with nested fields, and `{x=}`.
224
+ LisPy version
225
+ ```python
226
+ f"{{literal}} {x!r:>{width}} {x=}"
227
+ ```
228
+ Python version
229
+ ```python
230
+ f"{{literal}} {x!r:>{width}} {x=}"
231
+ ```
232
+
233
+ ## Template String Literals
234
+ Python 3.14+ ([PEP 750](https://peps.python.org/pep-0750/)). The `t` prefix
235
+ combines with `r` like `f` does (`rt"..."`, `Tr"..."`) and builds a
236
+ `string.templatelib.Template`. Each interpolation's `expression` is the LisPy
237
+ text inside the braces. On an older Python a t-string is a compile error naming
238
+ the version it needs.
239
+
240
+ LisPy version
241
+ ```python
242
+ (= name "World")
243
+ t"Hello, {(* 2 name)!r:>12}!"
244
+ ```
245
+ Python version
246
+ ```python
247
+ name = "World"
248
+ t"Hello, {2 * name!r:>12}!" # interpolations[0].expression == "(* 2 name)"
249
+ ```
202
250
 
203
251
  ## Attribute
204
252
  LisPy version
@@ -42,6 +42,62 @@ class class_name(base_class1, base_class2):
42
42
  body
43
43
  ```
44
44
 
45
+ ## Generic Functions and Classes
46
+ Python 3.12+ ([PEP 695](https://peps.python.org/pep-0695/)); defaults need
47
+ Python 3.13+ ([PEP 696](https://peps.python.org/pep-0696/)). Write the type
48
+ parameters glued to the name, with no space: `f[T]` is a generic name, while
49
+ `f [T]` is a name followed by a list. The bracket uses the argument markers of
50
+ `def`: `^` for a bound (a tuple bound is a constraint list), `*` for a
51
+ TypeVarTuple, `**` for a ParamSpec and `:` for a default. On an older Python
52
+ these forms are a compile error naming the version they need.
53
+
54
+ A glued name is only valid as the name of `def`, `async-def`, `class` and
55
+ `type`; to subscript, use `(sub xs ...)`.
56
+
57
+ LisPy version
58
+ ```python
59
+ (def first[T] [xs ^(sub list T)] ^T
60
+ (return (sub xs 0)))
61
+
62
+ (class Box[T ^(, int str) *Ts **P] [Base]
63
+ (pass))
64
+
65
+ (def f[:T str :U ^int bool :*Ts *(sub tuple int) :**P [int str]] [] ; Python 3.13+
66
+ (pass))
67
+ ```
68
+ Python version
69
+ ```python
70
+ def first[T](xs: list[T]) -> T:
71
+ return xs[0]
72
+
73
+ class Box[T: (int, str), *Ts, **P](Base):
74
+ pass
75
+
76
+ def f[T = str, U: int = bool, *Ts = *tuple[int], **P = [int, str]](): # Python 3.13+
77
+ pass
78
+ ```
79
+
80
+ ## Type Alias
81
+ Python 3.12+ ([PEP 695](https://peps.python.org/pep-0695/)). As in Python,
82
+ `type` is a soft keyword: `(type NAME value)` and `(type NAME[T ...] value)`
83
+ are a type alias only as a statement with exactly two operands whose first is a
84
+ plain name. Every other shape is a call to the builtin `type`, so
85
+ `(= t (type x))` and `(type "N" (, object) {})` are unchanged. If you rebind
86
+ `type` and need a bare two-argument call as a statement, write
87
+ `(= _ (type a b))`. On an older Python the alias is a compile error naming the
88
+ version it needs.
89
+
90
+ LisPy version
91
+ ```python
92
+ (type Number (| int float))
93
+ (type Vec[T] (sub list T))
94
+ ```
95
+ Python version
96
+ ```python
97
+ type Number = int | float
98
+ type Vec[T] = list[T]
99
+ ```
100
+
45
101
  ## `del`
46
102
  LisPy version
47
103
  ```python
@@ -277,6 +333,34 @@ match x:
277
333
  case [x] | y:
278
334
  ...
279
335
  ```
336
+ ### Signed Literal Patterns
337
+ A negative number or a complex literal is a value pattern. A leading `+` needs
338
+ Python 3.15+; on an older Python it is a compile error naming the version.
339
+
340
+ LisPy version
341
+ ```python
342
+ (match x
343
+ (case -1
344
+ ...)
345
+ (case -1-2j
346
+ ...)
347
+ (case {-1 _}
348
+ ...)
349
+ (case +1 ; Python 3.15+
350
+ ...))
351
+ ```
352
+ Python version
353
+ ```python
354
+ match x:
355
+ case -1:
356
+ ...
357
+ case -1 - 2j:
358
+ ...
359
+ case {-1: _}:
360
+ ...
361
+ case +1: # Python 3.15+
362
+ ...
363
+ ```
280
364
 
281
365
  ## `raise`
282
366
  LisPy version
@@ -391,6 +475,26 @@ from module import name1
391
475
  from module import name2 as alias
392
476
  from module import name3
393
477
  ```
478
+ ### Lazy imports
479
+ Python 3.15+ ([PEP 810](https://peps.python.org/pep-0810/)). `lazy-import` and
480
+ `lazy-from` take the same arguments as `import` and `from`; the module is
481
+ loaded when a name is first used. As in Python, `lazy-from` refuses `*` and
482
+ `__future__`, and a lazy import is not allowed inside a function, a class or a
483
+ `try` block. On an older Python these forms are a compile error naming the
484
+ version they need.
485
+
486
+ LisPy version
487
+ ```python
488
+ (lazy-import json)
489
+ (lazy-import numpy as np)
490
+ (lazy-from json [dumps loads as load-json])
491
+ ```
492
+ Python version
493
+ ```python
494
+ lazy import json
495
+ lazy import numpy as np
496
+ lazy from json import dumps, loads as load_json
497
+ ```
394
498
 
395
499
  ## `global`
396
500
  LisPy version
@@ -0,0 +1,13 @@
1
+ """Test matrix over every maintained CPython: ``uvx nox -s tests``."""
2
+
3
+ import nox
4
+
5
+ nox.options.default_venv_backend = "uv"
6
+
7
+ PYTHONS = ["3.11", "3.12", "3.13", "3.14", "3.15"]
8
+
9
+
10
+ @nox.session(python=PYTHONS)
11
+ def tests(session):
12
+ session.install(".", "pytest")
13
+ session.run("pytest", *session.posargs)
@@ -1,14 +1,23 @@
1
1
  [project]
2
2
  name = "lispython"
3
- version = "0.4.7"
3
+ version = "0.5.0"
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" }
7
7
  readme = "README.md"
8
8
  requires-python = ">=3.11"
9
+ classifiers = [
10
+ "Programming Language :: Python :: 3",
11
+ "Programming Language :: Python :: 3 :: Only",
12
+ "Programming Language :: Python :: 3.11",
13
+ "Programming Language :: Python :: 3.12",
14
+ "Programming Language :: Python :: 3.13",
15
+ "Programming Language :: Python :: 3.14",
16
+ "Programming Language :: Python :: 3.15",
17
+ ]
9
18
  dependencies = [
10
19
  "prompt-toolkit>=3.0.52",
11
- "pygls>=1.0.0",
20
+ "pygls>=2.0",
12
21
  ]
13
22
 
14
23
  [project.urls]
@@ -44,6 +53,13 @@ packages = ["src/lispy"]
44
53
  [tool.pytest.ini_options]
45
54
  testpaths = ["tests"]
46
55
  python_files = ["test_*.py"]
56
+ # AST constructor misuse (unknown keywords, missing fields) warns on 3.13/3.14
57
+ # and is a TypeError from 3.15: fail the suite on the warning.
58
+ filterwarnings = [
59
+ "error::DeprecationWarning",
60
+ # pygls (third-party) calls asyncio.iscoroutinefunction, deprecated in 3.14.
61
+ "ignore:'asyncio.iscoroutinefunction' is deprecated:DeprecationWarning",
62
+ ]
47
63
 
48
64
  [tool.ruff]
49
65
  line-length = 88