Skip to content

Commit

Permalink
Detecting duplicate aspects
Browse files Browse the repository at this point in the history
Fixes #714
  • Loading branch information
mhadhbir committed Feb 16, 2022
1 parent fdd23bb commit e39e680
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 25 deletions.
82 changes: 57 additions & 25 deletions rflx/specification/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ def create_message(
error, identifier, parameters, fields, types, filename
)
structure = create_message_structure(error, fields, filename)
checksum_aspects, byte_order_aspect = parse_aspects(message.f_aspects, filename)
checksum_aspects, byte_order_aspect = parse_aspects(message.f_aspects, filename, error)
try:
result = create_proven_message(
model.UnprovenMessage(
Expand Down Expand Up @@ -1193,12 +1193,27 @@ def merge_field_condition(


def parse_aspects(
aspects: lang.MessageAspectList, filename: Path
aspects: lang.MessageAspectList, filename: Path, error: RecordFluxError
) -> Tuple[Mapping[ID, Sequence[expr.Expr]], Optional[model.ByteOrder]]:
checksum_parsed = False
checksum_result = {}
byte_order_parsed = False
byte_order_result = None
for aspect in aspects:
if isinstance(aspect, lang.ChecksumAspect):
if (isinstance(aspect, lang.ChecksumAspect) and checksum_parsed) or (
isinstance(aspect, lang.ByteOrderAspect) and byte_order_parsed
):
error.extend(
[
(
f'duplicate aspect "{aspect.kind_name}"',
Subsystem.PARSER,
Severity.ERROR,
node_location(aspect, filename),
)
],
)
elif isinstance(aspect, lang.ChecksumAspect):
for assoc in aspect.f_associations:
exprs = []
for value in assoc.f_covered_fields:
Expand All @@ -1214,11 +1229,14 @@ def parse_aspects(
else:
raise NotImplementedError(f"Invalid checksum association {value.kind_name}")
checksum_result[create_id(assoc.f_identifier, filename)] = exprs
if isinstance(aspect, lang.ByteOrderAspect):
checksum_parsed = True
else:
assert isinstance(aspect, lang.ByteOrderAspect)
if isinstance(aspect.f_byte_order, lang.ByteOrderTypeLoworderfirst):
byte_order_result = model.ByteOrder.LOW_ORDER_FIRST
else:
byte_order_result = model.ByteOrder.HIGH_ORDER_FIRST
byte_order_parsed = True
return checksum_result, byte_order_result


Expand Down Expand Up @@ -1297,28 +1315,42 @@ def create_aspects(aspects: lang.AspectList) -> Tuple[expr.Expr, bool]:
always_valid = False
size = None
for a in aspects:
if a.f_identifier.text == "Size":
size = create_math_expression(a.f_value, filename)
if a.f_identifier.text == "Always_Valid":
if a.f_value:
av_expr = create_bool_expression(a.f_value, filename)
if av_expr == expr.Variable("True"):
always_valid = True
elif av_expr == expr.Variable("False"):
always_valid = False
else:
error.extend(
[
(
f"invalid Always_Valid expression: {av_expr}",
Subsystem.PARSER,
Severity.ERROR,
node_location(a.f_value, filename),
)
],
if (a.f_identifier.text == "Size" and size) or (
a.f_identifier.text == "Always_Valid" and always_valid
):
error.extend(
[
(
f'duplicate aspect "{a.f_identifier.text}"',
Subsystem.PARSER,
Severity.ERROR,
node_location(a, filename),
)
else:
always_valid = True
],
)
else:
if a.f_identifier.text == "Size":
size = create_math_expression(a.f_value, filename)
if a.f_identifier.text == "Always_Valid":
if a.f_value:
av_expr = create_bool_expression(a.f_value, filename)
if av_expr == expr.Variable("True"):
always_valid = True
elif av_expr == expr.Variable("False"):
always_valid = False
else:
error.extend(
[
(
f"invalid Always_Valid expression: {av_expr}",
Subsystem.PARSER,
Severity.ERROR,
node_location(a.f_value, filename),
)
],
)
else:
always_valid = True
if not size:
error.extend(
[
Expand Down
79 changes: 79 additions & 0 deletions tests/unit/specification/parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2043,6 +2043,85 @@ def test_parse_error_duplicate_channel_aspect_writable() -> None:
)


def test_parse_error_duplicate_checksum_aspect() -> None:
assert_error_string(
"""
package Test is
type T is mod 2**8;
type M is
message
F1 : T;
F2 : T;
C1 : T;
F3 : T;
C2 : T
then F4
if C1'Valid_Checksum and C2'Valid_Checksum;
F4 : T;
end message
with Checksum => (C1 => (F3, F1'First .. F2'Last),
C2 => (C1'Last + 1 .. C2'First - 1)),
Checksum => (C1 => (F3, F1'First .. F2'Last),
C2 => (C1'Last + 1 .. C2'First - 1)),
Byte_Order => Low_Order_First;
end Test;
""",
# ISSUE: Componolit/RecordFlux-parser#56
# Delete the following line when ticket mentioned above is solved.
"^<stdin>:17:27: parser: error: Expected 'Byte_Order', got 'First'",
# Uncomment this when ticket #56 is solved.
# r'^<stdin>:17:27: parser: error: duplicate aspect "Checksum"',
)


def test_parse_error_duplicate_byte_order_aspect() -> None:
assert_error_string(
"""
package Test is
type T is mod 2**8;
type M is
message
F1 : T;
F2 : T;
C1 : T;
F3 : T;
C2 : T
then F4
if C1'Valid_Checksum and C2'Valid_Checksum;
F4 : T;
end message
with Checksum => (C1 => (F3, F1'First .. F2'Last),
C2 => (C1'Last + 1 .. C2'First - 1)),
Byte_Order => Low_Order_First,
Byte_Order => Low_Order_First;
end Test;
""",
r'^<stdin>:18:27: parser: error: duplicate aspect "ByteOrderAspect"',
)


def test_parse_error_duplicate_always_valid_aspect() -> None:
assert_error_string(
"""
package Test is
type T is (A, B) with Always_Valid => True, Always_Valid => False;
end Test;
""",
r'^<stdin>:3:60: parser: error: duplicate aspect "Always_Valid"',
)


def test_parse_error_duplicate_size_aspect() -> None:
assert_error_string(
"""
package Test is
type T is (A, B) with Size => 12, Size => 10;
end Test;
""",
r'^<stdin>:3:50: parser: error: duplicate aspect "Size"',
)


@pytest.mark.parametrize("spec", ["empty_file", "comment_only", "empty_package", "context"])
def test_create_model_no_messages(spec: str) -> None:
assert_messages_files([f"{SPEC_DIR}/{spec}.rflx"], [])
Expand Down

0 comments on commit e39e680

Please sign in to comment.