diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 28c8574..a755b40 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -77,7 +77,7 @@ jobs: - run: | mk python-release owner=vkottler \ - repo=ifgen version=3.0.1 + repo=ifgen version=3.1.0 if: | matrix.python-version == '3.11' && matrix.system == 'ubuntu-latest' diff --git a/README.md b/README.md index 11771bf..dbbd28b 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ===================================== generator=datazen version=3.1.4 - hash=e2d67bfd7efcf9a8fd101e9a24006e5a + hash=81700982f3f009c9782a7d73261925b4 ===================================== --> -# ifgen ([3.0.1](https://pypi.org/project/ifgen/)) +# ifgen ([3.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 b8037ff..d9a0b93 100644 --- a/ifgen/__init__.py +++ b/ifgen/__init__.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.4 -# hash=75a8be659b7a0b9eac6bb40ac4061a5a +# hash=addf6d9c81cc0f844ae7e2c96f5f4bb4 # ===================================== """ @@ -10,4 +10,4 @@ DESCRIPTION = "An interface generator for distributed computing." PKG_NAME = "ifgen" -VERSION = "3.0.1" +VERSION = "3.1.0" diff --git a/ifgen/data/schemas/Enum.yaml b/ifgen/data/schemas/Enum.yaml index 23d3e8d..40f2039 100644 --- a/ifgen/data/schemas/Enum.yaml +++ b/ifgen/data/schemas/Enum.yaml @@ -26,6 +26,10 @@ properties: type: boolean default: true + identifier: + type: boolean + default: true + underlying: type: string default: uint8_t diff --git a/ifgen/data/schemas/Struct.yaml b/ifgen/data/schemas/Struct.yaml index 8d6ca91..27c9826 100644 --- a/ifgen/data/schemas/Struct.yaml +++ b/ifgen/data/schemas/Struct.yaml @@ -21,6 +21,10 @@ properties: type: boolean default: true + identifier: + type: boolean + default: true + instances: type: array items: diff --git a/ifgen/enum/header.py b/ifgen/enum/header.py index 4dc1c5f..5896536 100644 --- a/ifgen/enum/header.py +++ b/ifgen/enum/header.py @@ -51,7 +51,9 @@ def enum_header(task: GenerateTask, writer: IndentedFileWriter) -> None: runtime = task.enum() - with writer.padding(): + writer.empty() + + if task.instance["identifier"]: writer.write( ( "static constexpr " @@ -59,6 +61,7 @@ def enum_header(task: GenerateTask, writer: IndentedFileWriter) -> None: f"{task.name}_id = {runtime.id};" ) ) + writer.empty() enum_to_string_function( task, writer, task.instance["use_map"], definition=True diff --git a/ifgen/struct/__init__.py b/ifgen/struct/__init__.py index 1f9786b..ed49453 100644 --- a/ifgen/struct/__init__.py +++ b/ifgen/struct/__init__.py @@ -189,14 +189,15 @@ def create_struct(task: GenerateTask) -> None: with writer.trailing_comment_lines( style=CommentStyle.C_DOXYGEN ) as lines: - lines.append( - ( - "static constexpr " - f"{task.env.config.data['struct_id_underlying']} " - f"id = {task.protocol().id};", - f"{task.name}'s identifier.", + if task.instance["identifier"]: + lines.append( + ( + "static constexpr " + f"{task.env.config.data['struct_id_underlying']} " + f"id = {task.protocol().id};", + f"{task.name}'s identifier.", + ) ) - ) size = task.env.size(task.name) enforce_expected_size(size, task.instance, task.name) diff --git a/ifgen/struct/methods/fields.py b/ifgen/struct/methods/fields.py deleted file mode 100644 index e9e49cc..0000000 --- a/ifgen/struct/methods/fields.py +++ /dev/null @@ -1,232 +0,0 @@ -""" -A module implementing an interface for generating bit-field methods for -structs. -""" - -# built-in -from contextlib import ExitStack -from typing import Any, Optional - -# third-party -from vcorelib.io.file_writer import IndentedFileWriter - -# internal -from ifgen.generation.interface import GenerateTask -from ifgen.struct.methods.bit import bit_field_toggle_method - -STANDARD_INTS = [ - ("uint8_t", 8), - ("uint16_t", 16), - ("uint32_t", 32), - ("uint64_t", 64), -] - - -def bit_field_underlying(field: dict[str, Any]) -> str: - """Get the underlying type for a bit field.""" - - kind = field.get("type") - - # Automatically determine a sane primitive-integer type to use if one isn't - # specified. - if kind is None: - width = field["width"] - - if width == 1: - kind = "bool" - else: - for candidate, bit_width in STANDARD_INTS: - if field["width"] <= bit_width: - kind = candidate - break - - assert kind is not None, kind - return kind - - -def possible_array_arg(parent: dict[str, Any]) -> str: - """Determine if a method needs an array-index argument.""" - - array_length: Optional[int] = parent.get("array_length") - inner = "" - if array_length: - inner = "std::size_t index" - - return inner - - -def bit_field_get_method( - task: GenerateTask, - parent: dict[str, Any], - field: dict[str, Any], - writer: IndentedFileWriter, - header: bool, - kind: str, - method_slug: str, - alias: str = None, -) -> None: - """Generate a 'get' method for a bit-field.""" - - if not header: - return - - is_flag = field["width"] == 1 - - inner = possible_array_arg(parent) - - method = task.cpp_namespace(f"get_{method_slug}({inner})", header=header) - writer.empty() - - with writer.javadoc(): - writer.write( - ( - f"Get {parent['name']}'s {field['name']} " - f"{'field' if field['width'] > 1 else 'bit'}." - ) - ) - - line = f"{kind} " + method - - lhs = parent["name"] if not alias else alias - if inner: - lhs += "[index]" - - with ExitStack() as stack: - if is_flag: - writer.write(line) - stack.enter_context(writer.scope()) - stmt = f"{lhs} & (1u << {field['index']}u)" - else: - writer.write(line) - stack.enter_context(writer.scope()) - stmt = ( - f"({lhs} >> {field['index']}u) & " - f"{bit_mask_literal(field['width'])}" - ) - - if task.env.is_enum(kind): - stmt = f"{kind}({stmt})" - - writer.write(f"return {stmt};") - - -def bit_mask_literal(width: int) -> str: - """Get a bit-mask literal.""" - return "0b" + ("1" * width) + "u" - - -def bit_field_set_method( - task: GenerateTask, - parent: dict[str, Any], - field: dict[str, Any], - writer: IndentedFileWriter, - header: bool, - kind: str, - method_slug: str, - alias: str = None, -) -> None: - """Generate a 'set' method for a bit-field.""" - - # Generate a toggle method for bit fields. - if field["width"] == 1: - bit_field_toggle_method( - task, parent["name"], field, writer, header, method_slug - ) - else: - if not header: - return - - inner = possible_array_arg(parent) - if inner: - inner += ", " - inner += f"{kind} value" - - method = task.cpp_namespace( - f"set_{method_slug}({inner})", header=header - ) - writer.empty() - - if header: - with writer.javadoc(): - writer.write(f"Set {parent['name']}'s {field['name']} field.") - - writer.write("inline void " + method) - with writer.scope(): - rhs = parent["name"] if not alias else alias - if "index" in inner: - rhs += "[index]" - - writer.write(f"{parent['type']} curr = {rhs};") - - mask = bit_mask_literal(field["width"]) - - with writer.padding(): - writer.write(f"curr &= ~({mask} << {field['index']}u);") - - val_str = "value" - if task.env.is_enum(kind): - val_str = f"std::to_underlying({val_str})" - - writer.write( - f"curr |= ({val_str} & {mask}) << {field['index']}u;" - ) - - writer.write(f"{rhs} = curr;") - - -def bit_field( - task: GenerateTask, - parent: dict[str, Any], - field: dict[str, Any], - writer: IndentedFileWriter, - header: bool, - alias: str = None, -) -> None: - """Generate for an individual bit-field.""" - - kind = bit_field_underlying(field) - - type_size = task.env.size(parent["type"]) * 8 - - index = field["index"] - width = field["width"] - - # Validate field parameters. - assert index + width <= type_size, (index, width, type_size, field) - assert field["read"] or field["write"], field - - name = parent["name"] if not alias else alias - method_slug = f"{name}_{field['name']}" - - # Generate a 'get' method. - if field["read"]: - bit_field_get_method( - task, parent, field, writer, header, kind, method_slug, alias=alias - ) - - # Generate a 'set' method. - if field["write"]: - bit_field_set_method( - task, parent, field, writer, header, kind, method_slug, alias=alias - ) - - -def bit_fields( - task: GenerateTask, writer: IndentedFileWriter, header: bool -) -> None: - """Generate bit-field lines.""" - - for field in task.instance["fields"]: - for bfield in field.get("fields", []): - bit_field(task, field, bfield, writer, header) - - for alternate in field.get("alternates", []): - for bfield in alternate.get("fields", []): - bit_field( - task, - field, - bfield, - writer, - header, - alias=alternate["name"], - ) diff --git a/ifgen/struct/methods/fields/__init__.py b/ifgen/struct/methods/fields/__init__.py new file mode 100644 index 0000000..d6fff17 --- /dev/null +++ b/ifgen/struct/methods/fields/__init__.py @@ -0,0 +1,121 @@ +""" +A module implementing an interface for generating bit-field methods for +structs. +""" + +# built-in +from typing import Any + +# third-party +from vcorelib.io.file_writer import IndentedFileWriter + +# internal +from ifgen.generation.interface import GenerateTask +from ifgen.struct.methods.fields.common import BitField +from ifgen.struct.methods.fields.getter import ( + bit_field_get_all_method, + bit_field_get_method, +) +from ifgen.struct.methods.fields.setter import ( + bit_field_set_all_method, + bit_field_set_method, +) + + +def bit_field( + task: GenerateTask, + parent: dict[str, Any], + field: BitField, + writer: IndentedFileWriter, + header: bool, + read_fields: list[BitField], + write_fields: list[BitField], + alias: str = None, +) -> None: + """Generate for an individual bit-field.""" + + type_size = task.env.size(parent["type"]) * 8 + + index = field["index"] + width = field["width"] + + # Validate field parameters. + assert index + width <= type_size, (index, width, type_size, field) + assert field["read"] or field["write"], field + + # Generate a 'get' method. + if field["read"]: + bit_field_get_method(task, parent, field, writer, header, alias=alias) + read_fields.append(field) + + # Generate a 'set' method. + if field["write"]: + bit_field_set_method(task, parent, field, writer, header, alias=alias) + write_fields.append(field) + + +def handle_atomic_fields_methods( + task: GenerateTask, + writer: IndentedFileWriter, + header: bool, + field: dict[str, Any], + read_fields: list[BitField], + write_fields: list[BitField], + alias: str = None, +) -> None: + """Handle additional bit-field methods.""" + + if len(read_fields) > 1: + writer.empty() + bit_field_get_all_method( + task, writer, header, field, read_fields, alias=alias + ) + + if len(write_fields) > 1: + writer.empty() + bit_field_set_all_method( + task, writer, header, field, write_fields, alias=alias + ) + + +def bit_fields( + task: GenerateTask, writer: IndentedFileWriter, header: bool +) -> None: + """Generate bit-field lines.""" + + for field in task.instance["fields"]: + read_fields: list[BitField] = [] + write_fields: list[BitField] = [] + + for bfield in field.get("fields", []): + bit_field( + task, field, bfield, writer, header, read_fields, write_fields + ) + handle_atomic_fields_methods( + task, writer, header, field, read_fields, write_fields + ) + + for alternate in field.get("alternates", []): + read_fields = [] + write_fields = [] + + for bfield in alternate.get("fields", []): + bit_field( + task, + field, + bfield, + writer, + header, + read_fields, + write_fields, + alias=alternate["name"], + ) + handle_atomic_fields_methods( + task, + writer, + header, + field, + read_fields, + write_fields, + alias=alternate["name"], + ) diff --git a/ifgen/struct/methods/fields/common.py b/ifgen/struct/methods/fields/common.py new file mode 100644 index 0000000..08357d3 --- /dev/null +++ b/ifgen/struct/methods/fields/common.py @@ -0,0 +1,65 @@ +""" +Common utilities for generating bit-field related struct methods. +""" + +# built-in +from typing import Any, Optional + +BitField = dict[str, Any] + + +def bit_mask_literal(width: int) -> str: + """Get a bit-mask literal.""" + return "0b" + ("1" * width) + "u" + + +def possible_array_arg(parent: dict[str, Any]) -> str: + """Determine if a method needs an array-index argument.""" + + array_length: Optional[int] = parent.get("array_length") + inner = "" + if array_length: + inner = "std::size_t index" + + return inner + + +STANDARD_INTS = [ + ("uint8_t", 8), + ("uint16_t", 16), + ("uint32_t", 32), + ("uint64_t", 64), +] + + +def bit_field_underlying(field: dict[str, Any]) -> str: + """Get the underlying type for a bit field.""" + + kind = field.get("type") + + # Automatically determine a sane primitive-integer type to use if one isn't + # specified. + if kind is None: + width = field["width"] + + if width == 1: + kind = "bool" + else: + for candidate, bit_width in STANDARD_INTS: + if field["width"] <= bit_width: + kind = candidate + break + + assert kind is not None, kind + return kind + + +def bit_field_method_slug( + field: dict[str, Any], member: str = "", alias: str = None +) -> str: + """Get a method slug for a struct's bit-field method.""" + + name = str(field["name"]) if not alias else alias + if member: + name += "_" + member + return name diff --git a/ifgen/struct/methods/fields/getter.py b/ifgen/struct/methods/fields/getter.py new file mode 100644 index 0000000..06ef4a9 --- /dev/null +++ b/ifgen/struct/methods/fields/getter.py @@ -0,0 +1,125 @@ +""" +A module implementing 'get' methods for bit-fields. +""" + +# built-in +from typing import Any + +# third-party +from vcorelib.io.file_writer import IndentedFileWriter + +# internal +from ifgen.generation.interface import GenerateTask +from ifgen.struct.methods.fields.common import ( + BitField, + bit_field_method_slug, + bit_field_underlying, + bit_mask_literal, + possible_array_arg, +) + + +def bit_field_get_all_method( + task: GenerateTask, + writer: IndentedFileWriter, + header: bool, + field: dict[str, Any], + fields: list[BitField], + alias: str = None, +) -> None: + """Generate a 'get' method for multiple bit-field.""" + + if not header: + return + + name = field["name"] if not alias else alias + + inner = possible_array_arg(field) + if inner: + inner += ", " + + with writer.javadoc(): + writer.write(f"Get all of {name}'s bit fields.") + + # Add field args. + args = [] + for bit_field in fields: + args.append(f"{bit_field_underlying(bit_field)} &{bit_field['name']}") + + inner += ", ".join(args) + + writer.write(f"inline void get_{name}({inner})") + with writer.scope(): + rhs = field["name"] if not alias else alias + if "index" in inner: + rhs += "[index]" + + writer.write(f"{field['type']} curr = {rhs};") + writer.empty() + + for bit_field in fields: + stmt = get_bit_field_statement(task, bit_field, "curr") + writer.write(f"{bit_field['name']} = {stmt};") + + +def get_bit_field_statement( + task: GenerateTask, field: BitField, lhs: str +) -> str: + """ + Get the arithmetic statement associated with a bit-field get operation. + """ + + kind = bit_field_underlying(field) + + is_flag = field["width"] == 1 + + if is_flag: + stmt = f"{lhs} & (1u << {field['index']}u)" + else: + stmt = ( + f"({lhs} >> {field['index']}u) & " + f"{bit_mask_literal(field['width'])}" + ) + + if task.env.is_enum(kind): + stmt = f"{kind}({stmt})" + + return stmt + + +def bit_field_get_method( + task: GenerateTask, + parent: dict[str, Any], + field: BitField, + writer: IndentedFileWriter, + header: bool, + alias: str = None, +) -> None: + """Generate a 'get' method for a bit-field.""" + + if not header: + return + + inner = possible_array_arg(parent) + + method_slug = bit_field_method_slug(parent, field["name"], alias=alias) + method = task.cpp_namespace(f"get_{method_slug}({inner})", header=header) + writer.empty() + + with writer.javadoc(): + writer.write( + ( + f"Get {parent['name']}'s {field['name']} " + f"{'field' if field['width'] > 1 else 'bit'}." + ) + ) + + line = f"inline {bit_field_underlying(field)} " + method + + lhs = parent["name"] if not alias else alias + if inner: + lhs += "[index]" + + writer.write(line) + with writer.scope(): + writer.write(f"return {get_bit_field_statement(task, field, lhs)};") diff --git a/ifgen/struct/methods/fields/setter.py b/ifgen/struct/methods/fields/setter.py new file mode 100644 index 0000000..d0b84d1 --- /dev/null +++ b/ifgen/struct/methods/fields/setter.py @@ -0,0 +1,136 @@ +""" +A module implementing 'set' methods for bit-fields. +""" + +# built-in +from typing import Any, Iterator + +# third-party +from vcorelib.io.file_writer import IndentedFileWriter + +# internal +from ifgen.generation.interface import GenerateTask +from ifgen.struct.methods.bit import bit_field_toggle_method +from ifgen.struct.methods.fields.common import ( + BitField, + bit_field_method_slug, + bit_field_underlying, + bit_mask_literal, + possible_array_arg, +) + + +def bit_field_set_all_method( + task: GenerateTask, + writer: IndentedFileWriter, + header: bool, + field: dict[str, Any], + fields: list[BitField], + alias: str = None, +) -> None: + """Generate a 'set' method for multiple bit-field.""" + + if not header: + return + + name = field["name"] if not alias else alias + + inner = possible_array_arg(field) + if inner: + inner += ", " + + with writer.javadoc(): + writer.write(f"Set all of {name}'s bit fields.") + + # Add field args. + args = [] + for bit_field in fields: + args.append(f"{bit_field_underlying(bit_field)} {bit_field['name']}") + + inner += ", ".join(args) + writer.write(f"inline void set_{name}({inner})") + with writer.scope(): + rhs = field["name"] if not alias else alias + if "index" in inner: + rhs += "[index]" + + writer.write(f"{field['type']} curr = {rhs};") + + with writer.padding(): + for bit_field in fields: + for line in bit_field_set_lines( + task, bit_field, value=bit_field["name"] + ): + writer.write(line) + + writer.write(f"{rhs} = curr;") + + +def bit_field_set_lines( + task: GenerateTask, + field: BitField, + lhs: str = "curr", + value: str = "value", +) -> Iterator[str]: + """Get lines that perform a bit-field's assignment.""" + + mask = bit_mask_literal(field["width"]) + + yield f"{lhs} &= ~({mask} << {field['index']}u);" + + val_str = value + if task.env.is_enum(bit_field_underlying(field)): + val_str = f"std::to_underlying({val_str})" + + yield f"{lhs} |= ({val_str} & {mask}) << {field['index']}u;" + + +def bit_field_set_method( + task: GenerateTask, + parent: dict[str, Any], + field: BitField, + writer: IndentedFileWriter, + header: bool, + alias: str = None, +) -> None: + """Generate a 'set' method for a bit-field.""" + + method_slug = bit_field_method_slug(parent, field["name"], alias=alias) + kind = bit_field_underlying(field) + + # Generate a toggle method for bit fields. + if field["width"] == 1: + bit_field_toggle_method( + task, parent["name"], field, writer, header, method_slug + ) + else: + if not header: + return + + inner = possible_array_arg(parent) + if inner: + inner += ", " + inner += f"{kind} value" + + method = task.cpp_namespace( + f"set_{method_slug}({inner})", header=header + ) + writer.empty() + + if header: + with writer.javadoc(): + writer.write(f"Set {parent['name']}'s {field['name']} field.") + + writer.write("inline void " + method) + with writer.scope(): + rhs = parent["name"] if not alias else alias + if "index" in inner: + rhs += "[index]" + + writer.write(f"{parent['type']} curr = {rhs};") + + with writer.padding(): + for line in bit_field_set_lines(task, field): + writer.write(line) + + writer.write(f"{rhs} = curr;") diff --git a/ifgen/svd/group/fields.py b/ifgen/svd/group/fields.py index 0badeb6..234f59d 100644 --- a/ifgen/svd/group/fields.py +++ b/ifgen/svd/group/fields.py @@ -17,6 +17,7 @@ "codec": False, "methods": False, "unit_test": False, + "identifier": False, } @@ -148,6 +149,7 @@ def translate_enums(enum: EnumeratedValues) -> dict[str, Any]: "unit_test": False, "json": False, "use_map": False, + "identifier": False, } diff --git a/local/variables/package.yaml b/local/variables/package.yaml index aa7d92e..9e02b3f 100644 --- a/local/variables/package.yaml +++ b/local/variables/package.yaml @@ -1,5 +1,5 @@ --- major: 3 -minor: 0 -patch: 1 +minor: 1 +patch: 0 entry: ig diff --git a/pyproject.toml b/pyproject.toml index 793e00c..a9e648a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__" [project] name = "ifgen" -version = "3.0.1" +version = "3.1.0" description = "An interface generator for distributed computing." readme = "README.md" requires-python = ">=3.11"