Add user dynamic array field support
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Wed, 9 Sep 2020 20:50:51 +0000 (16:50 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Thu, 10 Sep 2020 01:30:17 +0000 (21:30 -0400)
This patch adds support for user dynamic array fields.

The element field type of a user dynamic array field type can be any of
the following:

* Bit array field type.
* String field type.
* Static array field type.

Note that it _cannot_ be another dynamic array field type.

The new `barectf.DynamicArrayFieldType` represents a dynamic array field
type. Its constructor accepts a length field type. As of this version,
this length field type must be within the same immediate structure field
type and before it.

The YAML ways to specify a dynamic array field type are:

barectf 2 configuration:
    class: array
    length: dynamic
    element-type:
      ...

barectf 3 configuration:
    class: dynamic-array
    element-field-type:
      ...

Note that in YAML, you don't specify the length field type: the parser
automatically creates a 32-bit, byte-aligned unsigned integer field type
before which, for a dynamic array field type named `a`, has the name
`__a_len`. This also becomes part of the corresponding tracing function
parameter's name.

In the future, I can add a `length-field-type` property to a barectf 3
YAML dynamic array field type to point to an anterior unsigned integer
field type using some kind of reference, for example:

    class: structure
    members:
      - my_length: uint16
      - my_array:
          field-type:
            class: dynamic-array
            length-field-type-name: my_length
            element-field-type:
              ...

This would make it possible for more than one dynamic array fields
to use the same length field, for example:

    class: structure
    members:
      - my_length: uint16
      - my_uint_array:
          field-type:
            class: dynamic-array
            length-field-type-name: my_length
            element-field-type: uint8
      - my_string_array:
          field-type:
            class: dynamic-array
            length-field-type-name: my_length
            element-field-type: string

The constructor of `barectf.StructureFieldType` calls
_set_dyn_array_ft_length_ft_member_names() which, for each member having
a dynamic array field type:

* Sets its `_length_ft_member_name` attribute to the name of the
  structure field type member having its length field type.

* Sets its length field type's `_is_len` attribute to `True`.

I consider those as hacks, but considering the current constraints, it
makes parts of the (barectf) code easier to implement and maintain.

The C code generation approach is similar to the static array field
case. The `*-write-static-array-statements.j2` templates are renamed to
`*-write-array-statements.j2` and use the `length_src` variable as the
loop's length's value. `*-write-static-array-statements.j2` and the new
`*-write-dynamic-array-statements.j2` set `length_src` before including
`*-write-array-statements.j2`.

To make things easier, barectf now systematically generates alignment
statements if the alignment is greater than one. This could be optimized
again in the future, considering arrays this time. The
`_WriteOp.offset_in_byte` optimization still exists, although as soon as
it's not statically known, it's now `None` and
`serialize-write-bit-array-statements.j2` uses the safe, dynamic
`ctx->at % 8` expression. try_create_align_op() does this, more or less:

    If `self._offset_in_byte` is not currently known and the requested
    alignment is 8:
      Set `self._offset_in_byte` to 0.
    Else:
      If we're currently within an array operation:
        Reset `self._offset_in_byte`.
      Else:
        If `self._offset_in_byte` is currently known:
          Align `self._offset_in_byte` with the requested alignment.

This ensures that each array field's element is aligned before being
written.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
19 files changed:
barectf/__init__.py
barectf/cgen.py
barectf/config.py
barectf/config_parse_v2.py
barectf/config_parse_v3.py
barectf/schemas/config/2/config.yaml
barectf/schemas/config/2/field-type.yaml
barectf/schemas/config/3/field-type.yaml
barectf/templates/c/barectf.c-macros.j2
barectf/templates/c/common.j2
barectf/templates/c/serialize-write-array-statements.j2 [new file with mode: 0644]
barectf/templates/c/serialize-write-bit-array-statements.j2
barectf/templates/c/serialize-write-dynamic-array-statements.j2 [new file with mode: 0644]
barectf/templates/c/serialize-write-static-array-statements.j2
barectf/templates/c/size-write-array-statements.j2 [new file with mode: 0644]
barectf/templates/c/size-write-dynamic-array-statements.j2 [new file with mode: 0644]
barectf/templates/c/size-write-static-array-statements.j2
barectf/templates/metadata/struct-ft.j2
barectf/tsdl182gen.py

index b33f04e66026dff1508234dc4d0628a2d425f4af..b47d71c779a71a3c341c4531fb25c6f841b286c1 100644 (file)
@@ -61,6 +61,7 @@ ConfigurationCodeGenerationOptions = barectf_config.ConfigurationCodeGenerationO
 ConfigurationOptions = barectf_config.ConfigurationOptions
 DEFAULT_FIELD_TYPE = barectf_config.DEFAULT_FIELD_TYPE
 DisplayBase = barectf_config.DisplayBase
+DynamicArrayFieldType = barectf_config.DynamicArrayFieldType
 EnumerationFieldTypeMapping = barectf_config.EnumerationFieldTypeMapping
 EnumerationFieldTypeMappingRange = barectf_config.EnumerationFieldTypeMappingRange
 EnumerationFieldTypeMappings = barectf_config.EnumerationFieldTypeMappings
index f634b1282837460832088f2d8eebe69d17f455bd..1b0b124ab83d81d09dc5d8cd401b320daad1e6cf 100644 (file)
@@ -106,22 +106,14 @@ class _CompoundOp(_Op):
 
 # Leaf operation (abstract class).
 class _LeafOp(_Op):
-    def __init__(self, offset_in_byte: Count, ft: barectf_config._FieldType, names: List[str],
-                 level: Count, templates: _OpTemplates):
-        super().__init__(ft, names, level, templates)
-        assert offset_in_byte >= 0 and offset_in_byte < 8
-        self._offset_in_byte = offset_in_byte
-
-    @property
-    def offset_in_byte(self) -> Count:
-        return self._offset_in_byte
+    pass
 
 
 # An "align" operation.
 class _AlignOp(_LeafOp):
-    def __init__(self, offset_in_byte: Count, ft: barectf_config._FieldType, names: List[str],
-                 level: Count, templates: _OpTemplates, value: Alignment):
-        super().__init__(offset_in_byte, ft, names, level, templates)
+    def __init__(self, ft: barectf_config._FieldType, names: List[str], level: Count,
+                 templates: _OpTemplates, value: Alignment):
+        super().__init__(ft, names, level, templates)
         self._value = value
 
     @property
@@ -131,7 +123,15 @@ class _AlignOp(_LeafOp):
 
 # A "write" operation.
 class _WriteOp(_LeafOp):
-    pass
+    def __init__(self, ft: barectf_config._FieldType, names: List[str], level: Count,
+                 templates: _OpTemplates, offset_in_byte: Optional[Count]):
+        super().__init__(ft, names, level, templates)
+        assert offset_in_byte is None or (offset_in_byte >= 0 and offset_in_byte < 8)
+        self._offset_in_byte = offset_in_byte
+
+    @property
+    def offset_in_byte(self) -> Optional[Count]:
+        return self._offset_in_byte
 
 
 _SpecSerializeWriteTemplates = Mapping[str, barectf_template._Template]
@@ -147,13 +147,16 @@ _SpecSerializeWriteTemplates = Mapping[str, barectf_template._Template]
 # and return it.
 class _OpBuilder:
     def __init__(self, cg: '_CodeGen'):
-        self._last_alignment: Optional[Alignment] = None
-        self._last_bit_array_size: Optional[Count] = None
         self._names: List[str] = []
         self._level = Count(0)
-        self._offset_in_byte = Count(0)
+        self._offset_in_byte: Optional[Count] = None
         self._cg = cg
 
+    # Whether or not we're within an array operation.
+    @property
+    def _in_array(self):
+        return self._level > 0
+
     # Creates and returns an operation for the root structure field type
     # `ft` named `name`.
     #
@@ -192,7 +195,7 @@ class _OpBuilder:
             assert type(ft) is not barectf_config.StructureFieldType
             offset_in_byte = self._offset_in_byte
 
-            if isinstance(ft, barectf_config._BitArrayFieldType):
+            if isinstance(ft, barectf_config._BitArrayFieldType) and self._offset_in_byte is not None:
                 self._offset_in_byte = Count((self._offset_in_byte + ft.size) % 8)
 
             serialize_write_templ: Optional[barectf_template._Template] = None
@@ -216,35 +219,33 @@ class _OpBuilder:
             elif type(ft) is barectf_config.StringFieldType:
                 size_write_templ = self._cg._size_write_string_statements_templ
 
-            return _WriteOp(offset_in_byte, ft, self._names, self._level,
-                            _OpTemplates(serialize_write_templ, size_write_templ))
+            return _WriteOp(ft, self._names, self._level,
+                            _OpTemplates(serialize_write_templ, size_write_templ), offset_in_byte)
 
         # Creates and returns an "align" operation for the field type
         # `ft` if needed.
         #
         # This function updates the builder's state.
-        def try_create_align_op(alignment: Alignment, do_align: bool,
-                                ft: barectf_config._FieldType) -> Optional[_AlignOp]:
+        def try_create_align_op(alignment: Alignment, ft: barectf_config._FieldType) -> Optional[_AlignOp]:
             def align(v: Count, alignment: Alignment) -> Count:
                 return Count((v + (alignment - 1)) & -alignment)
 
-            offset_in_byte = self._offset_in_byte
-            self._offset_in_byte = Count(align(self._offset_in_byte, alignment) % 8)
+            if self._offset_in_byte is None and alignment % 8 == 0:
+                self._offset_in_byte = Count(0)
+            else:
+                if self._in_array:
+                    self._offset_in_byte = None
+                elif self._offset_in_byte is not None:
+                    self._offset_in_byte = Count(align(self._offset_in_byte, alignment) % 8)
 
-            if do_align and alignment > 1:
-                return _AlignOp(offset_in_byte, ft, self._names, self._level,
+            if alignment > 1:
+                return _AlignOp(ft, self._names, self._level,
                                 _OpTemplates(self._cg._serialize_align_statements_templ,
                                              self._cg._size_align_statements_templ),
                                 alignment)
 
             return None
 
-        # Returns whether or not, considering the alignment requirement
-        # `align_req` and the builder's current state, we must create
-        # and append an "align" operation.
-        def must_align(align_req: Alignment) -> bool:
-            return self._last_alignment != align_req or typing.cast(Count, self._last_bit_array_size) % align_req != 0
-
         # Returns whether or not `ft` is a compound field type.
         def ft_is_compound(ft: barectf_config._FieldType) -> bool:
             return isinstance(ft, (barectf_config.StructureFieldType, barectf_config.StaticArrayFieldType))
@@ -255,39 +256,32 @@ class _OpBuilder:
         # operations to return
         ops: List[_Op] = []
 
-        is_uuid_ft = type(ft) is barectf_config.StaticArrayFieldType and self._names == [_RootFtPrefixes.PH, 'uuid']
-
-        if type(ft) is barectf_config.StringFieldType or is_uuid_ft:
+        if type(ft) is barectf_config.StringFieldType or self._names == [_RootFtPrefixes.PH, 'uuid']:
             # strings and UUID array are always byte-aligned
-            do_align = must_align(Alignment(8))
-            self._last_alignment = Alignment(8)
-            self._last_bit_array_size = Count(8)
-            op = try_create_align_op(Alignment(8), do_align, ft)
+            op = try_create_align_op(Alignment(8), ft)
 
             if op is not None:
                 ops.append(op)
 
             ops.append(create_write_op(ft))
         else:
-            do_align = must_align(ft.alignment)
-            self._last_alignment = ft.alignment
-
             if ft_is_compound(ft):
-                # reset last bit array size
-                self._last_bit_array_size = typing.cast(Count, ft.alignment)
-            else:
-                assert isinstance(ft, barectf_config._BitArrayFieldType)
-                ft = typing.cast(barectf_config._BitArrayFieldType, ft)
-                self._last_bit_array_size = ft.size
+                self._offset_in_byte = None
 
-            init_align_op = try_create_align_op(ft.alignment, do_align, ft)
+            init_align_op = try_create_align_op(ft.alignment, ft)
             subops: List[_Op] = []
 
             if type(ft) is barectf_config.StructureFieldType:
                 ft = typing.cast(barectf_config.StructureFieldType, ft)
 
                 if init_align_op is not None:
-                    # append structure field's alignment as a suboperation
+                    # Append structure field's alignment as a
+                    # suboperation.
+                    #
+                    # This is not strictly needed (could be appended to
+                    # `ops`), but the properties of `_StreamOps` and
+                    # `_EvOps` offer a single (structure field type)
+                    # operation.
                     subops.append(init_align_op)
 
                 # append suboperations for each member
@@ -300,12 +294,12 @@ class _OpBuilder:
                                        _OpTemplates(self._cg._serialize_write_struct_statements_templ,
                                                     self._cg._size_write_struct_statements_templ),
                                        subops))
