structured_data._stack_iter   A
last analyzed

Complexity

Total Complexity 9

Size/Duplication

Total Lines 68
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 68
rs 10
c 0
b 0
f 0
wmc 9

5 Methods

Rating   Name   Duplication   Size   Complexity  
A Action.handle() 0 3 1
A Extend.handle() 0 4 1
A Extend.__init__() 0 2 1
A Yield.__init__() 0 2 1
A Yield.handle() 0 4 1

2 Functions

Rating   Name   Duplication   Size   Complexity  
A stack_iter() 0 19 2
A handle() 0 6 2
1
"""Classes and functions for implementing self-referential iterators."""
2
3
import typing
4
5
Input = typing.TypeVar("Input")
6
Output = typing.TypeVar("Output")
7
8
9
class Action(typing.Generic[Input, Output]):
10
    """Abstract base class for reified stack iteration actions."""
11
12
    def handle(self, to_process: typing.List[Input]) -> typing.Iterator[Output]:
13
        """Yield a value or mutate the stack."""
14
        raise NotImplementedError
15
16
17
class Yield(Action[Input, Output]):
18
    """Reified action for yielding an output value."""
19
20
    def __init__(self, item: Output) -> None:
21
        self.item = item
22
23
    def handle(self, to_process: typing.List[Input]) -> typing.Iterator[Output]:
24
        """Yield out ``self.item``"""
25
        del to_process
26
        yield self.item
27
28
29
class Extend(Action[Input, Output]):
30
    """Reified action for pushing to the stack."""
31
32
    def __init__(self, iterable: typing.Iterable[Input]) -> None:
33
        self.iterable = iterable
34
35
    def handle(self, to_process: typing.List[Input]) -> typing.Iterator[Output]:
36
        """Extend the process list with ``iterable``, and yield nothing."""
37
        to_process.extend(self.iterable)
38
        yield from ()
39
40
41
def handle(
42
    action: typing.Optional[Action[Input, Output]], to_process: typing.List[Input]
43
) -> typing.Iterator[Output]:
44
    """If ``action`` is an ``Action``, delegate to its ``handle`` method."""
45
    if action is not None:
46
        yield from action.handle(to_process)
47
48
49
def stack_iter(
50
    first: Input,
51
    process: typing.Callable[[Input], typing.Optional[Action[Input, Output]]],
52
) -> typing.Iterator[Output]:
53
    """Stack iterate over the initial value using the processing function.
54
55
    To "stack iterate" is to build a stack, starting with the initial value.
56
    Then, as long as the stack is non-empty, pop the top value, pass it to the
57
    processing function, and use the ``handle`` helper to ignore ``None``
58
    values. If the processing function returns a value, it will be a subclass
59
    of Action, and its ``handle`` method can yield values or mutate the stack
60
    arbitrarily.
61
62
    The point of this setup is to write very little code in the service of
63
    desctructuring nested data without relying on unbounded recursion.
64
    """
65
    to_process = [first]
66
    while to_process:
67
        yield from handle(process(to_process.pop()), to_process)
68