lispython 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lispython-0.3.0 → lispython-0.3.2}/PKG-INFO +6 -3
- {lispython-0.3.0 → lispython-0.3.2}/README.md +5 -2
- {lispython-0.3.0 → lispython-0.3.2}/pyproject.toml +1 -1
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/compiler/expr.py +8 -13
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/macro.py +10 -6
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/meta_functions.py +15 -9
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/parser.py +54 -5
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core_meta_functions.lpy +8 -4
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/macros/sugar.lpy +2 -2
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/tools.lpy +24 -12
- lispython-0.3.2/tests/test_include_meta.py +37 -0
- {lispython-0.3.0 → lispython-0.3.2}/tests/test_literal.py +15 -0
- lispython-0.3.2/tests/test_meta_functions.py +26 -0
- {lispython-0.3.0 → lispython-0.3.2}/uv.lock +1 -1
- {lispython-0.3.0 → lispython-0.3.2}/.claude/settings.local.json +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/.gitignore +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/.pre-commit-config.yaml +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/LICENSE.md +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/docs/index.md +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/docs/macros.md +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/docs/syntax/expressions.md +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/docs/syntax/overview.md +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/docs/syntax/statements.md +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/docs/usage/cli.md +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/docs/usage/getting-started.md +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/docs/version_macro.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/docs/why-lispy.md +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/mkdocs.yml +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/__init__.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/compiler/__init__.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/compiler/literal.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/compiler/stmt.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/compiler/utils.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/importer.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/nodes.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/core/utils.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/macros/__init__.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/src/lispy/macros/init.lpy +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/tests/__init__.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/tests/test_expr.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/tests/test_parser.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/tests/test_stmt.py +0 -0
- {lispython-0.3.0 → lispython-0.3.2}/tests/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lispython
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -67,9 +67,12 @@ lpy -m unittest
|
|
|
67
67
|
# Todo
|
|
68
68
|
## Environment
|
|
69
69
|
- [ ] Test on more python versions
|
|
70
|
-
- [ ]
|
|
70
|
+
- [ ] REPL should track history and arrow key navigation
|
|
71
|
+
- [ ] REPL multi-line input support
|
|
72
|
+
- [ ] Better compileation error messages
|
|
71
73
|
## Macro System
|
|
72
74
|
- [ ] `as->` macro for syntactic sugar
|
|
73
75
|
- [ ] `gensym` for avoiding name collision
|
|
74
76
|
## Python AST
|
|
75
|
-
- [ ] `type_comment` never considered. Later, it should be covered
|
|
77
|
+
- [ ] `type_comment` never considered. Later, it should be covered
|
|
78
|
+
- [ ] Any missing AST nodes in the version 3.12+
|
|
@@ -46,9 +46,12 @@ lpy -m unittest
|
|
|
46
46
|
# Todo
|
|
47
47
|
## Environment
|
|
48
48
|
- [ ] Test on more python versions
|
|
49
|
-
- [ ]
|
|
49
|
+
- [ ] REPL should track history and arrow key navigation
|
|
50
|
+
- [ ] REPL multi-line input support
|
|
51
|
+
- [ ] Better compileation error messages
|
|
50
52
|
## Macro System
|
|
51
53
|
- [ ] `as->` macro for syntactic sugar
|
|
52
54
|
- [ ] `gensym` for avoiding name collision
|
|
53
55
|
## Python AST
|
|
54
|
-
- [ ] `type_comment` never considered. Later, it should be covered
|
|
56
|
+
- [ ] `type_comment` never considered. Later, it should be covered
|
|
57
|
+
- [ ] Any missing AST nodes in the version 3.12+
|
|
@@ -359,20 +359,15 @@ def paren_compiler(sexp, ctx):
|
|
|
359
359
|
|
|
360
360
|
def slice_compile(sexp):
|
|
361
361
|
[_, *args] = sexp.list
|
|
362
|
-
|
|
362
|
+
assert 2 <= len(args) <= 3 # temporarily block length 1 slice for breaking change alert
|
|
363
|
+
[lower, upper, step] = args if len(args) == 3 else args + ["_"] if len(args) == 2 else ["_"] + args + ["_"]
|
|
363
364
|
args_dict = {}
|
|
364
|
-
if
|
|
365
|
-
lower =
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if
|
|
369
|
-
|
|
370
|
-
if upper != "None" and upper != "_":
|
|
371
|
-
args_dict["upper"] = expr_compile(upper)
|
|
372
|
-
if args:
|
|
373
|
-
step = args.popleft()
|
|
374
|
-
if step != "None" and step != "_":
|
|
375
|
-
args_dict["step"] = expr_compile(step)
|
|
365
|
+
if lower != "None" and lower != "_":
|
|
366
|
+
args_dict["lower"] = expr_compile(lower)
|
|
367
|
+
if upper != "None" and upper != "_":
|
|
368
|
+
args_dict["upper"] = expr_compile(upper)
|
|
369
|
+
if step != "None" and step != "_":
|
|
370
|
+
args_dict["step"] = expr_compile(step)
|
|
376
371
|
return ast.Slice(**args_dict, **sexp.position_info)
|
|
377
372
|
|
|
378
373
|
|
|
@@ -13,7 +13,7 @@ def define_macro(sexp, scope, include_meta=True):
|
|
|
13
13
|
transformed = defmacro_transform(sexp)
|
|
14
14
|
eval(
|
|
15
15
|
compile(
|
|
16
|
-
ast.Interactive(body=macroexpand_then_compile([transformed])),
|
|
16
|
+
ast.Interactive(body=macroexpand_then_compile([transformed], include_meta=include_meta)),
|
|
17
17
|
"macro-defining",
|
|
18
18
|
"single",
|
|
19
19
|
),
|
|
@@ -26,7 +26,7 @@ def require_macro(sexp, scope, include_meta=True):
|
|
|
26
26
|
transformed = require_transform(sexp)
|
|
27
27
|
eval(
|
|
28
28
|
compile(
|
|
29
|
-
ast.Interactive(body=macroexpand_then_compile([transformed])),
|
|
29
|
+
ast.Interactive(body=macroexpand_then_compile([transformed], include_meta=include_meta)),
|
|
30
30
|
"macro-requiring",
|
|
31
31
|
"single",
|
|
32
32
|
),
|
|
@@ -49,14 +49,18 @@ def macroexpand_1(sexp, scope=globals()):
|
|
|
49
49
|
def macroexpand_1_and_check(sexp, scope=globals(), in_quasi=False, include_meta=True):
|
|
50
50
|
expanded = False
|
|
51
51
|
if isinstance(sexp, QuasiQuote):
|
|
52
|
-
sexp.value, expanded = macroexpand_1_and_check(
|
|
52
|
+
sexp.value, expanded = macroexpand_1_and_check(
|
|
53
|
+
sexp.value, scope, in_quasi=True, include_meta=include_meta
|
|
54
|
+
)
|
|
53
55
|
elif isinstance(sexp, Quote):
|
|
54
56
|
pass
|
|
55
57
|
elif isinstance(sexp, Unquote):
|
|
56
|
-
sexp.value, expanded = macroexpand_1_and_check(
|
|
58
|
+
sexp.value, expanded = macroexpand_1_and_check(
|
|
59
|
+
sexp.value, scope, include_meta=include_meta
|
|
60
|
+
)
|
|
57
61
|
elif isinstance(sexp, Wrapper):
|
|
58
62
|
sexp.value, expanded = macroexpand_1_and_check(
|
|
59
|
-
sexp.value, scope, in_quasi=in_quasi
|
|
63
|
+
sexp.value, scope, in_quasi=in_quasi, include_meta=include_meta
|
|
60
64
|
)
|
|
61
65
|
elif isinstance(sexp, Expression) and len(sexp) > 0:
|
|
62
66
|
[op, *operands] = sexp.list
|
|
@@ -70,7 +74,7 @@ def macroexpand_1_and_check(sexp, scope=globals(), in_quasi=False, include_meta=
|
|
|
70
74
|
else:
|
|
71
75
|
expanded_list, expanded_feedbacks = zip(
|
|
72
76
|
*map(
|
|
73
|
-
lambda x: macroexpand_1_and_check(x, scope, in_quasi=in_quasi),
|
|
77
|
+
lambda x: macroexpand_1_and_check(x, scope, in_quasi=in_quasi, include_meta=include_meta),
|
|
74
78
|
sexp.list,
|
|
75
79
|
)
|
|
76
80
|
)
|
|
@@ -34,6 +34,18 @@ def require_transform(sexp):
|
|
|
34
34
|
[macro_names] = optional
|
|
35
35
|
else:
|
|
36
36
|
macro_names = None
|
|
37
|
+
module_name_str = str(module_name).replace("-", "_")
|
|
38
|
+
is_relative = module_name_str.startswith(".")
|
|
39
|
+
import_call = (
|
|
40
|
+
Paren(
|
|
41
|
+
Symbol("importlib.import-module"),
|
|
42
|
+
module_name_str,
|
|
43
|
+
Keyword(Symbol("package")),
|
|
44
|
+
Symbol("__package__"),
|
|
45
|
+
)
|
|
46
|
+
if is_relative
|
|
47
|
+
else Paren(Symbol("importlib.import-module"), module_name_str)
|
|
48
|
+
)
|
|
37
49
|
return Paren(
|
|
38
50
|
Symbol("do"),
|
|
39
51
|
Paren(Symbol("import"), Symbol("importlib")),
|
|
@@ -41,13 +53,7 @@ def require_transform(sexp):
|
|
|
41
53
|
Symbol("="),
|
|
42
54
|
Symbol("___imported-macros"),
|
|
43
55
|
Paren(
|
|
44
|
-
Symbol("getattr"),
|
|
45
|
-
Paren(
|
|
46
|
-
Symbol("importlib.import-module"),
|
|
47
|
-
str(module_name).replace("-", "_"),
|
|
48
|
-
),
|
|
49
|
-
String('"__macro_namespace"'),
|
|
50
|
-
Brace(),
|
|
56
|
+
Symbol("getattr"), import_call, String('"__macro_namespace"'), Brace()
|
|
51
57
|
),
|
|
52
58
|
),
|
|
53
59
|
Paren(
|
|
@@ -111,11 +117,11 @@ def require_transform(sexp):
|
|
|
111
117
|
if __name__ == "__main__":
|
|
112
118
|
import os.path as osp
|
|
113
119
|
|
|
114
|
-
from lispy.tools import
|
|
120
|
+
from lispy.tools import l2py_s
|
|
115
121
|
|
|
116
122
|
path = osp.abspath(__file__)
|
|
117
123
|
with open(path, "r") as f:
|
|
118
124
|
org = f.read()
|
|
119
|
-
translated =
|
|
125
|
+
translated = l2py_s(org, no_lispy=True)
|
|
120
126
|
with open(osp.join(osp.dirname(path), "core", "meta_functions.py"), "w") as f:
|
|
121
127
|
f.write(translated)
|
|
@@ -7,8 +7,12 @@ from lispy.core.utils import augassignop_dict
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def tokenize(src):
|
|
10
|
-
# Remove commas except those following { or ( with optional whitespace
|
|
11
|
-
src = re.sub(
|
|
10
|
+
# Remove commas except those following { or ( with optional whitespace, but preserve commas in strings
|
|
11
|
+
src = re.sub(
|
|
12
|
+
r'(\w*"""(?:[^\\"]|\\.)*"""|\w*\'\'\'(?:[^\\\']|\\.)*\'\'\'|\w*"(?:[^\\"]|\\.)*"|[{(]\s*,)|,',
|
|
13
|
+
lambda m: m.group(1) if m.group(1) else " ",
|
|
14
|
+
src,
|
|
15
|
+
)
|
|
12
16
|
lines = src.split("\n")
|
|
13
17
|
tokens = deque([])
|
|
14
18
|
int_simple = r"\d+[\dA-Za-z]*(?:_[\dA-Za-z]+)*"
|
|
@@ -22,9 +26,17 @@ def tokenize(src):
|
|
|
22
26
|
)
|
|
23
27
|
number_simple = "|".join(scientific_simples + float_simples + [int_simple])
|
|
24
28
|
complex_simple = f"(?:(?:{number_simple})[+-])?(?:{number_simple})j"
|
|
29
|
+
# F-string pattern with nested quotes in braces: f"...{expr "str"}..."
|
|
30
|
+
inner_str = r'\"(?:[^\"\\]|\\.)*\"'
|
|
31
|
+
brace_content = r'(?:[^{}\"\\]|\\.|\{[^{}]*\}|' + inner_str + r')*'
|
|
32
|
+
brace_expr = r'\{' + brace_content + r'\}'
|
|
33
|
+
f_string_content = r'(?:[^\"{}\\]|\\.|' + brace_expr + r')*'
|
|
34
|
+
f_string_pattern = r'f\"' + f_string_content + r'\"'
|
|
25
35
|
pattern_labels = [
|
|
26
36
|
["\\^?\\*{0,2}[\\(\\{\\[]", "opening"],
|
|
27
37
|
["[\\)\\}\\]]", "closing"],
|
|
38
|
+
# F-string pattern must come before regular string patterns
|
|
39
|
+
[f_string_pattern, '"'],
|
|
28
40
|
*list(
|
|
29
41
|
zip(
|
|
30
42
|
map(
|
|
@@ -256,15 +268,52 @@ def string_parse(token, tktype, position_info):
|
|
|
256
268
|
conversion_dict = {"!s": 115, "!r": 114, "!a": 97}
|
|
257
269
|
|
|
258
270
|
|
|
271
|
+
def find_format_spec_separator(piece):
|
|
272
|
+
"""Find the index of ':' that separates expression from format spec.
|
|
273
|
+
Only matches ':' at top level (not inside strings or brackets)."""
|
|
274
|
+
depth = 0
|
|
275
|
+
in_string = None
|
|
276
|
+
i = 0
|
|
277
|
+
while i < len(piece):
|
|
278
|
+
char = piece[i]
|
|
279
|
+
if in_string:
|
|
280
|
+
if char == '\\' and i + 1 < len(piece):
|
|
281
|
+
i += 2
|
|
282
|
+
continue
|
|
283
|
+
str_len = len(in_string)
|
|
284
|
+
if piece[i:i+str_len] == in_string:
|
|
285
|
+
in_string = None
|
|
286
|
+
i += str_len
|
|
287
|
+
continue
|
|
288
|
+
else:
|
|
289
|
+
if piece[i:i+3] in ('"""', "'''"):
|
|
290
|
+
in_string = piece[i:i+3]
|
|
291
|
+
i += 3
|
|
292
|
+
continue
|
|
293
|
+
elif char in ('"', "'"):
|
|
294
|
+
in_string = char
|
|
295
|
+
elif char in '([{':
|
|
296
|
+
depth += 1
|
|
297
|
+
elif char in ')]}':
|
|
298
|
+
depth -= 1
|
|
299
|
+
elif char == ':' and depth == 0:
|
|
300
|
+
return i
|
|
301
|
+
i += 1
|
|
302
|
+
return -1
|
|
303
|
+
|
|
304
|
+
|
|
259
305
|
def f_string_parse(prefix, content, tktype, position_info):
|
|
260
306
|
splitted = re.split("[\\{\\}]", content)
|
|
261
307
|
processed = []
|
|
262
308
|
splitted.pop() if splitted[-1] == "" else None
|
|
263
309
|
for [i, piece] in enumerate(splitted):
|
|
264
310
|
if i % 2:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
311
|
+
# Unescape \" to " (backslash-quote is escape for quotes inside f-string braces)
|
|
312
|
+
piece = piece.replace('\\"', '"')
|
|
313
|
+
sep_idx = find_format_spec_separator(piece)
|
|
314
|
+
if sep_idx >= 0:
|
|
315
|
+
format_spec = piece[sep_idx + 1:]
|
|
316
|
+
piece = piece[:sep_idx]
|
|
268
317
|
else:
|
|
269
318
|
format_spec = None
|
|
270
319
|
if piece[-2:] in conversion_dict:
|
|
@@ -21,11 +21,15 @@
|
|
|
21
21
|
(if optional
|
|
22
22
|
(= [macro-names] optional)
|
|
23
23
|
(= macro-names None))
|
|
24
|
+
(= module-name-str (.replace (str module-name) "-" "_"))
|
|
25
|
+
(= is-relative (.startswith module-name-str "."))
|
|
26
|
+
(= import-call (ife is-relative
|
|
27
|
+
`(importlib.import-module ~module-name-str :package __package__)
|
|
28
|
+
`(importlib.import-module ~module-name-str)))
|
|
24
29
|
(return
|
|
25
30
|
`(do (import importlib)
|
|
26
31
|
(= ___imported-macros
|
|
27
|
-
(getattr
|
|
28
|
-
~(.replace (str module-name) "-" "_"))
|
|
32
|
+
(getattr ~import-call
|
|
29
33
|
"__macro_namespace"
|
|
30
34
|
{}))
|
|
31
35
|
~(conde (not macro-names)
|
|
@@ -47,14 +51,14 @@
|
|
|
47
51
|
|
|
48
52
|
(when (== __name__ "__main__")
|
|
49
53
|
(import os.path as osp)
|
|
50
|
-
(from lispy.tools [l2py-
|
|
54
|
+
(from lispy.tools [l2py-s])
|
|
51
55
|
|
|
52
56
|
(= path (osp.abspath __file__))
|
|
53
57
|
|
|
54
58
|
(with [(open path "r") as f]
|
|
55
59
|
(= org (f.read)))
|
|
56
60
|
|
|
57
|
-
(= translated (l2py-
|
|
61
|
+
(= translated (l2py-s org :no-lispy True))
|
|
58
62
|
|
|
59
63
|
(with [(open (-> path
|
|
60
64
|
(osp.dirname)
|
|
@@ -20,14 +20,16 @@
|
|
|
20
20
|
(from lispy.core.parser [parse])
|
|
21
21
|
(from lispy.core.macro [macroexpand-then-compile])
|
|
22
22
|
|
|
23
|
-
(def detect-package-name []
|
|
24
|
-
"Detect package name from
|
|
25
|
-
(=
|
|
26
|
-
|
|
23
|
+
(def detect-package-name [:path None]
|
|
24
|
+
"Detect package name from directory by checking for __init__.py"
|
|
25
|
+
(= dir-path (ife (is path None)
|
|
26
|
+
(os.getcwd)
|
|
27
|
+
(osp.dirname (osp.abspath path))))
|
|
28
|
+
(if (not (osp.exists (osp.join dir-path "__init__.py")))
|
|
27
29
|
(return ""))
|
|
28
30
|
;; Walk up to find package root
|
|
29
31
|
(= parts [])
|
|
30
|
-
(= current
|
|
32
|
+
(= current dir-path)
|
|
31
33
|
(while (osp.exists (osp.join current "__init__.py"))
|
|
32
34
|
(parts.insert 0 (osp.basename current))
|
|
33
35
|
(= parent (osp.dirname current))
|
|
@@ -87,18 +89,23 @@
|
|
|
87
89
|
(argparser.add-argument "--meta"
|
|
88
90
|
:dest "include_meta"
|
|
89
91
|
:action "store_true")
|
|
92
|
+
(argparser.add-argument "--no-lispy"
|
|
93
|
+
:dest "no_lispy"
|
|
94
|
+
:action "store_true")
|
|
90
95
|
|
|
91
|
-
(def l2py-s [src :include-meta False :fresh-scope False]
|
|
96
|
+
(def l2py-s [src :include-meta False :fresh-scope False :package "" :no-lispy False]
|
|
92
97
|
"Translate lispy source string to Python source string"
|
|
93
98
|
(if fresh-scope
|
|
94
99
|
(= scope {"__builtins__" __builtins__
|
|
95
|
-
"__package__"
|
|
100
|
+
"__package__" package
|
|
96
101
|
"__macro_namespace" {}})
|
|
97
102
|
(= scope SCOPE))
|
|
98
103
|
(= pysrc (src-to-python src :include-meta include_meta :scope scope))
|
|
99
|
-
(
|
|
104
|
+
(if no_lispy
|
|
105
|
+
(return pysrc))
|
|
106
|
+
(return (+ "import lispy\n\n" pysrc)))
|
|
100
107
|
|
|
101
|
-
(def l2py-f [file-path :include-meta False]
|
|
108
|
+
(def l2py-f [file-path :include-meta False :no-lispy False]
|
|
102
109
|
"Translate lispy file to Python source string (standalone with sys.path setup)"
|
|
103
110
|
(= script-dir (osp.dirname (osp.abspath file-path)))
|
|
104
111
|
(if (not (in script-dir sys.path))
|
|
@@ -118,13 +125,14 @@
|
|
|
118
125
|
|
|
119
126
|
(with [(open file-path "rb") as f]
|
|
120
127
|
(= src (.decode (f.read) "utf-8")))
|
|
121
|
-
(
|
|
128
|
+
(= pkg (detect-package-name :path file-path))
|
|
129
|
+
(return (+ (l2py-s src :include-meta include_meta :fresh-scope True :package pkg :no-lispy no_lispy) "\n")))
|
|
122
130
|
|
|
123
131
|
(def l2py []
|
|
124
132
|
"CLI entry point: parse args and translate file"
|
|
125
133
|
(= [args more-args] (argparser.parse-known-args))
|
|
126
134
|
(= file (osp.join (os.getcwd) (sub more-args 0)))
|
|
127
|
-
(print (l2py-f file :include-meta args.include_meta)))
|
|
135
|
+
(print (l2py-f file :include-meta args.include_meta :no-lispy args.no_lispy)))
|
|
128
136
|
|
|
129
137
|
(def read-multiline-input [prompt_str]
|
|
130
138
|
(= paren-depth 0)
|
|
@@ -170,9 +178,13 @@
|
|
|
170
178
|
)
|
|
171
179
|
|
|
172
180
|
(def repl [:translate False]
|
|
181
|
+
;; Add current directory to sys.path for local imports
|
|
182
|
+
(= cwd (os.getcwd))
|
|
183
|
+
(if (not (in cwd sys.path))
|
|
184
|
+
(sys.path.insert 0 cwd))
|
|
173
185
|
(= scope SCOPE)
|
|
174
186
|
(eval-and-print "(require lispy.macros *)
|
|
175
|
-
(from pprint [pprint])")
|
|
187
|
+
(from pprint [pprint])" scope)
|
|
176
188
|
(while True
|
|
177
189
|
(try
|
|
178
190
|
(do
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from lispy.tools import l2py_s
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_include_meta_flag_false():
|
|
5
|
+
src = "(defmacro m [x] x)"
|
|
6
|
+
out = l2py_s(src, include_meta=False)
|
|
7
|
+
# When include_meta is False, the defmacro form itself should not appear in the output
|
|
8
|
+
assert "defmacro" not in out and "def m" not in out
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_include_meta_flag_true():
|
|
12
|
+
src = "(defmacro m [x] x)"
|
|
13
|
+
out = l2py_s(src, include_meta=True)
|
|
14
|
+
# When include_meta is True, there should be some Python produced for the macro definition
|
|
15
|
+
assert "def" in out or "__macro_namespace" in out
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_include_meta_nested():
|
|
19
|
+
src = "(list (defmacro m [x] x) 1)"
|
|
20
|
+
out_false = l2py_s(src, include_meta=False)
|
|
21
|
+
# Nested defmacro should not appear when include_meta is False
|
|
22
|
+
assert "defmacro" not in out_false and "def m" not in out_false and "__macro_namespace" not in out_false
|
|
23
|
+
|
|
24
|
+
out_true = l2py_s(src, include_meta=True)
|
|
25
|
+
# With include_meta True the macro definition/import should be present
|
|
26
|
+
assert "def" in out_true or "__macro_namespace" in out_true
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_import_lispy_default_and_no_lispy():
|
|
30
|
+
src = "(+ 1 2)"
|
|
31
|
+
out_default = l2py_s(src)
|
|
32
|
+
# By default we should prepend import lispy
|
|
33
|
+
assert out_default.startswith("import lispy")
|
|
34
|
+
|
|
35
|
+
out_no_lispy = l2py_s(src, no_lispy=True)
|
|
36
|
+
# With no_lispy True we should not add the import
|
|
37
|
+
assert not out_no_lispy.startswith("import lispy")
|
|
@@ -45,6 +45,21 @@ c"''') == "'a\\nb\\nc'"
|
|
|
45
45
|
ast.parse('f"sin({a}) is {sin(a):.3}"')
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
+
def test_f_string_with_nested_quotes(self):
|
|
49
|
+
# F-string with nested double-quoted string inside braces
|
|
50
|
+
# Uses raw string to have literal backslash-quote as in .lpy files
|
|
51
|
+
assert stmt_to_dump(
|
|
52
|
+
r'f"Started at {(.strftime (datetime.now) \"%Y-%m-%d %H:%M:%S\")}"'
|
|
53
|
+
) == ast.dump(
|
|
54
|
+
ast.parse('f"Started at {datetime.now().strftime(\'%Y-%m-%d %H:%M:%S\')}"')
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def test_f_string_with_colon_in_nested_string(self):
|
|
58
|
+
# Colon inside nested string should not be treated as format spec
|
|
59
|
+
assert stmt_to_dump(r'f"time: {(.get d \"H:M:S\")}"') == ast.dump(
|
|
60
|
+
ast.parse('f"time: {d.get(\'H:M:S\')}"')
|
|
61
|
+
)
|
|
62
|
+
|
|
48
63
|
|
|
49
64
|
class TestListMethods:
|
|
50
65
|
def test_list(self):
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os.path as osp
|
|
2
|
+
|
|
3
|
+
from lispy.tools import l2py_s
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestMetaFunctionsSync:
|
|
7
|
+
"""Test that core/meta_functions.py matches the transpiled result of core_meta_functions.lpy"""
|
|
8
|
+
|
|
9
|
+
def test_meta_functions_in_sync(self):
|
|
10
|
+
"""core/meta_functions.py should match transpiled core_meta_functions.lpy"""
|
|
11
|
+
base_dir = osp.dirname(osp.dirname(osp.abspath(__file__)))
|
|
12
|
+
lpy_path = osp.join(base_dir, "src", "lispy", "core_meta_functions.lpy")
|
|
13
|
+
py_path = osp.join(base_dir, "src", "lispy", "core", "meta_functions.py")
|
|
14
|
+
|
|
15
|
+
with open(lpy_path, "r") as f:
|
|
16
|
+
lpy_src = f.read()
|
|
17
|
+
|
|
18
|
+
with open(py_path, "r") as f:
|
|
19
|
+
py_contents = f.read()
|
|
20
|
+
|
|
21
|
+
transpiled = l2py_s(lpy_src)
|
|
22
|
+
|
|
23
|
+
assert transpiled == py_contents, (
|
|
24
|
+
"core/meta_functions.py is out of sync with core_meta_functions.lpy. "
|
|
25
|
+
"Run `lispy src/lispy/core_meta_functions.lpy` to regenerate."
|
|
26
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|