-            elif type(ft) is barectf_config.StaticArrayFieldType:
-                ft = typing.cast(barectf_config.StaticArrayFieldType, ft)
+            elif isinstance(ft, barectf_config._ArrayFieldType):
+                ft = typing.cast(barectf_config._ArrayFieldType, ft)
+                assert ft.alignment == 1 or init_align_op is not None
 
                 if init_align_op is not None:
-                    # append static array field's alignment as a suboperation
-                    subops.append(init_align_op)
+                    ops.append(init_align_op)
 
                 # append element's suboperations
                 self._level = Count(self._level + 1)
@@ -314,11 +308,17 @@ class _OpBuilder:
                                              spec_serialize_write_templates)
                 self._level = Count(self._level - 1)
 
-                # create static array field's compound operation
-                ops.append(_CompoundOp(ft, self._names, self._level,
-                                       _OpTemplates(self._cg._serialize_write_static_array_statements_templ,
-                                                    self._cg._size_write_static_array_statements_templ),
-                                       subops))
+                # select the right templates
+                if type(ft) is barectf_config.StaticArrayFieldType:
+                    templates = _OpTemplates(self._cg._serialize_write_static_array_statements_templ,
+                                             self._cg._size_write_static_array_statements_templ)
+                else:
+                    assert type(ft) is barectf_config.DynamicArrayFieldType
+                    templates = _OpTemplates(self._cg._serialize_write_dynamic_array_statements_templ,
+                                             self._cg._size_write_dynamic_array_statements_templ)
+
+                # create array field's compound operation
+                ops.append(_CompoundOp(ft, self._names, self._level, templates, subops))
             else:
                 # leaf field: align + write
                 if init_align_op is not None:
