Skip to content

Commit cec7009

Browse files
committed
Make where (for let-where) error out in incorrect position
1 parent 9817e06 commit cec7009

File tree

7 files changed

+34
-16
lines changed

7 files changed

+34
-16
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ The same applies if you need the macro parts of `unpythonic` (i.e. import anythi
2020

2121
- **Python 3.8 and 3.9 support added.**
2222

23-
- Robustness: several auxiliary syntactic constructs such as `local[]`/`delete[]` (for `do[]`), `call_cc[]` (for `with continuations`), `it` (for `aif[]`), `with expr`/`with block` (for `with let_syntax`/`with abbrev`), and `q`/`u`/`kw` (for `with prefix`) now detect *at macro expansion time* if they appear outside any valid lexical context, and raise `SyntaxError` (with a descriptive message) if so. Previously these constructs could only raise an error at run time, and not all of them could detect the error even then.
23+
- Robustness: several auxiliary syntactic constructs now detect *at macro expansion time* if they appear outside any valid lexical context, and raise `SyntaxError` (with a descriptive message) if so.
24+
- The full list is:
25+
- `call_cc[]`, for `with continuations`
26+
- `it`, for `aif[]`
27+
- `local[]`/`delete[]`, for `do[]`
28+
- `q`/`u`/`kw`, for `with prefix`
29+
- `where`, for `let[body, where(k0=v0, ...)]` (also for `letseq`, `letrec`, `let_syntax`, `abbrev`)
30+
- `with expr`/`with block`, for `with let_syntax`/`with abbrev`
31+
- Previously these constructs could only raise an error at run time, and not all of them could detect the error even then.
2432

2533
- `with namedlambda` now understands the walrus operator, too. In the construct `f := lambda ...: ...`, the lambda will get the name `f`. (Python 3.8 and later.)
2634

unpythonic/dialects/lispython.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ def transform_ast(self, tree): # tree is an ast.Module
2929
__lang__ = "Lispython" # noqa: F841, just provide it to user code.
3030
from unpythonic.syntax import (macros, tco, autoreturn, # noqa: F401, F811
3131
multilambda, quicklambda, namedlambda, f,
32+
where,
3233
let, letseq, letrec,
3334
dlet, dletseq, dletrec,
3435
blet, bletseq, bletrec,
3536
local, delete, do, do0,
3637
let_syntax, abbrev,
3738
cond)
3839
# Auxiliary syntax elements for the macros.
39-
from unpythonic.syntax import where, block, expr # noqa: F401, F811
40+
from unpythonic.syntax import block, expr # noqa: F401, F811
4041
from unpythonic import cons, car, cdr, ll, llist, nil, prod, dyn # noqa: F401, F811
4142
with autoreturn, quicklambda, multilambda, tco, namedlambda:
4243
__paste_here__ # noqa: F821, just a splicing marker.

unpythonic/dialects/pytkell.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ def transform_ast(self, tree): # tree is an ast.Module
1818
with q as template:
1919
__lang__ = "Pytkell" # noqa: F841, just provide it to user code.
2020
from unpythonic.syntax import (macros, lazy, lazyrec, lazify, autocurry, # noqa: F401, F811
21+
where,
2122
let, letseq, letrec,
2223
dlet, dletseq, dletrec,
2324
blet, bletseq, bletrec,
2425
local, delete, do, do0,
2526
cond, forall)
2627
# Auxiliary syntax elements for the macros.
27-
from unpythonic.syntax import where, insist, deny # noqa: F401
28+
from unpythonic.syntax import insist, deny # noqa: F401
2829
# Functions that have a haskelly feel to them.
2930
from unpythonic import (foldl, foldr, scanl, scanr, # noqa: F401
3031
s, imathify, gmathify, frozendict,

unpythonic/dialects/tests/test_listhell.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
from ...syntax import macros, test # noqa: F401
88
from ...test.fixtures import session, testset
99

10-
from ...syntax import macros, let, local, delete, do # noqa: F401, F811
11-
from ...syntax import where # for let-where # noqa: F401
10+
from ...syntax import macros, let, where, local, delete, do # noqa: F401, F811
1211
from unpythonic import foldr, cons, nil, ll
1312

1413
def runtests():

unpythonic/syntax/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@
6969
# If the line `tree = expander.visit(tree)` is omitted, the macro expands outside-in.
7070
# Note this default is different from MacroPy's!
7171

72-
# TODO: Move `where` from letdoutil to letdo, make it a @namemacro, and declare it public.
73-
7472
# TODO: Consider reversing the MRO of @generic (to latest first), since the point is to be extensible.
7573

7674
# TODO: Add docs navigation to all documentation files, like `mcpyrate` has.
@@ -101,7 +99,6 @@
10199
from .lambdatools import * # noqa: F401, F403
102100
from .lazify import * # noqa: F401, F403
103101
from .letdo import * # noqa: F401, F403
104-
from .letdoutil import where # noqa: F401
105102
from .letsyntax import * # noqa: F401, F403
106103
from .nb import * # noqa: F401, F403
107104
from .prefix import * # noqa: F401, F403

unpythonic/syntax/letdo.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# -*- coding: utf-8 -*-
22
"""Local bindings (let), imperative code in expression position (do)."""
33

4-
__all__ = ["let", "letseq", "letrec",
4+
__all__ = ["where",
5+
"let", "letseq", "letrec",
56
"dlet", "dletseq", "dletrec",
67
"blet", "bletseq", "bletrec",
78
"local", "delete", "do", "do0"]
@@ -30,7 +31,7 @@
3031

3132
from mcpyrate.quotes import macros, q, u, n, a, t, h # noqa: F401
3233

33-
from mcpyrate import gensym, parametricmacro
34+
from mcpyrate import gensym, namemacro, parametricmacro
3435
from mcpyrate.quotes import capture_as_macro, is_captured_value
3536
from mcpyrate.walkers import ASTTransformer, ASTVisitor
3637

@@ -85,6 +86,22 @@ def _destructure_and_apply_let(tree, args, macro_expander, let_transformer, allo
8586
# --------------------------------------------------------------------------------
8687
# Macro interface - expr macros
8788

89+
@namemacro
90+
def where(tree, *, syntax, **kw):
91+
"""[syntax, special] `where` operator for let.
92+
93+
Usage::
94+
95+
let[body, where((k0, v0), ...)]
96+
97+
Only meaningful for declaring the bindings in a let-where, for all
98+
expression-form let constructs: `let`, `letseq`, `letrec`, `let_syntax`,
99+
`abbrev`.
100+
"""
101+
if syntax != "name":
102+
raise SyntaxError("where (unpythonic.syntax.letdo.where) is a name macro only") # pragma: no cover
103+
raise SyntaxError("where() is only meaningful in a let[body, where((k0, v0), ...)]") # pragma: no cover
104+
88105
@parametricmacro
89106
def let(tree, *, args, syntax, expander, **kw):
90107
"""[syntax, expr] Introduce expression-local variables.

unpythonic/syntax/letdoutil.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# -*- coding: utf-8 -*-
22
"""Detect let and do forms, and destructure them writably."""
33

4-
__all__ = ["where",
5-
"canonize_bindings", # used by the macro interface layer
4+
__all__ = ["canonize_bindings", # used by the macro interface layer
65
"isenvassign", "islet", "isdo",
76
"UnexpandedEnvAssignView", "UnexpandedLetView", "UnexpandedDoView",
87
"ExpandedLetView", "ExpandedDoView"]
@@ -16,10 +15,6 @@
1615
from .astcompat import getconstant, Str
1716
from .nameutil import isx, getname
1817

19-
def where(*bindings):
20-
"""[syntax] Only meaningful in a let[body, where((k0, v0), ...)]."""
21-
raise RuntimeError("where() is only meaningful in a let[body, where((k0, v0), ...)]") # pragma: no cover
22-
2318
letf_name = "letter" # must match what ``unpythonic.syntax.letdo._let_expr_impl`` uses in its output.
2419
dof_name = "dof" # name must match what ``unpythonic.syntax.letdo.do`` uses in its output.
2520
currycall_name = "currycall" # output of ``unpythonic.syntax.curry``

0 commit comments

Comments
 (0)