Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transpile yielding, i.e., co-routines #424

Merged
merged 1 commit into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions aas_core_codegen/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import ast
import inspect
import io
import itertools
import re
import textwrap
from typing import (
Expand All @@ -16,6 +17,7 @@
NoReturn,
Any,
Iterable,
TypeVar,
)

import asttokens
Expand Down Expand Up @@ -469,3 +471,30 @@ def wrap_text_into_lines(text: str, line_width: int = 60) -> List[str]:
segments.append("".join(accumulation))

return segments


T = TypeVar("T")


def pairwise(iterable: Iterable[T]) -> Iterator[Tuple[T, T]]:
"""
Iterate pair-wise over the iterator.

>>> list(pairwise("ABCDE"))
[('A', 'B'), ('B', 'C'), ('C', 'D'), ('D', 'E')]
"""
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)


def iterate_except_first(iterable: Iterable[T]) -> Iterator[T]:
"""
Iterate over ``iterable``, but skip the first item.

>>> list(iterate_except_first("ABCD"))
['B', 'C', 'D']
"""
iterator = iter(iterable)
next(iterator, None)
yield from iterator
1 change: 1 addition & 0 deletions aas_core_codegen/yielding/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Translate control flows to co-routines where they are not natively supported."""
139 changes: 139 additions & 0 deletions aas_core_codegen/yielding/flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""Provide structures to model the control flow."""

from typing import Optional, Union, Final, Sequence, List

from icontract import require

from aas_core_codegen.common import Stripped


class Command:
"""
Represent a command block to be executed which does not affect control flow.

We refer to control flow here in terms of yielding. The command block *can* include
statements changing the *execution* control flow as long as they do not affect
the co-routines. For example, it can contain an if-statement which does not
have any yield statements in its body.
"""

def __init__(self, code: Stripped) -> None:
"""Initialize with the given values."""
self.code = code


class IfTrue:
"""
Represent an if-statement in the control flow.

The condition should *not* contain any code that affects the control flow.
"""

condition: Final[Stripped]
body: Final[Sequence["Node"]]
or_else: Final[Optional[Sequence["Node"]]]

@require(
lambda body: len(body) >= 1,
"If-node should always execute something in the body. If the body is empty, "
"this node needs to be reformulated",
)
def __init__(
self,
condition: str,
body: Sequence["Node"],
or_else: Optional[Sequence["Node"]] = None,
) -> None:
"""Initialize with the given values."""
self.condition = Stripped(condition.strip())
self.body = body
self.or_else = or_else


class IfFalse:
"""
Represent an if-statement with the negated condition in the control flow.

The condition should *not* contain any code that affects the control flow.

We distinguish between if-true and if-false so that we can avoid redundant
double negations of the condition in the compiled code.
"""

condition: Final[Stripped]
body: Final[Sequence["Node"]]
or_else: Final[Optional[Sequence["Node"]]]

@require(
lambda body: len(body) >= 1,
"If-node should always execute something in the body. If the body is empty, "
"this node needs to be reformulated",
)
def __init__(
self,
condition: str,
body: Sequence["Node"],
or_else: Optional[Sequence["Node"]] = None,
) -> None:
"""Initialize with the given values."""
self.condition = Stripped(condition.strip())
self.body = body
self.or_else = or_else


class For:
"""
Represent a for-loop in a control flow.

The ``init``, ``condition`` and ``iteration`` should *not* contain any code that
affects the control flow.
"""

init: Final[Optional[Stripped]]
condition: Final[Stripped]
iteration: Final[Stripped]
body: Final[Sequence["Node"]]

def __init__(
self,
condition: str,
iteration: str,
body: Sequence["Node"],
init: Optional[str] = None,
) -> None:
"""Initialize with the given values."""
self.init = Stripped(init) if init is not None else None
self.condition = Stripped(condition.strip())
self.iteration = Stripped(iteration.strip())
self.body = body


class While:
"""Represent a while-loop in a control flow."""

condition: Final[Stripped]
body: Final[Sequence["Node"]]

def __init__(self, condition: str, body: Sequence["Node"]) -> None:
"""Initialize with the given values."""
self.condition = Stripped(condition.strip())
self.body = body


class Yield:
"""
Represent the yield statement.

Since we want to be as general as possible, we do not include a value to be
yielded here, as we do not know *how* the compiled code should return the value.
Therefore, this statement simply indicates that the control is yielded, not
the value.
"""


Node = Union[Command, IfTrue, IfFalse, For, While, Yield]


def command_from_text(text: str) -> Command:
"""Strip the text and create a command out of the stripped text."""
return Command(Stripped(text.strip()))
Loading
Loading