@@ -504,6 +504,7 @@ class _CodeGen:
         self._serialize_write_string_statements_templ = self._create_template('serialize-write-string-statements.j2')
         self._serialize_write_struct_statements_templ = self._create_template('serialize-write-struct-statements.j2')
         self._serialize_write_static_array_statements_templ = self._create_template('serialize-write-static-array-statements.j2')
+        self._serialize_write_dynamic_array_statements_templ = self._create_template('serialize-write-dynamic-array-statements.j2')
         self._serialize_write_magic_statements_templ = self._create_template('serialize-write-magic-statements.j2')
         self._serialize_write_uuid_statements_templ = self._create_template('serialize-write-uuid-statements.j2')
         self._serialize_write_stream_type_id_statements_templ = self._create_template('serialize-write-stream-type-id-statements.j2')
@@ -516,6 +517,7 @@ class _CodeGen:
         self._size_write_string_statements_templ = self._create_template('size-write-string-statements.j2')
         self._size_write_struct_statements_templ = self._create_template('size-write-struct-statements.j2')
         self._size_write_static_array_statements_templ = self._create_template('size-write-static-array-statements.j2')
+        self._size_write_dynamic_array_statements_templ = self._create_template('size-write-dynamic-array-statements.j2')
 
     # Creates and returns a template named `name` which is a file
     # template if `is_file_template` is `True`.
@@ -591,8 +593,8 @@ class _CodeGen:
         elif type(ft) is barectf_config.StringFieldType:
             return _PointerCType(_ArithCType('char', True), is_const)
         else:
-            assert type(ft) is barectf_config.StaticArrayFieldType
-            ft = typing.cast(barectf_config.StaticArrayFieldType, ft)
+            assert isinstance(ft, barectf_config._ArrayFieldType)
+            ft = typing.cast(barectf_config._ArrayFieldType, ft)
             return _PointerCType(self._ft_c_type(ft.element_field_type, True), is_const)
 
     # Returns the function prototype parameters for the members of the
@@ -616,7 +618,13 @@ class _CodeGen:
             if member_name in exclude_set:
                 continue
 
-            if only_dyn and not member.field_type.size_is_dynamic:
+            is_dyn = member.field_type.size_is_dynamic
+
+            if isinstance(member.field_type, barectf_config.UnsignedIntegerFieldType):
+                ft = typing.cast(barectf_config.UnsignedIntegerFieldType, member.field_type)
+                is_dyn = is_dyn or ft._is_len
+
+            if only_dyn and not is_dyn:
                 continue
 
             params.append(_FtParam(member.field_type, member_name))
index 0d6417cdfaee3f35344a42151e222ad94835ef37..30fdf5a03881794dda7f457c45029419f23a4b4b 100644 (file)
@@ -222,6 +222,21 @@ class StaticArrayFieldType(_ArrayFieldType):
         return self._length
 
 
