EnumECG – The code generation support¶
Overview¶
EnumECG (almost acronym for Enhanced Enum Code Generator) is a Python library accompanying the Enhanced Enum library. It is used to generate C++ boilerplate for the enhanced enum types. Please see Enhanced Enum – The guide for more information about the design of the library.
There are multiple ways to map a Python object to the C++ enum type. The following code examples all produce the same C++ boilerplate. For further discussion see Creating the enumeration.
1enum class StatusLabel {
2 INITIALIZING,
3 WAITING_FOR_INPUT,
4 BUSY,
5};
6
7struct EnhancedStatus : ::enhanced_enum::enum_base<EnhancedStatus, StatusLabel, std::string_view> {
8 using ::enhanced_enum::enum_base<EnhancedStatus, StatusLabel, std::string_view>::enum_base;
9 static constexpr std::array values {
10 value_type { "initializing" },
11 value_type { "waitingForInput" },
12 value_type { "busy" },
13 };
14};
15
16constexpr EnhancedStatus enhance(StatusLabel e) noexcept
17{
18 return e;
19}
20
21namespace Statuses {
22inline constexpr const EnhancedStatus::value_type& INITIALIZING_VALUE { std::get<0>(EnhancedStatus::values) };
23inline constexpr const EnhancedStatus::value_type& WAITING_FOR_INPUT_VALUE { std::get<1>(EnhancedStatus::values) };
24inline constexpr const EnhancedStatus::value_type& BUSY_VALUE { std::get<2>(EnhancedStatus::values) };
25inline constexpr EnhancedStatus INITIALIZING { StatusLabel::INITIALIZING };
26inline constexpr EnhancedStatus WAITING_FOR_INPUT { StatusLabel::WAITING_FOR_INPUT };
27inline constexpr EnhancedStatus BUSY { StatusLabel::BUSY };
28inline constexpr auto begin() noexcept { return EnhancedStatus::begin(); }
29inline constexpr auto end() noexcept { return EnhancedStatus::end(); }
30inline constexpr auto all() noexcept { return EnhancedStatus::all(); }
31}
Creating C++ enum from Python enum¶
The most idiomatic way to create an enhanced enum type is to give the generator a Python enum type.
>>> import enum
>>> class Status(enum.Enum):
... INITIALIZING = "initializing"
... WAITING_FOR_INPUT = "waitingForInput"
... BUSY = "busy"
>>> import enumecg
>>> enumecg.generate(Status)
'...'
The mapping between the name of the enum type, and the names and values of the enum members are obvious in this style.
Creating C++ enum from a mapping¶
This is a convenient method if the enum definition is loaded from a file using general purpose serialization format like JSON or YAML.
>>> status = {
... "typename": "Status",
... "members": [
... {
... "name": "INITIALIZING",
... "value": "initializing",
... },
... {
... "name": "WAITING_FOR_INPUT",
... "value": "waitingForInput",
... },
... {
... "name": "BUSY",
... "value": "busy",
... },
... ]
... }
>>> import enumecg
>>> enumecg.generate(status)
'...'
The supported keys are:
typename
: The enum typename.members
: Mapping between enumerator names and values.docstring
: An optional documentation accompanying the generated types, constants and functions. See Including documentation in the generator output for details.
Native representation¶
The code generator uses enumecg.definitions.EnumDefinition
as
its datatype holding the native representation of an enum
definition. They can be used with the generator directly if a very
fine control of the generated code is required.
>>> from enumecg.definitions import EnumDefinition, EnumMemberDefinition
>>> status = EnumDefinition(
... label_enum_typename="StatusLabel",
... enhanced_enum_typename="EnhancedStatus",
... value_type_typename="std::string_view",
... members=[
... EnumMemberDefinition(
... enumerator_name="INITIALIZING",
... enumerator_value_constant_name="INITIALIZING_VALUE",
... enumerator_value_initializers="initializing",
... ),
... EnumMemberDefinition(
... enumerator_name="WAITING_FOR_INPUT",
... enumerator_value_constant_name="WAITING_FOR_INPUT_VALUE",
... enumerator_value_initializers="waitingForInput",
... ),
... EnumMemberDefinition(
... enumerator_name="BUSY",
... enumerator_value_constant_name="BUSY_VALUE",
... enumerator_value_initializers="busy",
... ),
... ],
... associate_namespace_name="Statuses",
... )
>>> import enumecg
>>> enumecg.generate(status)
'...'
Note that in this style all names used in the C++ template are
explicit fields of the EnumDefinition
object.
Enum definitions in detail¶
Various aspects of code generation can be controlled by passing keyword arguments to the code generator functions.
Please note that when generating the code directly from
enumecg.definitions.EnumDefinition
object, the options have
no effect because the EnumDefinition
object is assumed to contain
all information required to generate the code already.
Identifiers¶
In the example above the type names follow CamelCase while the enumerator names are UPPER_SNAKE_CASE. The code generator tries to deduce the case style for the different kinds of identifiers and uses it to format the names of others.
The case style of the enum type name is used to format the names of the C++ enums and the associate namespace.
The case style of the enumerator names are used to format the names of the the C++ enumerators and value constants.
The following case styles are recognized:
Snake case with all lowercase letters:
lower_snake_case
Snake case with all uppercase letters:
UPPER_SNAKE_CASE
Camel case with every word capitalized:
CamelCase
Camel case with the first word starting with a lowercase letter:
mixedCase
. A single lower case word is recognized as snake_case instead of mixedCase.
Only ASCII alphanumeric characters are supported. Numbers may appear in any other position except at the start of a subword. All enumerators must follow the same case style. The following leads to an error:
>>> class BadExample(enum.Enum):
... mixedCaseValue = "value1"
... snake_case_value = "value2"
>>> enumecg.generate(BadExample)
Traceback (most recent call last):
...
enumecg.exceptions.Error: Could not find common case
Primary enum type¶
By default the label enum for Status
has the name StatusLabel
and the enhanced enum has the name EnhancedStatus
. Almost
certainly the user will want to call one of those types simply
Status
depending on the view whether the label enum or the
enhanced enum is considered the primary enum type.
To make the label enum the primary type, set primary_type
option
to “label” when invoking the code generation:
>>> enumecg.generate(Status, primary_type="label")
'...enum class Status {...'
Similarly, passing option “enhanced” will make the enhanced enum the primary type:
>>> enumecg.generate(Status, primary_type="enhanced")
'...struct Status : ::enhanced_enum::enum_base<...'
Enumerator types and values¶
Python has dynamic typing, but in C++ all enumerators within an enum type must have the same type known in advance. There are two ways to define the enumerator type:
Have the code generator deduce a C++ type automatically from the Python enumerator values
Specify it manually
Enumerator type deduction¶
In the examples above the enumerator values are strings, but the enumerator type can be any type that can be constexpr constructible from arbitrarily nested initializer lists of string, integer, float and bool literals.
For example:
>>> class MathConstants(enum.Enum):
... PI = 3.14
... NEPER = 2.71
>>> enumecg.generate(MathConstants)
'...enum_base<..., double>...'
Or even:
>>> class NestedExample(enum.Enum):
... EXPLICIT_VALUE = 0, ("string", True)
... DEFAULT_VALUE = ()
>>> enumecg.generate(NestedExample)
'...enum_base<..., std::tuple<long, std::tuple<std::string_view, bool>>>...'
The Python types are mapped to C++ types in the following way:
Integral types are mapped to
long
Other real numbers (like floats) are mapped to
double
str
andbytes
are mapped tostd::string_view
bool
is mapped tobool
Sequences are mapped to
std::tuple
whose template arguments are (recursively) the mapped types of the elements of the sequence.
All enumerator values must have a compatible types for the type
deduction to work. When deducing the type from multiple sequences, the
longest sequence determines the template arguments of the resulting
std::tuple
, and all prefixes of values must have types compatible
with the longest sequence. For example the following works:
>>> class GoodExample(enum.Enum):
... VALUE1 = 1, 2
... VALUE2 = 3,
>>> enumecg.generate(GoodExample)
'...enum_base<..., std::tuple<long, long>>...'
But the following doesn’t:
>>> class BadExample(enum.Enum):
... VALUE1 = 1, 2
... VALUE2 = "string",
>>> enumecg.generate(BadExample)
Traceback (most recent call last):
...
enumecg.exceptions.Error: Could not deduce compatible type
Specifying enumerator type manually¶
You can use an type as the enum value type. Simply pass value_type
option when invoking the code generation:
>>> enumecg.generate(Status, value_type="StatusValue")
'...enum_base<..., StatusValue>...'
StatusValue
must be constexpr constructible from the Status
member values, i.e. string literals. Let’s look into that closer in
the next section.
Enumerator value initialization¶
Warning
Converting Python object representations into C++ literals is done
in a very straight-forward manner from the built-in
repr()
. It may not handle edge cases correctly, leading to
compilation errors.
C++ enumerators are initialized with expressions based on the enum members used as arguments to the generator.
>>> enumecg.generate(Status)
'...value_type { "initializing" }...'
Sequences are converted to initializer lists recursively. Empty
sequences are simply converted to initializer lists. Using the
NestedExample
above:
>>> enumecg.generate(NestedExample)
'... value_type { 0, { "string", true } },\n value_type { },\n...'
Note that when generating the initializers, the underlying type is no
longer considered. The generator just examines the values and converts
them to possibly nested lists surrounded by braces. Thus empty tuple
assigned to NestedExample.DEFAULT_VALUE
was converted to an empty
initializer list, i.e. the corresponding C++ enumerator is value
initialized.
Overriding arbitrary fields in the definition¶
It is also possible to start with an
enumecg.definitions.EnumDefinition
object generated from any
of the above representations, and modifying it before actually using
it to generate the C++
code. enumecg.definitions.make_definition()
can first be used
to get an EnumDefinition
object, which can further be used
with the enumecg.generate()
function.
Including documentation in the generator output¶
Doxygen comments can be included by using the documentation
option:
>>> enumecg.generate(Status, documentation="doxygen")
'/** \\brief ...'
The generated documentation contains information about the usage of an enhanced enum type. The doxygen documentation of Primary enum type also includes the possible docstring of the Python enum.
Command line interface¶
The enumecg
module can be invoked as a command. Given a YAML
file status.yaml
:
typename: Status
members:
- name: INITIALIZING
value: initializing
- name: WAITING_FOR_INPUT
value: waitingForInput
- name: BUSY
value: busy
The command line interface can use this file as an input to print the generated code to stdout.
$ enumecg status.yaml
... # C++ boilerplate printed to stdout
The input file is a single YAML document containing an enum definition. See Creating C++ enum from a mapping for the details of the schema.
Invoking enumecg --help
will list the supported options and
arguments.
High level API¶
Most of the time the high level API is all you need to get started with code generation.
Generate Enhanced Enum definitions for C++¶
The top level module provides the high level code generation API for the Enhanced Enum library.
- enumecg.generate(enum: Union[enumecg.definitions.EnumDefinition, Mapping, enum.EnumMeta], *, documentation: Optional[Union[enumecg.generators.DocumentationStyle, str]] = None, primary_type: Optional[Union[enumecg.definitions.PrimaryType, str]] = None, value_type: Optional[str] = None) str ¶
Generate code for an enhanced enum
This function is a shorthand for creating and invoking a code generator in one call.
The enum definition may be:
An instance of
definitions.EnumDefinition
A
dict
object containing the enum definition. The required and optional keys are discussed in Creating C++ enum from a mapping.A native Python
enum.Enum
class. The typename is derived from the name of the enum class, and the enumerator definitions are derived from its members.
The exact way that the
enum
parameter is converted to an enum definition in the C++ code is covered in Enum definitions in detail.- Parameters
enum – The enum definition
documentation – A string or an enumerator indicating the documentation style. See Including documentation in the generator output.
primary_type – A string or an enumerator indicating the primary type. See Primary enum type.
value_type – See Specifying enumerator type manually.
- Returns
The enhanced enum definition created from the
enum
description.
- enumecg.generator(*, documentation: Optional[Union[enumecg.generators.DocumentationStyle, str]] = None) enumecg.generators.CodeGenerator ¶
Create code generator for an enhanced enum type
Creates an instance of
generators.CodeGenerator
.- Parameters
documentation – A string or an enumerator indicating the documentation style. See Including documentation in the generator output.
- Returns
The
generators.CodeGenerator
instance.
Module reference¶
The package contains lower level modules. These are used to implement the High level API, but can also be utilized directly to give greater control over the generated code.
Enum definitions¶
Contains the classes that the code generator uses as its representation of an enum definition.
- enumecg.definitions.Enum¶
Generic enum definition
Types accepted by
make_definition()
and other functions that are used to generate enhanced enum definition.alias of
Union
[enumecg.definitions.EnumDefinition
,Mapping
,enum.EnumMeta
]
- class enumecg.definitions.EnumDefinition(label_enum_typename: str, enhanced_enum_typename: str, value_type_typename: str, members: Sequence[enumecg.definitions.EnumMemberDefinition], associate_namespace_name: str, label_enum_documentation: Optional[enumecg.definitions.EnumDocumentation] = None, enhanced_enum_documentation: Optional[enumecg.definitions.EnumDocumentation] = None)¶
Enum definition
- class enumecg.definitions.EnumDocumentation(short_description: Optional[str], long_description: Optional[str])¶
Documentation associated with an enum
- class enumecg.definitions.EnumMemberDefinition(enumerator_name: str, enumerator_value_constant_name: str, enumerator_value_initializers: Union[Sequence, str])¶
Enum member definition
- class enumecg.definitions.PrimaryType(value)¶
Possible primary types when generating enum definitions
These are the accepted choices for the
primary_type
argument inmake_definition()
.- enhanced = 'enhanced'¶
Enhanced enum is the primary type
- label = 'label'¶
Label enum is the primary type
- enumecg.definitions.make_definition(enum: Union[enumecg.definitions.EnumDefinition, Mapping, enum.EnumMeta], *, primary_type: Optional[enumecg.definitions.PrimaryType] = None, value_type: Optional[str] = None) enumecg.definitions.EnumDefinition ¶
Make
EnumDefinition
instance from various typesThis function is used to convert various kinds of enum definitions (standard Python
enum.Enum
types,dict
instances etc.) into anEnumDefinition
instance usable by the code generator. It allows for an user to provide a simpler enum definition, and having the details filled in automatically.This function is mainly meant to be used by the high level functions in the top level
enumecg
module, but can also be invoked directly for greater control over the code generation process.- Parameters
enum – The enum definition.
primary_type – A
PrimaryType
enumerator indicating the primary type. See Primary enum type.value_type – See Specifying enumerator type manually.
- Raises
exceptions.Error – If
enum
is invalid and cannot be converted toEnumDefinition
.
Code generator¶
The module contains the code generator consuming enum definitions and outputting C++ code.
- class enumecg.generators.CodeGenerator(*, documentation: Optional[enumecg.generators.DocumentationStyle] = None)¶
Code generator for an enhanced enum type
Used to generate the necessary C++ boilerplate to make an enum type compatible with the Enhanced Enum library.
The recommended way to create an instance is by using the
enumecg.generator()
function.- Parameters
documentation – A
DocumentationStyle
enumerator indicating the documentation style. See Including documentation in the generator output.
- generate_enum_definitions(enum, **options)¶
Generate the C++ definitions needed for an enhanced enum
- Parameters
enum – The enum definition
options – The options passed to
definitions.make_definition()
.
- Returns
The generated code
- Raises
exceptions.Error – If the code generation fails due to an invalid enum definition.
- class enumecg.generators.DocumentationStyle(value)¶
Possible documentation styles
These are the accepted choices for the
documentation
argument inCodeGenerator()
.- doxygen = 'doxygen'¶
Doxygen documentation style
Utilities¶
Utilities to perform miscellaneous tasks that the library needs to
perform. While they are mainly targeted for internal use, they are may
also be useful outside the scope of the enumecg
package.
- class enumecg.utils.CppTypeDeducer(*values, type_name: Optional[str] = None)¶
Deduce C++ types and initializers from Python values
This class examines collections of Python values, and deduces a C++ type that is compatible with them. It implements the algorithm described in Enumerator types and values.
If the explicit
type_name
parameter is given, it is preferred and thevalues
are not examined.- Parameters
values – The values used to deduce the type
type_name – The type name
- Raises
exceptions.Error – If no C++ type compatible with
values
can be deduced.
- classmethod get_initializer(value)¶
Return C++ initializer for
value
- Parameters
value – A value consisting of string, numbers, booleans and nested sequences thereof.
- Returns
An expression that can be used in a C++ initializer list to initialize a type compatible with
value
at compile time
- property type_name: str¶
The deduced C++ type
- class enumecg.utils.NameFormatter(*names: str)¶
Format names in the same case style as sample names
This class is used to split a sample of names (variables, classes etc.) into subwords, and creating new names with the same case style. An example demonstrates this the best:
>>> formatter = NameFormatter("first_name", "second_name") >>> formatter.parts [['first', 'name'], ['second', 'name']] >>> formatter.join(["name", "in", "snake", "case"]) 'name_in_snake_case' >>> formatter.join(["snake", "case"], pluralize=True) 'snake_cases'
This class implements the identifier formatting described in Identifiers.
- Parameters
names – The names to analyze
- Raises
exceptions.Error – If at least one of the
names
doesn’t follow a known case style, or if the sample contains names that follow different case style.
- join(parts: Iterable[str], *, pluralize=False) str ¶
Create new name from
parts
- Parameters
parts – Parts (words) of the name
pluralize – If
True
, assume the argument is a singular noun, and return it pluralized.
- Returns
The new name as string, with the individual parts joined together using the case style inferred during the construction
- property parts: List[List[str]]¶
List of the name parts used to create the formatter
Exceptions¶
Exceptions related to the code generation process.
- exception enumecg.exceptions.Error¶
Generic error in the code generation process