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.
- lispython-0.5.0/.githooks/post-checkout +119 -0
- {lispython-0.4.7 → lispython-0.5.0}/.gitignore +4 -0
- lispython-0.5.0/CHANGELOG.md +83 -0
- {lispython-0.4.7 → lispython-0.5.0}/PKG-INFO +15 -10
- {lispython-0.4.7 → lispython-0.5.0}/README.md +5 -7
- {lispython-0.4.7 → lispython-0.5.0}/docs/syntax/expressions.md +48 -0
- {lispython-0.4.7 → lispython-0.5.0}/docs/syntax/statements.md +104 -0
- lispython-0.5.0/noxfile.py +13 -0
- {lispython-0.4.7 → lispython-0.5.0}/pyproject.toml +18 -2
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/compiler/expr.py +94 -21
- lispython-0.5.0/src/lispy/core/compiler/features.py +85 -0
- lispython-0.5.0/src/lispy/core/compiler/literal.py +187 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/compiler/stmt.py +217 -22
- lispython-0.5.0/src/lispy/core/errors.py +109 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/importer.py +24 -9
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/macro.py +15 -7
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/meta_functions.py +22 -7
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/nodes.py +107 -3
- lispython-0.5.0/src/lispy/core/parser.py +769 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core_meta_functions.lpy +22 -8
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/lsp/server.lpy +289 -31
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/tools.lpy +198 -58
- lispython-0.5.0/tests/test_ast_hygiene.py +89 -0
- lispython-0.5.0/tests/test_cli_module.py +54 -0
- lispython-0.5.0/tests/test_dx.py +561 -0
- {lispython-0.4.7 → lispython-0.5.0}/tests/test_expr.py +23 -0
- lispython-0.5.0/tests/test_glued_head.py +222 -0
- lispython-0.5.0/tests/test_literal.py +401 -0
- lispython-0.5.0/tests/test_lsp.py +218 -0
- lispython-0.5.0/tests/test_meta_functions.py +58 -0
- {lispython-0.4.7 → lispython-0.5.0}/tests/test_parser.py +33 -0
- lispython-0.5.0/tests/test_stmt.py +294 -0
- lispython-0.5.0/tests/test_version_features.py +1047 -0
- {lispython-0.4.7 → lispython-0.5.0}/uv.lock +2 -2
- lispython-0.4.7/src/lispy/core/compiler/literal.py +0 -24
- lispython-0.4.7/src/lispy/core/parser.py +0 -336
- lispython-0.4.7/tests/test_literal.py +0 -95
- lispython-0.4.7/tests/test_meta_functions.py +0 -26
- lispython-0.4.7/tests/test_stmt.py +0 -92
- {lispython-0.4.7 → lispython-0.5.0}/.pre-commit-config.yaml +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/.vscode/settings.json +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/LICENSE.md +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/docs/index.md +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/docs/macros.md +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/docs/syntax/overview.md +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/docs/usage/cli.md +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/docs/usage/getting-started.md +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/docs/version_macro.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/docs/why-lispy.md +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/mkdocs.yml +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/__init__.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/builtins.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/compiler/__init__.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/compiler/utils.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/core/utils.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/lsp/__init__.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/lsp/__main__.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/macros/__init__.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/macros/init.lpy +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/src/lispy/macros/sugar.lpy +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/tests/__init__.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/tests/test_as_thread.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/tests/test_gensym.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/tests/test_include_meta.py +0 -0
- {lispython-0.4.7 → lispython-0.5.0}/tests/test_literal_unwrap.py +0 -0
- {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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: lispython
|
|
3
|
-
Version: 0.
|
|
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>=
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
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>=
|
|
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
|