Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Generator for ProtoBuf (proto3) #476

Merged
merged 53 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4ec5294
excluded generated output from git
Apr 4, 2024
9bb7c74
created package structure for protobuf-generator containing the requi…
Apr 4, 2024
4a05ae0
corrected naming of "pascal case" to "snake case"
Apr 4, 2024
c633781
using renamed "snake case" method in naming.py
Apr 4, 2024
013ce88
adapted naming.py and common.py methods for ProtoBuf generator
Apr 4, 2024
c4af68d
created _generate.py for ProtoBuf constants (there will be none)
Apr 4, 2024
21c06b0
adapted method generate_enum in _generate.py
Apr 4, 2024
53c4334
added generate_class method to _generate.py
Apr 4, 2024
d94fe54
added generate method to _generate.py
Apr 4, 2024
ceb4e1d
added main function to ProtoBuf generator
Apr 4, 2024
2b3a50e
created new case for choosing the protobuf entry point
Apr 4, 2024
a360a0c
finalized ProtoBuf type #3 and enum generation #1
Apr 4, 2024
afa0822
added protobuf target to README
Apr 4, 2024
6b55cce
corrected keyword stacking of "optional" and "repeated"
Apr 4, 2024
9f7b9de
fixed syntax error in generated enums
Apr 4, 2024
5c234bc
fixed syntax error for last literal in generated enums
Apr 4, 2024
7f44e42
prefixing enum literals with enum name
Apr 5, 2024
6056b5b
do not generate proto-messages for Has* classes
Apr 5, 2024
b689657
added TODO for proto generation
Apr 16, 2024
0c7e955
adding a field message_type to each proto message that shall distingu…
Apr 19, 2024
199ade3
Merge branch 'main' into protobuf
TomGneuss Apr 19, 2024
7bfe5e3
reformatted code according to pre-commit guide
Apr 22, 2024
320fead
generating properties with interface types as "oneof"
Apr 22, 2024
c1d78c1
also considering properties of interfaces of a concrete type
Apr 22, 2024
beac72a
introduced choice-message for repeated oneof's
Apr 22, 2024
f44e71f
code-formatting
Apr 22, 2024
c3b9a4a
only generating each *choice message once
Apr 22, 2024
7811085
extracted local "oneof" (within message) to choice-object;
Apr 22, 2024
baa828e
code-formatting
Apr 22, 2024
e008f25
Update README.rst
g1zzm0 Apr 22, 2024
7937d42
Update common.py
g1zzm0 Apr 22, 2024
95af49a
Update common.py
g1zzm0 Apr 22, 2024
6b8e294
Update description.py
g1zzm0 Apr 22, 2024
b1d8795
Update live_test_main.py
g1zzm0 Apr 22, 2024
b191980
Update common.py
g1zzm0 Apr 22, 2024
e20b33b
Update aas_core_codegen/cpp/constants/_generate.py
g1zzm0 Apr 22, 2024
0aa360e
Update aas_core_codegen/cpp/main.py
g1zzm0 Apr 22, 2024
23e0e39
Update aas_core_codegen/cpp/description.py
g1zzm0 Apr 22, 2024
67b19b9
Update aas_core_codegen/cpp/optionaling.py
g1zzm0 Apr 22, 2024
f231a90
Update aas_core_codegen/cpp/enhancing/__init__.py
g1zzm0 Apr 22, 2024
4dfd1ac
Update aas_core_codegen/csharp/unrolling.py
g1zzm0 Apr 22, 2024
81fb62e
Apply suggestions from code review
g1zzm0 Apr 23, 2024
4fcd0a2
Merge branch 'main' into protobuf
g1zzm0 Apr 23, 2024
33ee108
Fix interface missmatch
g1zzm0 Apr 23, 2024
02a8f2e
Merge branch 'main' into protobuf
g1zzm0 Apr 23, 2024
b30b3a6
commented unimplemented methods in naming.py
Apr 23, 2024
9f830d9
commented unimplemented methods in common.py
Apr 23, 2024
c0cd860
removed constants generation
Apr 23, 2024
967f259
corrected f-strings
Apr 23, 2024
78e1272
Fix Todos and f-string error
g1zzm0 Apr 23, 2024
f6e0663
Merg f-string
g1zzm0 Apr 23, 2024
bb00a99
fixed too long lines
Apr 23, 2024
a66ed76
Merge branch 'protobuf' of github.com:TomGneuss/aas-core-codegen into…
Apr 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ Call the generator with the appropriate target:
.. code-block::

usage: aas-core-codegen [-h] --model_path MODEL_PATH --snippets_dir
SNIPPETS_DIR --output_dir OUTPUT_DIR --target
{csharp,cpp,golang,java,jsonschema,python,typescript,rdf_shacl,xsd,jsonld_context}
SNIPPETS_DIR --output_dir OUTPUT_DIR --target
{csharp,cpp,golang,java,jsonschema,python,typescript,rdf_shacl,xsd,jsonld_context,protobuf}
[--version]

Generate implementations and schemas based on an AAS meta-model.
Expand All @@ -153,7 +153,7 @@ Call the generator with the appropriate target:
specific code snippets
--output_dir OUTPUT_DIR
path to the generated code
--target {csharp,cpp,golang,java,jsonschema,python,typescript,rdf_shacl,xsd,jsonld_context}
--target {csharp,cpp,golang,java,jsonschema,python,typescript,rdf_shacl,xsd,jsonld_context,protobuf}
target language or schema
--version show the current version and exit

Expand Down
1 change: 1 addition & 0 deletions aas_core_codegen/cpp/aas_common/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Generate C++ code for common functions."""

from aas_core_codegen.cpp.aas_common import _generate

generate_header = _generate.generate_header
Expand Down
24 changes: 15 additions & 9 deletions aas_core_codegen/intermediate/_translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1044,9 +1044,11 @@ def _to_arguments(parsed: Sequence[parse.Argument]) -> List[Argument]:
Argument(
name=parsed_arg.name,
type_annotation=_to_type_annotation(parsed_arg.type_annotation),
default=_DefaultPlaceholder(parsed=parsed_arg.default) # type: ignore
if parsed_arg.default is not None
else None,
default=(
_DefaultPlaceholder(parsed=parsed_arg.default) # type: ignore
if parsed_arg.default is not None
else None
),
parsed=parsed_arg,
)
for parsed_arg in parsed
Expand Down Expand Up @@ -3446,9 +3448,11 @@ def _second_pass_to_stack_constructors_in_place(
if ancestor is None:
errors.append(
Error(
cls.constructor.parsed.node
if cls.constructor.parsed is not None
else cls.parsed.node,
(
cls.constructor.parsed.node
if cls.constructor.parsed is not None
else cls.parsed.node
),
f"In the constructor of the class {cls.name!r} "
f"the super-constructor for "
f"the class {statement.super_name!r} is invoked, "
Expand All @@ -3461,9 +3465,11 @@ def _second_pass_to_stack_constructors_in_place(
if id(ancestor) not in cls.inheritance_id_set:
errors.append(
Error(
cls.constructor.parsed.node
if cls.constructor.parsed is not None
else cls.parsed.node,
(
cls.constructor.parsed.node
if cls.constructor.parsed is not None
else cls.parsed.node
),
f"In the constructor of the class {cls.name!r} "
f"the super-constructor for "
f"the class {statement.super_name!r} is invoked, "
Expand Down
2 changes: 1 addition & 1 deletion aas_core_codegen/java/xmlization/_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2031,7 +2031,7 @@ def _generate_serialize(
public static void to(
{I}IClass that,
{I}XMLStreamWriter writer) throws SerializeException {{
{I}VisitorWithWriter visitor = new VisitorWithWriter();
{I}VisitorWithWriter visitor = new VisitorWithWriter();
{I}visitor.visit(
{II}that, writer);
}}"""
Expand Down
5 changes: 5 additions & 0 deletions aas_core_codegen/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import aas_core_codegen.typescript.main as typescript_main
import aas_core_codegen.xsd.main as xsd_main
import aas_core_codegen.jsonld.main as jsonld_main
import aas_core_codegen.protobuf.main as protobuf_main
from aas_core_codegen import run, specific_implementations
from aas_core_codegen.common import LinenoColumner, assert_never

Expand All @@ -36,6 +37,7 @@ class Target(enum.Enum):
RDF_SHACL = "rdf_shacl"
XSD = "xsd"
JSONLD_CONTEXT = "jsonld_context"
PROTOBUF = "protobuf"


class Parameters:
Expand Down Expand Up @@ -164,6 +166,9 @@ def execute(params: Parameters, stdout: TextIO, stderr: TextIO) -> int:
elif params.target is Target.JSONLD_CONTEXT:
return jsonld_main.execute(context=run_context, stdout=stdout, stderr=stderr)

elif params.target is Target.PROTOBUF:
return protobuf_main.execute(run_context, stdout=stdout, stderr=stderr)

else:
assert_never(params.target)

Expand Down
1 change: 1 addition & 0 deletions aas_core_codegen/protobuf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Generate ProtoBuf files based on the intermediate meta-model."""
232 changes: 232 additions & 0 deletions aas_core_codegen/protobuf/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
"""Provide common functions shared among different ProtoBuf code generation modules."""

import re
from typing import List, cast, Optional

from icontract import ensure, require

from aas_core_codegen import intermediate
from aas_core_codegen.common import Stripped, assert_never
from aas_core_codegen.protobuf import naming as proto_naming


@ensure(lambda result: result.startswith('"'))
@ensure(lambda result: result.endswith('"'))
def string_literal(text: str) -> Stripped:
"""Generate a ProtoBuf string literal from the ``text``."""
escaped = [] # type: List[str]

for character in text:
code_point = ord(character)

if character == "\a":
escaped.append("\\a")
elif character == "\b":
escaped.append("\\b")
elif character == "\f":
escaped.append("\\f")
elif character == "\n":
escaped.append("\\n")
elif character == "\r":
escaped.append("\\r")
elif character == "\t":
escaped.append("\\t")
elif character == "\v":
escaped.append("\\v")
elif character == '"':
escaped.append('\\"')
elif character == "\\":
escaped.append("\\\\")
elif code_point < 32:
# Non-printable ASCII characters
escaped.append(f"\\x{ord(character):x}")
elif 255 < code_point < 65536:
# Above ASCII
escaped.append(f"\\u{ord(character):04x}")
elif code_point >= 65536:
# Above Unicode Binary Multilingual Pane
escaped.append(f"\\U{ord(character):08x}")
else:
escaped.append(character)

return Stripped('"{}"'.format("".join(escaped)))


def needs_escaping(text: str) -> bool:
"""Check whether the ``text`` contains a character that needs escaping."""
for character in text:
if character == "\a":
return True
elif character == "\b":
return True
elif character == "\f":
return True
elif character == "\n":
return True
elif character == "\r":
return True
elif character == "\t":
return True
elif character == "\v":
return True
elif character == '"':
return True
elif character == "\\":
return True
else:
pass

return False


PRIMITIVE_TYPE_MAP = {
intermediate.PrimitiveType.BOOL: Stripped("bool"),
intermediate.PrimitiveType.INT: Stripped("int64"),
intermediate.PrimitiveType.FLOAT: Stripped("double"),
intermediate.PrimitiveType.STR: Stripped("string"),
intermediate.PrimitiveType.BYTEARRAY: Stripped("bytes"),
}


def _assert_all_primitive_types_are_mapped() -> None:
"""Assert that we have explicitly mapped all the primitive types to ProtoBuf."""
all_primitive_literals = set(literal.value for literal in PRIMITIVE_TYPE_MAP)

mapped_primitive_literals = set(
literal.value for literal in intermediate.PrimitiveType
)

all_diff = all_primitive_literals.difference(mapped_primitive_literals)
mapped_diff = mapped_primitive_literals.difference(all_primitive_literals)

messages = [] # type: List[str]
if len(mapped_diff) > 0:
messages.append(
f"More primitive maps are mapped than there were defined "
f"in the ``intermediate._types``: {sorted(mapped_diff)}"
)

if len(all_diff) > 0:
messages.append(
f"One or more primitive types in the ``intermediate._types`` were not "
f"mapped in PRIMITIVE_TYPE_MAP: {sorted(all_diff)}"
)

if len(messages) > 0:
raise AssertionError("\n\n".join(messages))


_assert_all_primitive_types_are_mapped()


# fmt: off
@require(
lambda our_type_qualifier:
not (our_type_qualifier is not None)
or not our_type_qualifier.endswith('.')
)
# fmt: on
def generate_type(
type_annotation: intermediate.TypeAnnotationUnion,
our_type_qualifier: Optional[Stripped] = None,
) -> Stripped:
"""
Generate the ProtoBuf type for the given type annotation.

``our_type_prefix`` is appended to all our types, if specified.
"""
our_type_prefix = "" if our_type_qualifier is None else f"{our_type_qualifier}."
if isinstance(type_annotation, intermediate.PrimitiveTypeAnnotation):
return PRIMITIVE_TYPE_MAP[type_annotation.a_type]

elif isinstance(type_annotation, intermediate.OurTypeAnnotation):
our_type = type_annotation.our_type

if isinstance(our_type, intermediate.Enumeration):
return Stripped(
our_type_prefix + proto_naming.enum_name(type_annotation.our_type.name)
)

elif isinstance(our_type, intermediate.ConstrainedPrimitive):
return PRIMITIVE_TYPE_MAP[our_type.constrainee]

elif isinstance(our_type, intermediate.Class):
return Stripped(our_type_prefix + proto_naming.class_name(our_type.name))

elif isinstance(type_annotation, intermediate.ListTypeAnnotation):
item_type = generate_type(
type_annotation=type_annotation.items, our_type_qualifier=our_type_qualifier
)

return Stripped(f"repeated {item_type}")

elif isinstance(type_annotation, intermediate.OptionalTypeAnnotation):
value = generate_type(
type_annotation=type_annotation.value, our_type_qualifier=our_type_qualifier
)

# careful: do not generate "optional" keyword for list-type elements since otherwise we get invalid
# constructs like "optional repeated <type> <name>"
if isinstance(type_annotation.value, intermediate.ListTypeAnnotation):
return Stripped(f"{value}")
else:
return Stripped(f"optional {value}")

else:
assert_never(type_annotation)

raise AssertionError("Should not have gotten here")


INDENT = " "
INDENT2 = INDENT * 2
INDENT3 = INDENT * 3
INDENT4 = INDENT * 4
INDENT5 = INDENT * 5
INDENT6 = INDENT * 6

# noinspection RegExpSimplifiable
NAMESPACE_IDENTIFIER_RE = re.compile(
r"[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*"
)


class NamespaceIdentifier(str):
"""Capture a namespace identifier."""

@require(lambda identifier: NAMESPACE_IDENTIFIER_RE.fullmatch(identifier))
def __new__(cls, identifier: str) -> "NamespaceIdentifier":
return cast(NamespaceIdentifier, identifier)


WARNING = Stripped(
"""\
/*
* This code has been automatically generated by aas-core-codegen.
* Do NOT edit or append.
*/"""
)


# fmt: off
@ensure(
lambda namespace, result:
not (namespace != "Aas") or len(result) == 1,
"Exactly one block of stripped text to be appended to the list of using directives "
"if this using directive is necessary"
)
@ensure(
lambda namespace, result:
not (namespace == "Aas") or len(result) == 0,
"Empty list if no directive is necessary"
)
# fmt: on
def generate_using_aas_directive_if_necessary(
namespace: NamespaceIdentifier,
) -> List[Stripped]:
"""
Generates the import directive for the AAS namespace.

This method is not to be used because proto3 does not need namespaces.
"""
raise NotImplementedError("Not using the Aas namespace.")
Loading
Loading