Skip to content

Commit c5ba974

Browse files
committed
improve internal attribute handling in env; make let constructs finalize their environments so no new bindings can be added; fix bug in lispylet, should create an env instance even if no bindings given
1 parent 5f27566 commit c5ba974

File tree

4 files changed

+42
-9
lines changed

4 files changed

+42
-9
lines changed

unpythonic/assignonce.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class assignonce(_envcls):
2525
2626
"""
2727
def __setattr__(self, name, value):
28-
if name == "_env" or name not in self:
28+
if name in self._reserved_names or name not in self:
2929
return super().__setattr__(name, value)
3030
else:
3131
raise AttributeError("name '{:s}' is already defined".format(name))

unpythonic/env.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,26 @@ class env:
4343
instance itself will remain alive due to Python's scoping rules.
4444
"""
4545
# do not allow bindings that would break functionality.
46-
reserved_names = ("_env", "set", "clear")
46+
_reserved_names = ("set", "clear", "finalize", "_env", "_allow_more_bindings",
47+
"_direct_write", "_reserved_names")
48+
_direct_write = ("_env", "_allow_more_bindings")
4749

4850
def __init__(self, **bindings):
4951
self._env = {}
52+
self._allow_more_bindings = True # "let" disables this once env setup done
5053
for name, value in bindings.items():
5154
setattr(self, name, value)
5255

5356
# item access by name
5457
# https://docs.python.org/3/reference/datamodel.html#object.__setattr__
5558
# https://docs.python.org/3/reference/datamodel.html#object.__getattr__
5659
def __setattr__(self, name, value):
57-
if name == "_env": # hook to allow creating _env directly in self
60+
if name in self._direct_write: # hook to allow creating internal variables directly in self
5861
return super().__setattr__(name, value)
59-
if name in self.reserved_names:
60-
raise AttributeError("cannot overwrite reserved name '{:s}'; complete list: {}".format(name, self.reserved_names))
62+
if name in self._reserved_names:
63+
raise AttributeError("cannot overwrite reserved name '{:s}'; complete list: {}".format(name, self._reserved_names))
64+
if not self._allow_more_bindings and name not in self:
65+
raise AttributeError("name '{:s}' is not defined; adding new bindings to a finalized environment is not allowed".format(name))
6166
# value = self._wrap(name, value) # for "e.x << value" rebind syntax.
6267
self._env[name] = value # make all other attrs else live inside _env
6368

@@ -130,6 +135,16 @@ def clear(self):
130135
"""Clear the environment, i.e. forget all bindings."""
131136
self._env = {}
132137

138+
def finalize(self):
139+
"""Finalize environment.
140+
141+
This stops the instance from accepting any more new bindings.
142+
143+
Existing bindings can still be overwritten even in a finalized
144+
environment.
145+
"""
146+
self._allow_more_bindings = False
147+
133148
# For rebind syntax: "e.foo << newval" --> "e.foo.__lshift__(newval)",
134149
# so foo.__lshift__() must be set up to rebind e.foo.
135150
#

unpythonic/let.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ def _let(mode, body, **bindings):
189189
for k in env:
190190
env[k] = env[k](env)
191191
# decorators need just the final env; else run body now
192+
env.finalize()
192193
return env if body is None else body(env)
193194

194195
# decorator factory: almost as fun as macros?
@@ -337,6 +338,15 @@ def result(*, env):
337338
else:
338339
assert False
339340

341+
try:
342+
@blet(x=1)
343+
def error1(*, env):
344+
env.y = 2 # cannot add new bindings to a let environment
345+
except AttributeError as err:
346+
pass
347+
else:
348+
assert False
349+
340350
print("All tests PASSED")
341351

342352
if __name__ == '__main__':

unpythonic/lispylet.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,17 @@ def bletrec(bindings):
152152
def _let(bindings, body, *, env=None, mode="let"):
153153
assert mode in ("let", "letrec")
154154

155+
env = env or _envcls()
156+
155157
if not bindings:
158+
env.finalize()
156159
# decorators need just the final env; else run body now
157160
return env if body is None else body(env)
158161

159-
env = env or _envcls()
160162
(k, v), *more = bindings
161-
162163
if mode == "letrec" and callable(v):
163164
v = v(env)
164-
165165
setattr(env, k, v)
166-
167166
return _let(more, body, env=env, mode=mode)
168167

169168
def _dlet(bindings, mode="let"): # let and letrec decorator factory
@@ -253,6 +252,15 @@ def result(*, env):
253252
else:
254253
assert False
255254

255+
try:
256+
@blet((('x', 1),))
257+
def error1(*, env):
258+
env.y = 2 # cannot add new bindings to a let environment
259+
except AttributeError as err:
260+
pass
261+
else:
262+
assert False
263+
256264
print("All tests PASSED")
257265

258266
if __name__ == '__main__':

0 commit comments

Comments
 (0)