@@ -23,11 +23,15 @@ class escape(Exception):
2323
2424 tag: anything comparable with ``==``
2525 Can be used to restrict which ``@setescape`` points may catch
26- this escape instance.
26+ this escape instance. Default ``None``, meaning "no tag".
2727
2828 allow_catchall: bool
2929 Whether untagged "catch-all" ``@setescape`` points may catch
30- this escape instance (regardless of whether or not we have a tag!).
30+ this escape instance (regardless of tag!).
31+
32+ Even if ``False``, if ``tag`` is ``None``, a ``@setescape``
33+ point may still override this with ``catch_untagged=True``.
34+ (See the table in ``help(setescape)``, case ``b0``.)
3135 """
3236 def __init__ (self , value , tag = None , allow_catchall = True ):
3337 self .value = value
@@ -52,28 +56,51 @@ def setescape(tags=None, catch_untagged=True):
5256 A tuple is OR'd, like in ``isinstance()``.
5357
5458 Restrict which escapes will be caught by this ``@setescape`` point.
55- If set, ignore any escapes **that have a tag**, which does not match
56- any of the given tags.
5759
5860 catch_untagged: bool
59- Choose whether this ``@setescape`` point catches untagged escapes.
61+ Whether this ``@setescape`` point catches untagged escapes.
62+
63+ Even if ``False``, if ``tags`` is ``None``, an escape instance
64+ may still override this with ``allow_catchall=True``. (See the
65+ table below, case ``a1``.)
6066
6167 The exact catch condition is::
6268
6369 # e is an escape instance
6470 if (tags is None and e.allow_catchall) or
65- (e.tag is None and catch_untagged ) or
71+ (catch_untagged and e.tag is None) or
6672 (tags is not None and e.tag is not None and e.tag in tags):
6773 # caught
6874
69- The same in English:
75+ resulting in the following table. Here a ``@setescape`` point with no tags
76+ is a "catch-all"::
77+
78+ escape instance @setescape point
79+ 0: no tag, ignore catch-alls a: no tags
80+ 1: no tag, allow catch-alls b: no tags, catch untagged
81+ 2: w/ tag, ignore catch-alls c: w/ tags
82+ 3: w/ tag, allow catch-alls d: w/ tags, catch untagged
83+
84+ 0 1 2 3
85+ a x x
86+ b x x x
87+ c t t
88+ d x x t t
7089
71- - If we are an untagged "catch-all" point, catch any ``e`` that allows
72- catchall. Don't care about whether or not it has a tag.
90+ = do not catch, pass on
91+ t = check tags, catch on match
92+ x = catch
7393
74- - If ``e`` is untagged, catch if we should catch untagged escapes.
94+ **How to use the table**:
7595
76- - Both us and ``e`` have tags. Catch if the tag of ``e`` matches one of ours.
96+ - If setting a ``@setescape`` point, pick a row. Read off what you'll catch.
97+
98+ - If raising an ``escape`` instance, pick a column. Read off what will catch it.
99+
100+ Default settings for both ends give case ``b1``. For a guaranteed one-to-one
101+ relationship, pick a unique tag, and use settings for case ``c2``.
102+
103+ **Examples**
77104
78105 Multi-return using escape continuation::
79106
@@ -132,7 +159,7 @@ def decorated(*args, **kwargs):
132159 return f (* args , ** kwargs )
133160 except escape as e :
134161 if (tags is None and e .allow_catchall ) or \
135- (e .tag is None and catch_untagged ) or \
162+ (catch_untagged and e .tag is None ) or \
136163 (tags is not None and e .tag is not None and e .tag in tags ):
137164 return e .value
138165 else : # meant for someone else, pass it on
@@ -187,7 +214,7 @@ def inner():
187214
188215 Similar usage is valid for named functions, too.
189216 """
190- # We need a process-wide unique id to tag the ec:
217+ # Create a process-wide unique id to tag the ec:
191218 anchor = object ()
192219 uid = id (anchor )
193220 # Closure property important here. "ec" itself lives as long as someone
@@ -205,8 +232,7 @@ def ec(value):
205232 # Be catchable only by our own escape point.
206233 raise escape (value , uid , allow_catchall = False )
207234 try :
208- # Set up a tagged escape point here and call f.
209- # Catch only the one specific ec we just set up.
235+ # Set up a tagged escape point that catches only the ec we just set up.
210236 @setescape (uid , catch_untagged = False )
211237 def wrapper ():
212238 return f (ec )
@@ -268,6 +294,29 @@ def erroneous(ec):
268294
269295 # tests with @looped in tco.py to prevent cyclic dependency
270296
297+ # def catching_truth_table():
298+ # def check(tags, catch_untagged, e):
299+ # if (tags is None and e.allow_catchall) or \
300+ # (catch_untagged and e.tag is None):
301+ # return 2 # unconditional catch
302+ # if (tags is not None and e.tag is not None): # and e.tag in tags):
303+ # return 1 # catch if tags match
304+ # return 0 # don't catch, pass on
305+ #
306+ # from itertools import product
307+ # _ = None
308+ # # @setescape point attributes
309+ # ps = ((None, False), (None, True),
310+ # (set(("tag",)), False), (set(("tag",)), True))
311+ # # escape instance attributes
312+ # es = (escape(_, None, False), escape(_, None, True),
313+ # escape(_, "tag", False), escape(_, "tag", True))
314+ # table = [check(t, c, e) for (t, c), e in product(ps, es)]
315+ # import numpy as np
316+ # a = np.reshape(table, (4, 4)) # row = p, col = e
317+ # print(a)
318+ # catching_truth_table()
319+
271320 print ("All tests PASSED" )
272321
273322if __name__ == '__main__' :
0 commit comments