Module refinery.lib.scripts.ps1.analysis.dispatch
Where Windows PowerShell 5.1 sends an error raised at a point of a script, read off the tree the way the engine runs it.
5.1 runs every statement list — a script, a function, the body of an if, a loop, a switch
clause, a try, a catch, a finally or a trap, the statements of a $( ) — inside one catch
per statement, and decides there what becomes of an error the statement raised. That decision reads
three things: whether the list declares a trap, whether the engine is passing errors outward
(PropagateExceptionsToEnclosingStatementBlock, set over a try body, its catch bodies, a list
that declares a trap and everything such a list runs, and put back before a finally body), and
what kind of error it is. Ps1Level is one such list and what it decides from; Ps1Dispatch
applies the decision from the list a point stands in outward until something takes the error, and
answers with every place it may end up, as a Ps1Fate: where the run carries on, which handlers
run, and whether the error ends the run or is handed to whatever called the body.
The rules, each named after the 5.1 code that holds it:
ExceptionHandlingOps.CheckActionPreference: where notrapis declared, an error is handed outward while the engine passes errors outward or once it is marked; otherwise the preference decides:SilentlyContinuecarries on after the statement,Stopmarks the error and hands it outward, and anything else reports it and carries on — except the error of athrow, which is handed outward.ExceptionHandlingOps.ProcessTraps: where atrapis declared, the one that takes the error runs;continuecarries on after the statement,breakmarks the error and hands it outward, and a body that runs off its end leaves it to the preference. An error notraptakes is marked and handed outward.Compiler.VisitTryStatement: an error leaving atrybody is offered to itscatchclauses; one no clause takes, and one leaving acatchbody or afinallybody, is disposed of by the list thetrystatement stands in. An error leaving afinallybody takes the place of whatever was in flight there: an error, or areturn,exit,breakorcontinue.Compiler.CompileTrap: atrapbody runs with notrapin force, so an error raised in it leaves the list that declared thetrap— or, where thetrapran because nocatchclause of atrystatement of that list took an error, leaves thattrystatement, and the list disposes of it.
Everything here is read off the tree. What a handler means — which errors a filter takes, which
member of a trap set is selected — is refinery.lib.scripts.ps1.analysis.handlers; how a trap
body ends is handed in by whoever builds the dispatch, which is
dispatch_for(), reading the body built on its own. The
control-flow builder draws its error routes from the answers here, so the graphs carry the same
routes without the state an error carries along them. A filter naming a type is read as one that
may or may not match.
Every answer is kept, per statement list and per statement, for as long as the tree is the one it was read from, so that asking of every point of a script costs the size of the script.
Expand source code Browse git
"""
Where Windows PowerShell 5.1 sends an error raised at a point of a script, read off the tree the way
the engine runs it.
5.1 runs every statement list — a script, a function, the body of an `if`, a loop, a `switch`
clause, a `try`, a `catch`, a `finally` or a `trap`, the statements of a `$( )` — inside one catch
per statement, and decides there what becomes of an error the statement raised. That decision reads
three things: whether the list declares a `trap`, whether the engine is passing errors outward
(`PropagateExceptionsToEnclosingStatementBlock`, set over a `try` body, its `catch` bodies, a list
that declares a `trap` and everything such a list runs, and put back before a `finally` body), and
what kind of error it is. `Ps1Level` is one such list and what it decides from; `Ps1Dispatch`
applies the decision from the list a point stands in outward until something takes the error, and
answers with every place it may end up, as a `Ps1Fate`: where the run carries on, which handlers
run, and whether the error ends the run or is handed to whatever called the body.
The rules, each named after the 5.1 code that holds it:
- `ExceptionHandlingOps.CheckActionPreference`: where no `trap` is declared, an error is handed
outward while the engine passes errors outward or once it is marked; otherwise the preference
decides: `SilentlyContinue` carries on after the statement, `Stop` marks the error and hands it
outward, and anything else reports it and carries on — except the error of a `throw`, which is
handed outward.
- `ExceptionHandlingOps.ProcessTraps`: where a `trap` is declared, the one that takes the error
runs; `continue` carries on after the statement, `break` marks the error and hands it outward,
and a body that runs off its end leaves it to the preference. An error no `trap` takes is marked
and handed outward.
- `Compiler.VisitTryStatement`: an error leaving a `try` body is offered to its `catch` clauses;
one no clause takes, and one leaving a `catch` body or a `finally` body, is disposed of by the
list the `try` statement stands in. An error leaving a `finally` body takes the place of whatever
was in flight there: an error, or a `return`, `exit`, `break` or `continue`.
- `Compiler.CompileTrap`: a `trap` body runs with no `trap` in force, so an error raised in it
leaves the list that declared the `trap` — or, where the `trap` ran because no `catch` clause of
a `try` statement of that list took an error, leaves that `try` statement, and the list disposes
of it.
Everything here is read off the tree. What a handler means — which errors a filter takes, which
member of a `trap` set is selected — is `refinery.lib.scripts.ps1.analysis.handlers`; how a `trap`
body ends is handed in by whoever builds the dispatch, which is
`refinery.lib.scripts.ps1.analysis.cfg.dispatch_for`, reading the body built on its own. The
control-flow builder draws its error routes from the answers here, so the graphs carry the same
routes without the state an error carries along them. A filter naming a type is read as one that
may or may not match.
Every answer is kept, per statement list and per statement, for as long as the tree is the one it
was read from, so that asking of every point of a script costs the size of the script.
"""
from __future__ import annotations
import enum
from functools import partial
from typing import AbstractSet, Callable, Iterable, NamedTuple, Protocol, Sequence
from refinery.lib.scripts import Block, Node, tree_root
from refinery.lib.scripts.ps1.analysis.handlers import (
Ps1TrapDisposition,
Ps1TrapSelection,
carries_on,
declared_traps,
handler_acts,
settled,
swallows_every_error,
)
from refinery.lib.scripts.ps1.analysis.world import runs_code_it_cannot_read
from refinery.lib.scripts.ps1.ast import STATEMENT_LIST_EXPRESSIONS
from refinery.lib.scripts.ps1.model import (
Ps1BreakStatement,
Ps1CatchClause,
Ps1Code,
Ps1CommandInvocation,
Ps1ContinueStatement,
Ps1DoLoop,
Ps1ExitStatement,
Ps1ForEachLoop,
Ps1ForLoop,
Ps1FunctionDefinition,
Ps1IfStatement,
Ps1InvokeMember,
Ps1ReturnStatement,
Ps1Script,
Ps1ScriptBlock,
Ps1SwitchStatement,
Ps1TrapStatement,
Ps1TryCatchFinally,
Ps1WhileLoop,
)
class Ps1ErrorKind(enum.Flag):
"""
The kinds of error 5.1 disposes of differently.
"""
NONE = 0
#: A statement-terminating error, such as a failed cast: reported where nothing is in force, and
#: the run carries on with the next statement.
STATEMENT = enum.auto()
#: The error of a `throw`: where nothing is in force it ends the run, unless the preference is
#: `SilentlyContinue`.
THROWN = enum.auto()
#: An error no list reports: one a command told to stop raises, and one `Stop`, a `trap` that
#: re-raises or a `trap` set that declines has handed on. Only a `catch` or a `trap` takes it.
MARKED = enum.auto()
#: A statement-terminating error a body the point runs would report: it reaches the list the
#: point stands in only while the engine passes errors outward there, which it does through
#: every body a `try`, a `catch` body or a list that declares a `trap` runs, and is a
#: statement-terminating error of that list once it does. Elsewhere the body reported it and
#: the point completed.
CALLED = enum.auto()
#: Every kind of error, one at a time.
ERROR_KINDS = (Ps1ErrorKind.STATEMENT, Ps1ErrorKind.THROWN, Ps1ErrorKind.MARKED, Ps1ErrorKind.CALLED)
#: What a call that may run a body of the script may hand back: whatever that body may raise.
HANDED_BACK = Ps1ErrorKind.CALLED | Ps1ErrorKind.THROWN | Ps1ErrorKind.MARKED
#: Every kind of error at once: what a point whose operands cannot be named may raise.
EVERY_KIND = Ps1ErrorKind.STATEMENT | HANDED_BACK
class Ps1Preference(enum.Enum):
"""
The values of `$ErrorActionPreference` that dispose of an error differently: `CONTINUE` stands
for every value that reports it.
"""
CONTINUE = enum.auto()
SILENT = enum.auto()
STOP = enum.auto()
def preferences_of(stops: bool, silenced: bool) -> frozenset[Ps1Preference]:
"""
The values `$ErrorActionPreference` may hold where a script may set `Stop` and where it may set
a value that silences an error. A script starts out reporting.
"""
found = {Ps1Preference.CONTINUE}
if stops:
found.add(Ps1Preference.STOP)
if silenced:
found.add(Ps1Preference.SILENT)
return frozenset(found)
class Ps1LevelKind(enum.Enum):
"""
What runs a statement list, which decides where an error leaving it goes and whether the engine
passes errors outward while it runs.
"""
#: The statements of a script, a script block or one of its named blocks.
BODY = enum.auto()
#: The body of an `if`, a loop or a `switch` clause, or the statements of a `$( )` or `@( )`.
NESTED = enum.auto()
TRY = enum.auto()
CATCH = enum.auto()
FINALLY = enum.auto()
TRAP = enum.auto()
class Ps1Level(NamedTuple):
"""
One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list
around it and *holder* the statement of that list it belongs to: the `try` for its `try`,
`catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or
statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap`
statements the list declares. *passes_on* is whether the engine passes errors outward while
the statements run, or `None` where that is whatever the caller of the body set: a `try` around
a call passes them outward through every body the call runs. *called* is whether the list
belongs to a script block, which a caller runs, rather than to the script, and *preferences*
holds the values the error preference may hold while the statements run. *guarding* reads the
values the outward flag may hold where the body holding the list was called.
"""
kind: Ps1LevelKind
owner: Node
parent: Ps1Level | None
holder: Node | None
traps: Ps1TrapSelection | None
passes_on: bool | None
called: bool
preferences: frozenset[Ps1Preference]
guarding: Callable[[], tuple[bool, ...]] = lambda: (False,)
def guards(self) -> tuple[bool, ...]:
"""
The values the flag that passes errors outward may hold where the body this list belongs
to was called: unset for the script; for a script block, set as well only where some call
that may run it stands where the flag is set.
"""
return self.guarding()
class Ps1Fate(NamedTuple):
"""
Every place an error may end up.
- *carried*: the statements the run may carry on after, each a member of the list that took the
error, whether it reported it, silenced it or ran a `trap` that carried on;
- *caught*: the `catch` clauses that may take it;
- *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after
which the run carries on after the `try`;
- *trapped*: the `trap` statements that may run on it;
- *acts*: whether one of those clauses or `trap` statements does something a run can see;
- *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not;
- *ends_run*: whether it may leave the script, which ends the run;
- *to_caller*: the kinds it may leave a script block with, handed to whatever called it;
- *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what
was in flight there;
- *outermost*: the depth in the tree of the outermost statement the run may carry on after, or
whose `catch` or acting `trap` may take it.
"""
carried: frozenset[Node] = frozenset()
caught: frozenset[Ps1CatchClause] = frozenset()
swallowed: frozenset[Ps1TryCatchFinally] = frozenset()
trapped: frozenset[Ps1TrapStatement] = frozenset()
acts: bool = False
offered_to_traps: bool = False
ends_run: bool = False
to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE
replaces: frozenset[Ps1TryCatchFinally] = frozenset()
outermost: float = float('inf')
@property
def leaves(self) -> bool:
"""
Whether the error may get past every handler of the body it was raised in.
"""
return self.ends_run or bool(self.to_caller)
def joined(self, other: Ps1Fate) -> Ps1Fate:
"""
The fate of an error that may end up as this one or as *other* does.
"""
if other is _NOWHERE:
return self
if self is _NOWHERE:
return other
return Ps1Fate(
self.carried | other.carried,
self.caught | other.caught,
self.swallowed | other.swallowed,
_union(self.trapped, other.trapped),
self.acts or other.acts,
self.offered_to_traps or other.offered_to_traps,
self.ends_run or other.ends_run,
self.to_caller | other.to_caller,
self.replaces | other.replaces,
min(self.outermost, other.outermost),
)
def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate:
"""
This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on
after *statement*.
"""
if statement not in self.carried:
return self
return self._replace(
carried=self.carried - {statement} | {to},
outermost=min(self.outermost, depth),
)
_NOWHERE = Ps1Fate()
def _union(first: frozenset, second: frozenset) -> frozenset:
"""
The union of two sets, the first itself where the second adds nothing, so that a fate joined
with one that adds no `trap` keeps the very set it had and a reader may key on it.
"""
if not second or second is first:
return first
if not first:
return second
return first | second
class _Jump(enum.Flag):
"""
The jumps that may carry control out of a node: `Ps1Dispatch._leaving_jumps`.
"""
NONE = 0
RETURN = enum.auto()
EXIT = enum.auto()
#: An unlabelled `break` or `continue`, which the innermost loop or `switch` takes.
LOOP = enum.auto()
#: A `break` or `continue` naming a label.
LABELLED = enum.auto()
#: Something that may run a body of the script or code it does not spell.
RUNS_CODE = enum.auto()
_LOOPS = (
Ps1DoLoop,
Ps1ForEachLoop,
Ps1ForLoop,
Ps1SwitchStatement,
Ps1WhileLoop,
)
def _list_owner(node: Node) -> bool:
return isinstance(node, (Block, Ps1Code, *STATEMENT_LIST_EXPRESSIONS))
def member_of(node: Node) -> Node | None:
"""
The statement of a statement list that holds *node*, *node* itself where it is one.
"""
cursor: Node | None = node
while cursor is not None:
parent = cursor.parent
if parent is None:
return None
if _list_owner(parent) and not isinstance(cursor, Block):
return cursor
cursor = parent
return None
def _last_statement(owner: Node) -> Node | None:
"""
The last statement of the list *owner* holds that runs, a `trap` being a declaration.
"""
for statement in reversed(getattr(owner, 'body', ())):
if not isinstance(statement, Ps1TrapStatement):
return statement
return None
def _kind(owner: Node) -> tuple[Ps1LevelKind, Node | None]:
if isinstance(owner, Ps1Code):
return Ps1LevelKind.BODY, None
parent = owner.parent
if isinstance(owner, Block):
if parent is None or isinstance(parent, Ps1Code):
return Ps1LevelKind.BODY, None
if isinstance(parent, Ps1TryCatchFinally):
if owner is parent.finally_block:
return Ps1LevelKind.FINALLY, parent
return Ps1LevelKind.TRY, parent
if isinstance(parent, Ps1CatchClause):
return Ps1LevelKind.CATCH, parent.parent
if isinstance(parent, Ps1TrapStatement):
return Ps1LevelKind.TRAP, parent
holder = member_of(owner)
if holder is None:
return Ps1LevelKind.BODY, None
return Ps1LevelKind.NESTED, holder
def _is_script(owner: Node) -> bool:
return isinstance(owner, Ps1Script) or isinstance(owner.parent, Ps1Script)
def _runs_code(node: Node) -> bool:
"""
Whether evaluating *node* may run a body of the script or code it does not spell, which may
itself jump out of the body it runs in.
"""
return isinstance(node, (Ps1CommandInvocation, Ps1InvokeMember, Ps1ScriptBlock))
class _Tree:
"""
What every reading of one tree shares: the depth of each node, the `trap` set each list
declares, how each `trap` body ends, which *dispositions* reads and keeps, the preferences the
error preference may hold, and the values the outward flag may hold where a body was called,
which *guards* reads for the body holding a list.
"""
def __init__(
self,
preferences: frozenset[Ps1Preference],
called_preferences: frozenset[Ps1Preference],
guards: Callable[[Node], tuple[bool, ...]],
dispositions: Callable[[Ps1TrapStatement], Ps1TrapDisposition],
shared: _Tree | None = None,
):
self.preferences = preferences
self.called = called_preferences
self.guards = guards
self.disposition = dispositions
self._depths: dict[int, int] = {} if shared is None else shared._depths
self._selections: dict[int, Ps1TrapSelection | None] = {} if shared is None else shared._selections
def reading(self, preference: Ps1Preference, called_preference: Ps1Preference) -> _Tree:
"""
This tree under one value of the error preference in the script and one in a called body,
sharing what no preference changes: the depths, the `trap` sets and how each body ends.
"""
single = frozenset((preference,)), frozenset((called_preference,))
return _Tree(*single, self.guards, self.disposition, self)
def depth_of(self, node: Node) -> int:
depths = self._depths
found = depths.get(id(node))
if found is None:
climbed: list[Node] = []
cursor: Node | None = node
while cursor is not None and (found := depths.get(id(cursor))) is None:
climbed.append(cursor)
cursor = cursor.parent
depth = -1 if found is None else found
for step in reversed(climbed):
depth += 1
depths[id(step)] = depth
found = depths[id(node)]
return found
def traps_of(self, owner: Node) -> Ps1TrapSelection | None:
key = id(owner)
if key in self._selections:
return self._selections[key]
traps = declared_traps(getattr(owner, 'body', ()))
found = self._selections[key] = Ps1TrapSelection(traps) if traps else None
return found
def _tries_a_trap_may_run_in(owner: Node) -> list[Ps1TryCatchFinally]:
"""
The `try` statements of the list *owner* holds that a `trap` of the list may run from inside:
each has `catch` clauses, and they may take nothing, so 5.1 offers the error to the list's
`trap` set from within the `try` statement.
"""
return [
statement for statement in getattr(owner, 'body', ())
if isinstance(statement, Ps1TryCatchFinally)
and statement.catch_clauses
and not swallows_every_error(statement)
]
def _unguarded() -> tuple[bool, ...]:
return (False,)
class Ps1CallSites(Protocol):
"""
The calls that may run each body of a script, which decide whether a caller may run it with the
outward flag set.
"""
def bodies(self) -> Iterable[Node]:
"""
Every script block of the script.
"""
...
def sites_running(self, body: Node) -> Sequence[Node] | None:
"""
The calls naming *body* that may run it, or `None` where its callers cannot all be named.
"""
...
def unattributed_sites(self) -> Sequence[Node]:
"""
The calls that run code nobody can read, each of which may run any function body.
"""
...
class _Levels:
"""
The statement lists of one tree as `Ps1Level` values. Where *moved* is a block and *around* a
level, the lists are read as though the statements of *moved* stood in the list of *around*:
the lists written inside *moved* are read anew against that, and every other list is the one
*real* reads.
"""
def __init__(
self,
tree: _Tree,
moved: Block | None = None,
around: Ps1Level | None = None,
real: _Levels | None = None,
replaced: tuple[Node, Ps1TrapSelection | None] | None = None,
):
self._tree = tree
self._moved = moved
self._around = around
self._real = real
self._replaced = replaced
self._levels: dict[int, Ps1Level] = {}
def level_of(self, statement: Node) -> Ps1Level | None:
owner = statement.parent
if owner is None or not _list_owner(owner) or isinstance(statement, Block):
return None
return self.level(owner)
def level(self, owner: Node) -> Ps1Level:
level = self._levels.get(id(owner))
if level is not None:
return level
if owner is self._moved and self._around is not None:
level = self._around
else:
kind, holder = _kind(owner)
parent = None
if holder is not None:
parent = self.level_of(holder)
if parent is None:
kind, holder = Ps1LevelKind.BODY, None
real = self._real
replaced = self._replaced
if real is not None and not self._owns(parent) and (replaced is None or replaced[0] is not owner):
level = real.level(owner)
else:
level = self._build(owner, kind, holder, parent)
self._levels[id(owner)] = level
return level
def _owns(self, level: Ps1Level | None) -> bool:
return level is not None and (level is self._around or self._levels.get(id(level.owner)) is level)
def _build(
self,
owner: Node,
kind: Ps1LevelKind,
holder: Node | None,
parent: Ps1Level | None,
) -> Ps1Level:
tree = self._tree
replaced = self._replaced
if replaced is not None and replaced[0] is owner:
traps = replaced[1]
else:
traps = tree.traps_of(owner)
called = parent.called if parent is not None else not _is_script(owner)
passes_on: bool | None
if traps is not None or kind in (Ps1LevelKind.TRY, Ps1LevelKind.CATCH, Ps1LevelKind.TRAP):
passes_on = True
elif parent is not None:
passes_on = parent.passes_on
else:
passes_on = None if called else False
if parent is not None:
guarding = parent.guarding
elif not called:
guarding = _unguarded
else:
guarding = partial(tree.guards, owner)
preferences = tree.called if called else tree.preferences
return Ps1Level(kind, owner, parent, holder, traps, passes_on, called, preferences, guarding)
class _SetFate(NamedTuple):
"""
What a `trap` set does with an error of one kind, apart from the statement it was raised in:
where it may end up when the set does not carry on, and whether the set may carry on after the
statement.
"""
fate: Ps1Fate
carries_on: bool
class Ps1Reading:
"""
The rules of the module docstring applied over one reading of the lists, *levels*, with every
answer kept for that reading.
"""
def __init__(self, tree: _Tree, levels: _Levels):
self.tree = tree
self._tree = tree
self.levels = levels
self._leaving: dict[tuple[int, Ps1ErrorKind, bool], Ps1Fate] = {}
self._disposed: dict[tuple[int, Ps1ErrorKind, bool], Ps1Fate] = {}
self._sets: dict[tuple[int, bool], _SetFate] = {}
self._out_of_traps: dict[tuple[int, Ps1ErrorKind, bool], Ps1Fate] = {}
def placed(self, point: Node) -> tuple[Ps1Level, Node] | None:
statement = member_of(point)
if statement is None:
return None
level = self.levels.level_of(statement)
if level is None:
return None
return level, statement
def fate(self, point: Node, kinds: Ps1ErrorKind, guarded: bool | None = None) -> Ps1Fate | None:
placed = self.placed(point)
if placed is None:
return None
level, statement = placed
fate = _NOWHERE
for kind in ERROR_KINDS:
if kind in kinds:
if guarded is None:
fate = fate.joined(self.disposed(level, statement, kind))
else:
fate = fate.joined(self.dispose(level, statement, kind, guarded))
return fate
def disposed(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind) -> Ps1Fate:
fate = _NOWHERE
for guarded in level.guards():
fate = fate.joined(self.dispose(level, statement, kind, guarded))
return fate
def dispose(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate:
key = (id(statement), kind, guarded)
fate = self._disposed.get(key)
if fate is None:
fate = self._disposed[key] = self.dispose_as(level, statement, kind, guarded)
return fate
def _carried(self, statement: Node) -> Ps1Fate:
return Ps1Fate(carried=frozenset((statement,)), outermost=self._tree.depth_of(statement))
def dispose_as(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate:
"""
Where an error of *kind* that *statement* raised may end up, were it a member of *level*,
where the caller of the body passes errors outward exactly when *guarded*. Not kept, since
a caller may ask it of a statement *level* does not hold.
"""
traps = level.traps
passes_on = guarded if level.passes_on is None else level.passes_on
if kind is Ps1ErrorKind.CALLED:
if traps is None and not passes_on:
return _NOWHERE
kind = Ps1ErrorKind.STATEMENT
if traps is not None:
taken = self._taken_by(level, traps, guarded)
if taken.carries_on:
return taken.fate.joined(self._carried(statement))
return taken.fate
if passes_on or kind is Ps1ErrorKind.MARKED:
return self.leaving(level, kind, guarded)
fate = _NOWHERE
carries_on = False
for preference in level.preferences:
if preference is Ps1Preference.STOP:
fate = fate.joined(self.leaving(level, Ps1ErrorKind.MARKED, guarded))
elif preference is Ps1Preference.SILENT or kind is not Ps1ErrorKind.THROWN:
carries_on = True
else:
fate = fate.joined(self.leaving(level, kind, guarded))
if carries_on:
fate = fate.joined(self._carried(statement))
return fate
def _taken_by(self, level: Ps1Level, traps: Ps1TrapSelection, guarded: bool) -> _SetFate:
key = (id(level.owner), guarded)
found = self._sets.get(key)
if found is None:
found = self._sets[key] = self._take(level, traps, guarded)
return found
def _take(self, level: Ps1Level, traps: Ps1TrapSelection, guarded: bool) -> _SetFate:
tree = self._tree
fate = Ps1Fate(offered_to_traps=True)
resumes = False
for trap in traps.selectable:
acting = handler_acts(trap)
depth = tree.depth_of(trap.parent) if acting and trap.parent is not None else float('inf')
fate = fate.joined(Ps1Fate(trapped=frozenset((trap,)), acts=acting, outermost=depth))
if carries_on(tree.disposition(trap)):
resumes = True
if self.lets_past(level, traps):
fate = fate.joined(self.leaving(level, Ps1ErrorKind.MARKED, guarded))
return _SetFate(fate, resumes)
def lets_past(self, level: Ps1Level, traps: Ps1TrapSelection) -> bool:
"""
Whether the `trap` set *traps* that *level* declares may hand an error it is offered on
outward: no member takes every error, or a member 5.1 may select re-raises it, as a body
that runs off its end does where `Stop` may be in force.
"""
if not traps.takes_every_error():
return True
stops = Ps1Preference.STOP in level.preferences
return any(
Ps1TrapDisposition.RETHROWS in settled(self._tree.disposition(trap), stops)
for trap in traps.selectable
)
def leaving(self, level: Ps1Level, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate:
key = (id(level.owner), kind, guarded)
fate = self._leaving.get(key)
if fate is None:
fate = self._leaving[key] = self._leave(level, kind, guarded)
return fate
def _leave(self, level: Ps1Level, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate:
parent = level.parent
holder = level.holder
if parent is None or holder is None:
if level.called:
return Ps1Fate(to_caller=kind)
return Ps1Fate(ends_run=True)
if level.kind is Ps1LevelKind.TRAP:
return self._out_of_a_trap(parent, kind, guarded)
if level.kind is Ps1LevelKind.TRY:
assert isinstance(holder, Ps1TryCatchFinally)
return self._offered(parent, holder, kind, guarded)
fate = self.dispose(parent, holder, kind, guarded)
if level.kind is Ps1LevelKind.FINALLY:
assert isinstance(holder, Ps1TryCatchFinally)
fate = fate.joined(Ps1Fate(replaces=frozenset((holder,))))
return fate
def _out_of_a_trap(self, level: Ps1Level, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate:
"""
Where an error of *kind* raised in the body of a `trap` that *level* declares may end up.
It leaves *level*, except where the `trap` ran because no `catch` clause of a `try`
statement of *level* took an error: 5.1 runs the `trap` from inside that `try`, so the error
leaves the `try` statement, and *level* disposes of it as one the statement raised, which
may run the `trap` again. Every `trap` of *level* shares the answer.
"""
key = (id(level.owner), kind, guarded)
fate = self._out_of_traps.get(key)
if fate is None:
fate = self.leaving(level, kind, guarded)
for statement in _tries_a_trap_may_run_in(level.owner):
fate = fate.joined(self.dispose(level, statement, kind, guarded))
self._out_of_traps[key] = fate
return fate
def _offered(
self,
level: Ps1Level,
statement: Ps1TryCatchFinally,
kind: Ps1ErrorKind,
guarded: bool,
) -> Ps1Fate:
"""
Where an error of *kind* leaving the `try` body of *statement*, a member of *level*, may end
up: in any of its `catch` clauses, or, where none is certain to take it, wherever *level*
disposes of an error of the `try` statement itself.
5.1 matches the exception an error wraps before the error itself, against every clause, so
a clause naming `RuntimeException`, which every error is, may still lose a failed cast to a
later clause naming `InvalidCastException`: no clause is passed over.
"""
fate = _NOWHERE
depth = self._tree.depth_of(statement)
for clause in statement.catch_clauses:
empty = clause.body is None or not clause.body.body
fate = fate.joined(Ps1Fate(
caught=frozenset((clause,)),
swallowed=frozenset((statement,)) if empty else frozenset(),
acts=handler_acts(clause),
outermost=depth,
))
if swallows_every_error(statement):
return fate
return fate.joined(self.dispose(level, statement, kind, guarded))
class Ps1Lift:
"""
The body of one `catch` clause read as though its statements stood in place of the `try` the
clause belongs to, with the run carrying on after the `try` wherever it would carry on after
the last of them: the reading the `catch` lift asks whether an error raised in the body ends
up where it does now.
Two things change for such an error. What a statement of the body lets out is disposed of by
the list around the `try` rather than handed out of the `catch` body, which passes every error
outward; and the lists written inside the body run with errors passed outward only where the
list around the `try` passes them. Where it passes them whatever called the body,
`keeps_the_flag` holds, every list inside the body runs alike in both readings, and only the
first change is left: `member_alike` weighs it at the cost of one disposal per statement.
Otherwise `point_alike` reads a point through the lists read anew.
"""
def __init__(
self,
real: Ps1Reading,
tree: _Tree,
catch: Ps1Level,
around: Ps1Level,
body: Block,
statement: Ps1TryCatchFinally,
):
self._real = real
self._tree = tree
self._catch = catch
self._around = around
self._body = body
self._statement = statement
self._last = _last_statement(body)
self.keeps_the_flag = around.passes_on is True
self._lifted: Ps1Reading | None = None
def member_alike(self, member: Node, kinds: Ps1ErrorKind) -> bool:
"""
Whether an error of any of *kinds* that *member*, a statement of the body, raises or lets out
of a list it holds ends up where it does now. Read only where `keeps_the_flag` holds.
"""
if not self.keeps_the_flag:
raise ValueError('the lists inside the body run otherwise once it stands in place')
if member is self._last:
return True
real = self._real
for kind in ERROR_KINDS:
if kind not in kinds:
continue
for guarded in self._catch.guards():
if real.dispose(self._around, self._statement, kind, guarded).carried:
return False
return True
def point_alike(self, point: Node, kinds: Ps1ErrorKind) -> bool:
"""
Whether an error of any of *kinds* raised at *point*, inside the body, ends up where it does
now, under every value of the flag a caller of the body may have set.
"""
lifted = self._lifted
if lifted is None:
levels = _Levels(self._tree, self._body, self._around, self._real.levels)
lifted = self._lifted = Ps1Reading(self._tree, levels)
depth = self._tree.depth_of(self._statement)
for kind in ERROR_KINDS:
if kind not in kinds:
continue
for guarded in self._catch.guards():
before = self._real.fate(point, kind, guarded)
after = lifted.fate(point, kind, guarded)
if before is None or after is None:
return False
if self._last is not None:
after = after.renamed(self._last, self._statement, depth)
if before != after:
return False
return True
class Ps1Dispatch:
"""
The dispatch of one script's errors, read off its tree. *preferences* holds the values
`$ErrorActionPreference` may hold in the script's own body, *called_preferences* those it may
hold in a script block, which a call may hand a preference of its own.
A script block is read as a body some caller may guard: an error its lists hand outward leaves
it, and so may one its body would report, since a `try` around the call passes errors outward
through every body the call runs. Which callers guard a body is read off the call sites *sites*
names for it; without *sites*, every caller may. The script is read as run on its own, which
reports what its body would report. *dispositions* reads how the body of a `trap` ends, and
keeps its answers for as long as the tree is the one it read them from.
"""
def __init__(
self,
preferences: AbstractSet[Ps1Preference],
called_preferences: AbstractSet[Ps1Preference],
dispositions: Callable[[Ps1TrapStatement], Ps1TrapDisposition],
sites: Ps1CallSites | None = None,
):
self._sites = sites
self._guarded: set[int] | None = None
self._readings: list[Ps1Reading] | None = None
self._tree = _Tree(
frozenset(preferences) | {Ps1Preference.CONTINUE},
frozenset(called_preferences) | {Ps1Preference.CONTINUE},
self._body_guards,
dispositions,
)
self._evaluation = Ps1Reading(self._tree, _Levels(self._tree))
self._tails: dict[int, frozenset[Node]] = {}
self._jumping: dict[int, bool] = {}
self._jumps: dict[int, _Jump] = {}
def depth_of(self, node: Node) -> int:
"""
How many ancestors *node* has in its tree.
"""
return self._tree.depth_of(node)
def level_of(self, statement: Node) -> Ps1Level | None:
"""
The statement list *statement* is a member of, or `None` where it is a member of none.
"""
return self._evaluation.levels.level_of(statement)
def fate(self, point: Node, kinds: Ps1ErrorKind) -> Ps1Fate | None:
"""
Where an error of any of *kinds* raised at *point* may end up, under every caller of the
body, or `None` where *point* stands in no statement list this reads.
"""
return self._evaluation.fate(point, kinds)
def fate_under_any_caller(self, point: Node, kinds: Ps1ErrorKind) -> Ps1Fate | None:
"""
`fate`, read as though a caller of the body may pass errors outward or may not, which reads
no call site: the answer the control-flow builder draws from, since which calls run a body
is read off graphs drawn from that answer.
"""
evaluation = self._evaluation
found = evaluation.fate(point, kinds, False)
guarded = evaluation.fate(point, kinds, True)
if found is None or guarded is None:
return None
return found.joined(guarded)
def disposition(self, trap: Ps1TrapStatement) -> Ps1TrapDisposition:
"""
How the body of *trap* may end.
"""
return self._tree.disposition(trap)
def lets_past(self, owner: Node) -> bool:
"""
Whether the `trap` set the list *owner* holds declares may hand an error it is offered on
outward; see `Ps1Reading.lets_past`. A list declaring none lets every error past.
"""
level = self.level(owner)
traps = level.traps
return traps is None or self._evaluation.lets_past(level, traps)
def traps_run_inside_a_try(self, owner: Node) -> bool:
"""
Whether a `trap` of the list *owner* holds may run from inside one of its `try` statements,
for an error no `catch` clause of that statement took: an error the `trap` body raises then
leaves that `try` statement, and the list offers it to its `trap` set again.
"""
return bool(_tries_a_trap_may_run_in(owner))
def disposed(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind) -> Ps1Fate:
"""
Where an error of *kind* that *statement*, a member of *level*, raised may end up, under
every caller of the body.
"""
return self._evaluation.disposed(level, statement, kind)
def tail_of(self, statement: Node) -> frozenset[Node]:
"""
The statements a run on which *statement* completes goes on after doing nothing else on
the way, where every statement around it completes as well: *statement* itself, and where it
is the last of its list, the statement that list belongs to as well, and so on outward,
through lists that end where the statement holding them ends — the body of an `if`, a
`try`, a `catch` or a `finally`. A `finally` body entered with an error or a jump in flight
goes on with that instead, which is not a completion; see `jumps_through`. A `$( )` written
as the test of an `if` ends where the test is made, not where the `if` ends.
"""
found = self._tails.get(id(statement))
if found is None:
found = frozenset((statement,))
level = self.level_of(statement)
if (
level is not None
and level.holder is not None
and _last_statement(level.owner) is statement
and (
level.kind in (Ps1LevelKind.TRY, Ps1LevelKind.CATCH, Ps1LevelKind.FINALLY)
or (
isinstance(level.holder, Ps1IfStatement)
and isinstance(level.owner, Block)
and level.owner.parent is level.holder
)
)
):
found = found | self.tail_of(level.holder)
self._tails[id(statement)] = found
return found
def jumps_through(self, statement: Ps1TryCatchFinally) -> bool:
"""
Whether the `finally` body of *statement* may be entered with a jump in flight: a `return`
or an `exit` in the `try` body or one of its `catch` bodies, a `break` or `continue` there
that leaves it, or a call there that may run a body able to jump out of itself — which some
body of the script is where it writes an `exit` anywhere, or a `break` or `continue` that
leaves a script block, or runs code it does not spell. 5.1 carries the jump through the
`finally` body, and an error leaving that body takes its place.
"""
regions: list[Node] = [clause.body for clause in statement.catch_clauses if clause.body is not None]
if statement.try_block is not None:
regions.append(statement.try_block)
return any(self.may_jump_out_of(region) for region in regions)
def may_jump_out_of(self, node: Node) -> bool:
"""
Whether a jump may carry control out of *node* to somewhere other than what follows it: a
`return` or an `exit`, a `break` or `continue` nothing in *node* takes, or a call that may run
a body able to jump out of itself.
"""
leaving = self._leaving_jumps(node)
if leaving & ~_Jump.RUNS_CODE:
return True
return _Jump.RUNS_CODE in leaving and self._a_body_jumps_out(node)
def _leaving_jumps(self, node: Node) -> _Jump:
"""
The jumps that may carry control out of *node*, read bottom up and kept for every node: what
its children let out, less what a loop or `switch` takes, what a `trap` body turns into a
resumption or a re-raise, and the `return` of a script block; `RUNS_CODE` where something in
it may run a body that jumps on its own. A labelled `break` or `continue` is not matched
against the constructs it passes, and a script block lets out a `break` or `continue`
nothing in it takes, which 5.1 hands to the code that ran the block.
"""
memo = self._jumps
stack: list[tuple[Node, bool]] = [(node, False)]
while stack:
current, expanded = stack.pop()
if id(current) in memo:
continue
children = list(current.children())
if not expanded:
stack.append((current, True))
stack.extend((child, False) for child in children if id(child) not in memo)
continue
leaving = _Jump.NONE
for child in children:
leaving |= memo[id(child)]
if isinstance(current, Ps1ExitStatement):
leaving |= _Jump.EXIT
elif isinstance(current, Ps1ReturnStatement):
leaving |= _Jump.RETURN
elif isinstance(current, (Ps1BreakStatement, Ps1ContinueStatement)):
leaving |= _Jump.LABELLED if current.label is not None else _Jump.LOOP
elif isinstance(current, _LOOPS):
leaving &= ~_Jump.LOOP
elif isinstance(current, Ps1TrapStatement):
leaving &= ~(_Jump.LOOP | _Jump.LABELLED)
if isinstance(current, Ps1ScriptBlock):
leaving &= ~_Jump.RETURN
if _runs_code(current):
leaving |= _Jump.RUNS_CODE
memo[id(current)] = leaving
return memo[id(node)]
def _a_body_jumps_out(self, node: Node) -> bool:
"""
Whether some body of the tree *node* stands in may jump out of itself into the code that
ran it: a script block lets out an `exit`, or a `break` or `continue` nothing in it takes,
or the tree runs code it does not spell.
"""
root = tree_root(node)
found = self._jumping.get(id(root))
if found is None:
escaping = _Jump.EXIT | _Jump.LOOP | _Jump.LABELLED
found = self._jumping[id(root)] = any(
runs_code_it_cannot_read(inner)
or isinstance(inner, Ps1ScriptBlock) and bool(self._leaving_jumps(inner) & escaping)
for inner in root.walk()
)
return found
@property
def evaluation(self) -> Ps1Reading:
"""
The reading of the tree as it stands, under every value the error preference may hold.
"""
return self._evaluation
def _body_guards(self, owner: Node) -> tuple[bool, ...]:
"""
The values the outward flag may hold where the body holding the list *owner* was called.
Without call sites to read, every body may be called with the flag set.
"""
if self._sites is None:
return (False, True)
block = owner if isinstance(owner, Ps1ScriptBlock) else owner.parent
guarded = self._guarded
if guarded is None:
guarded = self._guarded = self._guarded_bodies(self._sites)
return (False, True) if block is None or id(block) in guarded else (False,)
def _guarded_bodies(self, sites: Ps1CallSites) -> set[int]:
"""
The bodies some call site may run with the outward flag set: one called from a site whose
list passes errors outward, from a site *sites* cannot name, or from a site whose list
leaves the flag as the caller of its own body set it, where that body is one of these. A
call *sites* cannot attribute is a site of every function body. One table for the tree, read
breadth-first rather than by recursion over the call chain.
"""
levels = self._evaluation.levels
direct: set[int] = set()
inherits: dict[int, list[int]] = {}
every_function = -1
def read(key: int, callers: Sequence[Node]) -> None:
for site in callers:
statement = member_of(site)
level = None if statement is None else levels.level_of(statement)
if level is None or level.passes_on is True:
direct.add(key)
return
if level.passes_on is None:
root = level
while root.parent is not None:
root = root.parent
owner = root.owner
caller = owner if isinstance(owner, Ps1ScriptBlock) else owner.parent
inherits.setdefault(id(caller), []).append(key)
functions: list[int] = []
for body in sites.bodies():
if isinstance(body.parent, Ps1FunctionDefinition):
functions.append(id(body))
named = sites.sites_running(body)
if named is None:
direct.add(id(body))
continue
read(id(body), named)
read(every_function, sites.unattributed_sites())
guarded: set[int] = set()
stack = list(direct)
while stack:
key = stack.pop()
if key in guarded:
continue
guarded.add(key)
if key == every_function:
stack.extend(functions)
continue
stack.extend(inherits.get(key, ()))
guarded.discard(every_function)
return guarded
def level(self, owner: Node) -> Ps1Level:
"""
The statement list *owner* holds.
"""
return self._evaluation.levels.level(owner)
def without(self, reading: Ps1Reading, owner: Node, removed: AbstractSet[int]) -> Ps1Reading:
"""
*reading* as though the `trap` statements of the list *owner* holds whose identities
*removed* names were not written.
"""
tree = reading.tree
traps = tree.traps_of(owner)
kept = [] if traps is None else [trap for trap in traps.members if id(trap) not in removed]
selection = Ps1TrapSelection(kept) if kept else None
levels = _Levels(tree, real=reading.levels, replaced=(owner, selection))
return Ps1Reading(tree, levels)
def readings(self) -> list[Ps1Reading]:
"""
One reading of the tree per pair of values the error preference may hold in the script and
in a called body, each under that pair alone. Comparing two readings joined over the values
would compare an error one value takes with one another value leaves.
"""
found = self._readings
if found is None:
tree = self._tree
if len(tree.preferences) == 1 and len(tree.called) == 1:
found = [self._evaluation]
else:
found = []
for preference in sorted(tree.preferences, key=lambda p: p.name):
for called in sorted(tree.called, key=lambda p: p.name):
single = tree.reading(preference, called)
found.append(Ps1Reading(single, _Levels(single)))
self._readings = found
return found
def lifted(self, clause: Ps1CatchClause) -> Ps1Lift | None:
"""
The body of *clause* read as standing in place of its `try`; `None` where it cannot stand
there: the clause has no body, its `try` stands in no statement list this reads, or the body
declares a `trap`, which would then guard the list around the `try`.
"""
body = clause.body
statement = clause.parent
if body is None or not isinstance(statement, Ps1TryCatchFinally):
return None
around = self.level_of(statement)
catch = self._evaluation.levels.level(body)
if around is None or catch.traps is not None:
return None
return Ps1Lift(self._evaluation, self._tree, catch, around, body, statement)
Global variables
var ERROR_KINDS-
Every kind of error, one at a time.
var HANDED_BACK-
What a call that may run a body of the script may hand back: whatever that body may raise.
var EVERY_KIND-
Every kind of error at once: what a point whose operands cannot be named may raise.
Functions
def preferences_of(stops, silenced)-
The values
$ErrorActionPreferencemay hold where a script may setStopand where it may set a value that silences an error. A script starts out reporting.Expand source code Browse git
def preferences_of(stops: bool, silenced: bool) -> frozenset[Ps1Preference]: """ The values `$ErrorActionPreference` may hold where a script may set `Stop` and where it may set a value that silences an error. A script starts out reporting. """ found = {Ps1Preference.CONTINUE} if stops: found.add(Ps1Preference.STOP) if silenced: found.add(Ps1Preference.SILENT) return frozenset(found) def member_of(node)-
The statement of a statement list that holds node, node itself where it is one.
Expand source code Browse git
def member_of(node: Node) -> Node | None: """ The statement of a statement list that holds *node*, *node* itself where it is one. """ cursor: Node | None = node while cursor is not None: parent = cursor.parent if parent is None: return None if _list_owner(parent) and not isinstance(cursor, Block): return cursor cursor = parent return None
Classes
class Ps1ErrorKind (*args, **kwds)-
The kinds of error 5.1 disposes of differently.
Expand source code Browse git
class Ps1ErrorKind(enum.Flag): """ The kinds of error 5.1 disposes of differently. """ NONE = 0 #: A statement-terminating error, such as a failed cast: reported where nothing is in force, and #: the run carries on with the next statement. STATEMENT = enum.auto() #: The error of a `throw`: where nothing is in force it ends the run, unless the preference is #: `SilentlyContinue`. THROWN = enum.auto() #: An error no list reports: one a command told to stop raises, and one `Stop`, a `trap` that #: re-raises or a `trap` set that declines has handed on. Only a `catch` or a `trap` takes it. MARKED = enum.auto() #: A statement-terminating error a body the point runs would report: it reaches the list the #: point stands in only while the engine passes errors outward there, which it does through #: every body a `try`, a `catch` body or a list that declares a `trap` runs, and is a #: statement-terminating error of that list once it does. Elsewhere the body reported it and #: the point completed. CALLED = enum.auto()Ancestors
- enum.Flag
- enum.Enum
Class variables
var NONE-
The type of the None singleton.
var STATEMENT-
A statement-terminating error, such as a failed cast: reported where nothing is in force, and the run carries on with the next statement.
var THROWN-
The error of a
throw: where nothing is in force it ends the run, unless the preference isSilentlyContinue. var MARKED-
An error no list reports: one a command told to stop raises, and one
Stop, atrapthat re-raises or atrapset that declines has handed on. Only acatchor atraptakes it. var CALLED-
A statement-terminating error a body the point runs would report: it reaches the list the point stands in only while the engine passes errors outward there, which it does through every body a
try, acatchbody or a list that declares atrapruns, and is a statement-terminating error of that list once it does. Elsewhere the body reported it and the point completed.
class Ps1Preference (*args, **kwds)-
The values of
$ErrorActionPreferencethat dispose of an error differently:CONTINUEstands for every value that reports it.Expand source code Browse git
class Ps1Preference(enum.Enum): """ The values of `$ErrorActionPreference` that dispose of an error differently: `CONTINUE` stands for every value that reports it. """ CONTINUE = enum.auto() SILENT = enum.auto() STOP = enum.auto()Ancestors
- enum.Enum
Class variables
var CONTINUE-
The type of the None singleton.
var SILENT-
The type of the None singleton.
var STOP-
The type of the None singleton.
class Ps1LevelKind (*args, **kwds)-
What runs a statement list, which decides where an error leaving it goes and whether the engine passes errors outward while it runs.
Expand source code Browse git
class Ps1LevelKind(enum.Enum): """ What runs a statement list, which decides where an error leaving it goes and whether the engine passes errors outward while it runs. """ #: The statements of a script, a script block or one of its named blocks. BODY = enum.auto() #: The body of an `if`, a loop or a `switch` clause, or the statements of a `$( )` or `@( )`. NESTED = enum.auto() TRY = enum.auto() CATCH = enum.auto() FINALLY = enum.auto() TRAP = enum.auto()Ancestors
- enum.Enum
Class variables
var BODY-
The statements of a script, a script block or one of its named blocks.
var NESTED-
The body of an
if, a loop or aswitchclause, or the statements of a$( )or@( ). var TRY-
The type of the None singleton.
var CATCH-
The type of the None singleton.
var FINALLY-
The type of the None singleton.
var TRAP-
The type of the None singleton.
class Ps1Level (kind, owner, parent, holder, traps, passes_on, called, preferences, guarding=<function Ps1Level.<lambda>>)-
One statement list as 5.1 runs it, owner being the node that holds it. parent is the list around it and holder the statement of that list it belongs to: the
tryfor itstry,catchandfinallybodies, thetrapfor atrapbody, theif, loop,switchor statement holding a$( )for a nested list. A body has neither. traps is the set oftrapstatements the list declares. passes_on is whether the engine passes errors outward while the statements run, orNonewhere that is whatever the caller of the body set: atryaround a call passes them outward through every body the call runs. called is whether the list belongs to a script block, which a caller runs, rather than to the script, and preferences holds the values the error preference may hold while the statements run. guarding reads the values the outward flag may hold where the body holding the list was called.Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding()Ancestors
- builtins.tuple
Instance variables
var kind-
Alias for field number 0
Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding() var owner-
Alias for field number 1
Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding() var parent-
Alias for field number 2
Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding() var holder-
Alias for field number 3
Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding() var traps-
Alias for field number 4
Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding() var passes_on-
Alias for field number 5
Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding() var called-
Alias for field number 6
Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding() var preferences-
Alias for field number 7
Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding() var guarding-
Alias for field number 8
Expand source code Browse git
class Ps1Level(NamedTuple): """ One statement list as 5.1 runs it, *owner* being the node that holds it. *parent* is the list around it and *holder* the statement of that list it belongs to: the `try` for its `try`, `catch` and `finally` bodies, the `trap` for a `trap` body, the `if`, loop, `switch` or statement holding a `$( )` for a nested list. A body has neither. *traps* is the set of `trap` statements the list declares. *passes_on* is whether the engine passes errors outward while the statements run, or `None` where that is whatever the caller of the body set: a `try` around a call passes them outward through every body the call runs. *called* is whether the list belongs to a script block, which a caller runs, rather than to the script, and *preferences* holds the values the error preference may hold while the statements run. *guarding* reads the values the outward flag may hold where the body holding the list was called. """ kind: Ps1LevelKind owner: Node parent: Ps1Level | None holder: Node | None traps: Ps1TrapSelection | None passes_on: bool | None called: bool preferences: frozenset[Ps1Preference] guarding: Callable[[], tuple[bool, ...]] = lambda: (False,) def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding()
Methods
def guards(self)-
The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set.
Expand source code Browse git
def guards(self) -> tuple[bool, ...]: """ The values the flag that passes errors outward may hold where the body this list belongs to was called: unset for the script; for a script block, set as well only where some call that may run it stands where the flag is set. """ return self.guarding()
class Ps1Fate (carried=frozenset(), caught=frozenset(), swallowed=frozenset(), trapped=frozenset(), acts=False, offered_to_traps=False, ends_run=False, to_caller=Ps1ErrorKind.NONE, replaces=frozenset(), outermost=inf)-
Every place an error may end up.
- carried: the statements the run may carry on after, each a member of the list that took the
error, whether it reported it, silenced it or ran a
trapthat carried on; - caught: the
catchclauses that may take it; - swallowed: the
trystatements whosecatchclause takes it with an empty body, after which the run carries on after thetry; - trapped: the
trapstatements that may run on it; - acts: whether one of those clauses or
trapstatements does something a run can see; - offered_to_traps: whether a
trapset may be offered it, whether it takes it or not; - ends_run: whether it may leave the script, which ends the run;
- to_caller: the kinds it may leave a script block with, handed to whatever called it;
- replaces: the
trystatements whosefinallybody it may leave, taking the place of what was in flight there; - outermost: the depth in the tree of the outermost statement the run may carry on after, or
whose
catchor actingtrapmay take it.
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), )Ancestors
- builtins.tuple
Instance variables
var carried-
Alias for field number 0
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var caught-
Alias for field number 1
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var swallowed-
Alias for field number 2
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var trapped-
Alias for field number 3
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var acts-
Alias for field number 4
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var offered_to_traps-
Alias for field number 5
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var ends_run-
Alias for field number 6
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var to_caller-
Alias for field number 7
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var replaces-
Alias for field number 8
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var outermost-
Alias for field number 9
Expand source code Browse git
class Ps1Fate(NamedTuple): """ Every place an error may end up. - *carried*: the statements the run may carry on after, each a member of the list that took the error, whether it reported it, silenced it or ran a `trap` that carried on; - *caught*: the `catch` clauses that may take it; - *swallowed*: the `try` statements whose `catch` clause takes it with an empty body, after which the run carries on after the `try`; - *trapped*: the `trap` statements that may run on it; - *acts*: whether one of those clauses or `trap` statements does something a run can see; - *offered_to_traps*: whether a `trap` set may be offered it, whether it takes it or not; - *ends_run*: whether it may leave the script, which ends the run; - *to_caller*: the kinds it may leave a script block with, handed to whatever called it; - *replaces*: the `try` statements whose `finally` body it may leave, taking the place of what was in flight there; - *outermost*: the depth in the tree of the outermost statement the run may carry on after, or whose `catch` or acting `trap` may take it. """ carried: frozenset[Node] = frozenset() caught: frozenset[Ps1CatchClause] = frozenset() swallowed: frozenset[Ps1TryCatchFinally] = frozenset() trapped: frozenset[Ps1TrapStatement] = frozenset() acts: bool = False offered_to_traps: bool = False ends_run: bool = False to_caller: Ps1ErrorKind = Ps1ErrorKind.NONE replaces: frozenset[Ps1TryCatchFinally] = frozenset() outermost: float = float('inf') @property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller) def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), ) var leaves-
Whether the error may get past every handler of the body it was raised in.
Expand source code Browse git
@property def leaves(self) -> bool: """ Whether the error may get past every handler of the body it was raised in. """ return self.ends_run or bool(self.to_caller)
Methods
def joined(self, other)-
The fate of an error that may end up as this one or as other does.
Expand source code Browse git
def joined(self, other: Ps1Fate) -> Ps1Fate: """ The fate of an error that may end up as this one or as *other* does. """ if other is _NOWHERE: return self if self is _NOWHERE: return other return Ps1Fate( self.carried | other.carried, self.caught | other.caught, self.swallowed | other.swallowed, _union(self.trapped, other.trapped), self.acts or other.acts, self.offered_to_traps or other.offered_to_traps, self.ends_run or other.ends_run, self.to_caller | other.to_caller, self.replaces | other.replaces, min(self.outermost, other.outermost), ) def renamed(self, statement, to, depth)-
This fate with the run carrying on after to, at depth in the tree, where it carries on after statement.
Expand source code Browse git
def renamed(self, statement: Node, to: Node, depth: int) -> Ps1Fate: """ This fate with the run carrying on after *to*, at *depth* in the tree, where it carries on after *statement*. """ if statement not in self.carried: return self return self._replace( carried=self.carried - {statement} | {to}, outermost=min(self.outermost, depth), )
- carried: the statements the run may carry on after, each a member of the list that took the
error, whether it reported it, silenced it or ran a
class Ps1CallSites (*args, **kwargs)-
The calls that may run each body of a script, which decide whether a caller may run it with the outward flag set.
Expand source code Browse git
class Ps1CallSites(Protocol): """ The calls that may run each body of a script, which decide whether a caller may run it with the outward flag set. """ def bodies(self) -> Iterable[Node]: """ Every script block of the script. """ ... def sites_running(self, body: Node) -> Sequence[Node] | None: """ The calls naming *body* that may run it, or `None` where its callers cannot all be named. """ ... def unattributed_sites(self) -> Sequence[Node]: """ The calls that run code nobody can read, each of which may run any function body. """ ...Ancestors
- typing.Protocol
- typing.Generic
Methods
def bodies(self)-
Every script block of the script.
Expand source code Browse git
def bodies(self) -> Iterable[Node]: """ Every script block of the script. """ ... def sites_running(self, body)-
The calls naming body that may run it, or
Nonewhere its callers cannot all be named.Expand source code Browse git
def sites_running(self, body: Node) -> Sequence[Node] | None: """ The calls naming *body* that may run it, or `None` where its callers cannot all be named. """ ... def unattributed_sites(self)-
The calls that run code nobody can read, each of which may run any function body.
Expand source code Browse git
def unattributed_sites(self) -> Sequence[Node]: """ The calls that run code nobody can read, each of which may run any function body. """ ...
class Ps1Reading (tree, levels)-
The rules of the module docstring applied over one reading of the lists, levels, with every answer kept for that reading.
Expand source code Browse git
class Ps1Reading: """ The rules of the module docstring applied over one reading of the lists, *levels*, with every answer kept for that reading. """ def __init__(self, tree: _Tree, levels: _Levels): self.tree = tree self._tree = tree self.levels = levels self._leaving: dict[tuple[int, Ps1ErrorKind, bool], Ps1Fate] = {} self._disposed: dict[tuple[int, Ps1ErrorKind, bool], Ps1Fate] = {} self._sets: dict[tuple[int, bool], _SetFate] = {} self._out_of_traps: dict[tuple[int, Ps1ErrorKind, bool], Ps1Fate] = {} def placed(self, point: Node) -> tuple[Ps1Level, Node] | None: statement = member_of(point) if statement is None: return None level = self.levels.level_of(statement) if level is None: return None return level, statement def fate(self, point: Node, kinds: Ps1ErrorKind, guarded: bool | None = None) -> Ps1Fate | None: placed = self.placed(point) if placed is None: return None level, statement = placed fate = _NOWHERE for kind in ERROR_KINDS: if kind in kinds: if guarded is None: fate = fate.joined(self.disposed(level, statement, kind)) else: fate = fate.joined(self.dispose(level, statement, kind, guarded)) return fate def disposed(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind) -> Ps1Fate: fate = _NOWHERE for guarded in level.guards(): fate = fate.joined(self.dispose(level, statement, kind, guarded)) return fate def dispose(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate: key = (id(statement), kind, guarded) fate = self._disposed.get(key) if fate is None: fate = self._disposed[key] = self.dispose_as(level, statement, kind, guarded) return fate def _carried(self, statement: Node) -> Ps1Fate: return Ps1Fate(carried=frozenset((statement,)), outermost=self._tree.depth_of(statement)) def dispose_as(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate: """ Where an error of *kind* that *statement* raised may end up, were it a member of *level*, where the caller of the body passes errors outward exactly when *guarded*. Not kept, since a caller may ask it of a statement *level* does not hold. """ traps = level.traps passes_on = guarded if level.passes_on is None else level.passes_on if kind is Ps1ErrorKind.CALLED: if traps is None and not passes_on: return _NOWHERE kind = Ps1ErrorKind.STATEMENT if traps is not None: taken = self._taken_by(level, traps, guarded) if taken.carries_on: return taken.fate.joined(self._carried(statement)) return taken.fate if passes_on or kind is Ps1ErrorKind.MARKED: return self.leaving(level, kind, guarded) fate = _NOWHERE carries_on = False for preference in level.preferences: if preference is Ps1Preference.STOP: fate = fate.joined(self.leaving(level, Ps1ErrorKind.MARKED, guarded)) elif preference is Ps1Preference.SILENT or kind is not Ps1ErrorKind.THROWN: carries_on = True else: fate = fate.joined(self.leaving(level, kind, guarded)) if carries_on: fate = fate.joined(self._carried(statement)) return fate def _taken_by(self, level: Ps1Level, traps: Ps1TrapSelection, guarded: bool) -> _SetFate: key = (id(level.owner), guarded) found = self._sets.get(key) if found is None: found = self._sets[key] = self._take(level, traps, guarded) return found def _take(self, level: Ps1Level, traps: Ps1TrapSelection, guarded: bool) -> _SetFate: tree = self._tree fate = Ps1Fate(offered_to_traps=True) resumes = False for trap in traps.selectable: acting = handler_acts(trap) depth = tree.depth_of(trap.parent) if acting and trap.parent is not None else float('inf') fate = fate.joined(Ps1Fate(trapped=frozenset((trap,)), acts=acting, outermost=depth)) if carries_on(tree.disposition(trap)): resumes = True if self.lets_past(level, traps): fate = fate.joined(self.leaving(level, Ps1ErrorKind.MARKED, guarded)) return _SetFate(fate, resumes) def lets_past(self, level: Ps1Level, traps: Ps1TrapSelection) -> bool: """ Whether the `trap` set *traps* that *level* declares may hand an error it is offered on outward: no member takes every error, or a member 5.1 may select re-raises it, as a body that runs off its end does where `Stop` may be in force. """ if not traps.takes_every_error(): return True stops = Ps1Preference.STOP in level.preferences return any( Ps1TrapDisposition.RETHROWS in settled(self._tree.disposition(trap), stops) for trap in traps.selectable ) def leaving(self, level: Ps1Level, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate: key = (id(level.owner), kind, guarded) fate = self._leaving.get(key) if fate is None: fate = self._leaving[key] = self._leave(level, kind, guarded) return fate def _leave(self, level: Ps1Level, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate: parent = level.parent holder = level.holder if parent is None or holder is None: if level.called: return Ps1Fate(to_caller=kind) return Ps1Fate(ends_run=True) if level.kind is Ps1LevelKind.TRAP: return self._out_of_a_trap(parent, kind, guarded) if level.kind is Ps1LevelKind.TRY: assert isinstance(holder, Ps1TryCatchFinally) return self._offered(parent, holder, kind, guarded) fate = self.dispose(parent, holder, kind, guarded) if level.kind is Ps1LevelKind.FINALLY: assert isinstance(holder, Ps1TryCatchFinally) fate = fate.joined(Ps1Fate(replaces=frozenset((holder,)))) return fate def _out_of_a_trap(self, level: Ps1Level, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate: """ Where an error of *kind* raised in the body of a `trap` that *level* declares may end up. It leaves *level*, except where the `trap` ran because no `catch` clause of a `try` statement of *level* took an error: 5.1 runs the `trap` from inside that `try`, so the error leaves the `try` statement, and *level* disposes of it as one the statement raised, which may run the `trap` again. Every `trap` of *level* shares the answer. """ key = (id(level.owner), kind, guarded) fate = self._out_of_traps.get(key) if fate is None: fate = self.leaving(level, kind, guarded) for statement in _tries_a_trap_may_run_in(level.owner): fate = fate.joined(self.dispose(level, statement, kind, guarded)) self._out_of_traps[key] = fate return fate def _offered( self, level: Ps1Level, statement: Ps1TryCatchFinally, kind: Ps1ErrorKind, guarded: bool, ) -> Ps1Fate: """ Where an error of *kind* leaving the `try` body of *statement*, a member of *level*, may end up: in any of its `catch` clauses, or, where none is certain to take it, wherever *level* disposes of an error of the `try` statement itself. 5.1 matches the exception an error wraps before the error itself, against every clause, so a clause naming `RuntimeException`, which every error is, may still lose a failed cast to a later clause naming `InvalidCastException`: no clause is passed over. """ fate = _NOWHERE depth = self._tree.depth_of(statement) for clause in statement.catch_clauses: empty = clause.body is None or not clause.body.body fate = fate.joined(Ps1Fate( caught=frozenset((clause,)), swallowed=frozenset((statement,)) if empty else frozenset(), acts=handler_acts(clause), outermost=depth, )) if swallows_every_error(statement): return fate return fate.joined(self.dispose(level, statement, kind, guarded))Methods
def placed(self, point)-
Expand source code Browse git
def placed(self, point: Node) -> tuple[Ps1Level, Node] | None: statement = member_of(point) if statement is None: return None level = self.levels.level_of(statement) if level is None: return None return level, statement def fate(self, point, kinds, guarded=None)-
Expand source code Browse git
def fate(self, point: Node, kinds: Ps1ErrorKind, guarded: bool | None = None) -> Ps1Fate | None: placed = self.placed(point) if placed is None: return None level, statement = placed fate = _NOWHERE for kind in ERROR_KINDS: if kind in kinds: if guarded is None: fate = fate.joined(self.disposed(level, statement, kind)) else: fate = fate.joined(self.dispose(level, statement, kind, guarded)) return fate def disposed(self, level, statement, kind)-
Expand source code Browse git
def disposed(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind) -> Ps1Fate: fate = _NOWHERE for guarded in level.guards(): fate = fate.joined(self.dispose(level, statement, kind, guarded)) return fate def dispose(self, level, statement, kind, guarded)-
Expand source code Browse git
def dispose(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate: key = (id(statement), kind, guarded) fate = self._disposed.get(key) if fate is None: fate = self._disposed[key] = self.dispose_as(level, statement, kind, guarded) return fate def dispose_as(self, level, statement, kind, guarded)-
Where an error of kind that statement raised may end up, were it a member of level, where the caller of the body passes errors outward exactly when guarded. Not kept, since a caller may ask it of a statement level does not hold.
Expand source code Browse git
def dispose_as(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate: """ Where an error of *kind* that *statement* raised may end up, were it a member of *level*, where the caller of the body passes errors outward exactly when *guarded*. Not kept, since a caller may ask it of a statement *level* does not hold. """ traps = level.traps passes_on = guarded if level.passes_on is None else level.passes_on if kind is Ps1ErrorKind.CALLED: if traps is None and not passes_on: return _NOWHERE kind = Ps1ErrorKind.STATEMENT if traps is not None: taken = self._taken_by(level, traps, guarded) if taken.carries_on: return taken.fate.joined(self._carried(statement)) return taken.fate if passes_on or kind is Ps1ErrorKind.MARKED: return self.leaving(level, kind, guarded) fate = _NOWHERE carries_on = False for preference in level.preferences: if preference is Ps1Preference.STOP: fate = fate.joined(self.leaving(level, Ps1ErrorKind.MARKED, guarded)) elif preference is Ps1Preference.SILENT or kind is not Ps1ErrorKind.THROWN: carries_on = True else: fate = fate.joined(self.leaving(level, kind, guarded)) if carries_on: fate = fate.joined(self._carried(statement)) return fate def lets_past(self, level, traps)-
Whether the
trapset traps that level declares may hand an error it is offered on outward: no member takes every error, or a member 5.1 may select re-raises it, as a body that runs off its end does whereStopmay be in force.Expand source code Browse git
def lets_past(self, level: Ps1Level, traps: Ps1TrapSelection) -> bool: """ Whether the `trap` set *traps* that *level* declares may hand an error it is offered on outward: no member takes every error, or a member 5.1 may select re-raises it, as a body that runs off its end does where `Stop` may be in force. """ if not traps.takes_every_error(): return True stops = Ps1Preference.STOP in level.preferences return any( Ps1TrapDisposition.RETHROWS in settled(self._tree.disposition(trap), stops) for trap in traps.selectable ) def leaving(self, level, kind, guarded)-
Expand source code Browse git
def leaving(self, level: Ps1Level, kind: Ps1ErrorKind, guarded: bool) -> Ps1Fate: key = (id(level.owner), kind, guarded) fate = self._leaving.get(key) if fate is None: fate = self._leaving[key] = self._leave(level, kind, guarded) return fate
class Ps1Lift (real, tree, catch, around, body, statement)-
The body of one
catchclause read as though its statements stood in place of thetrythe clause belongs to, with the run carrying on after thetrywherever it would carry on after the last of them: the reading thecatchlift asks whether an error raised in the body ends up where it does now.Two things change for such an error. What a statement of the body lets out is disposed of by the list around the
tryrather than handed out of thecatchbody, which passes every error outward; and the lists written inside the body run with errors passed outward only where the list around thetrypasses them. Where it passes them whatever called the body,keeps_the_flagholds, every list inside the body runs alike in both readings, and only the first change is left:member_alikeweighs it at the cost of one disposal per statement. Otherwisepoint_alikereads a point through the lists read anew.Expand source code Browse git
class Ps1Lift: """ The body of one `catch` clause read as though its statements stood in place of the `try` the clause belongs to, with the run carrying on after the `try` wherever it would carry on after the last of them: the reading the `catch` lift asks whether an error raised in the body ends up where it does now. Two things change for such an error. What a statement of the body lets out is disposed of by the list around the `try` rather than handed out of the `catch` body, which passes every error outward; and the lists written inside the body run with errors passed outward only where the list around the `try` passes them. Where it passes them whatever called the body, `keeps_the_flag` holds, every list inside the body runs alike in both readings, and only the first change is left: `member_alike` weighs it at the cost of one disposal per statement. Otherwise `point_alike` reads a point through the lists read anew. """ def __init__( self, real: Ps1Reading, tree: _Tree, catch: Ps1Level, around: Ps1Level, body: Block, statement: Ps1TryCatchFinally, ): self._real = real self._tree = tree self._catch = catch self._around = around self._body = body self._statement = statement self._last = _last_statement(body) self.keeps_the_flag = around.passes_on is True self._lifted: Ps1Reading | None = None def member_alike(self, member: Node, kinds: Ps1ErrorKind) -> bool: """ Whether an error of any of *kinds* that *member*, a statement of the body, raises or lets out of a list it holds ends up where it does now. Read only where `keeps_the_flag` holds. """ if not self.keeps_the_flag: raise ValueError('the lists inside the body run otherwise once it stands in place') if member is self._last: return True real = self._real for kind in ERROR_KINDS: if kind not in kinds: continue for guarded in self._catch.guards(): if real.dispose(self._around, self._statement, kind, guarded).carried: return False return True def point_alike(self, point: Node, kinds: Ps1ErrorKind) -> bool: """ Whether an error of any of *kinds* raised at *point*, inside the body, ends up where it does now, under every value of the flag a caller of the body may have set. """ lifted = self._lifted if lifted is None: levels = _Levels(self._tree, self._body, self._around, self._real.levels) lifted = self._lifted = Ps1Reading(self._tree, levels) depth = self._tree.depth_of(self._statement) for kind in ERROR_KINDS: if kind not in kinds: continue for guarded in self._catch.guards(): before = self._real.fate(point, kind, guarded) after = lifted.fate(point, kind, guarded) if before is None or after is None: return False if self._last is not None: after = after.renamed(self._last, self._statement, depth) if before != after: return False return TrueMethods
def member_alike(self, member, kinds)-
Whether an error of any of kinds that member, a statement of the body, raises or lets out of a list it holds ends up where it does now. Read only where
keeps_the_flagholds.Expand source code Browse git
def member_alike(self, member: Node, kinds: Ps1ErrorKind) -> bool: """ Whether an error of any of *kinds* that *member*, a statement of the body, raises or lets out of a list it holds ends up where it does now. Read only where `keeps_the_flag` holds. """ if not self.keeps_the_flag: raise ValueError('the lists inside the body run otherwise once it stands in place') if member is self._last: return True real = self._real for kind in ERROR_KINDS: if kind not in kinds: continue for guarded in self._catch.guards(): if real.dispose(self._around, self._statement, kind, guarded).carried: return False return True def point_alike(self, point, kinds)-
Whether an error of any of kinds raised at point, inside the body, ends up where it does now, under every value of the flag a caller of the body may have set.
Expand source code Browse git
def point_alike(self, point: Node, kinds: Ps1ErrorKind) -> bool: """ Whether an error of any of *kinds* raised at *point*, inside the body, ends up where it does now, under every value of the flag a caller of the body may have set. """ lifted = self._lifted if lifted is None: levels = _Levels(self._tree, self._body, self._around, self._real.levels) lifted = self._lifted = Ps1Reading(self._tree, levels) depth = self._tree.depth_of(self._statement) for kind in ERROR_KINDS: if kind not in kinds: continue for guarded in self._catch.guards(): before = self._real.fate(point, kind, guarded) after = lifted.fate(point, kind, guarded) if before is None or after is None: return False if self._last is not None: after = after.renamed(self._last, self._statement, depth) if before != after: return False return True
class Ps1Dispatch (preferences, called_preferences, dispositions, sites=None)-
The dispatch of one script's errors, read off its tree. preferences holds the values
$ErrorActionPreferencemay hold in the script's own body, called_preferences those it may hold in a script block, which a call may hand a preference of its own.A script block is read as a body some caller may guard: an error its lists hand outward leaves it, and so may one its body would report, since a
tryaround the call passes errors outward through every body the call runs. Which callers guard a body is read off the call sites sites names for it; without sites, every caller may. The script is read as run on its own, which reports what its body would report. dispositions reads how the body of atrapends, and keeps its answers for as long as the tree is the one it read them from.Expand source code Browse git
class Ps1Dispatch: """ The dispatch of one script's errors, read off its tree. *preferences* holds the values `$ErrorActionPreference` may hold in the script's own body, *called_preferences* those it may hold in a script block, which a call may hand a preference of its own. A script block is read as a body some caller may guard: an error its lists hand outward leaves it, and so may one its body would report, since a `try` around the call passes errors outward through every body the call runs. Which callers guard a body is read off the call sites *sites* names for it; without *sites*, every caller may. The script is read as run on its own, which reports what its body would report. *dispositions* reads how the body of a `trap` ends, and keeps its answers for as long as the tree is the one it read them from. """ def __init__( self, preferences: AbstractSet[Ps1Preference], called_preferences: AbstractSet[Ps1Preference], dispositions: Callable[[Ps1TrapStatement], Ps1TrapDisposition], sites: Ps1CallSites | None = None, ): self._sites = sites self._guarded: set[int] | None = None self._readings: list[Ps1Reading] | None = None self._tree = _Tree( frozenset(preferences) | {Ps1Preference.CONTINUE}, frozenset(called_preferences) | {Ps1Preference.CONTINUE}, self._body_guards, dispositions, ) self._evaluation = Ps1Reading(self._tree, _Levels(self._tree)) self._tails: dict[int, frozenset[Node]] = {} self._jumping: dict[int, bool] = {} self._jumps: dict[int, _Jump] = {} def depth_of(self, node: Node) -> int: """ How many ancestors *node* has in its tree. """ return self._tree.depth_of(node) def level_of(self, statement: Node) -> Ps1Level | None: """ The statement list *statement* is a member of, or `None` where it is a member of none. """ return self._evaluation.levels.level_of(statement) def fate(self, point: Node, kinds: Ps1ErrorKind) -> Ps1Fate | None: """ Where an error of any of *kinds* raised at *point* may end up, under every caller of the body, or `None` where *point* stands in no statement list this reads. """ return self._evaluation.fate(point, kinds) def fate_under_any_caller(self, point: Node, kinds: Ps1ErrorKind) -> Ps1Fate | None: """ `fate`, read as though a caller of the body may pass errors outward or may not, which reads no call site: the answer the control-flow builder draws from, since which calls run a body is read off graphs drawn from that answer. """ evaluation = self._evaluation found = evaluation.fate(point, kinds, False) guarded = evaluation.fate(point, kinds, True) if found is None or guarded is None: return None return found.joined(guarded) def disposition(self, trap: Ps1TrapStatement) -> Ps1TrapDisposition: """ How the body of *trap* may end. """ return self._tree.disposition(trap) def lets_past(self, owner: Node) -> bool: """ Whether the `trap` set the list *owner* holds declares may hand an error it is offered on outward; see `Ps1Reading.lets_past`. A list declaring none lets every error past. """ level = self.level(owner) traps = level.traps return traps is None or self._evaluation.lets_past(level, traps) def traps_run_inside_a_try(self, owner: Node) -> bool: """ Whether a `trap` of the list *owner* holds may run from inside one of its `try` statements, for an error no `catch` clause of that statement took: an error the `trap` body raises then leaves that `try` statement, and the list offers it to its `trap` set again. """ return bool(_tries_a_trap_may_run_in(owner)) def disposed(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind) -> Ps1Fate: """ Where an error of *kind* that *statement*, a member of *level*, raised may end up, under every caller of the body. """ return self._evaluation.disposed(level, statement, kind) def tail_of(self, statement: Node) -> frozenset[Node]: """ The statements a run on which *statement* completes goes on after doing nothing else on the way, where every statement around it completes as well: *statement* itself, and where it is the last of its list, the statement that list belongs to as well, and so on outward, through lists that end where the statement holding them ends — the body of an `if`, a `try`, a `catch` or a `finally`. A `finally` body entered with an error or a jump in flight goes on with that instead, which is not a completion; see `jumps_through`. A `$( )` written as the test of an `if` ends where the test is made, not where the `if` ends. """ found = self._tails.get(id(statement)) if found is None: found = frozenset((statement,)) level = self.level_of(statement) if ( level is not None and level.holder is not None and _last_statement(level.owner) is statement and ( level.kind in (Ps1LevelKind.TRY, Ps1LevelKind.CATCH, Ps1LevelKind.FINALLY) or ( isinstance(level.holder, Ps1IfStatement) and isinstance(level.owner, Block) and level.owner.parent is level.holder ) ) ): found = found | self.tail_of(level.holder) self._tails[id(statement)] = found return found def jumps_through(self, statement: Ps1TryCatchFinally) -> bool: """ Whether the `finally` body of *statement* may be entered with a jump in flight: a `return` or an `exit` in the `try` body or one of its `catch` bodies, a `break` or `continue` there that leaves it, or a call there that may run a body able to jump out of itself — which some body of the script is where it writes an `exit` anywhere, or a `break` or `continue` that leaves a script block, or runs code it does not spell. 5.1 carries the jump through the `finally` body, and an error leaving that body takes its place. """ regions: list[Node] = [clause.body for clause in statement.catch_clauses if clause.body is not None] if statement.try_block is not None: regions.append(statement.try_block) return any(self.may_jump_out_of(region) for region in regions) def may_jump_out_of(self, node: Node) -> bool: """ Whether a jump may carry control out of *node* to somewhere other than what follows it: a `return` or an `exit`, a `break` or `continue` nothing in *node* takes, or a call that may run a body able to jump out of itself. """ leaving = self._leaving_jumps(node) if leaving & ~_Jump.RUNS_CODE: return True return _Jump.RUNS_CODE in leaving and self._a_body_jumps_out(node) def _leaving_jumps(self, node: Node) -> _Jump: """ The jumps that may carry control out of *node*, read bottom up and kept for every node: what its children let out, less what a loop or `switch` takes, what a `trap` body turns into a resumption or a re-raise, and the `return` of a script block; `RUNS_CODE` where something in it may run a body that jumps on its own. A labelled `break` or `continue` is not matched against the constructs it passes, and a script block lets out a `break` or `continue` nothing in it takes, which 5.1 hands to the code that ran the block. """ memo = self._jumps stack: list[tuple[Node, bool]] = [(node, False)] while stack: current, expanded = stack.pop() if id(current) in memo: continue children = list(current.children()) if not expanded: stack.append((current, True)) stack.extend((child, False) for child in children if id(child) not in memo) continue leaving = _Jump.NONE for child in children: leaving |= memo[id(child)] if isinstance(current, Ps1ExitStatement): leaving |= _Jump.EXIT elif isinstance(current, Ps1ReturnStatement): leaving |= _Jump.RETURN elif isinstance(current, (Ps1BreakStatement, Ps1ContinueStatement)): leaving |= _Jump.LABELLED if current.label is not None else _Jump.LOOP elif isinstance(current, _LOOPS): leaving &= ~_Jump.LOOP elif isinstance(current, Ps1TrapStatement): leaving &= ~(_Jump.LOOP | _Jump.LABELLED) if isinstance(current, Ps1ScriptBlock): leaving &= ~_Jump.RETURN if _runs_code(current): leaving |= _Jump.RUNS_CODE memo[id(current)] = leaving return memo[id(node)] def _a_body_jumps_out(self, node: Node) -> bool: """ Whether some body of the tree *node* stands in may jump out of itself into the code that ran it: a script block lets out an `exit`, or a `break` or `continue` nothing in it takes, or the tree runs code it does not spell. """ root = tree_root(node) found = self._jumping.get(id(root)) if found is None: escaping = _Jump.EXIT | _Jump.LOOP | _Jump.LABELLED found = self._jumping[id(root)] = any( runs_code_it_cannot_read(inner) or isinstance(inner, Ps1ScriptBlock) and bool(self._leaving_jumps(inner) & escaping) for inner in root.walk() ) return found @property def evaluation(self) -> Ps1Reading: """ The reading of the tree as it stands, under every value the error preference may hold. """ return self._evaluation def _body_guards(self, owner: Node) -> tuple[bool, ...]: """ The values the outward flag may hold where the body holding the list *owner* was called. Without call sites to read, every body may be called with the flag set. """ if self._sites is None: return (False, True) block = owner if isinstance(owner, Ps1ScriptBlock) else owner.parent guarded = self._guarded if guarded is None: guarded = self._guarded = self._guarded_bodies(self._sites) return (False, True) if block is None or id(block) in guarded else (False,) def _guarded_bodies(self, sites: Ps1CallSites) -> set[int]: """ The bodies some call site may run with the outward flag set: one called from a site whose list passes errors outward, from a site *sites* cannot name, or from a site whose list leaves the flag as the caller of its own body set it, where that body is one of these. A call *sites* cannot attribute is a site of every function body. One table for the tree, read breadth-first rather than by recursion over the call chain. """ levels = self._evaluation.levels direct: set[int] = set() inherits: dict[int, list[int]] = {} every_function = -1 def read(key: int, callers: Sequence[Node]) -> None: for site in callers: statement = member_of(site) level = None if statement is None else levels.level_of(statement) if level is None or level.passes_on is True: direct.add(key) return if level.passes_on is None: root = level while root.parent is not None: root = root.parent owner = root.owner caller = owner if isinstance(owner, Ps1ScriptBlock) else owner.parent inherits.setdefault(id(caller), []).append(key) functions: list[int] = [] for body in sites.bodies(): if isinstance(body.parent, Ps1FunctionDefinition): functions.append(id(body)) named = sites.sites_running(body) if named is None: direct.add(id(body)) continue read(id(body), named) read(every_function, sites.unattributed_sites()) guarded: set[int] = set() stack = list(direct) while stack: key = stack.pop() if key in guarded: continue guarded.add(key) if key == every_function: stack.extend(functions) continue stack.extend(inherits.get(key, ())) guarded.discard(every_function) return guarded def level(self, owner: Node) -> Ps1Level: """ The statement list *owner* holds. """ return self._evaluation.levels.level(owner) def without(self, reading: Ps1Reading, owner: Node, removed: AbstractSet[int]) -> Ps1Reading: """ *reading* as though the `trap` statements of the list *owner* holds whose identities *removed* names were not written. """ tree = reading.tree traps = tree.traps_of(owner) kept = [] if traps is None else [trap for trap in traps.members if id(trap) not in removed] selection = Ps1TrapSelection(kept) if kept else None levels = _Levels(tree, real=reading.levels, replaced=(owner, selection)) return Ps1Reading(tree, levels) def readings(self) -> list[Ps1Reading]: """ One reading of the tree per pair of values the error preference may hold in the script and in a called body, each under that pair alone. Comparing two readings joined over the values would compare an error one value takes with one another value leaves. """ found = self._readings if found is None: tree = self._tree if len(tree.preferences) == 1 and len(tree.called) == 1: found = [self._evaluation] else: found = [] for preference in sorted(tree.preferences, key=lambda p: p.name): for called in sorted(tree.called, key=lambda p: p.name): single = tree.reading(preference, called) found.append(Ps1Reading(single, _Levels(single))) self._readings = found return found def lifted(self, clause: Ps1CatchClause) -> Ps1Lift | None: """ The body of *clause* read as standing in place of its `try`; `None` where it cannot stand there: the clause has no body, its `try` stands in no statement list this reads, or the body declares a `trap`, which would then guard the list around the `try`. """ body = clause.body statement = clause.parent if body is None or not isinstance(statement, Ps1TryCatchFinally): return None around = self.level_of(statement) catch = self._evaluation.levels.level(body) if around is None or catch.traps is not None: return None return Ps1Lift(self._evaluation, self._tree, catch, around, body, statement)Instance variables
var evaluation-
The reading of the tree as it stands, under every value the error preference may hold.
Expand source code Browse git
@property def evaluation(self) -> Ps1Reading: """ The reading of the tree as it stands, under every value the error preference may hold. """ return self._evaluation
Methods
def depth_of(self, node)-
How many ancestors node has in its tree.
Expand source code Browse git
def depth_of(self, node: Node) -> int: """ How many ancestors *node* has in its tree. """ return self._tree.depth_of(node) def level_of(self, statement)-
The statement list statement is a member of, or
Nonewhere it is a member of none.Expand source code Browse git
def level_of(self, statement: Node) -> Ps1Level | None: """ The statement list *statement* is a member of, or `None` where it is a member of none. """ return self._evaluation.levels.level_of(statement) def fate(self, point, kinds)-
Where an error of any of kinds raised at point may end up, under every caller of the body, or
Nonewhere point stands in no statement list this reads.Expand source code Browse git
def fate(self, point: Node, kinds: Ps1ErrorKind) -> Ps1Fate | None: """ Where an error of any of *kinds* raised at *point* may end up, under every caller of the body, or `None` where *point* stands in no statement list this reads. """ return self._evaluation.fate(point, kinds) def fate_under_any_caller(self, point, kinds)-
fate, read as though a caller of the body may pass errors outward or may not, which reads no call site: the answer the control-flow builder draws from, since which calls run a body is read off graphs drawn from that answer.Expand source code Browse git
def fate_under_any_caller(self, point: Node, kinds: Ps1ErrorKind) -> Ps1Fate | None: """ `fate`, read as though a caller of the body may pass errors outward or may not, which reads no call site: the answer the control-flow builder draws from, since which calls run a body is read off graphs drawn from that answer. """ evaluation = self._evaluation found = evaluation.fate(point, kinds, False) guarded = evaluation.fate(point, kinds, True) if found is None or guarded is None: return None return found.joined(guarded) def disposition(self, trap)-
How the body of trap may end.
Expand source code Browse git
def disposition(self, trap: Ps1TrapStatement) -> Ps1TrapDisposition: """ How the body of *trap* may end. """ return self._tree.disposition(trap) def lets_past(self, owner)-
Whether the
trapset the list owner holds declares may hand an error it is offered on outward; seePs1Reading.lets_past(). A list declaring none lets every error past.Expand source code Browse git
def lets_past(self, owner: Node) -> bool: """ Whether the `trap` set the list *owner* holds declares may hand an error it is offered on outward; see `Ps1Reading.lets_past`. A list declaring none lets every error past. """ level = self.level(owner) traps = level.traps return traps is None or self._evaluation.lets_past(level, traps) def traps_run_inside_a_try(self, owner)-
Whether a
trapof the list owner holds may run from inside one of itstrystatements, for an error nocatchclause of that statement took: an error thetrapbody raises then leaves thattrystatement, and the list offers it to itstrapset again.Expand source code Browse git
def traps_run_inside_a_try(self, owner: Node) -> bool: """ Whether a `trap` of the list *owner* holds may run from inside one of its `try` statements, for an error no `catch` clause of that statement took: an error the `trap` body raises then leaves that `try` statement, and the list offers it to its `trap` set again. """ return bool(_tries_a_trap_may_run_in(owner)) def disposed(self, level, statement, kind)-
Where an error of kind that statement, a member of level, raised may end up, under every caller of the body.
Expand source code Browse git
def disposed(self, level: Ps1Level, statement: Node, kind: Ps1ErrorKind) -> Ps1Fate: """ Where an error of *kind* that *statement*, a member of *level*, raised may end up, under every caller of the body. """ return self._evaluation.disposed(level, statement, kind) def tail_of(self, statement)-
The statements a run on which statement completes goes on after doing nothing else on the way, where every statement around it completes as well: statement itself, and where it is the last of its list, the statement that list belongs to as well, and so on outward, through lists that end where the statement holding them ends — the body of an
if, atry, acatchor afinally. Afinallybody entered with an error or a jump in flight goes on with that instead, which is not a completion; seejumps_through. A$( )written as the test of anifends where the test is made, not where theifends.Expand source code Browse git
def tail_of(self, statement: Node) -> frozenset[Node]: """ The statements a run on which *statement* completes goes on after doing nothing else on the way, where every statement around it completes as well: *statement* itself, and where it is the last of its list, the statement that list belongs to as well, and so on outward, through lists that end where the statement holding them ends — the body of an `if`, a `try`, a `catch` or a `finally`. A `finally` body entered with an error or a jump in flight goes on with that instead, which is not a completion; see `jumps_through`. A `$( )` written as the test of an `if` ends where the test is made, not where the `if` ends. """ found = self._tails.get(id(statement)) if found is None: found = frozenset((statement,)) level = self.level_of(statement) if ( level is not None and level.holder is not None and _last_statement(level.owner) is statement and ( level.kind in (Ps1LevelKind.TRY, Ps1LevelKind.CATCH, Ps1LevelKind.FINALLY) or ( isinstance(level.holder, Ps1IfStatement) and isinstance(level.owner, Block) and level.owner.parent is level.holder ) ) ): found = found | self.tail_of(level.holder) self._tails[id(statement)] = found return found def jumps_through(self, statement)-
Whether the
finallybody of statement may be entered with a jump in flight: areturnor anexitin thetrybody or one of itscatchbodies, abreakorcontinuethere that leaves it, or a call there that may run a body able to jump out of itself — which some body of the script is where it writes anexitanywhere, or abreakorcontinuethat leaves a script block, or runs code it does not spell. 5.1 carries the jump through thefinallybody, and an error leaving that body takes its place.Expand source code Browse git
def jumps_through(self, statement: Ps1TryCatchFinally) -> bool: """ Whether the `finally` body of *statement* may be entered with a jump in flight: a `return` or an `exit` in the `try` body or one of its `catch` bodies, a `break` or `continue` there that leaves it, or a call there that may run a body able to jump out of itself — which some body of the script is where it writes an `exit` anywhere, or a `break` or `continue` that leaves a script block, or runs code it does not spell. 5.1 carries the jump through the `finally` body, and an error leaving that body takes its place. """ regions: list[Node] = [clause.body for clause in statement.catch_clauses if clause.body is not None] if statement.try_block is not None: regions.append(statement.try_block) return any(self.may_jump_out_of(region) for region in regions) def may_jump_out_of(self, node)-
Whether a jump may carry control out of node to somewhere other than what follows it: a
returnor anexit, abreakorcontinuenothing in node takes, or a call that may run a body able to jump out of itself.Expand source code Browse git
def may_jump_out_of(self, node: Node) -> bool: """ Whether a jump may carry control out of *node* to somewhere other than what follows it: a `return` or an `exit`, a `break` or `continue` nothing in *node* takes, or a call that may run a body able to jump out of itself. """ leaving = self._leaving_jumps(node) if leaving & ~_Jump.RUNS_CODE: return True return _Jump.RUNS_CODE in leaving and self._a_body_jumps_out(node) def level(self, owner)-
The statement list owner holds.
Expand source code Browse git
def level(self, owner: Node) -> Ps1Level: """ The statement list *owner* holds. """ return self._evaluation.levels.level(owner) def without(self, reading, owner, removed)-
reading as though the
trapstatements of the list owner holds whose identities removed names were not written.Expand source code Browse git
def without(self, reading: Ps1Reading, owner: Node, removed: AbstractSet[int]) -> Ps1Reading: """ *reading* as though the `trap` statements of the list *owner* holds whose identities *removed* names were not written. """ tree = reading.tree traps = tree.traps_of(owner) kept = [] if traps is None else [trap for trap in traps.members if id(trap) not in removed] selection = Ps1TrapSelection(kept) if kept else None levels = _Levels(tree, real=reading.levels, replaced=(owner, selection)) return Ps1Reading(tree, levels) def readings(self)-
One reading of the tree per pair of values the error preference may hold in the script and in a called body, each under that pair alone. Comparing two readings joined over the values would compare an error one value takes with one another value leaves.
Expand source code Browse git
def readings(self) -> list[Ps1Reading]: """ One reading of the tree per pair of values the error preference may hold in the script and in a called body, each under that pair alone. Comparing two readings joined over the values would compare an error one value takes with one another value leaves. """ found = self._readings if found is None: tree = self._tree if len(tree.preferences) == 1 and len(tree.called) == 1: found = [self._evaluation] else: found = [] for preference in sorted(tree.preferences, key=lambda p: p.name): for called in sorted(tree.called, key=lambda p: p.name): single = tree.reading(preference, called) found.append(Ps1Reading(single, _Levels(single))) self._readings = found return found def lifted(self, clause)-
The body of clause read as standing in place of its
try;Nonewhere it cannot stand there: the clause has no body, itstrystands in no statement list this reads, or the body declares atrap, which would then guard the list around thetry.Expand source code Browse git
def lifted(self, clause: Ps1CatchClause) -> Ps1Lift | None: """ The body of *clause* read as standing in place of its `try`; `None` where it cannot stand there: the clause has no body, its `try` stands in no statement list this reads, or the body declares a `trap`, which would then guard the list around the `try`. """ body = clause.body statement = clause.parent if body is None or not isinstance(statement, Ps1TryCatchFinally): return None around = self.level_of(statement) catch = self._evaluation.levels.level(body) if around is None or catch.traps is not None: return None return Ps1Lift(self._evaluation, self._tree, catch, around, body, statement)