You've already forked RecordFlux
mirror of
https://github.com/AdaCore/RecordFlux.git
synced 2026-02-12 13:01:56 -08:00
5145 lines
192 KiB
Python
5145 lines
192 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
import textwrap
|
|
from collections.abc import Mapping, Sequence
|
|
from itertools import zip_longest
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from rflx import common, expr, lang, model
|
|
from rflx.const import RESERVED_WORDS
|
|
from rflx.error import fail
|
|
from rflx.identifier import ID
|
|
from rflx.model import (
|
|
BOOLEAN,
|
|
FINAL,
|
|
INITIAL,
|
|
OPAQUE,
|
|
Enumeration,
|
|
Field,
|
|
Link,
|
|
Message,
|
|
State,
|
|
UnsignedInteger,
|
|
declaration as decl,
|
|
statement as stmt,
|
|
)
|
|
from rflx.model.message import ByteOrder
|
|
from rflx.model.type_decl import UncheckedInteger, UncheckedUnsignedInteger
|
|
from rflx.rapidflux import Location, RecordFluxError, Severity
|
|
from rflx.specification import parser
|
|
from rflx.ty import UNDEFINED
|
|
from tests.const import SPEC_DIR
|
|
from tests.data import models
|
|
from tests.utils import (
|
|
check_regex,
|
|
parse,
|
|
parse_bool_expression,
|
|
parse_expression,
|
|
parse_math_expression,
|
|
parse_range_type,
|
|
parse_unsigned_type,
|
|
)
|
|
|
|
T = UnsignedInteger("Test::T", expr.Number(8))
|
|
|
|
# Generated from the Ada 202x LRM source code (http://ada-auth.org/arm-files/ARM_SRC.zip) retrieved
|
|
# on 2022-09-16 using the following command:
|
|
#
|
|
# $ sed -ne "s/.*@key{\([^} ]*\)}.*/\"\L\1\",/p" *.MSS *.mss | sort -u
|
|
#
|
|
ADA_KEYWORDS = [
|
|
"abort",
|
|
"abs",
|
|
"abstract",
|
|
"accept",
|
|
"access",
|
|
"aliased",
|
|
"all",
|
|
"and",
|
|
"array",
|
|
"at",
|
|
"begin",
|
|
"body",
|
|
"case",
|
|
"constant",
|
|
"declare",
|
|
"delay",
|
|
"delta",
|
|
"digits",
|
|
"do",
|
|
"else",
|
|
"elsif",
|
|
"end",
|
|
"entry",
|
|
"exception",
|
|
"exit",
|
|
"for",
|
|
"function",
|
|
"generic",
|
|
"goto",
|
|
"if",
|
|
"in",
|
|
"interface",
|
|
"is",
|
|
"limited",
|
|
"loop",
|
|
"mod",
|
|
"new",
|
|
"not",
|
|
"null",
|
|
"of",
|
|
"or",
|
|
"others",
|
|
"out",
|
|
"overriding",
|
|
"package",
|
|
"parallel",
|
|
"pragma",
|
|
"private",
|
|
"procedure",
|
|
"protected",
|
|
"raise",
|
|
"range",
|
|
"record",
|
|
"rem",
|
|
"renames",
|
|
"requeue",
|
|
"return",
|
|
"reverse",
|
|
"select",
|
|
"separate",
|
|
"some",
|
|
"subtype",
|
|
"synchronized",
|
|
"tagged",
|
|
"task",
|
|
"terminate",
|
|
"then",
|
|
"type",
|
|
"until",
|
|
"use",
|
|
"when",
|
|
"while",
|
|
"with",
|
|
"xor",
|
|
]
|
|
|
|
ILLEGAL_IDENTIFIERS = ["RFLX_Foo"]
|
|
|
|
|
|
def to_dict(node: Any) -> Any: # type: ignore[misc]
|
|
if node is None:
|
|
return None
|
|
if node.is_list_type:
|
|
return [to_dict(e) for e in node.children]
|
|
result = {name[2:]: to_dict(getattr(node, name)) for name in dir(node) if name.startswith("f_")}
|
|
if result:
|
|
result["_kind"] = node.kind_name
|
|
return result
|
|
return {"_kind": node.kind_name, "_value": node.text}
|
|
|
|
|
|
def assert_ast_files( # type: ignore[misc]
|
|
filenames: Sequence[str],
|
|
expected: Mapping[str, Any],
|
|
) -> None:
|
|
p = parser.Parser()
|
|
p.parse(*[Path(f) for f in filenames])
|
|
result = {f: to_dict(s) for f, s in p.specifications.items()}
|
|
p.create_model()
|
|
assert result == expected, filenames
|
|
|
|
|
|
def assert_ast_string(string: str, expected: Mapping[str, Any]) -> None: # type: ignore[misc]
|
|
p = parser.Parser()
|
|
p.parse_string(string)
|
|
p.create_model()
|
|
assert to_dict(next(iter(p.specifications.items()))[1]) == expected
|
|
|
|
|
|
def assert_error_files(filenames: Sequence[str], regex: str) -> None:
|
|
check_regex(regex)
|
|
p = parser.Parser()
|
|
with pytest.raises(RecordFluxError, match=regex):
|
|
p.parse(*[Path(f) for f in filenames])
|
|
|
|
|
|
def assert_error_string(string: str, regex: str) -> None:
|
|
check_regex(regex)
|
|
p = parser.Parser()
|
|
with pytest.raises(RecordFluxError, match=regex): # noqa: PT012
|
|
p.parse_string(string)
|
|
p.create_model()
|
|
|
|
|
|
def assert_messages_files(filenames: Sequence[str], messages: Sequence[model.Message]) -> None:
|
|
p = parser.Parser()
|
|
for filename in filenames:
|
|
p.parse(Path(filename))
|
|
m = p.create_model()
|
|
assert_messages(m.messages, messages)
|
|
|
|
|
|
def assert_messages_string(string: str, messages: Sequence[model.Message]) -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(string)
|
|
m = p.create_model()
|
|
assert_messages(m.messages, messages)
|
|
|
|
|
|
def assert_messages(
|
|
actual_messages: Sequence[model.Message],
|
|
expected_messages: Sequence[model.Message],
|
|
) -> None:
|
|
for actual, expected in zip_longest(actual_messages, expected_messages):
|
|
assert actual.full_name == expected.full_name
|
|
assert actual.structure == expected.structure, expected.full_name
|
|
assert actual.types == expected.types, expected.full_name
|
|
assert actual.fields == expected.fields, expected.full_name
|
|
assert actual_messages == expected_messages
|
|
|
|
|
|
def assert_refinements_string(string: str, refinements: Sequence[model.Refinement]) -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(string)
|
|
m = p.create_model()
|
|
assert m.refinements == refinements
|
|
|
|
|
|
def raise_parser_error() -> None:
|
|
fail("TEST", Severity.ERROR)
|
|
|
|
|
|
def parse_statement(data: str) -> stmt.Statement:
|
|
parser_statement, filename = parse(data, lang.GrammarRule.action_rule)
|
|
assert isinstance(parser_statement, lang.Statement)
|
|
error = RecordFluxError()
|
|
statement = parser.create_statement(error, parser_statement, filename)
|
|
error.propagate()
|
|
assert isinstance(statement, stmt.Statement)
|
|
return statement
|
|
|
|
|
|
def parse_declaration(data: str) -> decl.Declaration:
|
|
parser_declaration, filename = parse(data, lang.GrammarRule.declaration_rule)
|
|
assert isinstance(parser_declaration, lang.LocalDecl)
|
|
error = RecordFluxError()
|
|
declaration = parser.create_declaration(error, parser_declaration, ID("Package"), filename)
|
|
error.propagate()
|
|
assert isinstance(declaration, decl.Declaration)
|
|
return declaration
|
|
|
|
|
|
def parse_formal_declaration(data: str) -> decl.Declaration:
|
|
error = RecordFluxError()
|
|
parser_declaration, filename = parse(data, lang.GrammarRule.state_machine_parameter_rule)
|
|
assert isinstance(parser_declaration, lang.FormalDecl)
|
|
declaration = parser.create_formal_declaration(
|
|
error,
|
|
parser_declaration,
|
|
ID("Package"),
|
|
filename,
|
|
)
|
|
error.propagate()
|
|
assert isinstance(declaration, decl.Declaration)
|
|
return declaration
|
|
|
|
|
|
def parse_state(data: str) -> State:
|
|
parser_state, source = parse(data, lang.GrammarRule.state_rule)
|
|
assert isinstance(parser_state, lang.State)
|
|
error = RecordFluxError()
|
|
state = parser.create_state(error, parser_state, ID("Package"), source)
|
|
error.propagate()
|
|
assert isinstance(state, State)
|
|
return state
|
|
|
|
|
|
def check_diagnostics_error(unit: lang.AnalysisUnit, error: RecordFluxError) -> None:
|
|
if parser.diagnostics_to_error(unit.diagnostics, error, common.STDIN):
|
|
error.propagate()
|
|
|
|
|
|
def parse_state_machine_error(string: str) -> None:
|
|
unit = lang.AnalysisContext().get_from_buffer(
|
|
"<stdin>",
|
|
string,
|
|
rule=lang.GrammarRule.state_machine_declaration_rule,
|
|
)
|
|
error = RecordFluxError()
|
|
check_diagnostics_error(unit, error)
|
|
assert isinstance(unit.root, lang.StateMachineDecl)
|
|
result = parser.create_state_machine(error, unit.root, ID("Package"), Path("<stdin>"))
|
|
error.propagate()
|
|
assert isinstance(result, model.UncheckedStateMachine)
|
|
|
|
|
|
def parse_state_machine(string: str) -> model.UncheckedStateMachine:
|
|
unit = lang.AnalysisContext().get_from_buffer(
|
|
"<stdin>",
|
|
string,
|
|
rule=lang.GrammarRule.state_machine_declaration_rule,
|
|
)
|
|
error = RecordFluxError()
|
|
check_diagnostics_error(unit, error)
|
|
assert isinstance(unit.root, lang.StateMachineDecl)
|
|
result = parser.create_state_machine(error, unit.root, ID("Package"), Path("<stdin>"))
|
|
error.propagate()
|
|
return result
|
|
|
|
|
|
def parse_id(data: str, rule: str) -> ID:
|
|
unit = lang.AnalysisContext().get_from_buffer("<stdin>", data, rule=rule)
|
|
assert isinstance(unit.root, lang.AbstractID)
|
|
error = RecordFluxError()
|
|
result = parser.create_id(error, unit.root, Path("<stdin>"))
|
|
error.propagate()
|
|
return result
|
|
|
|
|
|
# This test must fail to get full coverage of assert_error_files.
|
|
@pytest.mark.xfail()
|
|
def test_assert_error_files() -> None:
|
|
assert_error_files([], "^ error: $")
|
|
|
|
|
|
def test_create_model() -> None:
|
|
p = parser.Parser()
|
|
p.parse(SPEC_DIR / "tlv.rflx")
|
|
p.create_model()
|
|
|
|
|
|
def test_parse_string_error() -> None:
|
|
p = parser.Parser()
|
|
with pytest.raises(
|
|
RecordFluxError,
|
|
match=(
|
|
"^{}$".format(
|
|
re.escape(
|
|
'a/b.rflx:1:9: error: source file name does not match the package name "A"\n'
|
|
'a/b.rflx:1:9: help: either rename the file to "a.rflx" or change the package '
|
|
'name to "B"\n'
|
|
'a/b.rflx:1:9: help: rename to "B"\n'
|
|
'a/b.rflx:3:5: help: rename to "B"\n'
|
|
"a/b.rflx:2:1: error: unexpected keyword indentation (expected 3 or 6) "
|
|
"[style:indentation]",
|
|
),
|
|
)
|
|
),
|
|
):
|
|
p.parse_string(
|
|
"package A is\ntype T is range 0 .. 0 with Size => 1;\nend A;",
|
|
filename=Path("a/b.rflx"),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("value", "error"),
|
|
[
|
|
("", "<stdin>:1:1: error: Expected 'package', got Termination"),
|
|
("\n", "<stdin>:2:1: error: Expected 'package', got Termination"),
|
|
],
|
|
)
|
|
def test_parse_string_empty_file_error(value: str, error: str) -> None:
|
|
p = parser.Parser()
|
|
with pytest.raises(
|
|
RecordFluxError,
|
|
match=(rf"^{error}$"),
|
|
):
|
|
p.parse_string(value)
|
|
|
|
|
|
def test_parse_duplicate_specifications() -> None:
|
|
files = [f"{SPEC_DIR}/empty_package.rflx", f"{SPEC_DIR}/empty_package.rflx"]
|
|
assert_ast_files(
|
|
files,
|
|
{
|
|
"Empty_Package": {
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [],
|
|
"end_identifier": {"_kind": "UnqualifiedID", "_value": "Empty_Package"},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Empty_Package"},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
def test_parse_empty_package_spec() -> None:
|
|
assert_ast_files(
|
|
[f"{SPEC_DIR}/empty_package.rflx"],
|
|
{
|
|
"Empty_Package": {
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [],
|
|
"end_identifier": {"_kind": "UnqualifiedID", "_value": "Empty_Package"},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Empty_Package"},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
def test_parse_context_spec() -> None:
|
|
assert_ast_files(
|
|
[f"{SPEC_DIR}/context.rflx"],
|
|
{
|
|
"Context": {
|
|
"_kind": "Specification",
|
|
"context_clause": [
|
|
{
|
|
"_kind": "ContextItem",
|
|
"item": {"_kind": "UnqualifiedID", "_value": "Empty_Package"},
|
|
},
|
|
],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [],
|
|
"end_identifier": {"_kind": "UnqualifiedID", "_value": "Context"},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Context"},
|
|
},
|
|
},
|
|
"Empty_Package": {
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [],
|
|
"end_identifier": {"_kind": "UnqualifiedID", "_value": "Empty_Package"},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Empty_Package"},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
def test_parse_integer_type_spec() -> None:
|
|
spec = {
|
|
"Integer_Type": {
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "RangeTypeDef",
|
|
"first": {"_kind": "NumericLiteral", "_value": "1"},
|
|
"size": {
|
|
"_kind": "Aspect",
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Size"},
|
|
"value": {"_kind": "NumericLiteral", "_value": "16"},
|
|
},
|
|
"last": {"_kind": "NumericLiteral", "_value": "2_000"},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Page_Num"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "UnsignedTypeDef",
|
|
"size": {"_kind": "NumericLiteral", "_value": "8"},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Line_Size"},
|
|
"parameters": None,
|
|
},
|
|
],
|
|
"end_identifier": {"_kind": "UnqualifiedID", "_value": "Integer_Type"},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Integer_Type"},
|
|
},
|
|
},
|
|
}
|
|
|
|
assert_ast_files([f"{SPEC_DIR}/integer_type.rflx"], spec)
|
|
|
|
|
|
def test_parse_enumeration_type_spec() -> None:
|
|
spec = {
|
|
"Enumeration_Type": {
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "EnumerationTypeDef",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Size"},
|
|
"value": {"_kind": "NumericLiteral", "_value": "3"},
|
|
},
|
|
],
|
|
"elements": {
|
|
"_kind": "NamedEnumerationDef",
|
|
"elements": [
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Mon",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "1"},
|
|
},
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Tue",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "2"},
|
|
},
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Wed",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "3"},
|
|
},
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Thu",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "4"},
|
|
},
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Fri",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "5"},
|
|
},
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Sat",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "6"},
|
|
},
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Sun",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "7"},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Day"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "EnumerationTypeDef",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Size"},
|
|
"value": {"_kind": "NumericLiteral", "_value": "3"},
|
|
},
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Always_Valid",
|
|
},
|
|
"value": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {"_kind": "UnqualifiedID", "_value": "True"},
|
|
"package": None,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"elements": {
|
|
"_kind": "NamedEnumerationDef",
|
|
"elements": [
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "P1"},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "1"},
|
|
},
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "P2"},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "2"},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Protocol"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "EnumerationTypeDef",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Size"},
|
|
"value": {"_kind": "NumericLiteral", "_value": "1"},
|
|
},
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Always_Valid",
|
|
},
|
|
"value": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "False",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"elements": {
|
|
"_kind": "PositionalEnumerationDef",
|
|
"elements": [
|
|
{"_kind": "UnqualifiedID", "_value": "M"},
|
|
{"_kind": "UnqualifiedID", "_value": "F"},
|
|
],
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Gender"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "EnumerationTypeDef",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Always_Valid",
|
|
},
|
|
"value": None,
|
|
},
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Size"},
|
|
"value": {"_kind": "NumericLiteral", "_value": "8"},
|
|
},
|
|
],
|
|
"elements": {
|
|
"_kind": "NamedEnumerationDef",
|
|
"elements": [
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Low",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "1"},
|
|
},
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Medium",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "4"},
|
|
},
|
|
{
|
|
"_kind": "ElementValueAssoc",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "High",
|
|
},
|
|
"literal": {"_kind": "NumericLiteral", "_value": "7"},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Priority"},
|
|
"parameters": None,
|
|
},
|
|
],
|
|
"end_identifier": {"_kind": "UnqualifiedID", "_value": "Enumeration_Type"},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Enumeration_Type"},
|
|
},
|
|
},
|
|
}
|
|
assert_ast_files([f"{SPEC_DIR}/enumeration_type.rflx"], spec)
|
|
|
|
|
|
def test_parse_sequence_type_spec() -> None:
|
|
spec = {
|
|
"Sequence_Type": {
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "UnsignedTypeDef",
|
|
"size": {"_kind": "NumericLiteral", "_value": "8"},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Byte"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "SequenceTypeDef",
|
|
"element_type": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Byte",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bytes",
|
|
},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "MessageTypeDef",
|
|
"aspects": [],
|
|
"message_fields": {
|
|
"_kind": "MessageFields",
|
|
"fields": [
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Length",
|
|
},
|
|
"thens": [
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Size",
|
|
},
|
|
"value": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Length",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpMul",
|
|
"_value": "*",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "8",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"condition": None,
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bytes",
|
|
},
|
|
},
|
|
],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Byte",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bytes",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bytes",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
],
|
|
"initial_field": None,
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Foo"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "SequenceTypeDef",
|
|
"element_type": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Foo",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Bar"},
|
|
"parameters": None,
|
|
},
|
|
],
|
|
"end_identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Sequence_Type",
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Sequence_Type",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
assert_ast_files([f"{SPEC_DIR}/sequence_type.rflx"], spec)
|
|
|
|
|
|
def test_parse_message_type_spec() -> None:
|
|
spec = {
|
|
"Message_Type": {
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "UnsignedTypeDef",
|
|
"size": {"_kind": "NumericLiteral", "_value": "8"},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "T"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "MessageTypeDef",
|
|
"aspects": [],
|
|
"message_fields": {
|
|
"_kind": "MessageFields",
|
|
"fields": [
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Foo",
|
|
},
|
|
"thens": [
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [],
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Foo",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpLe",
|
|
"_value": "<=",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16#1E#",
|
|
},
|
|
},
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bar",
|
|
},
|
|
},
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [],
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Foo",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpGt",
|
|
"_value": ">",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16#1E#",
|
|
},
|
|
},
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Baz",
|
|
},
|
|
},
|
|
],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "T",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bar",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "T",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Size",
|
|
},
|
|
"value": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "8",
|
|
},
|
|
"op": {
|
|
"_kind": "OpMul",
|
|
"_value": "*",
|
|
},
|
|
"right": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Foo",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Baz",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Opaque",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
],
|
|
"initial_field": None,
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "PDU"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "MessageTypeDef",
|
|
"aspects": [],
|
|
"message_fields": {
|
|
"_kind": "MessageFields",
|
|
"fields": [
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bar",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "T",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Size",
|
|
},
|
|
"value": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "8",
|
|
},
|
|
"op": {
|
|
"_kind": "OpMul",
|
|
"_value": "*",
|
|
},
|
|
"right": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bar",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Baz",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Opaque",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
],
|
|
"initial_field": None,
|
|
},
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Simple_PDU",
|
|
},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "NullMessageTypeDef",
|
|
"_value": "null message",
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Empty_PDU",
|
|
},
|
|
"parameters": None,
|
|
},
|
|
],
|
|
"end_identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Message_Type",
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Message_Type"},
|
|
},
|
|
},
|
|
}
|
|
|
|
assert_ast_files([f"{SPEC_DIR}/message_type.rflx"], spec)
|
|
|
|
|
|
def test_parse_type_refinement_spec() -> None:
|
|
spec = {
|
|
"Message_Type": {
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "UnsignedTypeDef",
|
|
"size": {"_kind": "NumericLiteral", "_value": "8"},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "T"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "MessageTypeDef",
|
|
"aspects": [],
|
|
"message_fields": {
|
|
"_kind": "MessageFields",
|
|
"fields": [
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Foo",
|
|
},
|
|
"thens": [
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [],
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Foo",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpLe",
|
|
"_value": "<=",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16#1E#",
|
|
},
|
|
},
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bar",
|
|
},
|
|
},
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [],
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Foo",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpGt",
|
|
"_value": ">",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16#1E#",
|
|
},
|
|
},
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Baz",
|
|
},
|
|
},
|
|
],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "T",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bar",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "T",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Size",
|
|
},
|
|
"value": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "8",
|
|
},
|
|
"op": {
|
|
"_kind": "OpMul",
|
|
"_value": "*",
|
|
},
|
|
"right": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Foo",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Baz",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Opaque",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
],
|
|
"initial_field": None,
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "PDU"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "MessageTypeDef",
|
|
"aspects": [],
|
|
"message_fields": {
|
|
"_kind": "MessageFields",
|
|
"fields": [
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bar",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "T",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Size",
|
|
},
|
|
"value": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "8",
|
|
},
|
|
"op": {
|
|
"_kind": "OpMul",
|
|
"_value": "*",
|
|
},
|
|
"right": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bar",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Baz",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Opaque",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
],
|
|
"initial_field": None,
|
|
},
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Simple_PDU",
|
|
},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "NullMessageTypeDef",
|
|
"_value": "null message",
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Empty_PDU",
|
|
},
|
|
"parameters": None,
|
|
},
|
|
],
|
|
"end_identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Message_Type",
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Message_Type"},
|
|
},
|
|
},
|
|
"Type_Refinement": {
|
|
"_kind": "Specification",
|
|
"context_clause": [
|
|
{
|
|
"_kind": "ContextItem",
|
|
"item": {"_kind": "UnqualifiedID", "_value": "Message_Type"},
|
|
},
|
|
],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [
|
|
{
|
|
"_kind": "RefinementDecl",
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Bar",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {"_kind": "OpEq", "_value": "="},
|
|
"right": {"_kind": "NumericLiteral", "_value": "42"},
|
|
},
|
|
"field": {"_kind": "UnqualifiedID", "_value": "Baz"},
|
|
"pdu": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Simple_PDU",
|
|
},
|
|
"package": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Message_Type",
|
|
},
|
|
},
|
|
"sdu": {
|
|
"_kind": "ID",
|
|
"name": {"_kind": "UnqualifiedID", "_value": "PDU"},
|
|
"package": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Message_Type",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"_kind": "RefinementDecl",
|
|
"condition": None,
|
|
"field": {"_kind": "UnqualifiedID", "_value": "Baz"},
|
|
"pdu": {
|
|
"_kind": "ID",
|
|
"name": {"_kind": "UnqualifiedID", "_value": "PDU"},
|
|
"package": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Message_Type",
|
|
},
|
|
},
|
|
"sdu": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Simple_PDU",
|
|
},
|
|
"package": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Message_Type",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"end_identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Refinement",
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Refinement",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
assert_ast_files([f"{SPEC_DIR}/message_type.rflx", f"{SPEC_DIR}/type_refinement.rflx"], spec)
|
|
|
|
|
|
def test_parse_type_derivation_spec() -> None:
|
|
assert_ast_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type Foo is
|
|
message
|
|
N : T;
|
|
end message;
|
|
type Bar is new Foo;
|
|
end Test;
|
|
""",
|
|
{
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "UnsignedTypeDef",
|
|
"size": {"_kind": "NumericLiteral", "_value": "8"},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "T"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "MessageTypeDef",
|
|
"aspects": [],
|
|
"message_fields": {
|
|
"_kind": "MessageFields",
|
|
"fields": [
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "N"},
|
|
"thens": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {"_kind": "UnqualifiedID", "_value": "T"},
|
|
"package": None,
|
|
},
|
|
"type_arguments": [],
|
|
},
|
|
],
|
|
"initial_field": None,
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Foo"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "TypeDerivationDef",
|
|
"base": {
|
|
"_kind": "ID",
|
|
"name": {"_kind": "UnqualifiedID", "_value": "Foo"},
|
|
"package": None,
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Bar"},
|
|
"parameters": None,
|
|
},
|
|
],
|
|
"end_identifier": {"_kind": "UnqualifiedID", "_value": "Test"},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Test"},
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
def test_parse_ethernet_spec() -> None:
|
|
# black does not manage to honor the line limit here
|
|
|
|
spec = {
|
|
"Ethernet": {
|
|
"_kind": "Specification",
|
|
"context_clause": [],
|
|
"package_declaration": {
|
|
"_kind": "PackageNode",
|
|
"declarations": [
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "UnsignedTypeDef",
|
|
"size": {"_kind": "NumericLiteral", "_value": "48"},
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Address",
|
|
},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "RangeTypeDef",
|
|
"first": {"_kind": "NumericLiteral", "_value": "46"},
|
|
"last": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "2",
|
|
},
|
|
"op": {"_kind": "OpPow", "_value": "**"},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16",
|
|
},
|
|
},
|
|
"op": {"_kind": "OpSub", "_value": "-"},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "1",
|
|
},
|
|
},
|
|
"size": {
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Size",
|
|
},
|
|
"value": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16",
|
|
},
|
|
},
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length",
|
|
},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "RangeTypeDef",
|
|
"first": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16#8100#",
|
|
},
|
|
"last": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16#8100#",
|
|
},
|
|
"size": {
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Size",
|
|
},
|
|
"value": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16",
|
|
},
|
|
},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "TPID"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "UnsignedTypeDef",
|
|
"size": {"_kind": "NumericLiteral", "_value": "16"},
|
|
},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "TCI"},
|
|
"parameters": None,
|
|
},
|
|
{
|
|
"_kind": "TypeDecl",
|
|
"definition": {
|
|
"_kind": "MessageTypeDef",
|
|
"aspects": [],
|
|
"message_fields": {
|
|
"_kind": "MessageFields",
|
|
"fields": [
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Destination",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Address",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Source",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Address",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length_TPID",
|
|
},
|
|
"thens": [
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "First",
|
|
},
|
|
"value": {
|
|
"_kind": "Attribute",
|
|
"expression": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length_TPID", # noqa: E501
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"kind": {
|
|
"_kind": "AttrFirst",
|
|
"_value": "First",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length_TPID",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpEq",
|
|
"_value": "=",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16#8100#",
|
|
},
|
|
},
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "TPID",
|
|
},
|
|
},
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "First",
|
|
},
|
|
"value": {
|
|
"_kind": "Attribute",
|
|
"expression": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length_TPID", # noqa: E501
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"kind": {
|
|
"_kind": "AttrFirst",
|
|
"_value": "First",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length_TPID",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpNeq",
|
|
"_value": "/=",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "16#8100#",
|
|
},
|
|
},
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length",
|
|
},
|
|
},
|
|
],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "TPID",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "TPID",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "TCI",
|
|
},
|
|
"thens": [],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "TCI",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length",
|
|
},
|
|
"thens": [
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [
|
|
{
|
|
"_kind": "Aspect",
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Size",
|
|
},
|
|
"value": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpMul",
|
|
"_value": "*",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "8",
|
|
},
|
|
},
|
|
},
|
|
],
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpLe",
|
|
"_value": "<=",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "1500",
|
|
},
|
|
},
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Payload",
|
|
},
|
|
},
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [],
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpGe",
|
|
"_value": ">=",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "1536",
|
|
},
|
|
},
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Payload",
|
|
},
|
|
},
|
|
],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Type_Length",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
{
|
|
"_kind": "MessageField",
|
|
"aspects": [],
|
|
"condition": None,
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Payload",
|
|
},
|
|
"thens": [
|
|
{
|
|
"_kind": "ThenNode",
|
|
"aspects": [],
|
|
"condition": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Attribute",
|
|
"expression": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID", # noqa: E501
|
|
"_value": "Payload",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"kind": {
|
|
"_kind": "AttrSize",
|
|
"_value": "Size",
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpDiv",
|
|
"_value": "/",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "8",
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpGe",
|
|
"_value": ">=",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "46",
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpAnd",
|
|
"_value": "and",
|
|
},
|
|
"right": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "BinOp",
|
|
"left": {
|
|
"_kind": "Attribute",
|
|
"expression": {
|
|
"_kind": "Variable",
|
|
"identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID", # noqa: E501
|
|
"_value": "Payload",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
"kind": {
|
|
"_kind": "AttrSize",
|
|
"_value": "Size",
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpDiv",
|
|
"_value": "/",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "8",
|
|
},
|
|
},
|
|
"op": {
|
|
"_kind": "OpLe",
|
|
"_value": "<=",
|
|
},
|
|
"right": {
|
|
"_kind": "NumericLiteral",
|
|
"_value": "1500",
|
|
},
|
|
},
|
|
},
|
|
"target": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "null",
|
|
},
|
|
},
|
|
],
|
|
"type_arguments": [],
|
|
"type_identifier": {
|
|
"_kind": "ID",
|
|
"name": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Opaque",
|
|
},
|
|
"package": None,
|
|
},
|
|
},
|
|
],
|
|
"initial_field": None,
|
|
},
|
|
},
|
|
"identifier": {
|
|
"_kind": "UnqualifiedID",
|
|
"_value": "Frame",
|
|
},
|
|
"parameters": None,
|
|
},
|
|
],
|
|
"end_identifier": {"_kind": "UnqualifiedID", "_value": "Ethernet"},
|
|
"identifier": {"_kind": "UnqualifiedID", "_value": "Ethernet"},
|
|
},
|
|
},
|
|
}
|
|
|
|
assert_ast_files([f"{SPEC_DIR}/ethernet.rflx"], spec)
|
|
|
|
|
|
def test_parse_error_illegal_package_identifiers() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package RFLX_Types is
|
|
end RFLX_Types;
|
|
""",
|
|
r'^<stdin>:1:9: error: illegal prefix "RFLX" in package identifier "RFLX_Types"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_inconsistent_package_identifiers() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package A is
|
|
end B;
|
|
""",
|
|
r'^<stdin>:2:5: error: inconsistent package identifier "B"\n'
|
|
r'<stdin>:1:9: note: previous identifier was "A"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_incorrect_name() -> None:
|
|
assert_error_files(
|
|
[f"{SPEC_DIR}/invalid/incorrect_name.rflx"],
|
|
f"^{SPEC_DIR}/invalid/incorrect_name.rflx:1:9: error: "
|
|
'source file name does not match the package name "Test"\n'
|
|
f'{SPEC_DIR}/invalid/incorrect_name.rflx:1:9: help: either rename the file to "test.rflx" '
|
|
f'or change the package name to "Incorrect_Name"\n'
|
|
f'{SPEC_DIR}/invalid/incorrect_name.rflx:1:9: help: rename to "Incorrect_Name"\n'
|
|
f'{SPEC_DIR}/invalid/incorrect_name.rflx:3:5: help: rename to "Incorrect_Name"$',
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("filename", ["Tls", "TLS", "BadCasing"])
|
|
def test_parse_error_incorrect_name_should_rename_only(
|
|
filename: str,
|
|
) -> None:
|
|
assert_error_files(
|
|
[f"{SPEC_DIR}/invalid/{filename}.rflx"],
|
|
f'^{SPEC_DIR}/invalid/{filename}.rflx:1:9: error: source file name "{filename}.rflx" must '
|
|
"be in lower case characters only\n"
|
|
f"{SPEC_DIR}/invalid/{filename}.rflx:1:9: help: rename the file "
|
|
f'to "{filename.lower()}.rflx"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_incorrect_specification() -> None:
|
|
assert_error_files(
|
|
[f"{SPEC_DIR}/invalid/incorrect_specification.rflx"],
|
|
f"^{SPEC_DIR}/invalid/incorrect_specification.rflx:3:10: error: Expected 'is', got ';'$",
|
|
)
|
|
|
|
|
|
def test_parse_error_unexpected_exception_in_parser(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
p = parser.Parser()
|
|
monkeypatch.setattr(parser, "check_naming", lambda _x, _e, _o: raise_parser_error())
|
|
with pytest.raises(RecordFluxError, match=r"^error: TEST$"):
|
|
p.parse_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
end Test;
|
|
""",
|
|
)
|
|
|
|
|
|
def test_parse_error_context_dependency_cycle() -> None:
|
|
p = parser.Parser()
|
|
|
|
with pytest.raises(
|
|
RecordFluxError,
|
|
match=(
|
|
r"^"
|
|
f"{SPEC_DIR}/invalid/context_cycle.rflx:1:6: error: dependency cycle when "
|
|
f'including "Context_Cycle_1"\n'
|
|
f"{SPEC_DIR}/invalid/context_cycle_1.rflx:1:6: "
|
|
'note: when including "Context_Cycle_2"\n'
|
|
f"{SPEC_DIR}/invalid/context_cycle_2.rflx:1:6: "
|
|
'note: when including "Context_Cycle_3"\n'
|
|
f"{SPEC_DIR}/invalid/context_cycle_3.rflx:1:6: "
|
|
'note: when including "Context_Cycle_1"'
|
|
r"$"
|
|
),
|
|
):
|
|
p.parse(Path(f"{SPEC_DIR}/invalid/context_cycle.rflx"))
|
|
|
|
p.parse(Path(f"{SPEC_DIR}/integer_type.rflx"))
|
|
|
|
|
|
def test_parse_error_context_dependency_cycle_2() -> None:
|
|
assert_error_files(
|
|
[f"{SPEC_DIR}/invalid/context_cycle_1.rflx"],
|
|
f"^"
|
|
f"{SPEC_DIR}/invalid/context_cycle_1.rflx:1:6: error: dependency cycle when "
|
|
f'including "Context_Cycle_2"\n'
|
|
f"{SPEC_DIR}/invalid/context_cycle_2.rflx:1:6: "
|
|
'note: when including "Context_Cycle_3"\n'
|
|
f"{SPEC_DIR}/invalid/context_cycle_3.rflx:1:6: "
|
|
'note: when including "Context_Cycle_1"'
|
|
f"$",
|
|
)
|
|
|
|
|
|
def test_parse_string_error_context_dependency_cycle() -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(Path(f"{SPEC_DIR}/invalid/context_cycle.rflx").read_text())
|
|
p.parse_string(Path(f"{SPEC_DIR}/invalid/context_cycle_1.rflx").read_text())
|
|
p.parse_string(Path(f"{SPEC_DIR}/invalid/context_cycle_2.rflx").read_text())
|
|
|
|
with pytest.raises(
|
|
RecordFluxError,
|
|
match=(
|
|
r"^"
|
|
r'<stdin>:1:6: error: dependency cycle when including "Context_Cycle_1"\n'
|
|
r'<stdin>:1:6: note: when including "Context_Cycle_2"\n'
|
|
r'<stdin>:1:6: note: when including "Context_Cycle_3"'
|
|
r"$"
|
|
),
|
|
):
|
|
p.parse_string(Path(f"{SPEC_DIR}/invalid/context_cycle_3.rflx").read_text())
|
|
|
|
p.parse_string(Path(f"{SPEC_DIR}/integer_type.rflx").read_text())
|
|
|
|
|
|
def test_parse_error_message_undefined_message_field() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type PDU is
|
|
message
|
|
Foo : T
|
|
then Bar if Foo < 100
|
|
then null if Foo >= 100;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:6:18: error: undefined field "Bar"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_invalid_location_expression() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type PDU is
|
|
message
|
|
Foo : T
|
|
then Bar
|
|
with Foo => 1;
|
|
Bar : T;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:7:21: error: invalid aspect "Foo"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_sequence_undefined_type() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is sequence of Foo;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:2:26: error: undefined element type "Test::Foo"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_refinement_undefined_message() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
for PDU use (Foo => Bar);
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:2:8: error: undefined type "Test::PDU" in refinement\n'
|
|
r'<stdin>:2:24: error: undefined type "Test::Bar" in refinement of "Test::PDU"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_refinement_undefined_sdu() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type PDU is
|
|
message
|
|
Foo : T;
|
|
end message;
|
|
for PDU use (Foo => Bar);
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:7:24: error: undefined type "Test::Bar" in refinement of "Test::PDU"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_derivation_undefined_type() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type Bar is new Foo;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:2:20: error: undefined base message "Test::Foo" in derived message$',
|
|
)
|
|
|
|
|
|
def test_parse_error_derivation_unsupported_type() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type Foo is unsigned 8;
|
|
type Bar is new Foo;
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
r"<stdin>:3:9: error: invalid derivation\n"
|
|
r"<stdin>:2:9: note: base type must be a message"
|
|
r"$",
|
|
)
|
|
|
|
|
|
def test_parse_error_multiple_initial_node_edges() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type PDU is
|
|
message
|
|
null
|
|
then Foo,
|
|
then Bar;
|
|
Foo : T;
|
|
Bar : T;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:6:21: error: Expected ';', got ','$",
|
|
)
|
|
|
|
|
|
def test_parse_error_multiple_initial_nodes() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type PDU is
|
|
message
|
|
null
|
|
then Foo;
|
|
null
|
|
then Bar;
|
|
Foo : T;
|
|
Bar : T;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:8:13: error: Expected ':', got 'then'$",
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("keyword", ADA_KEYWORDS)
|
|
def test_parse_error_reserved_word_in_type_name(keyword: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
type {keyword.title()} is unsigned 8;
|
|
end Test;
|
|
""",
|
|
rf'^<stdin>:2:9: error: reserved word "{keyword.title()}" used as identifier$',
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("keyword", [*ADA_KEYWORDS, "message"])
|
|
def test_parse_error_reserved_word_in_message_field(keyword: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type PDU is
|
|
message
|
|
{keyword.title()} : T;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
rf'^<stdin>:5:10: error: reserved word "{keyword.title()}" used as identifier$',
|
|
)
|
|
|
|
|
|
def test_parse_error_reserved_word_in_refinement_field() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type PDU is
|
|
message
|
|
End : Opaque;
|
|
end message;
|
|
for PDU use (End => PDU);
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
r'<stdin>:4:10: error: reserved word "End" used as identifier\n'
|
|
r'<stdin>:6:17: error: reserved word "End" used as identifier'
|
|
r"$",
|
|
)
|
|
|
|
|
|
def test_parse_error_reserved_word_in_state_machine_identifier() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
generic
|
|
machine End is
|
|
begin
|
|
state S
|
|
is
|
|
begin
|
|
transition
|
|
goto null
|
|
end S;
|
|
end End;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:3:12: error: reserved word "End" used as identifier$',
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("identifier", ILLEGAL_IDENTIFIERS)
|
|
def test_parse_error_illegal_identifier_in_type_name(identifier: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
type {identifier} is unsigned 8;
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
rf'<stdin>:2:9: error: illegal identifier "{identifier}"\n'
|
|
r'<stdin>:2:9: note: identifiers starting with "RFLX_"'
|
|
r" are reserved for internal use"
|
|
r"$",
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("identifier", ILLEGAL_IDENTIFIERS)
|
|
def test_parse_error_illegal_identifier_in_message_field(identifier: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type PDU is
|
|
message
|
|
{identifier} : T;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
rf'<stdin>:5:10: error: illegal identifier "{identifier}"\n'
|
|
r'<stdin>:5:10: note: identifiers starting with "RFLX_"'
|
|
r" are reserved for internal use"
|
|
r"$",
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("identifier", ILLEGAL_IDENTIFIERS)
|
|
def test_parse_error_illegal_identifier_in_refinement_field(identifier: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
type PDU is
|
|
message
|
|
{identifier} : Opaque;
|
|
end message;
|
|
for PDU use ({identifier} => PDU);
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
rf'<stdin>:4:10: error: illegal identifier "{identifier}"\n'
|
|
r'<stdin>:4:10: note: identifiers starting with "RFLX_"'
|
|
r" are reserved for internal use\n"
|
|
rf'<stdin>:6:17: error: illegal identifier "{identifier}"\n'
|
|
r'<stdin>:6:17: note: identifiers starting with "RFLX_"'
|
|
r" are reserved for internal use"
|
|
r"$",
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("identifier", ILLEGAL_IDENTIFIERS)
|
|
def test_parse_error_illegal_identifier_in_state_machine_identifier(identifier: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
generic
|
|
machine {identifier} is
|
|
begin
|
|
state S
|
|
is
|
|
begin
|
|
transition
|
|
goto null
|
|
end S;
|
|
end {identifier};
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
rf'<stdin>:3:12: error: illegal identifier "{identifier}"\n'
|
|
r'<stdin>:3:12: note: identifiers starting with "RFLX_"'
|
|
r" are reserved for internal use"
|
|
r"$",
|
|
)
|
|
|
|
|
|
def test_parse_error_invalid_context_clause(tmp_path: Path) -> None:
|
|
test_file = tmp_path / "test.rflx"
|
|
|
|
test_file.write_text("with invalid", encoding="utf-8")
|
|
|
|
p = parser.Parser()
|
|
|
|
with pytest.raises(
|
|
RecordFluxError,
|
|
match=rf"^{test_file}:1:13: error: Expected ';', got Termination$",
|
|
):
|
|
p.parse(test_file)
|
|
|
|
|
|
@pytest.mark.parametrize("spec", ["empty_package", "context"])
|
|
def test_create_model_no_messages(spec: str) -> None:
|
|
assert_messages_files([f"{SPEC_DIR}/{spec}.rflx"], [])
|
|
|
|
|
|
def test_create_model_message_type_message() -> None:
|
|
simple_structure = [
|
|
model.Link(model.INITIAL, model.Field("Bar"), location=Location((1, 1))),
|
|
model.Link(
|
|
model.Field("Bar"),
|
|
model.Field("Baz"),
|
|
size=expr.Mul(expr.Number(8), expr.Variable("Bar"), location=Location((2, 2))),
|
|
location=Location((2, 2)),
|
|
),
|
|
model.Link(
|
|
model.Field("Baz"),
|
|
model.FINAL,
|
|
),
|
|
]
|
|
|
|
simple_types = {
|
|
model.Field(ID("Bar", location=Location((1, 1)))): model.UnsignedInteger(
|
|
"Message_Type::T",
|
|
expr.Number(8),
|
|
),
|
|
model.Field(ID("Baz", location=Location((1, 1)))): model.Opaque(),
|
|
}
|
|
|
|
simple_message = model.Message(
|
|
ID("Message_Type::Simple_PDU", Location((1, 1))),
|
|
simple_structure,
|
|
simple_types,
|
|
location=Location((1, 1), end=(1, 2)),
|
|
)
|
|
|
|
structure = [
|
|
model.Link(model.INITIAL, model.Field("Foo"), location=Location((1, 1))),
|
|
model.Link(
|
|
model.Field("Foo"),
|
|
model.Field("Bar"),
|
|
expr.LessEqual(expr.Variable("Foo"), expr.Number(30, 16), location=Location((3, 3))),
|
|
location=Location((3, 3)),
|
|
),
|
|
model.Link(
|
|
model.Field("Foo"),
|
|
model.Field("Baz"),
|
|
expr.Greater(expr.Variable("Foo"), expr.Number(30, 16), location=Location((5, 5))),
|
|
size=expr.Mul(expr.Number(8), expr.Variable("Foo"), location=Location((5, 5))),
|
|
location=Location((5, 5)),
|
|
),
|
|
model.Link(
|
|
model.Field("Bar"),
|
|
model.Field("Baz"),
|
|
size=expr.Mul(expr.Number(8), expr.Variable("Foo"), location=Location((6, 6))),
|
|
location=Location((6, 6)),
|
|
),
|
|
model.Link(
|
|
model.Field("Baz"),
|
|
model.FINAL,
|
|
location=Location((7, 7)),
|
|
),
|
|
]
|
|
|
|
types = {
|
|
**simple_types,
|
|
model.Field(ID("Foo", location=Location((1, 1)))): model.UnsignedInteger(
|
|
"Message_Type::T",
|
|
expr.Number(8),
|
|
),
|
|
}
|
|
|
|
message = model.Message(
|
|
ID("Message_Type::PDU", Location((1, 1))),
|
|
structure,
|
|
types,
|
|
location=Location((1, 1), end=(1, 2)),
|
|
)
|
|
|
|
empty_message = model.Message(
|
|
ID("Message_Type::Empty_PDU", Location((1, 1))),
|
|
[],
|
|
{},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
)
|
|
|
|
assert_messages_files(
|
|
[f"{SPEC_DIR}/message_type.rflx"],
|
|
[message, simple_message, empty_message],
|
|
)
|
|
|
|
|
|
def test_create_model_message_in_message() -> None:
|
|
length = model.UnsignedInteger(
|
|
"Message_In_Message::Length",
|
|
expr.Number(16),
|
|
)
|
|
|
|
length_value = model.Message(
|
|
ID("Message_In_Message::Length_Value", Location((1, 1))),
|
|
[
|
|
model.Link(model.INITIAL, model.Field("Length"), location=Location((1, 1))),
|
|
model.Link(
|
|
model.Field("Length"),
|
|
model.Field("Value"),
|
|
size=expr.Mul(expr.Number(8), expr.Variable("Length"), location=Location((2, 2))),
|
|
location=Location((2, 2)),
|
|
),
|
|
model.Link(model.Field("Value"), model.FINAL, location=Location((3, 3))),
|
|
],
|
|
{
|
|
model.Field(ID("Length", location=Location((1, 1)))): length,
|
|
model.Field(ID("Value", location=Location((2, 2)))): model.Opaque(),
|
|
},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
)
|
|
|
|
derived_length_value = model.DerivedMessage(
|
|
ID("Message_In_Message::Derived_Length_Value", Location((1, 1))),
|
|
length_value,
|
|
)
|
|
|
|
message = model.Message(
|
|
ID("Message_In_Message::Message", Location((1, 1))),
|
|
[
|
|
model.Link(model.INITIAL, model.Field("Foo_Length"), location=Location((1, 1))),
|
|
model.Link(
|
|
model.Field("Foo_Value"),
|
|
model.Field("Bar_Length"),
|
|
location=Location((2, 2)),
|
|
),
|
|
model.Link(model.Field("Bar_Value"), model.FINAL, location=Location((3, 3))),
|
|
model.Link(
|
|
model.Field("Foo_Length"),
|
|
model.Field("Foo_Value"),
|
|
size=expr.Mul(
|
|
expr.Variable("Foo_Length"),
|
|
expr.Number(8),
|
|
location=Location((4, 4)),
|
|
),
|
|
location=Location((4, 4)),
|
|
),
|
|
model.Link(
|
|
model.Field("Bar_Length"),
|
|
model.Field("Bar_Value"),
|
|
size=expr.Mul(
|
|
expr.Variable("Bar_Length"),
|
|
expr.Number(8),
|
|
location=Location((5, 5)),
|
|
),
|
|
location=Location((5, 5)),
|
|
),
|
|
],
|
|
{
|
|
model.Field(ID("Foo_Length", location=Location((1, 1)))): length,
|
|
model.Field(ID("Foo_Value", location=Location((2, 2)))): model.Opaque(),
|
|
model.Field(ID("Bar_Length", location=Location((3, 3)))): length,
|
|
model.Field(ID("Bar_Value", location=Location((4, 4)))): model.Opaque(),
|
|
},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
)
|
|
|
|
derived_message = model.DerivedMessage(
|
|
ID("Message_In_Message::Derived_Message", Location((1, 1))),
|
|
message,
|
|
)
|
|
|
|
assert_messages_files(
|
|
[f"{SPEC_DIR}/message_in_message.rflx"],
|
|
[length_value, derived_length_value, message, derived_message],
|
|
)
|
|
|
|
|
|
def test_create_model_ethernet_frame() -> None:
|
|
assert_messages_files([f"{SPEC_DIR}/ethernet.rflx"], [models.ethernet_frame()])
|
|
|
|
|
|
def test_create_model_type_derivation_message() -> None:
|
|
t = model.UnsignedInteger("Test::T", expr.Number(8))
|
|
|
|
structure = [
|
|
model.Link(model.INITIAL, model.Field("Baz"), location=Location((1, 1))),
|
|
model.Link(model.Field("Baz"), model.FINAL, location=Location((2, 2))),
|
|
]
|
|
|
|
types = {model.Field(ID("Baz", location=Location((1, 1)))): t}
|
|
|
|
message_foo = model.Message(
|
|
ID("Test::Foo", Location((1, 1))),
|
|
structure,
|
|
types,
|
|
location=Location((1, 1), end=(1, 2)),
|
|
)
|
|
message_bar = model.DerivedMessage(ID("Test::Bar", Location((1, 1))), message_foo)
|
|
|
|
assert_messages_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type Foo is
|
|
message
|
|
Baz : T;
|
|
end message;
|
|
type Bar is new Foo;
|
|
end Test;
|
|
""",
|
|
[message_foo, message_bar],
|
|
)
|
|
|
|
|
|
def test_create_model_type_derivation_refinements() -> None:
|
|
message_foo = model.Message(
|
|
ID("Test::Foo", Location((1, 1))),
|
|
[
|
|
model.Link(
|
|
model.INITIAL,
|
|
model.Field("Baz"),
|
|
size=expr.Number(48, location=Location((1, 1))),
|
|
location=Location((1, 1)),
|
|
),
|
|
model.Link(model.Field("Baz"), model.FINAL, location=Location((2, 2))),
|
|
],
|
|
{model.Field(ID("Baz", location=Location((1, 1)))): model.Opaque()},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
)
|
|
message_bar = model.DerivedMessage(ID("Test::Bar", Location((1, 1))), message_foo)
|
|
|
|
assert_refinements_string(
|
|
"""\
|
|
package Test is
|
|
type Foo is
|
|
message
|
|
null
|
|
then Baz
|
|
with Size => 48;
|
|
Baz : Opaque;
|
|
end message;
|
|
for Foo use (Baz => Foo);
|
|
type Bar is new Foo;
|
|
for Bar use (Baz => Bar);
|
|
end Test;
|
|
""",
|
|
[
|
|
model.Refinement("Test", message_foo, model.Field("Baz"), message_foo),
|
|
model.Refinement("Test", message_bar, model.Field("Baz"), message_bar),
|
|
],
|
|
)
|
|
|
|
|
|
def test_create_model_message_locations() -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
F1 : T;
|
|
F2 : T;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
)
|
|
m = p.create_model()
|
|
assert [f.identifier.location for f in m.messages[0].fields] == [
|
|
Location((5, 10), Path("<stdin>"), (5, 12)),
|
|
Location((6, 10), Path("<stdin>"), (6, 12)),
|
|
]
|
|
|
|
|
|
def test_create_model_state_machine_locations() -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(
|
|
"""\
|
|
package Test is
|
|
generic
|
|
with function F return Boolean;
|
|
machine S is
|
|
Y : Boolean := F;
|
|
begin
|
|
state A is
|
|
Z : Boolean := Y;
|
|
begin
|
|
Z := False;
|
|
transition
|
|
goto null
|
|
if Z = False
|
|
goto A
|
|
exception
|
|
goto null
|
|
end A;
|
|
end S;
|
|
end Test;
|
|
""",
|
|
)
|
|
m = p.create_model()
|
|
assert [p.location for p in m.state_machines[0].parameters] == [
|
|
Location((3, 21), Path("<stdin>"), (3, 22)),
|
|
]
|
|
|
|
|
|
def test_create_model_sequence_with_imported_element_type() -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
end Test;
|
|
""",
|
|
)
|
|
p.parse_string(
|
|
"""\
|
|
with Test;
|
|
package Sequence_Test is
|
|
type T is sequence of Test::T;
|
|
end Sequence_Test;
|
|
""",
|
|
)
|
|
m = p.create_model()
|
|
sequences = [t for t in m.types if isinstance(t, model.Sequence)]
|
|
assert len(sequences) == 1
|
|
assert sequences[0].identifier == ID("Sequence_Test::T")
|
|
assert sequences[0].element_type == model.UnsignedInteger(
|
|
"Test::T",
|
|
expr.Number(8),
|
|
)
|
|
|
|
|
|
def test_create_model_checksum() -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 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));
|
|
end Test;
|
|
""",
|
|
)
|
|
m = p.create_model()
|
|
|
|
assert m.messages[0].checksums == {
|
|
ID("C1"): [expr.Variable("F3"), expr.ValueRange(expr.First("F1"), expr.Last("F2"))],
|
|
ID("C2"): [
|
|
expr.ValueRange(
|
|
expr.Add(expr.Last("C1"), expr.Number(1)),
|
|
expr.Sub(expr.First("C2"), expr.Number(1)),
|
|
),
|
|
],
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("spec", "byte_order"),
|
|
[
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
F1 : T;
|
|
F2 : T;
|
|
F3 : T;
|
|
end message
|
|
with Byte_Order => Low_Order_First;
|
|
end Test;
|
|
""",
|
|
model.ByteOrder.LOW_ORDER_FIRST,
|
|
),
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
F1 : T;
|
|
F2 : T;
|
|
F3 : T;
|
|
end message
|
|
with Byte_Order => High_Order_First;
|
|
end Test;
|
|
""",
|
|
model.ByteOrder.HIGH_ORDER_FIRST,
|
|
),
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
F1 : T;
|
|
F2 : T;
|
|
F3 : T;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
model.ByteOrder.HIGH_ORDER_FIRST,
|
|
),
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 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;
|
|
end Test;
|
|
""",
|
|
model.ByteOrder.LOW_ORDER_FIRST,
|
|
),
|
|
],
|
|
)
|
|
def test_create_model_byteorder(spec: str, byte_order: ByteOrder) -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(spec)
|
|
m = p.create_model()
|
|
for v in m.messages[0].byte_order.values():
|
|
assert v == byte_order
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"spec",
|
|
[
|
|
"""\
|
|
type M is
|
|
message
|
|
A : T
|
|
then B
|
|
if A > 10 and A < 100;
|
|
B : T;
|
|
end message;
|
|
""",
|
|
],
|
|
)
|
|
def test_message_field_condition(spec: str) -> None:
|
|
spec = textwrap.indent(textwrap.dedent(spec), " ")
|
|
assert_messages_string(
|
|
f"""\
|
|
package Test is
|
|
|
|
type T is unsigned 8;
|
|
{spec}
|
|
end Test;
|
|
""",
|
|
[
|
|
Message(
|
|
ID("Test::M", Location((1, 1))),
|
|
[
|
|
Link(INITIAL, Field("A"), location=Location((1, 1))),
|
|
Link(
|
|
Field("A"),
|
|
Field("B"),
|
|
condition=expr.And(
|
|
expr.Greater(expr.Variable("A"), expr.Number(10)),
|
|
expr.Less(expr.Variable("A"), expr.Number(100)),
|
|
location=Location((2, 1)),
|
|
),
|
|
location=Location((2, 2)),
|
|
),
|
|
Link(Field("B"), FINAL, location=Location((3, 3))),
|
|
],
|
|
{
|
|
Field(ID("A", location=Location((1, 1)))): T,
|
|
Field(ID("B", location=Location((2, 2)))): T,
|
|
},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"spec",
|
|
[
|
|
"""\
|
|
type M is
|
|
message
|
|
A : T
|
|
then B
|
|
with First => A'First;
|
|
B : T;
|
|
end message;
|
|
""",
|
|
"""\
|
|
type M is
|
|
message
|
|
A : T;
|
|
B : T
|
|
with First => A'First;
|
|
end message;
|
|
""",
|
|
],
|
|
)
|
|
def test_message_field_first(spec: str) -> None:
|
|
spec = textwrap.indent(textwrap.dedent(spec), " ")
|
|
assert_messages_string(
|
|
f"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
{spec}
|
|
end Test;
|
|
""",
|
|
[
|
|
Message(
|
|
ID("Test::M", Location((1, 1))),
|
|
[
|
|
Link(INITIAL, Field("A"), location=Location((1, 1))),
|
|
Link(Field("A"), Field("B"), first=expr.First("A"), location=Location((2, 2))),
|
|
Link(Field("B"), FINAL, location=Location((3, 3))),
|
|
],
|
|
{
|
|
Field(ID("A", location=Location((1, 1)))): T,
|
|
Field(ID("B", location=Location((2, 2)))): T,
|
|
},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"spec",
|
|
[
|
|
"""\
|
|
type M is
|
|
message
|
|
A : T
|
|
then B
|
|
with Size => 8;
|
|
B : Opaque;
|
|
end message;
|
|
""",
|
|
"""\
|
|
type M is
|
|
message
|
|
A : T;
|
|
B : Opaque
|
|
with Size => 8;
|
|
end message;
|
|
""",
|
|
],
|
|
)
|
|
def test_message_field_size(spec: str) -> None:
|
|
spec = textwrap.indent(textwrap.dedent(spec), " ")
|
|
assert_messages_string(
|
|
f"""\
|
|
package Test is
|
|
|
|
type T is unsigned 8;
|
|
|
|
{spec}
|
|
end Test;
|
|
""",
|
|
[
|
|
Message(
|
|
ID("Test::M", Location((1, 1))),
|
|
[
|
|
Link(INITIAL, Field("A"), location=Location((1, 1))),
|
|
Link(
|
|
Field("A"),
|
|
Field("B"),
|
|
size=expr.Number(8, location=Location((2, 2))),
|
|
location=Location((2, 2)),
|
|
),
|
|
Link(Field("B"), FINAL, location=Location((3, 3))),
|
|
],
|
|
{
|
|
Field(ID("A", location=Location((1, 1)))): T,
|
|
Field(ID("B", location=Location((2, 2)))): OPAQUE,
|
|
},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("link", "field_b"),
|
|
[
|
|
("with First => A'First if A > 10 and A < 100", "with Size => 8"),
|
|
("with First => A'First, Size => 8 if A > 10 and A < 100", ""),
|
|
("with Size => 8 if A > 10 and A < 100", "with First => A'First"),
|
|
("if A > 10 and A < 100", "with First => A'First, Size => 8"),
|
|
],
|
|
)
|
|
def test_message_field_condition_and_aspects(link: str, field_b: str) -> None:
|
|
link = f"\n {link}" if link else ""
|
|
field_b = f"\n {field_b}" if field_b else ""
|
|
assert_messages_string(
|
|
f"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
A : T
|
|
then B{link};
|
|
B : Opaque{field_b};
|
|
end message;
|
|
end Test;
|
|
""",
|
|
[
|
|
Message(
|
|
ID("Test::M", Location((1, 1))),
|
|
[
|
|
Link(INITIAL, Field("A"), location=Location((1, 1))),
|
|
Link(
|
|
Field("A"),
|
|
Field("B"),
|
|
first=expr.First("A"),
|
|
size=expr.Number(8, location=Location((2, 2))),
|
|
condition=expr.And(
|
|
expr.Greater(expr.Variable("A"), expr.Number(10)),
|
|
expr.Less(expr.Variable("A"), expr.Number(100)),
|
|
location=Location((2, 3)),
|
|
),
|
|
location=Location((2, 2)),
|
|
),
|
|
Link(Field("B"), FINAL, location=Location((3, 3))),
|
|
],
|
|
{
|
|
Field(ID("A", location=Location((1, 1)))): T,
|
|
Field(ID("B", location=Location((2, 2)))): OPAQUE,
|
|
},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
def test_parameterized_messages() -> None:
|
|
assert_messages_string(
|
|
"""\
|
|
package Test is
|
|
|
|
type T is unsigned 8;
|
|
|
|
type M (P : T) is
|
|
message
|
|
F : Opaque
|
|
with Size => P * 8
|
|
then null
|
|
if P < 100;
|
|
end message;
|
|
|
|
type M_S is
|
|
message
|
|
F : M (P => 16);
|
|
end message;
|
|
|
|
type M_D is
|
|
message
|
|
L : T;
|
|
F : M (P => L);
|
|
end message;
|
|
|
|
end Test;
|
|
""",
|
|
[
|
|
Message(
|
|
ID("Test::M", Location((1, 1))),
|
|
[
|
|
Link(
|
|
INITIAL,
|
|
Field("F"),
|
|
size=expr.Mul(
|
|
expr.Variable("P"),
|
|
expr.Number(8),
|
|
location=Location((1, 1)),
|
|
),
|
|
location=Location((1, 1)),
|
|
),
|
|
Link(
|
|
Field("F"),
|
|
FINAL,
|
|
condition=expr.Less(
|
|
expr.Variable("P"),
|
|
expr.Number(100),
|
|
location=Location((2, 1)),
|
|
),
|
|
location=Location((2, 2)),
|
|
),
|
|
],
|
|
{
|
|
Field(ID("P", location=Location((1, 1)))): T,
|
|
Field(ID("F", location=Location((2, 2)))): OPAQUE,
|
|
},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
),
|
|
Message(
|
|
ID("Test::M_S", Location((1, 1))),
|
|
[
|
|
Link(
|
|
INITIAL,
|
|
Field("F_F"),
|
|
size=expr.Mul(expr.Number(16), expr.Number(8), location=Location((1, 1))),
|
|
location=Location((1, 1)),
|
|
),
|
|
Link(Field("F_F"), FINAL, condition=expr.TRUE, location=Location((2, 2))),
|
|
],
|
|
{Field(ID("F_F", location=Location((1, 1)))): OPAQUE},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
),
|
|
Message(
|
|
ID("Test::M_D", Location((1, 1))),
|
|
[
|
|
Link(INITIAL, Field("L"), location=Location((1, 1))),
|
|
Link(
|
|
Field("L"),
|
|
Field("F_F"),
|
|
size=expr.Mul(
|
|
expr.Variable("L"),
|
|
expr.Number(8),
|
|
location=Location((2, 2)),
|
|
),
|
|
location=Location((2, 2)),
|
|
),
|
|
Link(
|
|
Field("F_F"),
|
|
FINAL,
|
|
condition=expr.Less(
|
|
expr.Variable("L"),
|
|
expr.Number(100),
|
|
location=Location((3, 2)),
|
|
),
|
|
location=Location((3, 3)),
|
|
),
|
|
],
|
|
{
|
|
Field(ID("L", location=Location((1, 1)))): T,
|
|
Field(ID("F_F", location=Location((2, 2)))): OPAQUE,
|
|
},
|
|
location=Location((1, 1), end=(1, 2)),
|
|
),
|
|
],
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("parameters", "error"),
|
|
[
|
|
(
|
|
"",
|
|
r"^"
|
|
r"<stdin>:15:14: error: missing argument\n"
|
|
r'<stdin>:5:14: help: expected argument for parameter "P"'
|
|
r"$",
|
|
),
|
|
(
|
|
" (Q => 16)",
|
|
r"^"
|
|
r'<stdin>:15:19: error: unexpected argument "Q"\n'
|
|
r'<stdin>:15:19: help: expected argument for parameter "P"'
|
|
r"$",
|
|
),
|
|
(
|
|
" (P => 16, Q => 16)",
|
|
r"^"
|
|
r'<stdin>:15:28: error: unexpected argument "Q"\n'
|
|
r"<stdin>:15:28: help: expected no argument"
|
|
r"$",
|
|
),
|
|
(
|
|
" (Q => 16, P => 16)",
|
|
r"^"
|
|
r'<stdin>:15:19: error: unexpected argument "Q"\n'
|
|
r'<stdin>:15:19: help: expected argument for parameter "P"\n'
|
|
r'<stdin>:15:28: error: unexpected argument "P"\n'
|
|
r"<stdin>:15:28: help: expected no argument"
|
|
r"$",
|
|
),
|
|
(
|
|
" (P => 16, P => 16)",
|
|
r"^"
|
|
r'<stdin>:15:28: error: unexpected argument "P"\n'
|
|
r"<stdin>:15:28: help: expected no argument"
|
|
r"$",
|
|
),
|
|
],
|
|
)
|
|
def test_parse_error_invalid_arguments_for_parameterized_messages(
|
|
parameters: str,
|
|
error: str,
|
|
) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
|
|
type T is unsigned 8;
|
|
|
|
type M_P (P : T) is
|
|
message
|
|
F : Opaque
|
|
with Size => P * 8
|
|
then null
|
|
if P < 100;
|
|
end message;
|
|
|
|
type M is
|
|
message
|
|
F : M_P{parameters};
|
|
end message;
|
|
|
|
end Test;
|
|
""",
|
|
error,
|
|
)
|
|
|
|
|
|
def test_parse_error_invalid_range_aspect() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is range 1 .. 200 with Invalid => 42;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:2:14: error: invalid aspect "Invalid" for range type "Test::T"$',
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("spec", "error"),
|
|
[
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is (A, B) with Foo;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:2:9: error: no size set for "Test::T"$',
|
|
),
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is (A, B) with Always_Valid => Invalid;
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
"<stdin>:2:42: error: invalid Always_Valid expression: Invalid\n"
|
|
'<stdin>:2:9: error: no size set for "Test::T"'
|
|
r"$",
|
|
),
|
|
],
|
|
)
|
|
def test_parse_error_invalid_enum(spec: str, error: str) -> None:
|
|
assert_error_string(spec, error)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"spec",
|
|
[
|
|
"type X (P : Y) is range 1 .. 100 with Size => 8",
|
|
"type X (P : Y) is (A, B) with Size => 8",
|
|
"type X (P : Y) is sequence of T",
|
|
"type X (P : Y) is new M",
|
|
"type X (P : Y) is null message",
|
|
],
|
|
)
|
|
def test_parse_error_invalid_parameterized_type(spec: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
F : T;
|
|
end message;
|
|
{spec};
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:7:12: error: only message types can be parameterized$",
|
|
)
|
|
|
|
|
|
def test_parse_error_undefined_parameter() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M (P : X) is
|
|
message
|
|
F : T;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:3:16: error: undefined type "Test::X"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_name_conflict_between_parameters() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M (P : T; P : T) is
|
|
message
|
|
F : T
|
|
then null
|
|
if P = F;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
r'<stdin>:3:19: error: name conflict for "P"\n'
|
|
r"<stdin>:3:12: note: conflicting name"
|
|
r"$",
|
|
)
|
|
|
|
|
|
def test_parse_error_name_conflict_between_field_and_parameter() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M (P : T) is
|
|
message
|
|
P : T;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
r'<stdin>:5:10: error: name conflict for "P"\n'
|
|
r"<stdin>:3:12: note: conflicting name"
|
|
r"$",
|
|
)
|
|
|
|
|
|
def test_parse_error_duplicate_spec_file() -> None:
|
|
p = parser.Parser()
|
|
p.parse(SPEC_DIR / "message_type.rflx")
|
|
with pytest.raises(
|
|
RecordFluxError,
|
|
match=(
|
|
"^tests/data/specs/subdir/message_type.rflx:1:9: error: duplicate"
|
|
" specification\n"
|
|
"tests/data/specs/message_type.rflx:1:9: note: previous specification$"
|
|
),
|
|
):
|
|
p.parse(SPEC_DIR / "subdir/message_type.rflx")
|
|
|
|
|
|
def test_parse_error_duplicate_spec_stdin_file() -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(
|
|
"""\
|
|
package Message_Type is
|
|
type T is unsigned 32;
|
|
type M is
|
|
message
|
|
F : T;
|
|
end message;
|
|
end Message_Type;
|
|
""",
|
|
)
|
|
with pytest.raises(
|
|
RecordFluxError,
|
|
match=(
|
|
"^tests/data/specs/subdir/message_type.rflx:1:9: error: duplicate"
|
|
" specification\n"
|
|
"<stdin>:1:9: note: previous specification$"
|
|
),
|
|
):
|
|
p.parse(SPEC_DIR / "subdir/message_type.rflx")
|
|
|
|
|
|
@pytest.mark.parametrize("keyword", ADA_KEYWORDS)
|
|
def test_parse_reserved_words_as_enum_literals(keyword: str) -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(
|
|
f"""\
|
|
package Test is
|
|
type A is (Reset => 1, {keyword.title()} => 2) with Size => 8;
|
|
end Test;
|
|
""",
|
|
)
|
|
|
|
|
|
def test_parse_reserved_word_as_channel_name() -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(
|
|
"""\
|
|
package Test is
|
|
generic
|
|
Channel : Channel with Readable, Writable;
|
|
machine S is
|
|
begin
|
|
state A
|
|
is
|
|
Avail : Boolean := Channel'Has_Data;
|
|
begin
|
|
transition
|
|
goto A if Avail
|
|
goto null
|
|
end A;
|
|
end S;
|
|
end Test;
|
|
""",
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"keyword",
|
|
RESERVED_WORDS,
|
|
)
|
|
def test_parse_reserved_word_in_link_condition(keyword: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
type I is unsigned 8;
|
|
type M (A : Boolean) is
|
|
message
|
|
X : Boolean
|
|
then Z
|
|
if A and {keyword};
|
|
Z : Opaque
|
|
with Size => 8;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
rf'^<stdin>:7:25: error: reserved word "{keyword}" used as identifier\n'
|
|
rf'<stdin>:7:25: error: undefined variable "{keyword}"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_duplicate_message_aspect() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
A : T;
|
|
B : Opaque
|
|
with First => A'First, Size => A'Size, First => A'First, Size => A'Size;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:7:52: error: duplicate aspect "First"\n'
|
|
"<stdin>:7:18: note: previous location\n"
|
|
'<stdin>:7:70: error: duplicate aspect "Size"\n'
|
|
"<stdin>:7:36: note: previous location$",
|
|
)
|
|
|
|
|
|
def test_parse_error_duplicate_channel_decl_aspect() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type Message is
|
|
message
|
|
A : T;
|
|
end message;
|
|
generic
|
|
C : Channel with Readable, Writable, Readable;
|
|
machine S is
|
|
M : Test::Message;
|
|
begin
|
|
state I
|
|
is
|
|
begin
|
|
C'Read (M);
|
|
transition
|
|
goto null
|
|
end I;
|
|
end S;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:8:44: error: duplicate aspect "Readable"\n'
|
|
"<stdin>:8:24: note: previous location$",
|
|
)
|
|
|
|
|
|
def test_parse_error_unsupported_binding() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type Message is
|
|
message
|
|
A : T;
|
|
end message;
|
|
generic
|
|
C : Channel with Writable;
|
|
machine S is
|
|
M : Test::Message;
|
|
begin
|
|
state I
|
|
is
|
|
begin
|
|
C'Write (M where M = Test::Message'(A => 1));
|
|
transition
|
|
goto null
|
|
end I;
|
|
end S;
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:15:19: error: bindings are not supported$",
|
|
)
|
|
|
|
|
|
def test_parse_error_unsupported_modular_integer_type() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is mod 2 ** 16;
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
r"<stdin>:2:9: error: modular integer types are not supported\n"
|
|
r'<stdin>:2:9: help: use "type T is unsigned 16" instead'
|
|
r"$",
|
|
)
|
|
|
|
|
|
def test_parse_error_duplicate_integer_size_aspect() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is range 1 .. 10 with Size => 8, Size => 16;
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:2:42: error: Expected ';', got ','$",
|
|
)
|
|
|
|
|
|
def test_parse_error_duplicate_enumeration_aspect() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type E is (L1, L2, L3) with Size => 8, Always_Valid, Size => 16, Always_Valid;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:2:57: error: duplicate aspect "Size"\n'
|
|
r"<stdin>:2:32: note: previous location\n"
|
|
r'<stdin>:2:69: error: duplicate aspect "Always_Valid"\n'
|
|
r"<stdin>:2:43: note: previous location$",
|
|
)
|
|
|
|
|
|
def test_parse_error_duplicate_message_checksum_aspect() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
F1 : T;
|
|
F2 : T;
|
|
end message
|
|
with Checksum => (F1 => (F1'First .. F1'Last)),
|
|
Checksum => (F1 => (F1'First .. F1'Last));
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:9:15: error: Expected 'Byte_Order', got 'First'$",
|
|
)
|
|
|
|
|
|
def test_parse_error_duplicate_message_byte_order_aspect() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
F1 : T;
|
|
F2 : T;
|
|
end message
|
|
with Byte_Order => Low_Order_First,
|
|
Byte_Order => High_Order_First;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:9:15: error: duplicate aspect "Byte_Order"\n'
|
|
"<stdin>:8:15: note: previous location$",
|
|
)
|
|
|
|
|
|
def test_parse_error_duplicate_state_desc() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type Message is
|
|
message
|
|
A : T;
|
|
end message;
|
|
generic
|
|
C : Channel with Readable, Writable;
|
|
machine S is
|
|
M : Test::Message;
|
|
begin
|
|
state I
|
|
with Desc => "D1", Desc => "D2"
|
|
is
|
|
begin
|
|
C'Read (M);
|
|
transition
|
|
goto null
|
|
end I;
|
|
end S;
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:13:27: error: Expected 'is', got ','$",
|
|
)
|
|
|
|
|
|
def test_parse_error_duplicate_transition_desc() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type Message is
|
|
message
|
|
A : T;
|
|
end message;
|
|
generic
|
|
C : Channel with Readable, Writable;
|
|
machine S is
|
|
M : Test::Message;
|
|
begin
|
|
state I
|
|
is
|
|
begin
|
|
C'Read (M);
|
|
transition
|
|
goto null
|
|
with Desc => "D1", Desc => "D2"
|
|
end I;
|
|
end S;
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:18:30: error: Expected 'end', got ','$",
|
|
)
|
|
|
|
|
|
def test_enumeration() -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(
|
|
"""\
|
|
package Test is
|
|
type E is (E1 => 1, E2 => 2) with Always_Valid => False, Size => 8;
|
|
type A is (A1 => 10) with Always_Valid => True, Size => 6;
|
|
end Test;
|
|
""",
|
|
)
|
|
m = p.create_model()
|
|
assert m.types == [
|
|
BOOLEAN,
|
|
OPAQUE,
|
|
Enumeration(
|
|
"Test::E",
|
|
[("E1", expr.Number(1)), ("E2", expr.Number(2))],
|
|
size=expr.Number(8),
|
|
always_valid=False,
|
|
),
|
|
Enumeration(
|
|
"Test::A",
|
|
[("A1", expr.Number(10))],
|
|
size=expr.Number(6),
|
|
always_valid=True,
|
|
),
|
|
]
|
|
|
|
|
|
def test_parse_dependencies_given_as_argument(tmp_path: Path) -> None:
|
|
a = tmp_path / "a" / "a.rflx"
|
|
a.parent.mkdir()
|
|
a.write_text("with B; package A is end A;")
|
|
|
|
b = tmp_path / "b" / "b.rflx"
|
|
b.parent.mkdir()
|
|
b.write_text("package B is end B;")
|
|
|
|
p = parser.Parser()
|
|
p.parse(a, b)
|
|
|
|
assert set(p.specifications.keys()) == {"A", "B"}
|
|
|
|
p = parser.Parser()
|
|
p.parse(b, a)
|
|
|
|
assert set(p.specifications.keys()) == {"A", "B"}
|
|
|
|
|
|
def test_parse_dependencies_not_given_as_argument(tmp_path: Path) -> None:
|
|
a = tmp_path / "a" / "a.rflx"
|
|
a.parent.mkdir()
|
|
a.write_text("with C; package A is end A;")
|
|
|
|
b = tmp_path / "b" / "b.rflx"
|
|
b.parent.mkdir()
|
|
b.write_text("package B is end B;")
|
|
|
|
(tmp_path / "b" / "c.rflx").write_text("with D; package C is end C;")
|
|
|
|
(tmp_path / "a" / "d.rflx").write_text("package D is end D;")
|
|
|
|
p = parser.Parser()
|
|
p.parse(a, b)
|
|
|
|
assert set(p.specifications.keys()) == {"A", "B", "C", "D"}
|
|
|
|
p = parser.Parser()
|
|
p.parse(b, a)
|
|
|
|
assert set(p.specifications.keys()) == {"A", "B", "C", "D"}
|
|
|
|
|
|
def test_parse_order_of_include_paths(tmp_path: Path) -> None:
|
|
a = tmp_path / "a" / "a.rflx"
|
|
a.parent.mkdir()
|
|
a.write_text("with C; package A is end A;")
|
|
|
|
b = tmp_path / "b" / "b.rflx"
|
|
b.parent.mkdir()
|
|
b.write_text("package B is end B;")
|
|
|
|
c = tmp_path / "b" / "c.rflx"
|
|
c.write_text("with D; package C is end C;")
|
|
|
|
(tmp_path / "a" / "d.rflx").write_text("package D is end D;")
|
|
|
|
invalid = tmp_path / "b" / "d.rflx"
|
|
invalid.write_text("INVALID")
|
|
|
|
p = parser.Parser()
|
|
p.parse(a, b)
|
|
|
|
assert set(p.specifications.keys()) == {"A", "B", "C", "D"}
|
|
|
|
with pytest.raises(
|
|
RecordFluxError,
|
|
match=(rf"^{invalid}:1:1: error: Expected 'package', got 'First'$"),
|
|
):
|
|
parser.Parser().parse(b, a)
|
|
|
|
|
|
def test_parse_non_existent_dependencies(tmp_path: Path) -> None:
|
|
a = tmp_path / "a" / "a.rflx"
|
|
a.parent.mkdir()
|
|
a.write_text("with C; package A is end A;")
|
|
|
|
b = tmp_path / "b" / "b.rflx"
|
|
b.parent.mkdir()
|
|
b.write_text("package B is end B;")
|
|
|
|
error = rf'^{a}:1:6: error: cannot find specification "C"$'
|
|
|
|
with pytest.raises(RecordFluxError, match=error):
|
|
parser.Parser().parse(a, b)
|
|
|
|
with pytest.raises(RecordFluxError, match=error):
|
|
parser.Parser().parse(b, a)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("expression", "message"),
|
|
[
|
|
("A + 1", r"^<stdin>:7:19: error: math expression in boolean context$"),
|
|
("42", r"^<stdin>:7:19: error: math expression in boolean context$"),
|
|
],
|
|
)
|
|
def test_parse_error_math_expression_in_bool_context(expression: str, message: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
A : T
|
|
then null
|
|
if {expression};
|
|
end message;
|
|
end Test;
|
|
""",
|
|
message,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("expression", "message"),
|
|
[
|
|
("True", r"^<stdin>:5:26: error: boolean expression in math context$"),
|
|
("3 < 4", r"^<stdin>:5:26: error: boolean expression in math context$"),
|
|
("A = B", r"^<stdin>:5:26: error: boolean expression in math context$"),
|
|
("A /= False", r"^<stdin>:5:26: error: boolean expression in math context$"),
|
|
],
|
|
)
|
|
def test_parse_error_bool_expression_in_math_context(expression: str, message: str) -> None:
|
|
assert_error_string(
|
|
f"""\
|
|
package Test is
|
|
type M is
|
|
message
|
|
A : Opaque
|
|
with Size => {expression};
|
|
end message;
|
|
end Test;
|
|
""",
|
|
message,
|
|
)
|
|
|
|
|
|
def test_parse_error_short_form_condition() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type U8 is unsigned 8;
|
|
type M is
|
|
message
|
|
A : U8
|
|
if A = 42;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:6:16: error: short form condition is not supported anymore\n"
|
|
r'<stdin>:6:16: help: add condition to all outgoing links of field "A" instead$',
|
|
)
|
|
|
|
|
|
def test_parse_error_missing_aspect_expression() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
type T is range 0 .. 1 with A;
|
|
end Test;
|
|
""",
|
|
r'^<stdin>:2:14: error: invalid aspect "A" for range type "Test::T"$',
|
|
)
|
|
|
|
|
|
def test_parse_error_unclosed_parenthesis() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
|
|
type M is
|
|
message
|
|
Payload : Opaque
|
|
with Size => (Length - () * 8;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:6:27: error: empty subexpression$",
|
|
)
|
|
|
|
|
|
def test_negated_case_expression() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
generic
|
|
machine S is
|
|
begin
|
|
state A is
|
|
begin
|
|
M := T'(E1 => - (case X is when Y => 42));
|
|
transition
|
|
goto null
|
|
end A;
|
|
end S;
|
|
end Test;
|
|
""",
|
|
r"^<stdin>:7:26: error: case expression unsupported in math expression context$",
|
|
)
|
|
|
|
|
|
def test_parse_only() -> None:
|
|
p = parser.Parser()
|
|
p.parse(*(SPEC_DIR / "parse_only").glob("*"))
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "regex"),
|
|
[
|
|
(
|
|
"""\
|
|
package Test is
|
|
type M is
|
|
message
|
|
F : T
|
|
then null if X = (X = );
|
|
end message;
|
|
end Test;
|
|
""",
|
|
"^<stdin>:5:31: error: missing right operand$",
|
|
),
|
|
(
|
|
"""\
|
|
package Message_Size is
|
|
type T is range 0 .. 2 ** 8 with Size => 8;
|
|
type M is
|
|
message
|
|
A : T;
|
|
B : Opaque
|
|
with Size => A * 8
|
|
then null
|
|
if Message'Size = (A + )'Size * 8
|
|
then C
|
|
if Message'Last = B'Last + 8;
|
|
C : T;
|
|
end message;
|
|
end Message_Size;
|
|
""",
|
|
"^<stdin>:9:35: error: missing right operand$",
|
|
),
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is range 0 .. 2 ** 8 - 1 with Size => (8 / )'Size;
|
|
end Test;
|
|
""",
|
|
"^<stdin>:2:50: error: missing right operand$",
|
|
),
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is range 0 .. (- + 1) with Size => 8;
|
|
end Test;
|
|
""",
|
|
"^<stdin>:2:26: error: negation of non-expression$",
|
|
),
|
|
(
|
|
"""\
|
|
package Test is
|
|
type M is
|
|
message
|
|
F : T then null if (A /= = B);
|
|
end message;
|
|
end Test;
|
|
""",
|
|
"^<stdin>:4:30: error: missing right operand$",
|
|
),
|
|
],
|
|
ids=range(1, 6),
|
|
)
|
|
def test_parse_error_duplicate_operator(string: str, regex: str) -> None:
|
|
assert_error_string(string, regex)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "regex"),
|
|
[
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is range 0 .. 2 ** 8 - 1 with Size;
|
|
end Test;
|
|
""",
|
|
'^<stdin>:2:41: error: "Size" aspect has no value$',
|
|
),
|
|
(
|
|
"""\
|
|
package Test is
|
|
type T is unsigned 8;
|
|
type M is
|
|
message
|
|
F1 : T
|
|
with First
|
|
then F2;
|
|
F2 : T
|
|
then null
|
|
if True;
|
|
end message;
|
|
end Test;
|
|
""",
|
|
'^<stdin>:6:18: error: "First" aspect has no value$',
|
|
),
|
|
],
|
|
ids=range(1, 3),
|
|
)
|
|
def test_parse_error_aspect_without_expression(string: str, regex: str) -> None:
|
|
assert_error_string(string, regex)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[("X", ID("X")), ("X2", ID("X2")), ("X_Y", ID("X_Y")), ("X_Y_3", ID("X_Y_3"))],
|
|
)
|
|
def test_unqualified_identifier(string: str, expected: ID) -> None:
|
|
actual = parse_id(string, lang.GrammarRule.unqualified_identifier_rule)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("X", ID("X")),
|
|
("X2", ID("X2")),
|
|
("X_Y", ID("X_Y")),
|
|
("X_Y_3", ID("X_Y_3")),
|
|
("X::Y", ID("X::Y")),
|
|
("X2::Y2", ID("X2::Y2")),
|
|
("X_Y::Z", ID("X_Y::Z")),
|
|
("X_Y_3::Z_4", ID("X_Y_3::Z_4")),
|
|
],
|
|
)
|
|
def test_qualified_identifier(string: str, expected: ID) -> None:
|
|
actual = parse_id(string, lang.GrammarRule.qualified_identifier_rule)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("1000", expr.Number(1000)),
|
|
("1_000", expr.Number(1000)),
|
|
("16#6664#", expr.Number(26212)),
|
|
("16#66_64#", expr.Number(26212)),
|
|
("-1000", expr.Neg(expr.Number(1000))),
|
|
("-1_000", expr.Neg(expr.Number(1000))),
|
|
("-16#6664#", expr.Neg(expr.Number(26212, base=16))),
|
|
("-16#66_64#", expr.Neg(expr.Number(26212, base=16))),
|
|
],
|
|
)
|
|
def test_expression_numeric_literal(string: str, expected: expr.Expr) -> None:
|
|
actual = parse_math_expression(string, extended=False)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "error"),
|
|
[
|
|
("3#100#", '<stdin>:1:2: error: End of input expected, got "Hash"'),
|
|
("2#555#", '<stdin>:1:2: error: End of input expected, got "Hash"'),
|
|
("50#123#", '<stdin>:1:3: error: End of input expected, got "Hash"'),
|
|
],
|
|
)
|
|
def test_invalid_expression_numeric_literal(string: str, error: expr.Expr) -> None:
|
|
with pytest.raises(RecordFluxError, match=rf"^{error}$"):
|
|
parse_math_expression(string, extended=False)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[("X", expr.Variable("X")), ("X::Y", expr.Variable("X::Y"))],
|
|
)
|
|
def test_variable(string: str, expected: decl.Declaration) -> None:
|
|
actual = parse_expression(string, lang.GrammarRule.variable_rule)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "error"),
|
|
[
|
|
(
|
|
"Double__Underscore",
|
|
"<stdin>:1:7: error: Invalid token, ignored\n"
|
|
"<stdin>:1:8: error: Invalid token, ignored\n"
|
|
'<stdin>:1:9: error: End of input expected, got "Unqualified_Identifier"',
|
|
),
|
|
(
|
|
"Trailing_Underscore_ < 5",
|
|
"<stdin>:1:20: error: Invalid token, ignored\n"
|
|
'<stdin>:1:22: error: End of input expected, got "Lt"',
|
|
),
|
|
],
|
|
)
|
|
def test_invalid_variable(string: str, error: str) -> None:
|
|
with pytest.raises(RecordFluxError, match=rf"^{error}$"):
|
|
parse_expression(string, lang.GrammarRule.variable_rule)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("X'First", expr.First(expr.Variable("X"))),
|
|
("X'Last", expr.Last(expr.Variable("X"))),
|
|
("X'Size", expr.Size(expr.Variable("X"))),
|
|
("X'Head", expr.Head(expr.Variable("X"))),
|
|
("X'Opaque", expr.Opaque(expr.Variable("X"))),
|
|
("X'Present", expr.Present(expr.Variable("X"))),
|
|
("X'Valid", expr.Valid(expr.Variable("X"))),
|
|
("X'Valid_Checksum", expr.ValidChecksum(expr.Variable("X"))),
|
|
("X'Has_Data", expr.HasData(expr.Variable("X"))),
|
|
("X'Head.Y", expr.Selected(expr.Head(expr.Variable("X")), "Y")),
|
|
],
|
|
)
|
|
def test_expression_suffix(string: str, expected: expr.Expr) -> None:
|
|
actual = parse_expression(string, lang.GrammarRule.extended_expression_rule)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
(
|
|
"range 1 .. 2_000 with Size => 16",
|
|
UncheckedInteger(
|
|
identifier=ID("T", Location((1, 1), Path("<stdin>"))),
|
|
first=expr.Number(
|
|
value=1,
|
|
base=0,
|
|
),
|
|
last=expr.Number(
|
|
value=2000,
|
|
base=0,
|
|
),
|
|
size=expr.Number(
|
|
value=16,
|
|
base=0,
|
|
),
|
|
location=Location((1, 1), Path("<stdin>"), (1, 33)),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_range_type(string: str, expected: UncheckedInteger) -> None:
|
|
actual = parse_range_type(string, lang.GrammarRule.range_type_definition_rule)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
(
|
|
"unsigned 6",
|
|
UncheckedUnsignedInteger(
|
|
identifier=ID("T", Location((1, 1), Path("<stdin>"))),
|
|
size=expr.Number(
|
|
value=6,
|
|
base=0,
|
|
),
|
|
location=Location((1, 1), Path("<stdin>"), (1, 11)),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_unsigned_type(string: str, expected: None) -> None:
|
|
actual = parse_unsigned_type(string, lang.GrammarRule.unsigned_type_definition_rule)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
(
|
|
"A - B * 2**3 - 1",
|
|
expr.Sub(
|
|
expr.Sub(
|
|
expr.Variable("A"),
|
|
expr.Mul(expr.Variable("B"), expr.Pow(expr.Number(2), expr.Number(3))),
|
|
),
|
|
expr.Number(1),
|
|
),
|
|
),
|
|
(
|
|
"(A - B) * 2**3 - 1",
|
|
expr.Sub(
|
|
expr.Mul(
|
|
expr.Sub(expr.Variable("A"), expr.Variable("B")),
|
|
expr.Pow(expr.Number(2), expr.Number(3)),
|
|
),
|
|
expr.Number(1),
|
|
),
|
|
),
|
|
(
|
|
"A - B * 2**(3 - 1)",
|
|
expr.Sub(
|
|
expr.Variable("A"),
|
|
expr.Mul(
|
|
expr.Variable("B"),
|
|
expr.Pow(expr.Number(2), expr.Sub(expr.Number(3), expr.Number(1))),
|
|
),
|
|
),
|
|
),
|
|
(
|
|
"A - (B * 2)**3 - 1",
|
|
expr.Sub(
|
|
expr.Sub(
|
|
expr.Variable("A"),
|
|
expr.Pow(expr.Mul(expr.Variable("B"), expr.Number(2)), expr.Number(3)),
|
|
),
|
|
expr.Number(1),
|
|
),
|
|
),
|
|
(
|
|
"A - (B * 2**3 - 1)",
|
|
expr.Sub(
|
|
expr.Variable("A"),
|
|
expr.Sub(
|
|
expr.Mul(expr.Variable("B"), expr.Pow(expr.Number(2), expr.Number(3))),
|
|
expr.Number(1),
|
|
),
|
|
),
|
|
),
|
|
(
|
|
"A + B * (-8)",
|
|
expr.Add(expr.Variable("A"), expr.Mul(expr.Variable("B"), expr.Neg(expr.Number(8)))),
|
|
),
|
|
(
|
|
"1 - - (2 + 3)",
|
|
expr.Sub(
|
|
expr.Number(1),
|
|
expr.Neg(
|
|
expr.Add(
|
|
expr.Number(2),
|
|
expr.Number(3),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
(
|
|
"A + B mod 8 * 2",
|
|
expr.Add(
|
|
expr.Variable("A"),
|
|
expr.Mul(expr.Mod(expr.Variable("B"), expr.Number(8)), expr.Number(2)),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_mathematical_expression(string: str, expected: expr.Expr) -> None:
|
|
actual = parse_math_expression(string, extended=False)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("X + Y", expr.Add(expr.Variable("X"), expr.Variable("Y"))),
|
|
(
|
|
"X + Y (Z)",
|
|
expr.Add(expr.Variable("X"), expr.Call("Y", UNDEFINED, [expr.Variable("Z")])),
|
|
),
|
|
],
|
|
)
|
|
def test_extended_mathematical_expression(string: str, expected: expr.Expr) -> None:
|
|
actual = parse_math_expression(string, extended=True)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("X = Y", expr.Equal(expr.Variable("X"), expr.Variable("Y"))),
|
|
("X /= Y", expr.NotEqual(expr.Variable("X"), expr.Variable("Y"))),
|
|
("42 < X", expr.Less(expr.Number(42), expr.Variable("X"))),
|
|
("42 <= X", expr.LessEqual(expr.Number(42), expr.Variable("X"))),
|
|
("X > 42", expr.Greater(expr.Variable("X"), expr.Number(42))),
|
|
("X >= 42", expr.GreaterEqual(expr.Variable("X"), expr.Number(42))),
|
|
("((X = 42))", expr.Equal(expr.Variable("X"), expr.Number(42))),
|
|
("X and Y", expr.And(expr.Variable("X"), expr.Variable("Y"))),
|
|
("X or Y", expr.Or(expr.Variable("X"), expr.Variable("Y"))),
|
|
("((X or Y))", expr.Or(expr.Variable("X"), expr.Variable("Y"))),
|
|
],
|
|
)
|
|
def test_boolean_expression(string: str, expected: expr.Expr) -> None:
|
|
actual = parse_bool_expression(string, extended=False)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("X and Y", expr.And(expr.Variable("X"), expr.Variable("Y"))),
|
|
(
|
|
"X and Y (Z)",
|
|
expr.And(expr.Variable("X"), expr.Call("Y", UNDEFINED, [expr.Variable("Z")])),
|
|
),
|
|
],
|
|
)
|
|
def test_extended_boolean_expression(string: str, expected: expr.Expr) -> None:
|
|
actual = parse_bool_expression(string, extended=True)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "error", "extended"),
|
|
[
|
|
("42 > X", "<stdin>:1:1: error: boolean expression in math context", False),
|
|
("42 > X", "<stdin>:1:1: error: boolean expression in math context", True),
|
|
("X and Y", "<stdin>:1:1: error: boolean expression in math context", False),
|
|
("X and Y", "<stdin>:1:1: error: boolean expression in math context", True),
|
|
("(5 + ()", "<stdin>:1:2: error: empty subexpression", False),
|
|
(
|
|
"(5 + ()",
|
|
"<stdin>:1:2: error: Cannot parse <extended_simple_expr>\n"
|
|
r"<stdin>:1:7: error: Expected 'case', got '\)'",
|
|
True,
|
|
),
|
|
],
|
|
)
|
|
def test_mathematical_expression_error(string: str, error: expr.Expr, extended: bool) -> None:
|
|
with pytest.raises(RecordFluxError, match=rf"^{error}$"):
|
|
parse_math_expression(string, extended=extended)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "error", "extended"),
|
|
[
|
|
("42", "<stdin>:1:1: error: math expression in boolean context", True),
|
|
("42", "<stdin>:1:1: error: math expression in boolean context", False),
|
|
("X * 3", "<stdin>:1:1: error: math expression in boolean context", True),
|
|
("X * 3", "<stdin>:1:1: error: math expression in boolean context", False),
|
|
(
|
|
"(True and ()",
|
|
"<stdin>:1:2: error: Cannot parse <extended_expression>\n"
|
|
r"<stdin>:1:12: error: Expected 'case', got '\)'",
|
|
True,
|
|
),
|
|
("(True and ()", "<stdin>:1:2: error: empty subexpression", False),
|
|
],
|
|
)
|
|
def test_boolean_expression_error(string: str, error: expr.Expr, extended: bool) -> None:
|
|
with pytest.raises(RecordFluxError, match=rf"^{error}$"):
|
|
parse_bool_expression(string, extended=extended)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("42", expr.Number(42)),
|
|
('"Foo Bar"', expr.String("Foo Bar")),
|
|
("[1]", expr.Aggregate(expr.Number(1))),
|
|
("[1, 2]", expr.Aggregate(expr.Number(1), expr.Number(2))),
|
|
(
|
|
'[137] & "PNG" & [13, 10, 26, 10]',
|
|
expr.Aggregate(
|
|
expr.Number(137),
|
|
expr.Number(80),
|
|
expr.Number(78),
|
|
expr.Number(71),
|
|
expr.Number(13),
|
|
expr.Number(10),
|
|
expr.Number(26),
|
|
expr.Number(10),
|
|
),
|
|
),
|
|
(
|
|
"for all X in Y => X = Z",
|
|
expr.ForAllIn(
|
|
"X",
|
|
expr.Variable("Y"),
|
|
expr.Equal(expr.Variable("X"), expr.Variable("Z")),
|
|
),
|
|
),
|
|
(
|
|
"for some X in Y => X = Z",
|
|
expr.ForSomeIn(
|
|
"X",
|
|
expr.Variable("Y"),
|
|
expr.Equal(expr.Variable("X"), expr.Variable("Z")),
|
|
),
|
|
),
|
|
(
|
|
"[for X in Y => X.A]",
|
|
expr.Comprehension(
|
|
"X",
|
|
expr.Variable("Y"),
|
|
expr.Selected(expr.Variable("X"), "A"),
|
|
expr.TRUE,
|
|
),
|
|
),
|
|
(
|
|
"[for X in Y if X.B = Z => X.A]",
|
|
expr.Comprehension(
|
|
"X",
|
|
expr.Variable("Y"),
|
|
expr.Selected(expr.Variable("X"), "A"),
|
|
expr.Equal(expr.Selected(expr.Variable("X"), "B"), expr.Variable("Z")),
|
|
),
|
|
),
|
|
(
|
|
'X (A, "S", 42)',
|
|
expr.Call("X", UNDEFINED, [expr.Variable("A"), expr.String("S"), expr.Number(42)]),
|
|
),
|
|
("X::Y (A)", expr.Conversion("X::Y", expr.Variable("A"))),
|
|
("X'(Y => Z)", expr.MessageAggregate("X", {ID("Y"): expr.Variable("Z")})),
|
|
(
|
|
"X'(Y => Z, A => B)",
|
|
expr.MessageAggregate("X", {ID("Y"): expr.Variable("Z"), ID("A"): expr.Variable("B")}),
|
|
),
|
|
("X'(null message)", expr.MessageAggregate("X", {})),
|
|
("X", expr.Variable("X")),
|
|
("X in Y", expr.In(expr.Variable("X"), expr.Variable("Y"))),
|
|
("X not in Y", expr.NotIn(expr.Variable("X"), expr.Variable("Y"))),
|
|
("X < Y", expr.Less(expr.Variable("X"), expr.Variable("Y"))),
|
|
],
|
|
)
|
|
def test_expression_base(string: str, expected: expr.Expr) -> None:
|
|
actual = parse_expression(string, lang.GrammarRule.extended_expression_rule)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("X'Valid = True", expr.Equal(expr.Valid(expr.Variable("X")), expr.Variable("True"))),
|
|
("X::Y /= Z", expr.NotEqual(expr.Variable("X::Y"), expr.Variable("Z"))),
|
|
("X.Y /= Z", expr.NotEqual(expr.Selected(expr.Variable("X"), "Y"), expr.Variable("Z"))),
|
|
(
|
|
"X = Y and Y /= Z",
|
|
expr.And(
|
|
expr.Equal(expr.Variable("X"), expr.Variable("Y")),
|
|
expr.NotEqual(expr.Variable("Y"), expr.Variable("Z")),
|
|
),
|
|
),
|
|
(
|
|
"X'Valid and Y'Valid",
|
|
expr.And(expr.Valid(expr.Variable("X")), expr.Valid(expr.Variable("Y"))),
|
|
),
|
|
(
|
|
"X'Valid = True and (Y'Valid = False or Z'Valid = False)",
|
|
expr.And(
|
|
expr.Equal(expr.Valid(expr.Variable("X")), expr.Variable("True")),
|
|
expr.Or(
|
|
expr.Equal(expr.Valid(expr.Variable("Y")), expr.Variable("False")),
|
|
expr.Equal(expr.Valid(expr.Variable("Z")), expr.Variable("False")),
|
|
),
|
|
),
|
|
),
|
|
(
|
|
"X'Valid = False or X.A /= A or X.B /= B or (X.C = 0 and X.D not in X.E)",
|
|
expr.Or(
|
|
expr.Or(
|
|
expr.Or(
|
|
expr.Equal(expr.Valid(expr.Variable("X")), expr.Variable("False")),
|
|
expr.NotEqual(expr.Selected(expr.Variable("X"), "A"), expr.Variable("A")),
|
|
),
|
|
expr.NotEqual(expr.Selected(expr.Variable("X"), "B"), expr.Variable("B")),
|
|
),
|
|
expr.And(
|
|
expr.Equal(expr.Selected(expr.Variable("X"), "C"), expr.Number(0)),
|
|
expr.NotIn(
|
|
expr.Selected(expr.Variable("X"), "D"),
|
|
expr.Selected(expr.Variable("X"), "E"),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
(
|
|
"for some A in X.B => (A.T = P.E and (G::E not in P::S (A.D).V))",
|
|
expr.ForSomeIn(
|
|
"A",
|
|
expr.Selected(expr.Variable("X"), "B"),
|
|
expr.And(
|
|
expr.Equal(
|
|
expr.Selected(expr.Variable("A"), "T"),
|
|
expr.Selected(expr.Variable("P"), "E"),
|
|
),
|
|
expr.NotIn(
|
|
expr.Variable("G::E"),
|
|
expr.Selected(
|
|
expr.Conversion("P::S", expr.Selected(expr.Variable("A"), "D")),
|
|
"V",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
("X::Y (Z) = 42", expr.Equal(expr.Conversion("X::Y", expr.Variable("Z")), expr.Number(42))),
|
|
("X (Y).Z", expr.Selected(expr.Call("X", UNDEFINED, [expr.Variable("Y")]), "Z")),
|
|
(
|
|
"X (Y).Z'Size",
|
|
expr.Size(expr.Selected(expr.Call("X", UNDEFINED, [expr.Variable("Y")]), "Z")),
|
|
),
|
|
(
|
|
"G::E not in P::S (E.D).V",
|
|
expr.NotIn(
|
|
expr.Variable("G::E"),
|
|
expr.Selected(expr.Conversion("P::S", expr.Selected(expr.Variable("E"), "D")), "V"),
|
|
),
|
|
),
|
|
(
|
|
"[for E in L if E.T = A => E.B]'Head",
|
|
expr.Head(
|
|
expr.Comprehension(
|
|
"E",
|
|
expr.Variable("L"),
|
|
expr.Selected(expr.Variable("E"), "B"),
|
|
expr.Equal(expr.Selected(expr.Variable("E"), "T"), expr.Variable("A")),
|
|
),
|
|
),
|
|
),
|
|
("A'Head.D", expr.Selected(expr.Head(expr.Variable("A")), "D")),
|
|
(
|
|
"[for E in L if E.T = A => E.B]'Head.D",
|
|
expr.Selected(
|
|
expr.Head(
|
|
expr.Comprehension(
|
|
"E",
|
|
expr.Variable("L"),
|
|
expr.Selected(expr.Variable("E"), "B"),
|
|
expr.Equal(expr.Selected(expr.Variable("E"), "T"), expr.Variable("A")),
|
|
),
|
|
),
|
|
"D",
|
|
),
|
|
),
|
|
(
|
|
"(for some S in P::X ([for E in C.A if E.T = P.L => E]'Head.D).H => S.G = G) = False",
|
|
expr.Equal(
|
|
expr.ForSomeIn(
|
|
"S",
|
|
expr.Selected(
|
|
expr.Conversion(
|
|
"P::X",
|
|
expr.Selected(
|
|
expr.Head(
|
|
expr.Comprehension(
|
|
"E",
|
|
expr.Selected(expr.Variable("C"), "A"),
|
|
expr.Variable("E"),
|
|
expr.Equal(
|
|
expr.Selected(expr.Variable("E"), "T"),
|
|
expr.Selected(expr.Variable("P"), "L"),
|
|
),
|
|
),
|
|
),
|
|
"D",
|
|
),
|
|
),
|
|
"H",
|
|
),
|
|
expr.Equal(expr.Selected(expr.Variable("S"), "G"), expr.Variable("G")),
|
|
),
|
|
expr.Variable("False"),
|
|
),
|
|
),
|
|
(
|
|
"(case C is when V1 | V2 => 8, when V3 => 16)",
|
|
expr.CaseExpr(
|
|
expr.Variable("C"),
|
|
[
|
|
([ID("V1"), ID("V2")], expr.Number(8)),
|
|
([ID("V3")], expr.Number(16)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_expression_complex(string: str, expected: expr.Expr) -> None:
|
|
actual = parse_bool_expression(string, extended=True)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("X : Channel with Readable", decl.ChannelDeclaration("X", readable=True)),
|
|
("X : Channel with Writable", decl.ChannelDeclaration("X", writable=True)),
|
|
(
|
|
"X : Channel with Readable, Writable",
|
|
decl.ChannelDeclaration("X", readable=True, writable=True),
|
|
),
|
|
("with function X return Y", decl.FunctionDeclaration("X", [], "Package::Y")),
|
|
("with function X return Package::Y", decl.FunctionDeclaration("X", [], "Package::Y")),
|
|
(
|
|
"with function X (A : B; C : D) return Y",
|
|
decl.FunctionDeclaration(
|
|
"X",
|
|
[decl.Parameter("A", "Package::B"), decl.Parameter("C", "Package::D")],
|
|
"Package::Y",
|
|
),
|
|
),
|
|
(
|
|
"with function X (A : Boolean) return Boolean",
|
|
decl.FunctionDeclaration(
|
|
"X",
|
|
[decl.Parameter("A", str(model.BOOLEAN.identifier))],
|
|
str(model.BOOLEAN.identifier),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_formal_declaration(string: str, expected: decl.Declaration) -> None:
|
|
actual = parse_formal_declaration(string)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("A : B", decl.VariableDeclaration("A", "Package::B")),
|
|
("A : B := C", decl.VariableDeclaration("A", "Package::B", expr.Variable("C"))),
|
|
("A : B := 1", decl.VariableDeclaration("A", "Package::B", expr.Number(1))),
|
|
],
|
|
)
|
|
def test_variable_declaration(string: str, expected: decl.Declaration) -> None:
|
|
actual = parse_declaration(string)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
def test_renaming_declaration() -> None:
|
|
expected = decl.RenamingDeclaration("A", "Package::B", expr.Selected(expr.Variable("C"), "D"))
|
|
actual = parse_declaration("A : B renames C.D")
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("A := B", stmt.VariableAssignment("A", expr.Variable("B"))),
|
|
("A.B := C", stmt.MessageFieldAssignment("A", "B", expr.Variable("C"))),
|
|
],
|
|
)
|
|
def test_assignment_statement(string: str, expected: stmt.Statement) -> None:
|
|
actual = parse_statement(string)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
("A'Append (B)", stmt.Append("A", expr.Variable("B"))),
|
|
("A'Extend (B)", stmt.Extend("A", expr.Variable("B"))),
|
|
("A'Read (B)", stmt.Read("A", expr.Variable("B"))),
|
|
("A'Write (B)", stmt.Write("A", expr.Variable("B"))),
|
|
("A'Reset", stmt.Reset("A")),
|
|
(
|
|
"A'Reset (B => 1, C => 2)",
|
|
stmt.Reset("A", {ID("B"): expr.Number(1), ID("C"): expr.Number(2)}),
|
|
),
|
|
],
|
|
)
|
|
def test_attribute_statement(string: str, expected: stmt.Statement) -> None:
|
|
actual = parse_statement(string)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "expected"),
|
|
[
|
|
(
|
|
"""
|
|
state A is
|
|
begin
|
|
transition
|
|
goto B
|
|
end A
|
|
""",
|
|
model.State("A", transitions=[model.Transition("B")]),
|
|
),
|
|
(
|
|
"""
|
|
state A is
|
|
begin
|
|
transition
|
|
goto B
|
|
if X = Y
|
|
goto C
|
|
end A
|
|
""",
|
|
model.State(
|
|
"A",
|
|
transitions=[
|
|
model.Transition("B", expr.Equal(expr.Variable("X"), expr.Variable("Y"))),
|
|
model.Transition("C"),
|
|
],
|
|
),
|
|
),
|
|
(
|
|
"""
|
|
state A is
|
|
begin
|
|
transition
|
|
goto B
|
|
with Desc => "rfc2549.txt+12:3-45:6"
|
|
if X = Y
|
|
goto C
|
|
with Desc => "rfc2549.txt+123:45-678:9"
|
|
end A
|
|
""",
|
|
model.State(
|
|
"A",
|
|
transitions=[
|
|
model.Transition(
|
|
"B",
|
|
expr.Equal(expr.Variable("X"), expr.Variable("Y")),
|
|
description="rfc2549.txt+12:3-45:6",
|
|
),
|
|
model.Transition("C", description="rfc2549.txt+123:45-678:9"),
|
|
],
|
|
),
|
|
),
|
|
(
|
|
"""
|
|
state A is
|
|
Z : Boolean := Y;
|
|
begin
|
|
transition
|
|
goto B
|
|
end A
|
|
""",
|
|
model.State(
|
|
"A",
|
|
transitions=[model.Transition("B")],
|
|
declarations=[
|
|
decl.VariableDeclaration("Z", "__BUILTINS__::Boolean", expr.Variable("Y")),
|
|
],
|
|
actions=[],
|
|
),
|
|
),
|
|
(
|
|
"""
|
|
state A is
|
|
begin
|
|
transition
|
|
goto B
|
|
exception
|
|
goto C
|
|
end A
|
|
""",
|
|
model.State(
|
|
"A",
|
|
transitions=[model.Transition("B")],
|
|
exception_transition=model.Transition("C"),
|
|
declarations=[],
|
|
actions=[],
|
|
),
|
|
),
|
|
(
|
|
"""
|
|
state A is
|
|
begin
|
|
transition
|
|
goto B
|
|
exception
|
|
goto C
|
|
with Desc => "rfc2549.txt+12:3-45:6"
|
|
end A
|
|
""",
|
|
model.State(
|
|
"A",
|
|
transitions=[model.Transition("B")],
|
|
exception_transition=model.Transition("C", description="rfc2549.txt+12:3-45:6"),
|
|
declarations=[],
|
|
actions=[],
|
|
),
|
|
),
|
|
],
|
|
ids=range(1, 7),
|
|
)
|
|
def test_state(string: str, expected: decl.Declaration) -> None:
|
|
actual = parse_state(string)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
def test_state_error() -> None:
|
|
string = """
|
|
state A is
|
|
begin
|
|
transition
|
|
goto B
|
|
end C
|
|
"""
|
|
error = (
|
|
r'<stdin>:6:12: error: inconsistent state identifier "C"\n'
|
|
r'<stdin>:2:14: note: previous identifier was "A"'
|
|
)
|
|
with pytest.raises(RecordFluxError, match=rf"^{error}$"):
|
|
parse_state(string)
|
|
|
|
|
|
def test_state_machine_declaration() -> None:
|
|
string = """
|
|
generic
|
|
X : Channel with Readable, Writable;
|
|
with function F return Boolean;
|
|
machine S is
|
|
Y : Boolean := True;
|
|
begin
|
|
state A is
|
|
Z : Boolean := Y;
|
|
begin
|
|
Z := False;
|
|
transition
|
|
goto null
|
|
if Z = False
|
|
goto A
|
|
end A;
|
|
end S
|
|
"""
|
|
actual = parse_state_machine(string)
|
|
expected = model.UncheckedStateMachine(
|
|
ID("Package::S"),
|
|
[
|
|
model.State(
|
|
"A",
|
|
declarations=[
|
|
decl.VariableDeclaration("Z", BOOLEAN.identifier, expr.Variable("Y")),
|
|
],
|
|
actions=[stmt.VariableAssignment("Z", expr.FALSE)],
|
|
transitions=[
|
|
model.Transition(
|
|
"null",
|
|
condition=expr.Equal(expr.Variable("Z"), expr.FALSE),
|
|
),
|
|
model.Transition("A"),
|
|
],
|
|
),
|
|
],
|
|
[decl.VariableDeclaration("Y", BOOLEAN.identifier, expr.TRUE)],
|
|
[
|
|
decl.ChannelDeclaration("X", readable=True, writable=True),
|
|
decl.FunctionDeclaration("F", [], BOOLEAN.identifier),
|
|
],
|
|
location=Location((2, 12), common.STDIN, (17, 17)),
|
|
)
|
|
assert actual == expected
|
|
assert actual.location
|
|
|
|
|
|
def test_parse_state_machine() -> None:
|
|
string = """
|
|
generic
|
|
machine X is
|
|
begin
|
|
state A is
|
|
begin
|
|
transition
|
|
goto null
|
|
end A;
|
|
end X
|
|
"""
|
|
parse_state_machine_error(string)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("string", "error"),
|
|
[
|
|
(
|
|
"""
|
|
generic
|
|
machine X is
|
|
begin
|
|
state A is
|
|
begin
|
|
transition
|
|
goto null
|
|
end A;
|
|
end Y
|
|
""",
|
|
r'<stdin>:10:20: error: inconsistent state machine identifier "Y"\n'
|
|
r'<stdin>:3:24: note: previous identifier was "X"',
|
|
),
|
|
(
|
|
"""
|
|
generic
|
|
machine X is
|
|
begin
|
|
state A is
|
|
begin
|
|
transition
|
|
goto null
|
|
end A
|
|
end Y
|
|
""",
|
|
"<stdin>:10:16: error: Expected ';', got 'end'",
|
|
),
|
|
],
|
|
)
|
|
def test_state_machine_error(string: str, error: str) -> None:
|
|
with pytest.raises(RecordFluxError, match=rf"^{error}$"):
|
|
parse_state_machine_error(string)
|
|
|
|
|
|
def test_state_machine() -> None:
|
|
p = parser.Parser()
|
|
p.parse_string(
|
|
"""\
|
|
package Test is
|
|
|
|
generic
|
|
machine S is
|
|
begin
|
|
state A is
|
|
begin
|
|
transition
|
|
goto B
|
|
end A;
|
|
|
|
state B is
|
|
begin
|
|
transition
|
|
goto null
|
|
end B;
|
|
end S;
|
|
|
|
end Test;
|
|
""",
|
|
)
|
|
p.create_model()
|
|
|
|
|
|
def test_parse_error_deprecated_session_keyword() -> None:
|
|
assert_error_string(
|
|
"""\
|
|
package Test is
|
|
|
|
generic
|
|
session S is
|
|
begin
|
|
state A is
|
|
begin
|
|
transition
|
|
goto null
|
|
end A;
|
|
end S;
|
|
|
|
end Test;
|
|
""",
|
|
r"^"
|
|
r"<stdin>:4:4: error: \"session\" keyword is deprecated\n"
|
|
r'<stdin>:4:4: help: use "machine" instead'
|
|
r"$",
|
|
)
|
|
|
|
|
|
def test_expression_aggregate_no_number() -> None:
|
|
with pytest.raises(
|
|
RecordFluxError,
|
|
match=(r"^<stdin>:1:5: error: Expected Numeral, got 'First'$"),
|
|
):
|
|
parse_expression("[1, Foo]", lang.GrammarRule.expression_rule)
|