Source code for ifgen.common

"""
A module for generating shared headers and sources.
"""

# third-party
from vcorelib.io import IndentedFileWriter

# internal
from ifgen.generation.interface import GenerateTask
from ifgen.generation.test import unit_test_boilerplate


[docs] def create_common_test(task: GenerateTask) -> None: """Create a unit test for the enum string-conversion methods.""" if task.is_python: return with unit_test_boilerplate(task, declare_namespace=True) as writer: writer.cpp_comment("TODO.")
[docs] def endianness_single(writer: IndentedFileWriter) -> None: """Add methods for byte-sized primitives.""" writer.c_comment("No action for byte-sized primitives.") writer.write( "template <std::endian endianness, byte_size T> " "inline void handle_endian_p(T *)" ) with writer.scope(): pass writer.write( "template <std::endian endianness, byte_size T> " "inline T handle_endian(T elem)" ) with writer.scope(): writer.write("return elem;")
[docs] def endianness_native(writer: IndentedFileWriter) -> None: """Add methods for native endianness (no swap).""" writer.c_comment("No action if endianness is native.") writer.write("template <std::endian endianness, std::integral T>") writer.write("inline void handle_endian_p(T *)") with writer.indented(): writer.write( "requires(not byte_size<T>) && " "(endianness == std::endian::native)" ) with writer.scope(): pass writer.write("template <std::endian endianness, std::integral T>") writer.write("inline T handle_endian(T elem)") with writer.indented(): writer.write( "requires(not byte_size<T>) && " "(endianness == std::endian::native)" ) with writer.scope(): writer.write("return elem;")
[docs] def endianness_integral(writer: IndentedFileWriter) -> None: """Add methods for integral types that require swapping.""" writer.c_comment("Swap any integral type.") writer.write("template <std::endian endianness, std::integral T>") writer.write("inline T handle_endian(T elem)") with writer.indented(): writer.write( "requires(not byte_size<T>) && (endianness != std::endian::native)" ) with writer.scope(): writer.write("return std::byteswap(elem);") writer.write("template <std::endian endianness, std::integral T>") writer.write("inline void handle_endian_p(T *elem)") with writer.indented(): writer.write( "requires(not byte_size<T>) && (endianness != std::endian::native)" ) with writer.scope(): writer.write("*elem = std::byteswap(*elem);")
[docs] def endianness_enum(writer: IndentedFileWriter) -> None: """Add methods for enum types that require swapping.""" writer.c_comment("Handler for enum class types.") writer.write("template <std::endian endianness, typename T>") writer.write("inline void handle_endian_p(T *elem)") with writer.indented(): writer.write("requires(std::is_enum_v<T>)") with writer.scope(): writer.write("using underlying = std::underlying_type_t<T>;") writer.write("handle_endian_p<endianness>(") with writer.indented(): writer.write("reinterpret_cast<underlying *>(elem));") writer.write("template <std::endian endianness, typename T>") writer.write("inline T handle_endian(T elem)") with writer.indented(): writer.write("requires(std::is_enum_v<T>)") with writer.scope(): writer.write("return static_cast<T>(handle_endian<endianness>(") with writer.indented(): writer.write("std::to_underlying(elem)));")
[docs] def endianness_float(writer: IndentedFileWriter) -> None: """Add methods for floating-point types that require swapping.""" for width in ["32", "64"]: writer.empty() writer.c_comment(f"Handler for {width}-bit float.") writer.write( "template <std::endian endianness, std::floating_point T>" ) writer.write("inline void handle_endian_p(T *elem)") prim = f"uint{width}_t" with writer.indented(): writer.write(f"requires(sizeof(T) == sizeof({prim}))") with writer.scope(): writer.write( f"handle_endian_p<endianness>" f"(reinterpret_cast<{prim} *>(elem));" ) writer.write( "template <std::endian endianness, std::floating_point T>" ) writer.write("inline T handle_endian(T elem)") with writer.indented(): writer.write(f"requires(sizeof(T) == sizeof({prim}))") with writer.scope(): writer.write("return std::bit_cast<T>(") with writer.indented(): writer.write( "handle_endian<endianness>" f"(std::bit_cast<{prim}>(elem)));" )
[docs] def common_endianness(writer: IndentedFileWriter, task: GenerateTask) -> None: """Write endianness-related content.""" writer.c_comment("Enforce that this isn't a mixed-endian system.") writer.write("static_assert(std::endian::native == std::endian::big or") writer.write(" std::endian::native == std::endian::little);") data = task.env.config.data endian = data["struct"]["default_endianness"] with writer.padding(): writer.c_comment("Default endianness configured.") writer.write( f"static constexpr auto default_endian = std::endian::{endian};" ) writer.c_comment("Detect primitives that don't need byte swapping.") writer.write("template <typename T>") writer.write("concept byte_size = sizeof(T) == 1;") with writer.padding(): endianness_single(writer) endianness_native(writer) writer.empty() endianness_integral(writer) endianness_float(writer) writer.empty() endianness_enum(writer)
[docs] def create_common(task: GenerateTask) -> None: """Create a unit test for the enum string-conversion methods.""" if task.is_python: return streams = task.stream_implementation includes = [ "<bit>", "<concepts>", "<cstdint>", "<span>" if not streams else "<spanstream>", "<utility>", ] # probably get rid of everything besides the spanstream if streams: includes.extend(["<streambuf>", "<istream>", "<ostream>"]) with task.boilerplate(includes=includes) as writer: common_endianness(writer, task) writer.empty() writer.c_comment("Configured primitives for identifiers.") data = task.env.config.data writer.write(f"using struct_id_t = {data['struct']['id_underlying']};") writer.write(f"using enum_id_t = {data['enum']['id_underlying']};") with writer.padding(): writer.c_comment("Create useful aliases for bytes.") writer.write("template <std::size_t Extent = std::dynamic_extent>") writer.write("using byte_span = std::span<std::byte, Extent>;") writer.write( ( "template <std::size_t size> using byte_array = " "std::array<std::byte, size>;" ) ) if streams: writer.c_comment("Abstract byte-stream interfaces.") writer.write("using byte_istream = std::basic_istream<std::byte>;") writer.write("using byte_ostream = std::basic_ostream<std::byte>;") writer.empty() writer.c_comment( "Concrete byte-stream interfaces (based on span)." ) writer.write("using byte_spanbuf = std::basic_spanbuf<std::byte>;") writer.write( "using byte_spanstream = std::basic_spanstream<std::byte>;" ) writer.empty() writer.c_comment("Constraint for generated structs.") writer.write("template <typename T>") writer.write("concept ifgen_struct = requires") with writer.scope(suffix=";"): writer.write("std::is_integral_v<decltype(T::id)>;") writer.write("std::is_same_v<decltype(T::size), std::size_t>;")