+class DynamicArrayFieldType(_ArrayFieldType):
+    def __init__(self, length_field_type: UnsignedIntegerFieldType, element_field_type: _FieldType):
+        super().__init__(element_field_type)
+        self._length_field_type = length_field_type
+        self._length_ft_member_name = None
+
+    @property
+    def length_field_type(self):
+        return self._length_field_type
+
+    @property
+    def size_is_dynamic(self):
+        return True
+
+
 class StructureFieldTypeMember:
     def __init__(self, field_type: _FieldType):
         self._field_type = field_type
@@ -262,6 +277,7 @@ class StructureFieldType(_FieldType):
             self._members = StructureFieldTypeMembers(members)
 
         self._set_alignment()
+        self._set_dyn_array_ft_length_ft_member_names()
 
     def _set_alignment(self):
         self._alignment: Alignment = self._minimum_alignment
@@ -270,6 +286,20 @@ class StructureFieldType(_FieldType):
             if member.field_type.alignment > self._alignment:
                 self._alignment = member.field_type.alignment
 
+    def _set_dyn_array_ft_length_ft_member_names(self):
+        for member in self._members.values():
+            if type(member.field_type) is DynamicArrayFieldType:
+                # Find length field type member name within the same
+                # structure field type members.
+                for len_name, len_member in self._members.items():
+                    if member.field_type.length_field_type is len_member.field_type:
+                        member.field_type._length_ft_member_name = len_name
+                        len_member.field_type._is_len = True
+                        break
+
+            if member.field_type.alignment > self._alignment:
+                self._alignment = member.field_type.alignment
+
     @property
     def minimum_alignment(self) -> Alignment:
         return self._minimum_alignment
index 59f9733e48be0d743a8d97272213fb6908e8bd41..668f0b9478ee599e662ab01e9d6a7304ce3368f7 100644 (file)
@@ -80,7 +80,7 @@ class _Parser(config_parse_common._Parser):
             'floating-point': self._conv_real_ft_node,
             'str': self._conv_string_ft_node,
             'string': self._conv_string_ft_node,
-            'array': self._conv_static_array_ft_node,
+            'array': self._conv_array_ft_node,
             'struct': self._conv_struct_ft_node,
             'structure': self._conv_struct_ft_node,
         }
@@ -222,12 +222,15 @@ class _Parser(config_parse_common._Parser):
 
     # Converts a v2 array field type node to a v3 (static) array field
     # type node and returns it.
-    def _conv_static_array_ft_node(self, v2_ft_node: _MapNode) -> _MapNode:
-        # class renamed to `static-array`
-        v3_ft_node: _MapNode = collections.OrderedDict({'class': 'static-array'})
-
-        # copy `length` property
-        _copy_prop_if_exists(v3_ft_node, v2_ft_node, 'length')
+    def _conv_array_ft_node(self, v2_ft_node: _MapNode) -> _MapNode:
+        # class renamed to `static-array` or `dynamic-array`
+        is_dynamic = v2_ft_node['length'] == 'dynamic'
+        array_type = 'dynamic' if is_dynamic else 'static'
+        v3_ft_node: _MapNode = collections.OrderedDict({'class': f'{array_type}-array'})
+
+        # copy `length` property if it's a static array field type
+        if not is_dynamic:
+            _copy_prop_if_exists(v3_ft_node, v2_ft_node, 'length')
 
         # convert element field type
         v3_ft_node['element-field-type'] = self._conv_ft_node(v2_ft_node['element-type'])
index 04cb23c445d00e94dc5a834d60d14105460f706c..99204448fa526e061d6cb8af11c0658d34a6b171 100644 (file)
@@ -26,7 +26,7 @@ from barectf.config_parse_common import _ConfigurationParseError
 from barectf.config_parse_common import _append_error_ctx
 from barectf.config_parse_common import _MapNode
 import barectf.config as barectf_config
-from barectf.config import _OptFt, _OptStructFt
+from barectf.config import _OptStructFt
 import collections
 import uuid
 from barectf.typing import Count, Alignment, VersionNumber
@@ -52,7 +52,8 @@ class _Parser(barectf_config_parse_common._Parser):
                  ignore_include_not_found: bool):
         super().__init__(root_file, node, with_pkg_include_dir, inclusion_dirs,
                          ignore_include_not_found, VersionNumber(3))
-        self._ft_cls_name_to_create_method: Dict[str, Callable[[_MapNode], barectf_config._FieldType]] = {
+        self._ft_cls_name_to_create_method: Dict[str, Callable[[_MapNode],
+                                                               List[barectf_config._FieldType]]] = {
             'unsigned-integer': self._create_int_ft,
             'signed-integer': self._create_int_ft,
             'unsigned-enumeration': self._create_enum_ft,
@@ -60,6 +61,7 @@ class _Parser(barectf_config_parse_common._Parser):
             'real': self._create_real_ft,
             'string': self._create_string_ft,
             'static-array': self._create_static_array_ft,
+            'dynamic-array': self._create_dynamic_array_ft,
             'structure': self._create_struct_ft,
         }
         self._parse()
@@ -155,16 +157,16 @@ class _Parser(barectf_config_parse_common._Parser):
 
     # Creates an integer field type from the unsigned/signed integer
     # field type node `ft_node`.
-    def _create_int_ft(self, ft_node: _MapNode) -> barectf_config._IntegerFieldType:
+    def _create_int_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]:
         ft_type = {
             'unsigned-integer': barectf_config.UnsignedIntegerFieldType,
             'signed-integer': barectf_config.SignedIntegerFieldType,
         }[ft_node['class']]
-        return self._create_common_int_ft(ft_node, ft_type)
+        return [self._create_common_int_ft(ft_node, ft_type)]
 
     # Creates an enumeration field type from the unsigned/signed
     # enumeration field type node `ft_node`.
