lispython 0.4.7__tar.gz → 0.6.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.4.7 → lispython-0.6.0}/.gitignore +4 -0
- lispython-0.6.0/CHANGELOG.md +447 -0
- {lispython-0.4.7 → lispython-0.6.0}/PKG-INFO +17 -10
- {lispython-0.4.7 → lispython-0.6.0}/README.md +7 -7
- {lispython-0.4.7 → lispython-0.6.0}/docs/macros.md +6 -5
- {lispython-0.4.7 → lispython-0.6.0}/docs/syntax/expressions.md +57 -0
- {lispython-0.4.7 → lispython-0.6.0}/docs/syntax/statements.md +106 -0
- {lispython-0.4.7 → lispython-0.6.0}/docs/version_macro.py +2 -1
- lispython-0.6.0/noxfile.py +13 -0
- lispython-0.6.0/pyproject.toml +109 -0
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/__init__.py +1 -1
- lispython-0.6.0/src/lispy/core/builtins.py +127 -0
- lispython-0.6.0/src/lispy/core/compiler/expr.py +871 -0
- lispython-0.6.0/src/lispy/core/compiler/features.py +210 -0
- lispython-0.6.0/src/lispy/core/compiler/literal.py +307 -0
- lispython-0.6.0/src/lispy/core/compiler/stmt.py +1025 -0
- lispython-0.6.0/src/lispy/core/compiler/utils.py +270 -0
- lispython-0.6.0/src/lispy/core/errors.py +175 -0
- lispython-0.6.0/src/lispy/core/importer.py +103 -0
- lispython-0.6.0/src/lispy/core/macro.py +156 -0
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/core/meta_functions.py +25 -7
- lispython-0.6.0/src/lispy/core/nodes.py +1129 -0
- lispython-0.6.0/src/lispy/core/parser.py +1952 -0
- lispython-0.6.0/src/lispy/core/utils.py +78 -0
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/core_meta_functions.lpy +27 -8
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/lsp/server.lpy +394 -77
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/macros/sugar.lpy +13 -6
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/tools.lpy +429 -107
- lispython-0.6.0/tests/conftest.py +37 -0
- lispython-0.6.0/tests/test_as_thread.py +240 -0
- lispython-0.6.0/tests/test_ast_hygiene.py +89 -0
- lispython-0.6.0/tests/test_cli_module.py +70 -0
- lispython-0.6.0/tests/test_dx.py +562 -0
- lispython-0.6.0/tests/test_expr.py +365 -0
- {lispython-0.4.7 → lispython-0.6.0}/tests/test_gensym.py +10 -0
- lispython-0.6.0/tests/test_glued_head.py +222 -0
- {lispython-0.4.7 → lispython-0.6.0}/tests/test_include_meta.py +24 -4
- lispython-0.6.0/tests/test_literal.py +401 -0
- lispython-0.6.0/tests/test_literal_unwrap.py +355 -0
- lispython-0.6.0/tests/test_lsp.py +294 -0
- lispython-0.6.0/tests/test_macro_positions.py +581 -0
- lispython-0.6.0/tests/test_meta_functions.py +58 -0
- lispython-0.6.0/tests/test_names.py +300 -0
- lispython-0.6.0/tests/test_nesting.py +1417 -0
- lispython-0.6.0/tests/test_nrepl.py +410 -0
- lispython-0.6.0/tests/test_parser.py +1786 -0
- lispython-0.6.0/tests/test_statement_in_expr.py +80 -0
- lispython-0.6.0/tests/test_stmt.py +852 -0
- lispython-0.6.0/tests/test_version_features.py +1060 -0
- {lispython-0.4.7 → lispython-0.6.0}/tests/utils.py +0 -1
- {lispython-0.4.7 → lispython-0.6.0}/uv.lock +3 -3
- lispython-0.4.7/.pre-commit-config.yaml +0 -22
- lispython-0.4.7/.vscode/settings.json +0 -3
- lispython-0.4.7/pyproject.toml +0 -56
- lispython-0.4.7/src/lispy/core/builtins.py +0 -29
- lispython-0.4.7/src/lispy/core/compiler/expr.py +0 -485
- lispython-0.4.7/src/lispy/core/compiler/literal.py +0 -24
- lispython-0.4.7/src/lispy/core/compiler/stmt.py +0 -511
- lispython-0.4.7/src/lispy/core/compiler/utils.py +0 -45
- lispython-0.4.7/src/lispy/core/importer.py +0 -72
- lispython-0.4.7/src/lispy/core/macro.py +0 -119
- lispython-0.4.7/src/lispy/core/nodes.py +0 -477
- lispython-0.4.7/src/lispy/core/parser.py +0 -336
- lispython-0.4.7/src/lispy/core/utils.py +0 -32
- lispython-0.4.7/tests/test_as_thread.py +0 -49
- lispython-0.4.7/tests/test_expr.py +0 -34
- lispython-0.4.7/tests/test_literal.py +0 -95
- lispython-0.4.7/tests/test_literal_unwrap.py +0 -73
- lispython-0.4.7/tests/test_meta_functions.py +0 -26
- lispython-0.4.7/tests/test_parser.py +0 -71
- lispython-0.4.7/tests/test_stmt.py +0 -92
- {lispython-0.4.7 → lispython-0.6.0}/LICENSE.md +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/docs/index.md +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/docs/syntax/overview.md +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/docs/usage/cli.md +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/docs/usage/getting-started.md +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/docs/why-lispy.md +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/mkdocs.yml +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/core/compiler/__init__.py +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/lsp/__init__.py +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/lsp/__main__.py +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/macros/__init__.py +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/src/lispy/macros/init.lpy +0 -0
- {lispython-0.4.7 → lispython-0.6.0}/tests/__init__.py +0 -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,447 @@
|
|
|
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
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.6.0] - 2026-11-02
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- The nREPL `eval` op reads the standard `file`, `line` and `column` fields
|
|
14
|
+
(where the code sits in the editor's buffer, 1-based). Tracebacks and
|
|
15
|
+
compile errors then name that file with buffer line numbers and print the
|
|
16
|
+
source line, including for definitions evaluated in earlier requests and
|
|
17
|
+
for `load-file`.
|
|
18
|
+
- `(raise)` compiles to a bare `raise`, re-raising the exception being
|
|
19
|
+
handled.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- A string opened with `\"` in an f/t-string replacement field must be closed
|
|
24
|
+
by `\"` (`\"\"\"` for `\"\"\"`): a bare `"` in it, as its close or its
|
|
25
|
+
content, is a compile error at the opener, where it was accepted before
|
|
26
|
+
(`f"{(g \"a")}"`). Its `{x=}` echo (and a t-string's expression text) now
|
|
27
|
+
shows the source as written: `f"{(.get d \"k\")=}"` echoes
|
|
28
|
+
`(.get d \"k\")=`, where it echoed `(.get d "k")=`.
|
|
29
|
+
- On 3.12 a single-quoted literal's format spec continued after a line
|
|
30
|
+
break in its text is read as CPython 3.12 reads it: the line break and
|
|
31
|
+
what follows are no longer part of the spec (`f"{x:ab\n}"` formats with
|
|
32
|
+
`ab`, where it used `ab\n`), only blanks, comments and replacement fields
|
|
33
|
+
may follow before the `}`, and any other text there (`f"{x:a\nb}"`, which
|
|
34
|
+
was accepted) is CPython's "expecting '}', or format specs" error or its
|
|
35
|
+
tokenizer error.
|
|
36
|
+
- An nREPL eval without `file` is named `<nrepl-N>` (a new N per eval)
|
|
37
|
+
instead of `<nrepl>`, which can break a client that matches `<nrepl>` in
|
|
38
|
+
error text.
|
|
39
|
+
- On Python 3.13+, a line break in a single-quoted f/t-string's format spec
|
|
40
|
+
is a compile error at the break even when the field then closes
|
|
41
|
+
(`f"{x:ab\n}"`), as CPython 3.13+ refuse it; it compiled before.
|
|
42
|
+
- An invalid `line` or `column` (not an integer from 1 to
|
|
43
|
+
`NREPL-MAX-POSITION-LINE`, 1,000,000, or `NREPL-MAX-POSITION-COLUMN`,
|
|
44
|
+
100,000) gets an `error` reply with the request id instead of being
|
|
45
|
+
accepted.
|
|
46
|
+
- Every name the compiler emits must be a Python name, or it is a compile
|
|
47
|
+
error at the symbol. Code that compiled and ran before is now refused:
|
|
48
|
+
a symbol whose mangled text is no Python identifier (`foo!`, `a.`, as in
|
|
49
|
+
`(= foo! 1)`), an operator whose mangled text is no identifier used as a
|
|
50
|
+
value (`(f +)`, `(print ~)`), and a hard keyword in any name slot:
|
|
51
|
+
attributes (`(. a if)`, `(.if x)`), keyword arguments (`(f :if 1)`, class
|
|
52
|
+
keywords), parameters (`(def f [:if 1] ...)`), type parameters, import
|
|
53
|
+
names and aliases, match captures and class pattern attributes. CPython
|
|
54
|
+
compiled such names from the AST, but no Python source spells them, so
|
|
55
|
+
`l2py` printed code that does not parse. Operators that mangle to a name
|
|
56
|
+
(`-` is `_`, `is-not` is `is_not`) are names as before.
|
|
57
|
+
- An attribute slot takes one name: `(. a b.c)` and `(.a.b x)` are compile
|
|
58
|
+
errors; write `(. a b c)` and `(.b x.a)`.
|
|
59
|
+
- `(. a)`, an attribute form with no attribute, is a compile error in every
|
|
60
|
+
context (value, assignment, `del`, annotated and augmented targets); it
|
|
61
|
+
used to compile to the plain name `a`. Write `a`, or `(. a b)` for an
|
|
62
|
+
attribute.
|
|
63
|
+
- A hard keyword called in an expression (`(f (else 1))`) is reported as
|
|
64
|
+
`else is a Python keyword and cannot be used as a name`, at the keyword,
|
|
65
|
+
instead of `else is a Python keyword and cannot be called` at the form.
|
|
66
|
+
- Forms that compiled to something other than what they say are compile
|
|
67
|
+
errors: `(ife a b c d)` (dropped `d`), `(yield a b)`, a comprehension clause
|
|
68
|
+
with a stray or dangling operand (`[x for y in z if]`, `(x for y in z if a
|
|
69
|
+
b)`), and a parameter list CPython refuses: a misplaced
|
|
70
|
+
or repeated `/`, `*` or `**kw` (`[a / /]` dropped `a`, `[*a *b]` made `b`
|
|
71
|
+
keyword-only), a bare `*` with no keyword-only parameter, and `[a :b 1
|
|
72
|
+
c]`, which compiled to `def f(a, b, c=1)`. An annotated lambda parameter (`(lambda [a ^int] 1)`, which `l2py`
|
|
73
|
+
printed as `lambda a: int: 1`) is refused too.
|
|
74
|
+
- Class bases are read as call arguments: `(class A [**kw])` compiles to
|
|
75
|
+
`class A(**kw)`.
|
|
76
|
+
- A statement form (`do`, `=`, `if`, `return`, ...) in expression position is
|
|
77
|
+
now a compile error instead of emitting an invalid call such as `do(...)`.
|
|
78
|
+
A Python function named `do`, `deco`, `async_def`, etc. can no longer be
|
|
79
|
+
called with lispython call syntax in expression position; they were already
|
|
80
|
+
unreachable as statements.
|
|
81
|
+
- A Python hard keyword in call position (e.g. `(not a b)`, which used to
|
|
82
|
+
compile to `not (a, b)`, silently false, or `(else ...)`) is now a compile
|
|
83
|
+
error.
|
|
84
|
+
- Statement forms that compiled while dropping part of what was written are
|
|
85
|
+
now compile errors: `(deco d (f))`, whose definition is not a `def`,
|
|
86
|
+
`async-def`, `class` or `deco` (the decorator was silently dropped);
|
|
87
|
+
`(assert a b c)` (the third operand was ignored); a form other than
|
|
88
|
+
`(case ...)` inside `match` (it was read as a case); and def arguments or
|
|
89
|
+
class bases written as anything but a `[...]` bracket (`(def f (x) ...)`
|
|
90
|
+
compiled). A `case` with `as` after its guard (`(case 1 if g as y ...)`,
|
|
91
|
+
which compiled `as y` into the body) is refused: `as` comes before `if`.
|
|
92
|
+
- A `;` ends a symbol or number, so a comment needs no space before it:
|
|
93
|
+
`a;c` was the symbol `a;c`, now `a` followed by a comment.
|
|
94
|
+
- A Python hard keyword as a symbol name, or as a part of a dotted symbol,
|
|
95
|
+
is a compile error: `(raise from)`, `(= o.if 1)` (which compiled to an
|
|
96
|
+
attribute named `if`), and a keyword as a def, class, global, nonlocal,
|
|
97
|
+
`case ... as` or `except [... as]` name (`(def if [] 1)`,
|
|
98
|
+
`(case 1 as as ...)`).
|
|
99
|
+
- `~` directly before a closing bracket, a comma, `;` or the end of input
|
|
100
|
+
(`(f ~)`, `(f ~,a)`, `~;c`) is the symbol `~`, as before a space, instead
|
|
101
|
+
of an unquote that took the next form or gave "unmatched ')'".
|
|
102
|
+
- A dotted name in a case pattern (`(case Color.RED ...)`) is a value
|
|
103
|
+
pattern, compared with `==` as in Python. It compiled to a capture pattern
|
|
104
|
+
that matched every subject, so code that relied on that now falls through
|
|
105
|
+
to the next case.
|
|
106
|
+
- A `()` in a case pattern whose head is an expression form (`(sub x)`,
|
|
107
|
+
`(ife a b c)`, `(is-not x)`, `(not-in x)`) is a compile error ("pattern
|
|
108
|
+
expected") instead of a class pattern on a class of that name.
|
|
109
|
+
- A sign before anything but a number in a case pattern (`(- a)`, `{-a 1}`)
|
|
110
|
+
reports "pattern can sign only a number literal, got (- a)" with the
|
|
111
|
+
pattern usage, instead of "only a number literal can be signed in a match
|
|
112
|
+
pattern".
|
|
113
|
+
- An f/t-string format spec reads braces as CPython does: `}` always ends
|
|
114
|
+
the spec and `{` opens a nested field, so `{{` and `}}` are no escapes
|
|
115
|
+
there, except that on 3.12+ `{{` is a literal `{` once a nested field has
|
|
116
|
+
opened in the spec. `f"{x:{{}}}"`, which formatted `x` with the literal
|
|
117
|
+
spec `{}`, is now `x` with a spec field holding `{}`, and `f"{x:{y}{{}}}"`,
|
|
118
|
+
whose spec was `{y}` then `{}`, has the spec `{y}` then `{` followed by a
|
|
119
|
+
literal `}` on 3.12+ (a spec field `{}` on 3.11).
|
|
120
|
+
- The operand after `^`, `*`, `**` or `:` keeps its own token type, so `*1`,
|
|
121
|
+
`**1`, `:1` and `^1` read a number `1` instead of a symbol named `1`. A
|
|
122
|
+
keyword with a number or other non-name after it (`(f :1 x)`, `(def g [:1
|
|
123
|
+
2] ...)`) is a compile error at it, a bare `:a` or `**x` used as a value
|
|
124
|
+
(`(= x :a)`, `[**1]`) is a compile error instead of silently the name `a`
|
|
125
|
+
(or a raw `AttributeError`), and a trailing `:a` with no value in a call,
|
|
126
|
+
def or class argument list is a compile error instead of a raw `IndexError`.
|
|
127
|
+
- Threading a literal into a call (`(-> 1 (f))`, `(->> 1 (f 2))`,
|
|
128
|
+
`(-> "a" (f))`, `(-> ... (f))`) compiles to `f(1)`, `f(2, 1)`, `f('a')`
|
|
129
|
+
and `f(...)` instead of failing with an `AttributeError`, and
|
|
130
|
+
`(-> None (f))`, which compiled to `f()`, now gives `f(None)`. A macro may
|
|
131
|
+
put Python values (`None`, booleans, numbers, `...`, str, bytes, and
|
|
132
|
+
lists and tuples of them) anywhere in the forms it returns, including
|
|
133
|
+
inside `*`, `**`, `^` and quote forms (`` `(list *~y) ``); each compiles
|
|
134
|
+
to the same value (including `inf`, `nan`, `-0.0`, complex numbers with a
|
|
135
|
+
signed zero, `inf` or `nan` part and ints of any size) at the macro
|
|
136
|
+
call's position, even where `float` or `complex` is rebound or inside a
|
|
137
|
+
match pattern. Converting nested lists does not recurse, so deep nesting
|
|
138
|
+
does not hit `RecursionError` there. Any other value (a dict, a set, an
|
|
139
|
+
object), or a form or list that contains itself (also one unquoted with
|
|
140
|
+
`~`), is a compile error at the call naming the macro.
|
|
141
|
+
- A str a macro returns keeps its backslashes literally: `"a\\b"` was
|
|
142
|
+
compiled as `"a\b"` (backspace), and a quote, line ending or control
|
|
143
|
+
character is kept as written. The same holds for quoted strings and
|
|
144
|
+
quoted symbols (`'a\b` was `Symbol('a\x08')`); bytes are supported.
|
|
145
|
+
- An assignment, augmented assignment, `del`, `for` or `with ... as` target
|
|
146
|
+
that Python cannot bind (`(= 1 2)`, `(= (f) 1)`, `(+= [] 1)`, `(del *a)`)
|
|
147
|
+
is a compile error at the target with CPython's message ("cannot assign to
|
|
148
|
+
literal"), and so is an annotation anywhere but right after a single
|
|
149
|
+
target (`(= ^int 1)`, `(= a b ^int 1)`) or a def/class name that is not a
|
|
150
|
+
name (`(def [] [] 1)`, `(class 1 [])`).
|
|
151
|
+
- A quote, quasiquote or unquote-splice with no form right after it
|
|
152
|
+
(`(f ' a)`, `(f *' a)`, `(f ~@ a)`, `(f ')`, `',a`) is a compile error at
|
|
153
|
+
the quote instead of a symbol `'`, `f(*', a)`, an unquote of `@` or
|
|
154
|
+
"unmatched ')'". `~` alone is still the invert operator.
|
|
155
|
+
- A malformed `match` case pattern is a compile error at the part at fault,
|
|
156
|
+
quoting the usage of its kind (class, mapping or sequence pattern, `(| ...)`),
|
|
157
|
+
instead of an `IndexError`, `ValueError` or `AttributeError` or an AST
|
|
158
|
+
that means something else: a dangling `:attr` (`(P :x)`), a positional
|
|
159
|
+
pattern after a keyword one (`(P :x 1 2)`, which compiled to `P(2, x=1)`),
|
|
160
|
+
a mapping key without a pattern (`{"a"}`), `**rest` not last (`{**r 1}`),
|
|
161
|
+
`**_` (the rest must be a capture name), a key that is no literal or
|
|
162
|
+
dotted name (`{a 1}`), a second `*rest` (`[*a *b]`), `*rest` outside a
|
|
163
|
+
sequence (`(case *a ...)`), `(| 1)`, a class pattern head that is no name
|
|
164
|
+
(`(1)`, `((f) 1)`), a `()` that is no call (`(+ 1 2)`, `(, a b)`,
|
|
165
|
+
`f"x"`), `...` and a `:x` keyword token as a pattern.
|
|
166
|
+
- A name after `*`, `**` or `:` in a case pattern that is no Python
|
|
167
|
+
identifier (`[*1a]`, `{**1a}`, `(P :1a 2)`) is a compile error instead of
|
|
168
|
+
an AST naming `1a`.
|
|
169
|
+
- A malformed dotted name in a case pattern (`.x`, `a..b`, `x.`), as a value
|
|
170
|
+
pattern, a mapping key or a class pattern head, is a compile error instead
|
|
171
|
+
of an attribute with an empty name.
|
|
172
|
+
- A string opened with a bare quote inside an f/t-string replacement field
|
|
173
|
+
reads as Python reads it: `\"` in it is an escaped quote and its
|
|
174
|
+
backslashes are kept. `f"{(g "a\" b \"c")}"` compiles to
|
|
175
|
+
`f"{g('a" b "c')}"`; it used to turn every `\"` into `"` before parsing
|
|
176
|
+
the field, which split the string into `g('a', b, 'c')` or refused it
|
|
177
|
+
(`f"{(g "say \"hi\"")}"`: "invalid string prefix").
|
|
178
|
+
|
|
179
|
+
### Deprecated
|
|
180
|
+
|
|
181
|
+
- Writing a string in an f/t-string replacement field with `\"`
|
|
182
|
+
(`f"{(.get d \"k\")}"`) is deprecated: each such string emits a
|
|
183
|
+
`SyntaxWarning` at its opening `\"`, shown by default (as CPython
|
|
184
|
+
3.12+'s invalid escape sequences are). Write plain quotes
|
|
185
|
+
(`f"{(.get d "k")}"`), which works on every supported Python. A
|
|
186
|
+
warnings filter that turns it into an error
|
|
187
|
+
(`PYTHONWARNINGS=error::SyntaxWarning`) makes it a compile error at the
|
|
188
|
+
opener.
|
|
189
|
+
|
|
190
|
+
### Fixed
|
|
191
|
+
|
|
192
|
+
- A replacement field whose expression starts with a bare `=` (`f"{=}"`,
|
|
193
|
+
`f"{=x}"`, `f"{=!r}"`, a source ending at `f"{=`) is refused with CPython
|
|
194
|
+
3.12+'s "valid expression required before '='" at the `=`, as compile() does
|
|
195
|
+
there. On 3.11 lispython reports that same 3.12+ message for a closed
|
|
196
|
+
literal, as it does for `f"{}"` (CPython 3.11's own wording differs); a
|
|
197
|
+
source ending inside the literal gets 3.11's whole-literal error. Before, a
|
|
198
|
+
closed literal was refused only by the compile step ("= is not a valid
|
|
199
|
+
Python name") or as a missing conversion (`f"{=!}"`), and on 3.12+ `f"{=`
|
|
200
|
+
gave "'{' was never closed" and `f"{=}` "unterminated f-string literal".
|
|
201
|
+
It is now refused when the source is read, so also inside quoted code.
|
|
202
|
+
- On 3.12+ an error after a line break in a single-quoted f/t-string's text
|
|
203
|
+
(`f"a\n}"`, `f"a\n{x!z}"`, `f"a\n{)`) reports CPython's "unterminated
|
|
204
|
+
f-string literal (detected at line N)" for that literal, as `compile()`
|
|
205
|
+
does, whether or not the literal later closes: CPython's tokenizer stops
|
|
206
|
+
at the break. A multi-line literal with no error still compiles.
|
|
207
|
+
- A comment ends at a lone CR as at LF and CRLF, as in CPython. A CR-only
|
|
208
|
+
source (`; c\r(f 1)`) used to lose every form after a comment.
|
|
209
|
+
- A source ending inside a string, an f/t-string, a replacement field's
|
|
210
|
+
expression, a format spec or a conversion reports the error `compile()`
|
|
211
|
+
raises for that text on the running Python: "unterminated ... literal
|
|
212
|
+
(detected at line N)" at the literal's prefix, 3.12+'s "'(' was never
|
|
213
|
+
closed" (or "'{' was never closed") at the innermost bracket or field
|
|
214
|
+
brace still open,
|
|
215
|
+
"expecting '}'" for a string whose quote closes the
|
|
216
|
+
literal it is in, 3.13+'s "newlines are not allowed in format specifiers",
|
|
217
|
+
3.15's "expecting '}' to close '{' on line N", and 3.11's whole-literal
|
|
218
|
+
error. A name glued to a quote after `!` reads as a string prefix (`!r"`),
|
|
219
|
+
and an invalid conversion name is reported at the name, after any error in
|
|
220
|
+
its field's format spec where CPython checks it only once the field closes.
|
|
221
|
+
- An f/t-string's closing quote inside a format spec (`f"{x:{y}"`) reports
|
|
222
|
+
CPython 3.12+'s "expecting '}', or format specs" at the quote.
|
|
223
|
+
- An empty replacement field ("valid expression required before '}'") is
|
|
224
|
+
reported where CPython 3.12+ report it: at the `}`, `!`, `:` or the `=` of
|
|
225
|
+
`{ = }`, not at the field's `{`. A missing conversion (`f"{x! }"`) is
|
|
226
|
+
reported at the token after the `!`, not at the `!`.
|
|
227
|
+
- An error in an f/t-string's field (an empty expression, more than one
|
|
228
|
+
expression, a missing or invalid conversion, a token out of place after
|
|
229
|
+
`!`) gives way, as in CPython, to a later tokenizer error outside any
|
|
230
|
+
literal, such as a string that never closes: `f"{x!z}" "b` and
|
|
231
|
+
`f"{a b}" "c` report "unterminated string literal" at the last string. On
|
|
232
|
+
3.11 a later f-string that never closes does too. On 3.12+ a closing
|
|
233
|
+
bracket out of place in a field after the error keeps it (`f"{x!r y)}" "b`),
|
|
234
|
+
and one right after a conversion is CPython's "f-string: unmatched ')'".
|
|
235
|
+
- Error positions count a lone CR as a line break, as CPython does (CRLF is
|
|
236
|
+
one break), so a source with CR line endings reports the same line as
|
|
237
|
+
`compile()`.
|
|
238
|
+
- Parsing no longer spends Python frames per nesting level, so these parse
|
|
239
|
+
at any depth and from a deep caller stack instead of raising
|
|
240
|
+
`RecursionError`: f/t-strings nested in each other's fields, format specs
|
|
241
|
+
nested in format specs, brackets nested deep inside one replacement
|
|
242
|
+
field, and long prefix chains on one token
|
|
243
|
+
(`^^^x`, `:::x`, `***x`, `^*^*x`), also glued to a bracket (`^^^a[int]`).
|
|
244
|
+
- Quasiquoting f-strings nested in each other's fields checks each field for
|
|
245
|
+
an unquote once, linear instead of quadratic in the nesting (4,000 levels
|
|
246
|
+
took 15 s), and a deep nest of `(do ...)` statements compiles in linear
|
|
247
|
+
time.
|
|
248
|
+
- Deeply nested forms compile in time linear in their nesting: a nested list
|
|
249
|
+
(`[[[...]]]`, also inside a macro call such as `when`) was exponential (20
|
|
250
|
+
levels took seconds) and a form in head position (`((((f))))`) quadratic.
|
|
251
|
+
Rendering a form and expanding macros no longer recurse, so a form nested
|
|
252
|
+
past Python's recursion limit renders and expands.
|
|
253
|
+
- A long run of digits that is not a number lexes in linear time instead of
|
|
254
|
+
quadratic.
|
|
255
|
+
- Forms nested deeper than about 245–330 levels (case patterns about 490)
|
|
256
|
+
are no longer refused with "form nested too deeply to compile": the
|
|
257
|
+
compiler and quote expansion no longer spend Python frames per level, so
|
|
258
|
+
nesting compiles as deep as CPython's compile() takes (about 10,000
|
|
259
|
+
Python AST levels on 3.12/3.13, more on 3.14+; forms that cost two AST
|
|
260
|
+
levels per lispython level, such as keyword arguments, starred arguments
|
|
261
|
+
and method calls, stop near half that). On 3.11 compile() itself still
|
|
262
|
+
stops near the recursion limit, with the positioned error below. `l2py`
|
|
263
|
+
still stops at a few hundred levels, because `ast.unparse` recurses: that
|
|
264
|
+
is the positioned error "form nested too deeply to translate".
|
|
265
|
+
- Nesting past compile()'s own limit is the positioned "form nested too
|
|
266
|
+
deeply to compile" error from `lpy`, imports, the REPL, nREPL, `defmacro`
|
|
267
|
+
and `require`, instead of a `RecursionError` traceback, at the bottom of the
|
|
268
|
+
deepest path in what compile() was given: for `lpy` files and imports the
|
|
269
|
+
first statement that fails to compile alone (the whole module when each
|
|
270
|
+
fits alone), the whole form for `defmacro` and `require`, and the statement
|
|
271
|
+
compile() fails on for `lpy -c`, the REPL and nREPL. Its message names
|
|
272
|
+
Python's recursion limit only where that is the limit hit (3.11, and
|
|
273
|
+
`l2py`), since raising it does not move compile()'s limit on 3.12+. A
|
|
274
|
+
macro's own runaway recursion still raises `RecursionError`.
|
|
275
|
+
- Data a macro puts in its expansion, spliced with `~x` or placed in a
|
|
276
|
+
form's list, that holds the same Python list or tuple in many places is
|
|
277
|
+
converted to nodes in time linear in the distinct data, instead of
|
|
278
|
+
exponential in the depth of the sharing. Compiling such data still emits
|
|
279
|
+
each shared value where it appears.
|
|
280
|
+
- A malformed expression form is a positioned compile error quoting its
|
|
281
|
+
usage instead of an IndexError, ValueError or AssertionError, or an AST
|
|
282
|
+
CPython rejects: `(:=)`, `(:= a ^int 1)`, `(.)`, `(.foo)`, `(ife a)`,
|
|
283
|
+
`(sub)`, `(lambda [a])`, `(await)`, `(yield-from)`, `(and)`, `(< a)`,
|
|
284
|
+
`[:]`, `{1 2 3}` and `(x for y)`. Hover and signature help for these forms
|
|
285
|
+
show the same usage line.
|
|
286
|
+
- `(= (sub a) ^int 1)` records `a` in `__annotations__`, as `(= a ^int 1)`
|
|
287
|
+
does.
|
|
288
|
+
- `~x` or `~@x` outside a quasiquote is a compile error at it (`~ (unquote)
|
|
289
|
+
is only allowed inside a quasiquote`) instead of a raw `ValueError`.
|
|
290
|
+
- A comprehension target that cannot be assigned (`[x for 1 in xs]`) and a
|
|
291
|
+
walrus target that is not a name (`(:= a.b 1)`, `(:= 1 2)`) are compile
|
|
292
|
+
errors with CPython's messages (`cannot assign to literal`, `cannot use
|
|
293
|
+
assignment expressions with attribute`) at the target, instead of a raw
|
|
294
|
+
`ValueError` or `TypeError`.
|
|
295
|
+
- `(case pattern as _ ...)` is a compile error at the `_` (`cannot use '_'
|
|
296
|
+
as a target`, as in CPython) instead of a raw `ValueError`.
|
|
297
|
+
- A dotted `defmacro` name (`(defmacro a.b ...)`) is refused as
|
|
298
|
+
`(defmacro ...) expects an undotted name, got a.b`, at the name, instead
|
|
299
|
+
of an error about the internal `___defmacro_temp___a.b` def.
|
|
300
|
+
- LSP hover and signature help for a statement form show the same usage
|
|
301
|
+
line as the compiler's error messages (`(def name [args] body...)`,
|
|
302
|
+
`(for target in iterable body...)`), and now cover the `except`,
|
|
303
|
+
`except*`, `else`, `finally` and `case` clauses, augmented assignment
|
|
304
|
+
(`+=` and friends), and `|` as an or-pattern. For `|`, hover and signature
|
|
305
|
+
help show both the bitwise-OR operator and the or-pattern usage.
|
|
306
|
+
Completion now also lists the augmented assignment operators and the
|
|
307
|
+
`except`, `else` and `finally` clauses as special forms (`except*` and
|
|
308
|
+
`case` were already offered as keywords).
|
|
309
|
+
Hover text for `defmacro`, `lambda` and `fn` says `[args]`, matching `def`.
|
|
310
|
+
- A complex number a macro returns shows in macroexpand output as `1+2j`
|
|
311
|
+
instead of `(1+2j)`, which read back as a call, not a number. A negative
|
|
312
|
+
nan (alone or as a complex part) shows as `(- (float "nan"))` instead of
|
|
313
|
+
losing its sign.
|
|
314
|
+
- A macro whose expansion puts Python data in a form's list compiles when
|
|
315
|
+
another attribute also holds that list, and when the form's list is a
|
|
316
|
+
tuple or deque, instead of failing with `AttributeError: 'int' object has
|
|
317
|
+
no attribute 'name'`. A form's list of any other type (a dict, a set) is a
|
|
318
|
+
compile error at the macro call naming the macro.
|
|
319
|
+
- Code a macro builds (from a quasiquote template) takes the position of the
|
|
320
|
+
macro call, so a traceback or compile error in it points at that call
|
|
321
|
+
instead of line 0; arguments written by the user keep their own positions.
|
|
322
|
+
- nREPL eval tracebacks no longer include the server's own `nrepl-eval`
|
|
323
|
+
frame.
|
|
324
|
+
- The nREPL server buffers bytes and decodes each complete line, so a
|
|
325
|
+
multi-byte UTF-8 character split across two socket reads no longer drops
|
|
326
|
+
the connection. An unknown op, a malformed request or a failing handler
|
|
327
|
+
(such as `eval` without `code`) now gets an `error` reply echoing the
|
|
328
|
+
request id, and the connection keeps serving; a line that is not valid
|
|
329
|
+
UTF-8 or JSON (or JSON that is not an object, or is nested too deeply to
|
|
330
|
+
parse) gets an id-less `error` reply. A result that cannot be
|
|
331
|
+
serialised is an `error` reply too, `(exit)` in an eval is reported as a
|
|
332
|
+
`SystemExit` eval error, and any socket error ends the connection quietly.
|
|
333
|
+
A request line over 4 MiB gets one id-less `error` reply and is dropped up
|
|
334
|
+
to its next newline, so a client that never sends a newline cannot grow the
|
|
335
|
+
server's memory; the connection keeps serving.
|
|
336
|
+
- `as->` expands to an expression (a tuple of `:=` assignments indexed with
|
|
337
|
+
`-1`), so it works in expression position such as a `return` value or a
|
|
338
|
+
call argument instead of emitting an invalid `do(...)` call.
|
|
339
|
+
- `as->` renames only references to the threading name: a keyword-argument
|
|
340
|
+
name (`(f :x x)` was `f(__x_0=__x_0)`, now `f(x=__x_0)`) and the params
|
|
341
|
+
and body of a `fn`/`lambda` that binds the same name are left alone (its
|
|
342
|
+
defaults are still renamed), and `~x` inside a quasiquote now gets the
|
|
343
|
+
threaded value.
|
|
344
|
+
- `as->` also renames a dotted reference (`x.real` becomes `__x_0.real`) and
|
|
345
|
+
a reference inside an f-string format spec (`f"{1:{x}}"`).
|
|
346
|
+
- A `for` or `while` with no target, iterable or test, or with no body
|
|
347
|
+
before its `(else ...)` (`(for x in)`, `(for [x [1]] (print x))`,
|
|
348
|
+
`(while x)`), is a compile error at the loop instead of an `IndexError`
|
|
349
|
+
or a loop with an empty body that Python rejects.
|
|
350
|
+
- `^` followed by stars before a bracket or a quote (`^*(`, `^**[`, `^*'`,
|
|
351
|
+
`^**~`, also with a space after the quote) or alone (`^*`, `^**`) is a
|
|
352
|
+
compile error at the prefix instead of a `KeyError`, an `AttributeError`
|
|
353
|
+
or a call such as `f(*', a)`; `^*Ts` and `^**P` are unchanged.
|
|
354
|
+
- Every malformed statement form is a compile error naming the problem and
|
|
355
|
+
the form's usage, at the form, clause or operand at fault, instead of a
|
|
356
|
+
`ValueError`, `IndexError`, `AssertionError`, `TypeError` or an AST that
|
|
357
|
+
Python rejects: `if`, `def`, `class`, `with`, `try` and its clauses,
|
|
358
|
+
`match` and `case`, `deco`, `raise`, `assert`, `=`, augmented assignment,
|
|
359
|
+
`del`, `global` and `nonlocal` with missing parts (`(if x)`, `(def f)`,
|
|
360
|
+
`(with [a])`, `(try (f))`, `(match x (case 1))`, `(= x)`, `(global)`, ...),
|
|
361
|
+
extra parts (`(raise a b)`, `(= x ^int 1 2)`) or a dangling `as`
|
|
362
|
+
(`(with [a as] ...)`, `(except [E as] ...)`). A `try` clause out of order
|
|
363
|
+
(except after else or finally, except mixed with except*, a form after
|
|
364
|
+
the clauses) is reported at that clause instead of as "except is a Python
|
|
365
|
+
keyword".
|
|
366
|
+
- An empty `()` is a compile error at it ("use (,) for an empty tuple")
|
|
367
|
+
instead of an `IndexError`, wherever it appears.
|
|
368
|
+
- Installing lispython no longer puts a `test` command (pytest's entry point)
|
|
369
|
+
on `PATH`.
|
|
370
|
+
|
|
371
|
+
## [0.5.0]
|
|
372
|
+
|
|
373
|
+
### Added
|
|
374
|
+
|
|
375
|
+
- Python 3.11 through 3.15 support, with each newer AST form gated on the
|
|
376
|
+
running interpreter and a clear compile error on older ones. A `nox` matrix
|
|
377
|
+
(`uvx nox -s tests`) runs the suite on every supported version.
|
|
378
|
+
- Type aliases: `(type NAME value)` compiles to a `type NAME = value`
|
|
379
|
+
statement (3.12+). The three-operand `(type name bases ns)` call is unchanged.
|
|
380
|
+
- Generic definitions: `def`, `async def` and `class` names accept glued type
|
|
381
|
+
parameters such as `f[T]`, including `ParamSpec`, `TypeVarTuple` and
|
|
382
|
+
defaults (parameters 3.12+, defaults 3.13+).
|
|
383
|
+
- t-strings compile to `TemplateStr` (3.14+).
|
|
384
|
+
- `lazy-import` and `lazy-from` (3.15+).
|
|
385
|
+
- Comprehension unpacking (3.15+), including `{**d for d dicts}`, which was
|
|
386
|
+
mistranslated as a dict display; unary plus is gated to 3.15+.
|
|
387
|
+
- Errors the CPython compiler raises on `.lpy` modules, and lispython's own
|
|
388
|
+
compile errors, are reported with the file, line and caret instead of a
|
|
389
|
+
traceback by `lpy` (file, `-m`, `-c`), `l2py`, the REPL and nREPL eval.
|
|
390
|
+
- LSP: `textDocument/signatureHelp` and `workspace/symbol`; `(type NAME ...)`
|
|
391
|
+
aliases are indexed as definitions.
|
|
392
|
+
- nREPL: completion responses carry type kinds, and the server writes
|
|
393
|
+
`.nrepl-port` in the working directory for editor auto-discovery.
|
|
394
|
+
|
|
395
|
+
### Changed
|
|
396
|
+
|
|
397
|
+
- **The REPL and nREPL compile every statement of an input before running any
|
|
398
|
+
of it**, and `lpy -c` compiles its whole input first. A compile error,
|
|
399
|
+
lispython's or CPython's, now stops the input before any statement runs
|
|
400
|
+
(macro expansion still happens first); a `SyntaxError` the program raises
|
|
401
|
+
at run time keeps its own traceback.
|
|
402
|
+
- **Stricter parsing** rejects input that used to be accepted silently: an
|
|
403
|
+
unclosed final form or mismatched closing bracket, an unterminated string,
|
|
404
|
+
an f-string field holding more than one form, a lone `}` in an f-string,
|
|
405
|
+
non-symbol macro names (`(defmacro m[T] ...)`), `(import "x")`,
|
|
406
|
+
`(lazy-import "numpy")`, `(return a b)`, and `name[...]` glued under a
|
|
407
|
+
prefix such as `^list[int]` in a parameter list. Malformed literals such as
|
|
408
|
+
`09` or `\x4` are reported at their token.
|
|
409
|
+
- **Literal values**: `-1+2j` is now `(-1+2j)` rather than `-(1+2j)`; `\r\n`
|
|
410
|
+
and lone `\r` become `\n` in f-strings too; a multi-line raw string no
|
|
411
|
+
longer gains a literal `\n`; backslash-newline continues a string; a lone
|
|
412
|
+
`"` inside `"""..."""` no longer ends the token.
|
|
413
|
+
- **F-strings follow Python's rules.** An uppercase `F` prefix (and `rf`,
|
|
414
|
+
`fR`, ...) is a real f-string rather than being evaluated at compile time,
|
|
415
|
+
`{x=}` echoes the expression text and defaults to `!r`, and format specs
|
|
416
|
+
may nest replacement fields (`{x:>{w}.{p}}`). Unquotes expand inside
|
|
417
|
+
f-string and t-string fields.
|
|
418
|
+
- **`(require mod [])` registers no macros**, like an empty import list; only
|
|
419
|
+
an absent list (`(require mod)`) registers all of them. The LSP follows the
|
|
420
|
+
same rule in go-to-definition and hover.
|
|
421
|
+
- `lpy -m` puts the current directory on `sys.path` like `python -m`,
|
|
422
|
+
honouring `PYTHONSAFEPATH`.
|
|
423
|
+
- Requires `pygls` 2.0 or later.
|
|
424
|
+
|
|
425
|
+
### Fixed
|
|
426
|
+
|
|
427
|
+
- `(return [])` compiled to a bare `return` (yielding `None`); it now returns
|
|
428
|
+
the empty list.
|
|
429
|
+
- `(case {})` accepts an empty mapping pattern.
|
|
430
|
+
- Signed literal match patterns: `(case -1 ...)` compiled to `case _(1):`.
|
|
431
|
+
- `(from . [a])` compiles; it raised `IndexError`.
|
|
432
|
+
- Quasiquoted f/t-string fields keep their conversion, spec and `=` text
|
|
433
|
+
(`f"{x!r:>6}"` used to come back as `{x}`), and `f"{~x}"` with a literal
|
|
434
|
+
macro argument no longer crashes.
|
|
435
|
+
- `l2py` emits valid Python for t-string interpolations.
|
|
436
|
+
- A lone starred subscript slice compiles to a one-element `Tuple`, like
|
|
437
|
+
Python's `t[*s]`.
|
|
438
|
+
- LSP: workspace symbols no longer include installed packages or function
|
|
439
|
+
locals, and report their real kinds; a buffer that is mid-edit keeps
|
|
440
|
+
working; signature help is not offered inside a quoted form.
|
|
441
|
+
- nREPL: completion kinds treat scope bindings as shadowing builtins, never
|
|
442
|
+
run user code to classify attributes, and report callables as functions;
|
|
443
|
+
an unwritable `.nrepl-port` no longer stops the server.
|
|
444
|
+
|
|
445
|
+
## [0.4.7]
|
|
446
|
+
|
|
447
|
+
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.6.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'
|
|
@@ -54,6 +61,8 @@ uv sync # install dependencies
|
|
|
54
61
|
uv pip install -e . # for development
|
|
55
62
|
```
|
|
56
63
|
|
|
64
|
+
Contributors using git worktrees: run `git config core.hooksPath .githooks` once in the main checkout; see [`.githooks/README.md`](.githooks/README.md). This replaces `.git/hooks`, so hooks installed there stop running.
|
|
65
|
+
|
|
57
66
|
## How to Run LisPython code
|
|
58
67
|
### Run from source
|
|
59
68
|
```bash
|
|
@@ -87,7 +96,8 @@ LisPython ships with a language server (`lpy-lsp`) that speaks LSP over stdio. I
|
|
|
87
96
|
- Diagnostics (parse / compile errors)
|
|
88
97
|
- Completions (special forms, builtins, file/workspace symbols)
|
|
89
98
|
- Hover documentation for special forms and builtins
|
|
90
|
-
-
|
|
99
|
+
- Signature help for special forms and builtins
|
|
100
|
+
- Document symbols and workspace symbol search
|
|
91
101
|
- Go-to-definition, including across `.lpy` files in the workspace
|
|
92
102
|
|
|
93
103
|
## nREPL Server
|
|
@@ -98,11 +108,13 @@ lpy --nrepl # start on a random port
|
|
|
98
108
|
lpy --nrepl 7888 # start on a specific port
|
|
99
109
|
```
|
|
100
110
|
|
|
111
|
+
While running, the server writes its port to `.nrepl-port` in the working directory for editor auto-discovery.
|
|
112
|
+
|
|
101
113
|
The server accepts newline-delimited JSON over TCP and supports:
|
|
102
114
|
- `eval` — evaluate LisPython code, return value/stdout/error
|
|
103
115
|
- `load-file` — load a `.lpy` file into the session
|
|
104
116
|
- `macroexpand` — expand macros and return the result
|
|
105
|
-
- `complete` — prefix and dot-completion from the live scope
|
|
117
|
+
- `complete` — prefix and dot-completion from the live scope, with a `kinds` map (class/function/variable/macro/keyword)
|
|
106
118
|
- `docs` — signature and docstring for a symbol
|
|
107
119
|
- `annotate` — type tag (function/class/module/macro)
|
|
108
120
|
|
|
@@ -113,8 +125,3 @@ Install the [LisPython](https://marketplace.visualstudio.com/items?itemName=jeta
|
|
|
113
125
|
|
|
114
126
|
#### Emacs
|
|
115
127
|
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+
|
|
@@ -31,6 +31,8 @@ uv sync # install dependencies
|
|
|
31
31
|
uv pip install -e . # for development
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
Contributors using git worktrees: run `git config core.hooksPath .githooks` once in the main checkout; see [`.githooks/README.md`](.githooks/README.md). This replaces `.git/hooks`, so hooks installed there stop running.
|
|
35
|
+
|
|
34
36
|
## How to Run LisPython code
|
|
35
37
|
### Run from source
|
|
36
38
|
```bash
|
|
@@ -64,7 +66,8 @@ LisPython ships with a language server (`lpy-lsp`) that speaks LSP over stdio. I
|
|
|
64
66
|
- Diagnostics (parse / compile errors)
|
|
65
67
|
- Completions (special forms, builtins, file/workspace symbols)
|
|
66
68
|
- Hover documentation for special forms and builtins
|
|
67
|
-
-
|
|
69
|
+
- Signature help for special forms and builtins
|
|
70
|
+
- Document symbols and workspace symbol search
|
|
68
71
|
- Go-to-definition, including across `.lpy` files in the workspace
|
|
69
72
|
|
|
70
73
|
## nREPL Server
|
|
@@ -75,11 +78,13 @@ lpy --nrepl # start on a random port
|
|
|
75
78
|
lpy --nrepl 7888 # start on a specific port
|
|
76
79
|
```
|
|
77
80
|
|
|
81
|
+
While running, the server writes its port to `.nrepl-port` in the working directory for editor auto-discovery.
|
|
82
|
+
|
|
78
83
|
The server accepts newline-delimited JSON over TCP and supports:
|
|
79
84
|
- `eval` — evaluate LisPython code, return value/stdout/error
|
|
80
85
|
- `load-file` — load a `.lpy` file into the session
|
|
81
86
|
- `macroexpand` — expand macros and return the result
|
|
82
|
-
- `complete` — prefix and dot-completion from the live scope
|
|
87
|
+
- `complete` — prefix and dot-completion from the live scope, with a `kinds` map (class/function/variable/macro/keyword)
|
|
83
88
|
- `docs` — signature and docstring for a symbol
|
|
84
89
|
- `annotate` — type tag (function/class/module/macro)
|
|
85
90
|
|
|
@@ -90,8 +95,3 @@ Install the [LisPython](https://marketplace.visualstudio.com/items?itemName=jeta
|
|
|
90
95
|
|
|
91
96
|
#### Emacs
|
|
92
97
|
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+
|
|
@@ -65,14 +65,15 @@ Unlike `->` (thread first) and `->>` (thread last), `as->` lets you name the thr
|
|
|
65
65
|
(str x)) ;; x = "20"
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
This expands to
|
|
68
|
+
This expands to a single expression, a tuple of walrus assignments indexed with `-1`:
|
|
69
69
|
```python
|
|
70
|
-
|
|
71
|
-
x = x + 10
|
|
72
|
-
x = 2 * x
|
|
73
|
-
x = str(x)
|
|
70
|
+
(__x_0 := 0, __x_0 := __x_0 + 10, __x_0 := 2 * __x_0, str(__x_0))[-1]
|
|
74
71
|
```
|
|
75
72
|
|
|
73
|
+
Because it is an expression, `as->` works anywhere: as a return value, a call argument, a lambda body, or nested. CPython refuses `:=` in some positions (e.g. a comprehension's iterable, annotations, type-alias values); there it is a loud compile error, so bind the value first.
|
|
74
|
+
|
|
75
|
+
The name is replaced only where it is read as a variable, not in keyword-argument names, quoted data, or the body of a `fn`/`lambda` that rebinds it.
|
|
76
|
+
|
|
76
77
|
Useful when the threaded value doesn't always go in the first or last position.
|
|
77
78
|
|
|
78
79
|
## gensym
|