-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pythonify TAGBODY/GO from Common Lisp #45
Comments
Updated draft for macro output, supporting nested # actually no explicit import; just use `hq[]` in the macro implementation.
from unpythonic import trampolined, jump
# define in unpythonic.syntax.tagbody; unhygienic_expose to have it available at the macro use site
class JumpToTag(Exception):
def __init_(self, tag):
self.tag = tag
# uncaught error message
self.args = ("go[] to a label '{}' not defined in any lexically enclosing with_tags section".format(tag),)
# macro output
def myfunc(): # may take args and kwargs; we pass them by closure
# define our set of tags (capture these in the syntax transformer)
our_tags = {"foo"} # gensym'd variable name on LHS
# top-level code of myfunc, split into helper functions
# Each must be trampolined, because a go[] from an inner with_tags
# (if two or more are nested) may jump into any of them.
@trampolined
def body(): # gensym'd function name for part before first tag
nonlocal x
x = 42
return jump(foo)
@trampolined
def foo(): # use the tag name as the function name
nonlocal x
x += 1
if x < 10:
return jump(foo)
return "whatever"
# runner harness
# Copy locals to have a guaranteed-frozen copy of the current state,
# so we retain references to the helper functions even if the user code
# overwrites those names.
our_funcs = dict(locals()) # gensym'd variable name on LHS
f = body # gensym'd variable name on LHS
while True:
try:
return f()
# catch nonlocal jumps from inner nested with_tags sections
except JumpToTag as jmp:
if jmp.tag in our_tags:
f = our_funcs[jmp.tag]
else: # not ours, let the jump propagate further out
raise
# never reached; just for scoping top-level locals of myfunc
x = None |
|
|
Meh, maybe we should just go for the previous idea, using exceptions. That gives better semantics. Nesting feels pretty important and a goto should act like a goto (even if it can only jump within the same level or outward).
|
I had hoped to get this into 0.14.3, but the technical cleanup has already produced enough material for a minor update. Better to push that out first, and then concentrate on these additions. |
See Peter Seibel: Practical Common Lisp, Chapter 20 for an explanation.
Rough draft of how we could pythonify this. User interface:
In a
with_tags
section, atag[...]
form at the top level of the function definition creates a label. Thego[...]
form jumps to the given label, and may appear anywhere lexically inside thewith_tags
section. To stay within Python semantics (following the principle of least surprise),myfunc
otherwise behaves like a regular function.Possible macro output:
Essentially, this is another instance of lambda, the ultimate goto.
Notes:
body
(the entry point, called by the expandedmyfunc
) needs to be trampolined, since none of the inner functions are accessible from the outside (their names being local tomyfunc
).unpythonic.syntax.scoping
) to determine which variable names are local tomyfunc
in the input. Then scope those tomyfunc
(by assigning aNone
at the end), and declare themnonlocal
in the helper functions, so that the top level ofmyfunc
forms just one scope (emulating Python's scoping rules), though the macro splits it into helper functions.def
or comprehension form - those should stay local to that innerdef
. Only top-level locals matter here. The scoping utility should already take this into account (stopping name collection at scope boundaries).return
from one of the helper functions will shut down the trampoline, and return that value from the TCO chain. This results in a top-level return frommyfunc
, which is exactly what we want. So we don't have to transform top-levelreturn
statements when we generate the expansion.with_tags
section (enforce this in the syntax transformer!), so we can use the tag names as the names of the helper functions. This is also informative in stack traces.tag[]
andgo[]
raise an error at runtime if used outside anywith_tags
. Usemacro_stub
.Things to consider:
with_tags
sections? Jumping to a tag in a lexically outerwith_tags
is the complex part. Possible solution below in a separate comment.jump(...)
to the outer trampoline.myfunc
in an exception handler, and makego[]
to an undefined label raise an exception with instructions where to jump?go[]
can be allowed to unwind the call stack, because nesting is lexical - when unresolved locally (i.e. by the nearest enclosingwith_tags
), ago[]
can only refer to tags that appear further out.with_tags
section has that label, jump to it if it does, and else re-raise. The exception can have anargs
message complaining aboutgo[]
to a label not defined in any lexically enclosingwith_tags
section, if uncaught.go[]
and call it later, after the original function has exited. Short of using general continuations, is that even possible? Does CL handle this case? If so, how?with continuations
? As always, this is the tricky part.prefix
andautoreturn
should go first.tag[...]
andgo[...]
? A string avoids upsetting IDEs with "undefined" names, but a bare name without quotes is shorter to type.with lazify
, since the helper functions take no arguments. Ifmyfunc
has parameters, we can allowwith lazify
to process them as usual.with_tags
sections should expand from inside out (so any inner ones are resolved first), so this is a second-pass macro.ContinuationsMarker
for an example), and modifywith tco
to leave alone anywith_tags
sections it encounters.The text was updated successfully, but these errors were encountered: