diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 87e738b..cd05779 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -75,7 +75,7 @@ jobs: - run: | mk python-release owner=vkottler \ - repo=ifgen version=1.0.0 + repo=ifgen version=1.1.0 if: | matrix.python-version == '3.11' && matrix.system == 'ubuntu-latest' diff --git a/README.md b/README.md index 278c3c5..090561f 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ===================================== generator=datazen version=3.1.3 - hash=839acebe38039a168ae7975ef1b82408 + hash=f027a094d4c73ea1fc44e8d63edcf8db ===================================== --> -# ifgen ([1.0.0](https://pypi.org/project/ifgen/)) +# ifgen ([1.1.0](https://pypi.org/project/ifgen/)) [![python](https://img.shields.io/pypi/pyversions/ifgen.svg)](https://pypi.org/project/ifgen/) ![Build Status](https://github.com/vkottler/ifgen/workflows/Python%20Package/badge.svg) diff --git a/ifgen/__init__.py b/ifgen/__init__.py index 5bcfd8c..066028e 100644 --- a/ifgen/__init__.py +++ b/ifgen/__init__.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.3 -# hash=7d1b42791b9f25d5e70560816ca96765 +# hash=98bf88c49dfee15d176169be4c894053 # ===================================== """ @@ -10,4 +10,4 @@ DESCRIPTION = "An interface generator for distributed computing." PKG_NAME = "ifgen" -VERSION = "1.0.0" +VERSION = "1.1.0" diff --git a/ifgen/enum/test.py b/ifgen/enum/test.py index 59b2fe8..4641265 100644 --- a/ifgen/enum/test.py +++ b/ifgen/enum/test.py @@ -7,15 +7,12 @@ # internal from ifgen.generation.interface import GenerateTask +from ifgen.generation.test import unit_test_boilerplate def unit_test_body(task: GenerateTask, writer: IndentedFileWriter) -> None: """Implement a simple unit test for the enumeration.""" - writer.write(f"using namespace {task.namespace()};") - - writer.empty() - for enum in task.instance.get("enum", {}): to_string = f"to_string({task.name}::{enum})" writer.write(f"std::cout << {to_string} << std::endl;") @@ -26,15 +23,5 @@ def unit_test_body(task: GenerateTask, writer: IndentedFileWriter) -> None: def create_enum_test(task: GenerateTask) -> None: """Create a unit test for the enum string-conversion methods.""" - include = task.env.rel_include(task.name, task.generator) - - with task.boilerplate( - includes=["", "", "", f'"{include}"'], - is_test=True, - use_namespace=False, - description=f"A unit test for {task.generator} {task.name}.", - ) as writer: - writer.write("int main(void)") - with writer.scope(): - unit_test_body(task, writer) - writer.write("return 0;") + with unit_test_boilerplate(task) as writer: + unit_test_body(task, writer) diff --git a/ifgen/environment/__init__.py b/ifgen/environment/__init__.py index b9ec3c5..7ab8184 100644 --- a/ifgen/environment/__init__.py +++ b/ifgen/environment/__init__.py @@ -5,10 +5,11 @@ # built-in from enum import StrEnum from pathlib import Path +from typing import Any # third-party +from runtimepy.codec.system import TypeSystem from vcorelib.logging import LoggerMixin -from vcorelib.namespace import CPP_DELIM, Namespace from vcorelib.paths import normalize, rel # internal @@ -23,6 +24,31 @@ class Generator(StrEnum): ENUMS = "enums" +def runtime_enum_data(data: dict[str, Any]) -> dict[str, int]: + """Get runtime enumeration data.""" + + result = {} + + curr_value = 0 + + for key, value in data.items(): + if value is None or "value" not in value: + result[key] = curr_value + curr_value += 1 + else: + result[key] = value["value"] + if value["value"] >= curr_value: + curr_value = value["value"] + 1 + + return result + + +def type_string(data: str) -> str: + """Handle some type name conversions.""" + + return data.replace("_t", "") + + class IfgenEnvironment(LoggerMixin): """A class for managing stateful information while generating outputs.""" @@ -48,15 +74,43 @@ def __init__(self, root: Path, config: Config) -> None: for path in [self.output, self.test_dir]: path.joinpath(subdir).mkdir(parents=True, exist_ok=True) - global_namespace = Namespace(delim=CPP_DELIM) - - # Register global names. - - self.root_namespace = global_namespace.child( - *self.config.data["namespace"] - ) - - # Register custom names for each generator. + self.types = TypeSystem(*self.config.data["namespace"]) + self._register_enums() + self._register_structs() + + def _register_enums(self) -> None: + """Register configuration enums.""" + + for name, enum in self.config.data.get("enums", {}).items(): + self.types.enum( + name, + runtime_enum_data(enum["enum"]), + *enum["namespace"], + primitive=type_string(enum["underlying"]), + ) + + self.logger.info( + "Registered enum '%s'.", + self.types.root_namespace.delim.join( + enum["namespace"] + [name] + ), + ) + + def _register_structs(self) -> None: + """Register configuration structs.""" + + for name, struct in self.config.data.get("structs", {}).items(): + self.types.register(name, *struct["namespace"]) + for field in struct["fields"]: + self.types.add(name, field["name"], type_string(field["type"])) + + self.logger.info( + "Registered struct '%s' (%d bytes).", + self.types.root_namespace.delim.join( + struct["namespace"] + [name] + ), + self.types.size(name, *struct["namespace"]), + ) def make_path( self, name: str, generator: Generator, from_output: bool = False diff --git a/ifgen/generation/__init__.py b/ifgen/generation/__init__.py index 72eb151..f038d04 100644 --- a/ifgen/generation/__init__.py +++ b/ifgen/generation/__init__.py @@ -12,10 +12,10 @@ from ifgen.enum import create_enum, create_enum_test from ifgen.environment import Generator, IfgenEnvironment from ifgen.generation.interface import GenerateTask, InstanceGenerator -from ifgen.struct import create_struct +from ifgen.struct import create_struct, create_struct_test GENERATORS: Dict[Generator, List[InstanceGenerator]] = { - Generator.STRUCTS: [create_struct], + Generator.STRUCTS: [create_struct, create_struct_test], Generator.ENUMS: [create_enum, create_enum_test], } diff --git a/ifgen/generation/interface.py b/ifgen/generation/interface.py index f307634..81ba19d 100644 --- a/ifgen/generation/interface.py +++ b/ifgen/generation/interface.py @@ -56,7 +56,7 @@ def config(self) -> IfgenConfig: def namespace(self) -> str: """Get this task's namespace.""" - nspace = self.env.root_namespace + nspace = self.env.types.root_namespace with nspace.pushed(*self.instance.get("namespace", [])): result = nspace.namespace(track=False) diff --git a/ifgen/generation/test.py b/ifgen/generation/test.py new file mode 100644 index 0000000..62d563e --- /dev/null +++ b/ifgen/generation/test.py @@ -0,0 +1,39 @@ +""" +A module implementing unit-testing related generation utilities. +""" + +# built-in +from contextlib import contextmanager +from typing import Iterator, List + +# third-party +from vcorelib.io import IndentedFileWriter + +# internal +from ifgen.generation.interface import GenerateTask + + +@contextmanager +def unit_test_boilerplate( + task: GenerateTask, includes: List[str] = None +) -> Iterator[IndentedFileWriter]: + """Handle standard unit-test boilerplate.""" + + include = task.env.rel_include(task.name, task.generator) + + if includes is None: + includes = [] + + with task.boilerplate( + includes=["", "", "", f'"{include}"'] + + includes, + is_test=True, + use_namespace=False, + description=f"A unit test for {task.generator} {task.name}.", + ) as writer: + writer.write("int main(void)") + with writer.scope(): + writer.write(f"using namespace {task.namespace()};") + writer.empty() + yield writer + writer.write("return 0;") diff --git a/ifgen/requirements.txt b/ifgen/requirements.txt index d936c5c..1e21e53 100644 --- a/ifgen/requirements.txt +++ b/ifgen/requirements.txt @@ -1 +1 @@ -vcorelib>=2.5.2 +runtimepy>=2.1.1 diff --git a/ifgen/struct/__init__.py b/ifgen/struct/__init__.py index 22b1482..5de54f8 100644 --- a/ifgen/struct/__init__.py +++ b/ifgen/struct/__init__.py @@ -7,7 +7,9 @@ # internal from ifgen.generation.interface import GenerateTask +from ifgen.struct.test import create_struct_test +__all__ = ["create_struct", "create_struct_test"] FieldConfig = Dict[str, Union[int, str]] @@ -62,7 +64,18 @@ def create_struct(task: GenerateTask) -> None: """Create a header file based on a struct definition.""" with task.boilerplate(includes=struct_includes(task), json=True) as writer: - writer.write(f"struct {task.name}") + attributes = ["gnu::packed"] + writer.write(f"struct [[{', '.join(attributes)}]] {task.name}") with writer.scope(suffix=";"): for field in task.instance["fields"]: writer.write(struct_line(field.pop("name"), field)) + + writer.empty() + + # Add size assertion. + writer.write( + ( + f"static_assert(sizeof({task.name}) " + f"== {task.env.types.size(task.name)});" + ) + ) diff --git a/ifgen/struct/test.py b/ifgen/struct/test.py new file mode 100644 index 0000000..5c94b79 --- /dev/null +++ b/ifgen/struct/test.py @@ -0,0 +1,26 @@ +""" +A module implementing a unit-test output generator for structs. +""" + +# third-party +from vcorelib.io import IndentedFileWriter + +# internal +from ifgen.generation.interface import GenerateTask +from ifgen.generation.test import unit_test_boilerplate + + +def unit_test_body(task: GenerateTask, writer: IndentedFileWriter) -> None: + """Implement a unit test for a struct.""" + + del task + + writer.cpp_comment("TODO.") + writer.empty() + + +def create_struct_test(task: GenerateTask) -> None: + """Create a unit test for the enum string-conversion methods.""" + + with unit_test_boilerplate(task) as writer: + unit_test_body(task, writer) diff --git a/local/configs/package.yaml b/local/configs/package.yaml index b2ac3c7..9d5249c 100644 --- a/local/configs/package.yaml +++ b/local/configs/package.yaml @@ -13,7 +13,7 @@ ci_local: - " if: matrix.system == 'ubuntu-latest'" requirements: - - vcorelib>=2.5.2 + - runtimepy>=2.1.1 dev_requirements: - pytest-cov diff --git a/local/variables/package.yaml b/local/variables/package.yaml index 079eace..3deef6a 100644 --- a/local/variables/package.yaml +++ b/local/variables/package.yaml @@ -1,5 +1,5 @@ --- major: 1 -minor: 0 +minor: 1 patch: 0 entry: ig diff --git a/pyproject.toml b/pyproject.toml index d95bc80..87df02f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__" [project] name = "ifgen" -version = "1.0.0" +version = "1.1.0" description = "An interface generator for distributed computing." readme = "README.md" requires-python = ">=3.11" diff --git a/tests/data/valid/scenarios/sample/ifgen.yaml b/tests/data/valid/scenarios/sample/ifgen.yaml index 65c2c1f..e9aa765 100644 --- a/tests/data/valid/scenarios/sample/ifgen.yaml +++ b/tests/data/valid/scenarios/sample/ifgen.yaml @@ -31,7 +31,7 @@ structs: Test3: fields: - name: field1 - type: int + type: int32_t - name: field2 type: C::Test1