-    def _create_enum_ft(self, ft_node: _MapNode) -> barectf_config._EnumerationFieldType:
+    def _create_enum_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]:
         ft_type = {
             'unsigned-enumeration': barectf_config.UnsignedEnumerationFieldType,
             'signed-enumeration': barectf_config.SignedEnumerationFieldType,
@@ -185,32 +187,54 @@ class _Parser(barectf_config_parse_common._Parser):
 
             mappings[label] = barectf_config.EnumerationFieldTypeMapping(ranges)
 
-        return typing.cast(barectf_config._EnumerationFieldType,
-                           self._create_common_int_ft(ft_node, ft_type,
-                                                      barectf_config.EnumerationFieldTypeMappings(mappings)))
+        return [typing.cast(barectf_config._EnumerationFieldType,
+                            self._create_common_int_ft(ft_node, ft_type,
+                                                       barectf_config.EnumerationFieldTypeMappings(mappings)))]
 
     # Creates a real field type from the real field type node `ft_node`.
-    def _create_real_ft(self, ft_node: _MapNode) -> barectf_config.RealFieldType:
-        return typing.cast(barectf_config.RealFieldType,
-                           self._create_common_bit_array_ft(ft_node, barectf_config.RealFieldType,
-                                                            Alignment(8)))
+    def _create_real_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]:
+        return [typing.cast(barectf_config.RealFieldType,
+                            self._create_common_bit_array_ft(ft_node, barectf_config.RealFieldType,
+                                                             Alignment(8)))]
 
     # Creates a string field type from the string field type node
     # `ft_node`.
-    def _create_string_ft(self, ft_node: _MapNode) -> barectf_config.StringFieldType:
-        return barectf_config.StringFieldType()
+    def _create_string_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]:
+        return [barectf_config.StringFieldType()]
 
-    # Creates a static array field type from the static array field type
-    # node `ft_node`.
-    def _create_static_array_ft(self, ft_node: _MapNode) -> barectf_config.StaticArrayFieldType:
+    def _create_array_ft(self, ft_type, ft_node: _MapNode, **kwargs) -> barectf_config._ArrayFieldType:
         prop_name = 'element-field-type'
 
         try:
-            element_ft = self._create_ft(ft_node[prop_name])
+            element_fts = self._create_fts(ft_node[prop_name])
         except _ConfigurationParseError as exc:
             _append_error_ctx(exc, f'`{prop_name}` property')
 
-        return barectf_config.StaticArrayFieldType(ft_node['length'], element_ft)
+        if len(element_fts) != 1 or isinstance(element_fts[0], (barectf_config.StructureFieldType,
+                                                                barectf_config.DynamicArrayFieldType)):
+            raise _ConfigurationParseError(f'`{prop_name}` property',
+                                           'Nested structure and dynamic array field types are not supported')
+
+        return ft_type(element_field_type=element_fts[0], **kwargs)
+
+    # Creates a static array field type from the static array field type
+    # node `ft_node`.
+    def _create_static_array_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]:
+        return [typing.cast(barectf_config.StaticArrayFieldType,
+                            self._create_array_ft(barectf_config.StaticArrayFieldType, ft_node,
+                                                  length=ft_node['length']))]
+
+    # Creates a dynamic array field type from the dynamic array field
+    # type node `ft_node`.
+    def _create_dynamic_array_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]:
+        # create length unsigned integer field type
+        len_ft = barectf_config.UnsignedIntegerFieldType(32, alignment=Alignment(8))
+        return [
+            len_ft,
+            typing.cast(barectf_config.DynamicArrayFieldType,
+                        self._create_array_ft(barectf_config.DynamicArrayFieldType, ft_node,
+                                              length_field_type=len_ft))
+        ]
 
     # Creates structure field type members from the structure field type
     # members node `members_node`.
@@ -240,19 +264,29 @@ class _Parser(barectf_config_parse_common._Parser):
                                                    'Nested structure field types are not supported')
 
                 try:
-                    member_ft = self._create_ft(ft_node)
+                    member_fts = self._create_fts(ft_node)
                 except _ConfigurationParseError as exc:
-                    exc._append_ctx(f'`{ft_prop_name}` property')
+                    _append_error_ctx(exc, f'`{ft_prop_name}` property')
             except _ConfigurationParseError as exc:
                 _append_error_ctx(exc, f'Structure field type member `{member_name}`')
 
-            members[member_name] = barectf_config.StructureFieldTypeMember(member_ft)
+            if len(member_fts) == 2:
+                # The only case where this happens is a dynamic array
+                # field type node which generates an unsigned integer
+                # field type for the length and the dynamic array field
+                # type itself.
+                assert type(member_fts[1]) is barectf_config.DynamicArrayFieldType
+                members[f'__{member_name}_len'] = barectf_config.StructureFieldTypeMember(member_fts[0])
+            else:
+                assert len(member_fts) == 1
+
+            members[member_name] = barectf_config.StructureFieldTypeMember(member_fts[-1])
 
         return barectf_config.StructureFieldTypeMembers(members)
 
     # Creates a structure field type from the structure field type node
     # `ft_node`.
-    def _create_struct_ft(self, ft_node: _MapNode) -> barectf_config.StructureFieldType:
+    def _create_struct_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]:
         minimum_alignment = self._alignment_prop(ft_node, 'minimum-alignment')
 
         if minimum_alignment is None:
@@ -265,32 +299,36 @@ class _Parser(barectf_config_parse_common._Parser):
         if members_node is not None:
             members = self._create_struct_ft_members(members_node, prop_name)
 
-        return barectf_config.StructureFieldType(minimum_alignment, members)
+        return [barectf_config.StructureFieldType(minimum_alignment, members)]
 
-    # Creates a field type from the field type node `ft_node`.
-    def _create_ft(self, ft_node: _MapNode) -> barectf_config._FieldType:
+    # Creates field types from the field type node `ft_node`.
+    def _create_fts(self, ft_node: _MapNode) -> List[barectf_config._FieldType]:
         return self._ft_cls_name_to_create_method[ft_node['class']](ft_node)
 
-    # Creates a field type from the field type node `parent_node[key]`
+    # Creates field types from the field type node `parent_node[key]`
     # if it exists.
-    def _try_create_ft(self, parent_node: _MapNode, key: str) -> _OptFt:
+    def _try_create_fts(self, parent_node: _MapNode, key: str) -> Optional[List[barectf_config._FieldType]]:
         if key not in parent_node:
             return None
 
         try:
-            return self._create_ft(parent_node[key])
+            return self._create_fts(parent_node[key])
         except _ConfigurationParseError as exc:
             _append_error_ctx(exc, f'`{key}` property')
 
             # satisfy static type checker (never reached)
             raise
 
-    # Like _try_create_ft(), but casts the result's type to
-    # `barectf_config.StructureFieldType` to satisfy static type
-    # checkers.
+    # Like _try_create_fts(), but casts the result's type (first and
+    # only element) to `barectf_config.StructureFieldType` to satisfy
+    # static type checkers.
     def _try_create_struct_ft(self, parent_node: _MapNode, key: str) -> _OptStructFt:
-        return typing.cast(barectf_config.StructureFieldType,
-                           self._try_create_ft(parent_node, key))
+        fts = self._try_create_fts(parent_node, key)
+
+        if fts is None:
+            return None
+
+        return typing.cast(barectf_config.StructureFieldType, fts[0])
 
     # Returns the total number of members in the structure field type
     # node `ft_node` if it exists, otherwise 0.
@@ -372,7 +410,7 @@ class _Parser(barectf_config_parse_common._Parser):
             return None
 
         assert type(ft_node) is collections.OrderedDict
-        return self._create_ft(ft_node)
+        return self._create_fts(ft_node)[0]
 
     def _create_stream_type(self, name: str, stream_type_node: _MapNode) -> barectf_config.StreamType:
         try:
index 55f8dc0acb2a7fe927ea47e514fa8f98f9563177..e7522b641f4bc1b5bc2fff27d057db521f240e2c 100644 (file)
@@ -66,7 +66,7 @@ definitions:
                           const: 32
                 uuid:
                   allOf:
-                    - $ref: https://barectf.org/schemas/config/2/field-type.json#/definitions/static-array-ft
+                    - $ref: https://barectf.org/schemas/config/2/field-type.json#/definitions/array-ft
                     - properties:
                         length:
                           const: 16
index d34e558963b89ef5403e6e645c52ae6135ab9aa0..2df28850f82aaa22e4c3a4a2e15d40bc43c51cfe 100644 (file)
@@ -198,20 +198,25 @@ definitions:
     required:
       - class
     additionalProperties: false
-  static-array-ft-class-prop:
+  array-ft-class-prop:
     type: string
     const: array
-  static-array-ft:
+  array-ft:
     title: Static array field type object
     type: object
     properties:
       class:
-        $ref: '#/definitions/static-array-ft-class-prop'
+        $ref: '#/definitions/array-ft-class-prop'
       element-type:
         $ref: '#/definitions/ft'
       length:
-        type: integer
-        minimum: 0
+        if:
+          type: integer
+        then:
+          minimum: 0
+        else:
+          type: string
+          const: dynamic
     required:
       - class
       - element-type
@@ -298,9 +303,9 @@ definitions:
       - if:
           properties:
             class:
-              $ref: '#/definitions/static-array-ft-class-prop'
+              $ref: '#/definitions/array-ft-class-prop'
         then:
-          $ref: '#/definitions/static-array-ft'
+          $ref: '#/definitions/array-ft'
       - if:
           properties:
             class:
index 4ab690d4d5661a3b8a57402f088f4b78f4c6b736..92c550219135aed58077daa58d882971c98a5d23 100644 (file)
@@ -180,29 +180,47 @@ definitions:
     properties:
       class: true
     additionalProperties: false
+  array-ft:
+    title: Array field type object
+    allOf:
+      - $ref: '#/definitions/ft-base'
+      - properties:
+          element-field-type:
+            $ref: '#/definitions/ft'
+        required:
+          - element-field-type
   static-array-ft-class-prop:
     type: string
     const: static-array
   static-array-ft:
     title: Static array field type object
     allOf:
-      - $ref: '#/definitions/ft-base'
+      - $ref: '#/definitions/array-ft'
       - properties:
           class:
             $ref: '#/definitions/static-array-ft-class-prop'
-          element-field-type:
-            $ref: '#/definitions/ft'
           length:
             type: integer
             minimum: 0
-        required:
-          - element-field-type
-          - length
     properties:
       class: true
       element-field-type: true
       length: true
     additionalProperties: false
+  dynamic-array-ft-class-prop:
+    type: string
+    const: dynamic-array
+  dynamic-array-ft-class-prop:
+    title: Dynamic array field type object
+    allOf:
+      - $ref: '#/definitions/array-ft'
+      - properties:
+          class:
+            $ref: '#/definitions/dynamic-array-ft-class-prop'
+    properties:
+      class: true
+      element-field-type: true
+    additionalProperties: false
   struct-ft-class-prop:
     type: string
     enum:
@@ -278,6 +296,7 @@ definitions:
               - str
               - string
               - static-array
+              - dynamic-array
               - struct
               - structure
       - if:
@@ -322,6 +341,12 @@ definitions:
               $ref: '#/definitions/static-array-ft-class-prop'
         then:
           $ref: '#/definitions/static-array-ft'
+      - if:
+          properties:
+            class:
+              $ref: '#/definitions/dynamic-array-ft-class-prop'
+        then:
+          $ref: '#/definitions/dynamic-array-ft'
       - if:
           properties:
             class:
index 4af19562a721753cacd398989d3d5d2e2544a3be..ca92b64949846743e366e394892d63639c533661 100644 (file)
@@ -58,7 +58,7 @@ const int saved_in_tracing_section = ctx->in_tracing_section;
 {% macro ft_call_params(param_prefix, ft, only_dyn=false) %}
 {% if ft %}
        {% for member_name, member in ft.members.items() %}
-               {% if not only_dyn or member.field_type.size_is_dynamic %}
+               {% if not only_dyn or member.field_type.size_is_dynamic or member.field_type._is_len %}
 , {{ param_prefix }}_{{ member_name }}
                {%- endif %}
        {% endfor %}
index 72d47c8070308762a7d70a4483a1c411b74aa1b8..c22292e0fa65491c1c94a2edb859e5e3e26a73fc 100644 (file)
 {% macro const_str(is_const) %}
 {{ 'const ' if is_const else '' }}
 {%- endmacro %}
+
+{#
+ # Generates the length variable name of the dynamic field type
+ # operation `op`.
+ #}
+{% macro dyn_array_ft_op_len_var_name(op) %}
+{{ op.names[0] }}_{{ op.ft._length_ft_member_name }}
+{%- endmacro %}
diff --git a/barectf/templates/c/serialize-write-array-statements.j2 b/barectf/templates/c/serialize-write-array-statements.j2
new file mode 100644 (file)
index 0000000..c12c040
--- /dev/null
@@ -0,0 +1,38 @@
+{#
+ # The MIT License (MIT)
+ #
+ # Copyright (c) 2020 Philippe Proulx <pproulx@efficios.com>
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining
+ # a copy of this software and associated documentation files (the
+ # "Software"), to deal in the Software without restriction, including
+ # without limitation the rights to use, copy, modify, merge, publish,
+ # distribute, sublicense, and/or sell copies of the Software, and to
+ # permit persons to whom the Software is furnished to do so, subject to
+ # the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ #
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ #}
+{% import 'c/common.j2' as c_common %}
+{% set var_name = op.level | loop_var_name %}
+{% include 'c/serialize-write-statements-comment.j2' %}
+
+{
+       uint32_t {{ var_name }};
+
+       for ({{ var_name }} = 0; {{ var_name }} < (uint32_t) {{ length_src }}; ++{{ var_name }}) {
+{% for subop in op.subops %}
+               {{ subop.serialize_str(stream_type=stream_type, ev_type=ev_type) | indent_tab(2) }}
+
+{% endfor %}
+       }
+}
index 9a2f164cdd26f6fd35717a7aaaf571c15c5b054e..bf7105c3f93b1d0e9f1bbd981046a1fb55f453ac 100644 (file)
@@ -35,7 +35,8 @@
 {%- else %}
        {% set bo = 'le' if cfg.target_byte_order == barectf_config.ByteOrder.LITTLE_ENDIAN else 'be' %}
        {% set c_type_non_const = c_type | replace('const ', '') %}
-bt_bitfield_write_{{ bo }}(&ctx->buf[_BITS_TO_BYTES(ctx->at)], {{ op.offset_in_byte }}, {{ op.ft.size }},
+       {% set offset_in_byte = 'ctx->at % 8' if op.offset_in_byte == none else op.offset_in_byte %}
+bt_bitfield_write_{{ bo }}(&ctx->buf[_BITS_TO_BYTES(ctx->at)], {{ offset_in_byte }}, {{ op.ft.size }},
        {{ c_type_non_const }}, ({{ c_type_non_const }}) {{ src }});
 {{ incr_at }};
 {%- endif %}
diff --git a/barectf/templates/c/serialize-write-dynamic-array-statements.j2 b/barectf/templates/c/serialize-write-dynamic-array-statements.j2
new file mode 100644 (file)
index 0000000..49a9d55
--- /dev/null
@@ -0,0 +1,27 @@
+{#
+ # The MIT License (MIT)
+ #
+ # Copyright (c) 2020 Philippe Proulx <pproulx@efficios.com>
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining
+ # a copy of this software and associated documentation files (the
+ # "Software"), to deal in the Software without restriction, including
+ # without limitation the rights to use, copy, modify, merge, publish,
+ # distribute, sublicense, and/or sell copies of the Software, and to
+ # permit persons to whom the Software is furnished to do so, subject to
+ # the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ #
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ #}
+{% import 'c/common.j2' as c_common %}
+{% set length_src = c_common.dyn_array_ft_op_len_var_name(op) %}
+{% include 'c/serialize-write-array-statements.j2' %}
index 6d818e5e9e8f9c52a7a9d32879da96c76ccae262..7cf5051d7fd639c90f7f91665f5df92b1d74ac04 100644 (file)
  # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  #}
-{% import 'c/common.j2' as c_common %}
-{% set var_name = op.level | loop_var_name %}
-{% include 'c/serialize-write-statements-comment.j2' %}
-
-{
-       uint32_t {{ var_name }};
-
-       for ({{ var_name }} = 0; {{ var_name }} < (uint32_t) {{ op.ft.length }}; ++{{ var_name }}) {
-{% for subop in op.subops %}
-               {{ subop.serialize_str(stream_type=stream_type, ev_type=ev_type) | indent_tab(2) }}
-{% endfor %}
-       }
-}
+{% set length_src %}{{ op.ft.length }}U{% endset %}
+{% include 'c/serialize-write-array-statements.j2' %}
diff --git a/barectf/templates/c/size-write-array-statements.j2 b/barectf/templates/c/size-write-array-statements.j2
new file mode 100644 (file)
index 0000000..c4ba116
--- /dev/null
@@ -0,0 +1,38 @@
+{#
+ # The MIT License (MIT)
+ #
+ # Copyright (c) 2020 Philippe Proulx <pproulx@efficios.com>
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining
+ # a copy of this software and associated documentation files (the
+ # "Software"), to deal in the Software without restriction, including
+ # without limitation the rights to use, copy, modify, merge, publish,
+ # distribute, sublicense, and/or sell copies of the Software, and to
+ # permit persons to whom the Software is furnished to do so, subject to
+ # the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ #
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ #}
+{% import 'c/common.j2' as c_common %}
+{% set var_name = op.level | loop_var_name %}
+{% include 'c/serialize-write-statements-comment.j2' %}
+
+{
+       uint32_t {{ var_name }};
+
+       for ({{ var_name }} = 0; {{ var_name }} < (uint32_t) {{ length_src }}; ++{{ var_name }}) {
+{% for subop in op.subops %}
+               {{ subop.size_str(stream_type=stream_type, ev_type=ev_type) | indent_tab(2) }}
+
+{% endfor %}
+       }
+}
diff --git a/barectf/templates/c/size-write-dynamic-array-statements.j2 b/barectf/templates/c/size-write-dynamic-array-statements.j2
new file mode 100644 (file)
index 0000000..96ef970
--- /dev/null
@@ -0,0 +1,27 @@
+{#
+ # The MIT License (MIT)
+ #
+ # Copyright (c) 2020 Philippe Proulx <pproulx@efficios.com>
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining
+ # a copy of this software and associated documentation files (the
+ # "Software"), to deal in the Software without restriction, including
+ # without limitation the rights to use, copy, modify, merge, publish,
+ # distribute, sublicense, and/or sell copies of the Software, and to
+ # permit persons to whom the Software is furnished to do so, subject to
+ # the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ #
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ #}
+{% import 'c/common.j2' as c_common %}
+{% set length_src = c_common.dyn_array_ft_op_len_var_name(op) %}
+{% include 'c/size-write-array-statements.j2' %}
index 40495899791a4b53f2dacaf4a7baf90b727653b5..3b41d9e9b5a23f0bcb57c187cba1f19db6e2d940 100644 (file)
  # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  #}
-{% import 'c/common.j2' as c_common %}
-{% set var_name = op.level | loop_var_name %}
-{% include 'c/serialize-write-statements-comment.j2' %}
-
-{
-       uint32_t {{ var_name }};
-
-       for ({{ var_name }} = 0; {{ var_name }} < (uint32_t) {{ op.ft.length }}; ++{{ var_name }}) {
-{% for subop in op.subops %}
-               {{ subop.size_str(stream_type=stream_type, ev_type=ev_type) | indent_tab(2) }}
-{% endfor %}
-       }
-}
+{% set length_src %}{{ op.ft.length }}U{% endset %}
+{% include 'c/size-write-array-statements.j2' %}
index 50de1df2eeb4a3827f405e00d4c4f51a63b7174f..a2affa14cd4e7d35e34a99ccf8019ee6a99c53c1 100644 (file)
  #}
 struct {
 {% for name, member in ft.members.items() %}
-       {#
-        # `chain` is a list of static array field types terminated with a
-        # non-array field type (the most nested).
-        #}
-       {% set chain = ft_chain(member.field_type) %}
-       {{ chain[-1] | ft_str | indent_tab }} {{ name }}
-       {%- for array_ft in chain[:-1] %}[{{ array_ft.length }}]{% endfor %};
+       {{ member.field_type | deepest_ft | ft_str | indent_tab }} {{ name }}
+       {%- for len in member.field_type | ft_lengths(name) %}[{{ len }}]{% endfor %};
 {% endfor %}
 } align({{ ft.minimum_alignment }})
index 01ee04b5a7a019ad301e10cc174afbc11c061725..e2c81c132b6588cef9ec4b06555b60afef26330f 100644 (file)
@@ -23,7 +23,7 @@
 
 import barectf.config as barectf_config
 import barectf.template as barectf_template
-from typing import List, Optional
+from typing import List, Optional, Union
 import typing
 
 
@@ -53,19 +53,32 @@ def _gen_str_ft(ft: barectf_config._FieldType) -> str:
     return _STR_FT_TEMPL.render(ft=ft)
 
 
-def _ft_chain(ft: barectf_config._FieldType) -> List[barectf_config._FieldType]:
-    chain: List[barectf_config._FieldType] = []
+def _filt_ft_lengths(ft: barectf_config._FieldType, name: str) -> List[Union[str, int]]:
+    lengths: List[Union[str, int]] = []
 
-    while isinstance(ft, barectf_config.StaticArrayFieldType):
-        chain.append(ft)
+    while isinstance(ft, barectf_config._ArrayFieldType):
+        if type(ft) is barectf_config.StaticArrayFieldType:
+            ft = typing.cast(barectf_config.StaticArrayFieldType, ft)
+            lengths.append(ft.length)
+        else:
+            assert type(ft) is barectf_config.DynamicArrayFieldType
+            ft = typing.cast(barectf_config.DynamicArrayFieldType, ft)
+            lengths.append(ft._length_ft_member_name)
+
+        ft = ft.element_field_type
+
+    return lengths
+
+
+def _filt_deepest_ft(ft: barectf_config._FieldType) -> barectf_config._FieldType:
+    while isinstance(ft, barectf_config._ArrayFieldType):
         ft = ft.element_field_type
 
-    chain.append(ft)
-    return chain
+    return ft
 
 
 def _gen_struct_ft(ft: barectf_config._FieldType) -> str:
-    return _STRUCT_FT_TEMPL.render(ft=ft, ft_chain=_ft_chain)
+    return _STRUCT_FT_TEMPL.render(ft=ft)
 
 
 _FT_CLS_TO_GEN_FT_FUNC = {
@@ -87,6 +100,8 @@ _TEMPL_FILTERS = {
     'disp_base_int': _filt_disp_base_int,
     'int_ft_str': _filt_int_ft_str,
     'ft_str': _filt_ft_str,
+    'ft_lengths': _filt_ft_lengths,
+    'deepest_ft': _filt_deepest_ft,
 }
 
 
This page took 0.04134 seconds and 4 git commands to generate.