For now we have only a jinja generator for code generation. As such this guide will only discuss using nunavut with jinja templates. There are no immediate plans to support any other template syntax.
Note
See :mod:`nunavut.jinja` and the language support modules within this one for detailed documentation on available filters and tests provided by nunavut.
The Jinja templates documentation is indispensible as nunavut embeds a full-featured version of jinja 2.
Each template has in its global environment the following:
A global nunavut
is available in the global namespace with the following properties:
A pep 440 compatible version number for the version of Nunavut that the template is running within.
Meta-data about built-in support for serialization.
bool
that is True if serialization support was switched off for this template.
An array of identifiers under which Nunavut support files and types are namespaced. The use of this value by built-in templates and generators is language dependant.
The T global contains the type for the given template. For example:
{{T.full_name}} {%- for attr in T.attributes %} {{ attr.data_type }} {%- endfor %}
The time UTC as a Python datetime.datetime
object. This is the system time right before
the file generation step for the current template began. This will be the same time for included
templates and parent templates.
Options and other language-specific facilities for all supported languages. This namespace provides access to other languages that are not the target language for the current template. This allows the development of mixed language templates.
In addition to the built-in Jinja filters and tests (again, see the Jinja templates documentation for details) pydsdl adds several global tests and filters to the template environment. See :mod:`nunavut.jinja` for full documentation on these. For example:
# typename filter returns the name of the value's type. {{ field | typename }}
Also, for every pydsdl type there is a test automatically appended to the global environment. This means you can do:
{% if field is IntegerType %} // stuff for integer fields {% endif %}
Finally, for every pydsdl object that ends in "Type" or "Field" a lower-case name ommitting these suffixes is made available. For example:
template = '{% if some_type is integer %}This is a pydsdl.IntegerType{% endif %}'
Some language provide named types to allow templates to use a type without making concrete decisions about the headers and conventions in use. For example, when using C it is common to use size_t as an unsigned, integer length type. To avoid hard-coding this type a C template can use the named type:
{{ typename_unsigned_length }} array_len;
Type name | Language(s) | Use |
---|---|---|
typename_unsigned_length | C, C++ | An unsigned integer type suitable for expressing the length of any valid type on the local system. |
typename_byte | C | An unsigned integer type used to represent a single byte (8-bits). |
Some languages can use different values to represent certain data like null references or boolean values. Named values allow templates to insert a token appropriate for the language and configurable by the generator in use. For example:
MyType* p = {{ valuetoken_null }};
Value name | Language(s) |
---|---|
valuetoken_true | C |
valuetoken_false | C |
valuetoken_null | C |
The target language for a template contributes options to the template globals. These options can be invented by users of the Nunavut library but a built-in set of defaults exists.
All language options are made available as globals within the options namespace. For example, a language option "target_arch" would be available as the "options.target_arch" global in templates.
For options that do not come with built-in defaults you'll need to test if the option is available before you use it. For example:
# This will throw an exception
template = '{% if options.foo %}bar{% endif %}'
Use the built-in test defined
to avoid these exceptions:
# Avoid the exception
template = '{% if options.foo is defined and options.foo %}bar{% endif %}'
The following options have built-in defaults for certain languages. These options will always be defined in templates targeting their languages.
This option is currently defined for C and C++; the possible values are as follows:
any
--- generate endianness-agnostic code that is compatible with big-endian and little-endian machines alike.big
--- generate code optimized for big-endian platforms only. Implementations may treat this option likeany
when no such optimizations are possible.little
--- generate code optimized for little-endian platforms only. Little-endian optimizations are made possible by the fact that DSDL is a little-endian format.
template = '{{ options.target_endianness }}'
# then
rendered = 'any'
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.filter_yamlfy :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.filter_type_to_template :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.filter_type_to_include_path :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.filter_typename :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.filter_alignment_prefix :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.filter_bit_length_set :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.filter_remove_blank_lines :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.filter_bits2bytes_ceil :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.is_None :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.is_saturated :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.is_service_request :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.is_service_response :noindex:
.. autofunction:: nunavut.jinja.DSDLCodeGenerator.is_deprecated :noindex:
.. autofunction:: nunavut.lang.c.filter_id :noindex:
.. autofunction:: nunavut.lang.c.filter_macrofy :noindex:
.. autofunction:: nunavut.lang.c.filter_type_from_primitive :noindex:
.. autofunction:: nunavut.lang.c.filter_to_snake_case :noindex:
.. autofunction:: nunavut.lang.c.filter_to_screaming_snake_case :noindex:
.. autofunction:: nunavut.lang.c.filter_to_template_unique_name :noindex:
.. autofunction:: nunavut.lang.c.filter_short_reference_name :noindex:
.. autofunction:: nunavut.lang.c.filter_includes :noindex:
.. autofunction:: nunavut.lang.c.filter_to_static_assertion_value :noindex:
.. autofunction:: nunavut.lang.c.filter_constant_value :noindex:
.. autofunction:: nunavut.lang.c.filter_literal :noindex:
.. autofunction:: nunavut.lang.c.filter_full_reference_name :noindex:
.. autofunction:: nunavut.lang.c.filter_to_standard_bit_length :noindex:
.. autofunction:: nunavut.lang.c.is_zero_cost_primitive :noindex:
.. autofunction:: nunavut.lang.cpp.filter_id :noindex:
.. autofunction:: nunavut.lang.cpp.filter_open_namespace :noindex:
.. autofunction:: nunavut.lang.cpp.filter_close_namespace :noindex:
.. autofunction:: nunavut.lang.cpp.filter_full_reference_name :noindex:
.. autofunction:: nunavut.lang.cpp.filter_short_reference_name :noindex:
.. autofunction:: nunavut.lang.cpp.filter_includes :noindex:
.. autofunction:: nunavut.lang.cpp.filter_destructor_name :noindex:
.. autofunction:: nunavut.lang.cpp.filter_declaration :noindex:
.. autofunction:: nunavut.lang.cpp.filter_to_namespace_qualifier :noindex:
.. autofunction:: nunavut.lang.cpp.filter_type_from_primitive :noindex:
.. autofunction:: nunavut.lang.cpp.filter_to_template_unique_name :noindex:
.. autofunction:: nunavut.lang.cpp.filter_as_boolean_value :noindex:
.. autofunction:: nunavut.lang.cpp.filter_indent_if_not :noindex:
.. autofunction:: nunavut.lang.cpp.filter_minimum_required_capacity_bits :noindex:
.. autofunction:: nunavut.lang.cpp.filter_block_comment :noindex:
.. autofunction:: nunavut.lang.cpp.uses_std_variant :noindex:
.. autofunction:: nunavut.lang.py.filter_id :noindex:
.. autofunction:: nunavut.lang.py.filter_to_template_unique_name :noindex:
.. autofunction:: nunavut.lang.py.filter_full_reference_name :noindex:
.. autofunction:: nunavut.lang.py.filter_short_reference_name :noindex:
.. autofunction:: nunavut.lang.py.filter_imports :noindex:
.. autofunction:: nunavut.lang.py.filter_longest_id_length :noindex:
.. autofunction:: nunavut.lang.html.filter_extent :noindex:
.. autofunction:: nunavut.lang.html.filter_max_bit_length :noindex:
.. autofunction:: nunavut.lang.html.filter_tag_id :noindex:
.. autofunction:: nunavut.lang.html.filter_url_from_type :noindex:
.. autofunction:: nunavut.lang.html.filter_make_unique :noindex:
.. autofunction:: nunavut.lang.html.filter_namespace_doc :noindex:
.. autofunction:: nunavut.lang.html.filter_display_type :noindex:
.. autofunction:: nunavut.lang.html.filter_natural_sort_namespace :noindex:
.. autofunction:: nunavut.lang.html.filter_natural_sort_type :noindex:
Templates are resolved as templates/path/[dsdl_typename].j2
This means you must, typically, start with four templates under the templates directory
given to the Generator
instance
ServiceType.j2 StructureType.j2 DelimitedType.j2 UnionType.j2
Note
You can chose to use a single template Any.j2
but this may lead to more complex
templates with many more control statements. By providing discreet templates named for top-level
data types and using jinja template inheritance and includes your templates will be smaller
and easier to maintain.
To share common formatting for these templates use Jinja template inheritance. For example,
given a template common_header.j2
:
/* * Cyphal data structure definition for nunavut. * * Auto-generated, do not edit. * * Source file: {{T.source_file_path.as_posix()}} * Generated at: {{now_utc}} * Template: {{ self._TemplateReference__context.name }} * deprecated: {{T.deprecated}} * fixed_port_id: {{T.fixed_port_id}} * full_name: {{T.full_name}} * full_namespace: {{T.full_namespace}} */ #ifndef {{T.full_name | ln.c.macrofy}} #define {{T.full_name | ln.c.macrofy}} {%- block contents %}{% endblock -%} #endif // {{T.full_name | ln.c.macrofy}} /* {{ T | yamlfy }} */
... your three top-level templates would each start out with something like this:
{% extends "common_header.j2" %} {% block contents %} // generate stuff here {% endblock %}
You can apply the same logic used by the top level generator to recursively include templates by type if this seems useful for your project. Simply use the :func:`nunavut.jinja.Generator.filter_type_to_template` filter:
{%- for attribute in T.attributes %} {%* include attribute.data_type | type_to_template %} {%- endfor %}
If the generate_namespace_types
parameter of :class:`~nunavut.jinja.Generator` is
YES
then the generator will always invoke a template for the root namespace and all
nested namespaces regardless of language. NO
suppresses this behavior and DEFAULT
will choose the behavior based on the target language. For example:
root_namespace = build_namespace_tree(compound_types, root_ns_folder, out_dir, language_context) generator = Generator(root_namespace, YesNoDefault.DEFAULT)
Would generate python __init__.py
files to define each namespace as a python module but
would not generate any additional headers for C++.
The :class:`~nunavut.jinja.Generator` will use the same template name resolution logic as used
for pydsdl data types. For namespaces this will resolve first to a template named
Namespace.j2
and then, if not found, Any.j2
.
Nunavut reserves all global identifiers that start with _nv_ as private internal globals.
This section will contain more information as the project matures about the build-in language support for generating code. Nunavut is both a framework that allows users to write their own dsdl transformation templates but also works, out-of-the-box, as a transplier for C, and C++. More languages may be added in the future.
Note
C++ support is currently experimental. You can only use this by setting the --experimental-languages
flag
when invoking nnvg.
Note
Documentation is provided in the generated source.
By default, the C structures generated will utilize C arrays sized by the maximum size of a
variable-length array. To override this behavior you can pre-define the _ARRAY_CAPACITY_
for individual
fields. For example:
#include <stdio.h> #define reg_drone_service_battery_Status_0_2_cell_voltages_ARRAY_CAPACITY_ 6 #include "inc/UAVCAN/reg/drone/service/battery/Status_0_2.h" int main(int argc, char *argv[]) { reg_drone_service_battery_Status_0_2 msg; printf("Size of reg_drone_service_battery_Status_0_2 %li\n", sizeof(msg)); }