Skip to content

Commit

Permalink
Add string representation for session model
Browse files Browse the repository at this point in the history
Ref. #291
  • Loading branch information
treiher committed Aug 19, 2020
1 parent 20d6146 commit f458028
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 13 deletions.
26 changes: 26 additions & 0 deletions rflx/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,19 @@ def __init__(
self.__type_name = ID(type_name) if type_name else None
self.__expression = expression

def __str__(self) -> str:
expression = f" := {self.__expression}" if self.__expression else ""
return f"{self.identifier} : {self.__type_name}{expression}"

def validate(self, declarations: Mapping[ID, "Declaration"]) -> None:
if self.__expression:
self.__expression.validate(declarations)


class PrivateDeclaration(Declaration):
def __str__(self) -> str:
return f"type {self.identifier} is private"

def validate(self, declarations: Mapping[ID, "Declaration"]) -> None:
pass

Expand All @@ -77,6 +84,9 @@ def __eq__(self, other: object) -> bool:
def __repr__(self) -> str:
return generic_repr(self.__class__.__name__, self.__dict__)

def __str__(self) -> str:
return f"{self.__name} : {self.__type_name}"

def validate(self, declarations: Mapping[ID, "Declaration"]) -> None:
pass

Expand All @@ -93,6 +103,10 @@ def __init__(
self.__arguments = arguments
self.__return_type = ID(return_type)

def __str__(self) -> str:
arguments = (" (" + "; ".join(map(str, self.__arguments)) + ")") if self.__arguments else ""
return f"with function {self.identifier}{arguments} return {self.__return_type}"

def validate(self, declarations: Mapping[ID, "Declaration"]) -> None:
for a in self.__arguments:
a.validate(declarations)
Expand All @@ -106,6 +120,9 @@ def __init__(
self.__type_name = ID(type_name)
self.__expression = expression

def __str__(self) -> str:
return f"{self.identifier} : {self.__type_name} renames {self.__expression}"

def validate(self, declarations: Mapping[ID, "Declaration"]) -> None:
self.__expression.validate(declarations)

Expand All @@ -123,6 +140,15 @@ def __init__(
self.__readable = readable
self.__writable = writable

def __str__(self) -> str:
aspects = []
if self.__readable:
aspects.append("Readable")
if self.__writable:
aspects.append("Writable")
with_aspects = " with " + ", ".join(aspects)
return f"{self.identifier} : Channel{with_aspects}"

@property
def readable(self) -> bool:
return self.__readable
Expand Down
8 changes: 5 additions & 3 deletions rflx/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -1962,7 +1962,9 @@ def __init__(self, name: StrID, data: Mapping[StrID, Expr], location: Location =
self.data = {ID(k): v for k, v in data.items()}

def _update_str(self) -> None:
data = ", ".join([f"{k} => {self.data[k]}" for k in self.data])
data = (
", ".join(f"{k} => {self.data[k]}" for k in self.data) if self.data else "null message"
)
self._str = intern(f"{self.name}'({data})")

def __neg__(self) -> Expr:
Expand Down Expand Up @@ -2009,8 +2011,8 @@ def __init__(self, expr: Expr, data: Mapping[StrID, Expr], location: Location =
self.data = {ID(k): v for k, v in data.items()}

def _update_str(self) -> None:
data = ", ".join(["{k} = {v}".format(k=k, v=self.data[k]) for k in self.data])
self._str = intern(f"{self.expr} where {data}")
data = ",\n".join("{k} = {v}".format(k=k, v=self.data[k]) for k in self.data)
self._str = intern(f"{self.expr}\n where {indent_next(data, 9)}")

def __neg__(self) -> Expr:
raise NotImplementedError
Expand Down
2 changes: 1 addition & 1 deletion rflx/parser/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ def parse_mathematical_operator(string: str, location: int, tokens: ParseResults
@fatalexceptions
def parse_logical_expression(string: str, location: int, tokens: ParseResults) -> Expr:
log_expr = tokens[0][0]
if isinstance(log_expr, (And, Or, Relation, Valid, ValidChecksum)):
if isinstance(log_expr, (And, Or, Relation, Valid, ValidChecksum, QuantifiedExpression)):
return log_expr
raise ParseFatalException(
string, location, f'unexpected expression type "{type(log_expr).__name__}"'
Expand Down
60 changes: 60 additions & 0 deletions rflx/session.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Dict, List, Sequence

from rflx.common import indent, indent_next
from rflx.declaration import (
ChannelDeclaration,
Declaration,
Expand Down Expand Up @@ -28,6 +29,22 @@ def __init__(
self.location = location
self.description = description

def __repr__(self) -> str:
return (
f"\n{self.__class__.__name__}(\n"
f"{indent(repr(self.target), 4)},\n"
f"{indent(repr(self.condition), 4)},\n"
f"{indent(repr(self.description), 4)},\n"
f"\n)" + self._prefixed_str
)

def __str__(self) -> str:
with_aspects = f'\n with Desc => "{self.description}"' if self.description else ""
if_condition = (
f"\n if {indent_next(str(self.condition), 6)}" if self.condition != TRUE else ""
)
return f"then {self.target}{with_aspects}{if_condition}"

def validate(self, declarations: Dict[ID, Declaration]) -> None:
self.condition.simplified().validate(declarations)

Expand All @@ -47,6 +64,27 @@ def __init__(
self.declarations = {d.identifier: d for d in declarations} if declarations else {}
self.location = location

def __repr__(self) -> str:
return (
f"\n{self.__class__.__name__}(\n"
f"{indent(repr(self.name), 4)},\n"
f"{indent(repr(self.transitions), 4)},\n"
f"{indent(repr(self.actions), 4)},\n"
f"{indent(repr(self.declarations), 4)},\n"
f"\n)" + self._prefixed_str
)

def __str__(self) -> str:
if not self.declarations and not self.actions and not self.transitions:
return f"state {self.name} is null state"
declarations = "".join(f"{d};\n" for d in self.declarations.values())
actions = "".join(f"{a};\n" for a in self.actions)
transitions = "\n".join(f"{p}" for p in self.transitions)
return (
f"state {self.name} is\n{indent(declarations, 3)}begin\n{indent(actions, 3)}"
f"transition\n{indent(transitions, 3)}\nend {self.name}"
)

@property
def name(self) -> ID:
return self.__name
Expand Down Expand Up @@ -93,6 +131,28 @@ def __init__(
self.__validate_declarations()
self.error.propagate()

def __repr__(self) -> str:
return (
f"\n{self.__class__.__name__}(\n"
f"{indent(repr(self.name), 4)},\n"
f"{indent(repr(self.initial), 4)},\n"
f"{indent(repr(self.final), 4)},\n"
f"{indent(repr(self.states), 4)},\n"
f"{indent(repr(self.declarations), 4)},\n"
f"{indent(repr(self.parameters), 4)},\n"
f"\n)" + self._prefixed_str
)

def __str__(self) -> str:
parameters = "".join(f"{p};\n" for p in self.parameters.values())
declarations = "".join(f"{d};\n" for d in self.declarations.values())
states = "\n\n".join(f"{s};" for s in self.states)
return (
f"generic\n{indent(parameters, 3)}session {self.name} with\n"
f" Initial => {self.initial},\n Final => {self.final}\n"
f"is\n{indent(declarations, 3)}begin\n{indent(states, 3)}\nend {self.name}"
)

def __validate_conditions(self) -> None:
for s in self.states:
declarations = s.declarations
Expand Down
9 changes: 1 addition & 8 deletions tests/test_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,11 @@
Variable,
)
from rflx.identifier import ID
from tests.utils import assert_equal
from tests.utils import assert_equal, multilinestr

EXPR = Equal(Variable("UNDEFINED_1"), Variable("UNDEFINED_2"))


def multilinestr(string: str) -> str:
assert all(
l.startswith(15 * " ") for l in string.split("\n")[1:]
), "invalid format of multi-line string"
return string.replace(15 * " ", "")


def test_true_neg() -> None:
assert -TRUE == FALSE

Expand Down
59 changes: 58 additions & 1 deletion tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import pytest

import rflx.declaration as decl
import rflx.statement as stmt
from rflx.error import Location, RecordFluxError
from rflx.expression import (
TRUE,
Expand Down Expand Up @@ -53,8 +55,9 @@
UnprovenDerivedMessage,
UnprovenMessage,
)
from rflx.session import Session, State, Transition
from tests.models import ENUMERATION, ETHERNET_FRAME, MODULAR_INTEGER, RANGE_INTEGER
from tests.utils import assert_equal, assert_message_model_error
from tests.utils import assert_equal, assert_message_model_error, multilinestr

M_NO_REF = UnprovenMessage(
"P.No_Ref",
Expand Down Expand Up @@ -1708,3 +1711,57 @@ def test_opaque_length_valid_multiple_of_8_dynamic_cond() -> None:
],
{Field("L"): MODULAR_INTEGER, Field("O"): Opaque()},
)


def test_session_str() -> None:
assert_equal(
str(
Session(
"Session",
"A",
"B",
[
State(
"A",
declarations=[decl.VariableDeclaration("Z", "Boolean", Variable("Y"))],
actions=[stmt.Assignment("Z", Number(1))],
transitions=[
Transition("B", condition=Equal(Variable("Z"), Number(1))),
Transition("A"),
],
),
State("B"),
],
[decl.VariableDeclaration("Y", "Boolean", Number(0))],
[
decl.ChannelDeclaration("X", readable=True, writable=True),
decl.PrivateDeclaration("T"),
decl.SubprogramDeclaration("F", [], "T"),
],
)
),
multilinestr(
"""generic
X : Channel with Readable, Writable;
type T is private;
with function F return T;
session Session with
Initial => A,
Final => B
is
Y : Boolean := 0;
begin
state A is
Z : Boolean := Y;
begin
Z := 1;
transition
then B
if Z = 1
then A
end A;
state B is null state;
end Session"""
),
)
8 changes: 8 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,11 @@ def _create_files(tmp_path: pathlib.Path, model: Model, prefix: str = None) -> N
generator.write_units(tmp_path)
generator.write_library_files(tmp_path)
generator.write_top_level_package(tmp_path)


def multilinestr(string: str) -> str:
correct_indentation = [not l or l.startswith(15 * " ") for l in string.split("\n")[1:]]
assert all(
correct_indentation
), f"invalid indentation of line {correct_indentation.index(False) + 2}"
return string.replace(15 * " ", "")

0 comments on commit f458028

Please sign in to comment.