source: rtems-central/rtemsspec/interface.py @ 82ba161

Last change on this file since 82ba161 was 82ba161, checked in by Sebastian Huber <sebastian.huber@…>, on 02/16/23 at 08:58:04

interface: Fix pylint warning

  • Property mode set to 100644
File size: 35.4 KB
Line 
1# SPDX-License-Identifier: BSD-2-Clause
2""" This module provides functions for the generation of interfaces. """
3
4# Copyright (C) 2020, 2021 embedded brains GmbH (http://www.embedded-brains.de)
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
19# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25# POSSIBILITY OF SUCH DAMAGE.
26
27import collections
28from contextlib import contextmanager
29import functools
30import itertools
31import os
32from typing import Any, Callable, Dict, Iterator, List, NamedTuple, Optional, \
33    Union, Set, Tuple
34
35from rtemsspec.content import CContent, CInclude, enabled_by_to_exp, \
36    ExpressionMapper, forward_declaration, get_value_compound, \
37    get_value_double_colon, get_value_doxygen_function, \
38    get_value_doxygen_group, get_value_doxygen_ref, \
39    get_value_forward_declaration, get_value_hash, get_value_header_file, \
40    get_value_params, get_value_plural, to_camel_case
41from rtemsspec.items import Item, ItemCache, ItemGetValueMap, ItemMapper
42
43ItemMap = Dict[str, Item]
44Lines = Union[str, List[str]]
45GetLines = Callable[["Node", Item, Any], Lines]
46
47
48def _get_ingroups(item: Item) -> ItemMap:
49    ingroups: ItemMap = {}
50    for group in item.parents("interface-ingroup"):
51        ingroups[group.uid] = group
52    return ingroups
53
54
55def _get_group_identifiers(groups: ItemMap) -> List[str]:
56    return [item["identifier"] for item in groups.values()]
57
58
59class _InterfaceMapper(ItemMapper):
60
61    def __init__(self, node: "Node"):
62        super().__init__(node.item)
63        self._node = node
64        self._code_or_doc = "doc"
65        self.add_get_value("interface/struct/code:/name", get_value_compound)
66        self.add_get_value("interface/union/code:/name", get_value_compound)
67        self.add_get_value("glossary/term/doc:/plural", get_value_plural)
68        self.add_get_value("interface/forward-declaration/code:/name",
69                           get_value_forward_declaration)
70        self.add_get_value("interface/forward-declaration/doc:/name",
71                           get_value_forward_declaration)
72        self.add_get_value("interface/function/doc:/name",
73                           get_value_doxygen_function)
74        self.add_get_value("interface/function/doc:/params/name",
75                           get_value_params)
76        self.add_get_value("interface/enumerator/doc:/name",
77                           get_value_double_colon)
78        self.add_get_value("interface/typedef/doc:/name",
79                           get_value_double_colon)
80        self.add_get_value("interface/define/doc:/name", get_value_hash)
81        self.add_get_value("interface/enum/doc:/name", get_value_hash)
82        self.add_get_value("interface/group/doc:/name",
83                           get_value_doxygen_group)
84        self.add_get_value("interface/header-file/doc:/path",
85                           get_value_header_file)
86        self.add_get_value("interface/macro/doc:/name",
87                           get_value_doxygen_function)
88        self.add_get_value("interface/macro/doc:/params/name",
89                           get_value_params)
90        self.add_get_value("interface/variable/doc:/name", get_value_hash)
91        for opt in ["feature-enable", "feature", "initializer", "integer"]:
92            name = f"interface/appl-config-option/{opt}/doc:/name"
93            self.add_get_value(name, get_value_doxygen_ref)
94        self.add_get_value("interface/unspecified-function/doc:/name",
95                           get_value_doxygen_function)
96
97    @contextmanager
98    def code(self) -> Iterator[None]:
99        """ Enables code mapping. """
100        code_or_doc = self._code_or_doc
101        self._code_or_doc = "code"
102        yield
103        self._code_or_doc = code_or_doc
104
105    def get_value_map(self, item: Item) -> ItemGetValueMap:
106        if self._code_or_doc == "code" and item["type"] == "interface":
107            node = self._node
108            header_file = node.header_file
109            if item["interface-type"] == "enumerator":
110                for child in item.children("interface-enumerator"):
111                    header_file.add_includes(child)
112            else:
113                header_file.add_includes(item)
114            header_file.add_dependency(node, item)
115        return self._get_value_map.get(f"{item.type}/{self._code_or_doc}", {})
116
117    def enabled_by_to_defined(self, enabled_by: str) -> str:
118        """
119        Maps an item-level enabled-by attribute value to the corresponding
120        defined expression.
121        """
122        return self._node.header_file.enabled_by_defined[enabled_by]
123
124
125class _InterfaceExpressionMapper(ExpressionMapper):
126
127    def __init__(self, mapper: _InterfaceMapper):
128        super().__init__()
129        self._mapper = mapper
130
131    def map_symbol(self, symbol: str) -> str:
132        with self._mapper.code():
133            return self._mapper.substitute(symbol)
134
135
136class _ItemLevelExpressionMapper(ExpressionMapper):
137
138    def __init__(self, mapper: _InterfaceMapper):
139        super().__init__()
140        self._mapper = mapper
141
142    def map_symbol(self, symbol: str) -> str:
143        with self._mapper.code():
144            return self._mapper.substitute(
145                self._mapper.enabled_by_to_defined(symbol))
146
147
148class _HeaderExpressionMapper(ExpressionMapper):
149
150    def __init__(self, item: Item, enabled_by_defined: Dict[str, str]):
151        super().__init__()
152        self._mapper = ItemMapper(item)
153        self._enabled_by_defined = enabled_by_defined
154
155    def map_symbol(self, symbol: str) -> str:
156        return self._mapper.substitute(self._enabled_by_defined[symbol])
157
158
159def _add_definition(node: "Node", item: Item, prefix: str,
160                    value: Dict[str, Any], get_lines: GetLines) -> CContent:
161    content = CContent()
162    default = value["default"]
163    variants = value["variants"]
164    if variants:
165        ifelse = "#if "
166        for index, variant in enumerate(variants):
167            prefix_2 = os.path.join(prefix, f"variants[{index}]")
168            with node.mapper.prefix(prefix_2):
169                enabled_by = enabled_by_to_exp(
170                    variant["enabled-by"],
171                    _InterfaceExpressionMapper(node.mapper))
172                content.append(f"{ifelse}{enabled_by}")
173            with node.mapper.prefix(os.path.join(prefix_2, "definition")):
174                with content.indent():
175                    content.append(get_lines(node, item,
176                                             variant["definition"]))
177                ifelse = "#elif "
178        if default is not None:
179            content.append("#else")
180            with node.mapper.prefix(os.path.join(prefix, "default")):
181                with content.indent():
182                    content.append(get_lines(node, item, default))
183        content.append("#endif")
184    else:
185        with node.mapper.prefix(os.path.join(prefix, "default")):
186            content.append(get_lines(node, item, default))
187    return content
188
189
190class _RegisterMemberContext(NamedTuple):
191    sizes: Dict[int, int]
192    regs: Dict[str, Any]
193    reg_counts: Dict[str, int]
194    reg_indices: Dict[str, int]
195
196
197def _add_register_padding(content: CContent, new_offset: int, old_offset: int,
198                          default_padding: int) -> None:
199    delta = new_offset - old_offset
200    if delta > 0:
201        padding = default_padding
202        while delta % padding != 0:
203            padding //= 2
204        count = delta // padding
205        array = f"[ {count} ]" if count > 1 else ""
206        content.add(f"uint{padding * 8}_t "
207                    f"reserved_{old_offset:x}_{new_offset:x}{array};")
208
209
210def _get_register_name(definition: Dict[str, Any]) -> Tuple[str, str]:
211    name = definition["name"]
212    try:
213        name, alias = name.split(":")
214    except ValueError:
215        alias = name
216    return name, alias
217
218
219_CONSTRAINT_TARGET = {
220    "interface/function": "this directive",
221    "interface/define": "this constant",
222    "interface/macro": "this directive",
223    "interface/typedef": "functions of this type",
224    "interface/variable": "this object",
225}
226
227
228class Node:
229    """ Nodes of a header file. """
230
231    # pylint: disable=too-many-instance-attributes
232    def __init__(self, header_file: "_HeaderFile", item: Item):
233        self.header_file = header_file
234        self.item = item
235        self.ingroups = _get_ingroups(item)
236        self.dependents: Set[Node] = set()
237        self.depends_on: Set[Node] = set()
238        self.content = CContent()
239        self.mapper = _InterfaceMapper(self)
240        try:
241            group = item.child("placement-order")
242        except IndexError:
243            self.index = None
244        else:
245            self.index = (group.uid,
246                          list(group.parents("placement-order")).index(item))
247
248    def __lt__(self, other: "Node") -> bool:
249        return self.item.uid < other.item.uid
250
251    @contextmanager
252    def _enum_struct_or_union(self) -> Iterator[None]:
253        self.content.add(self._get_description(self.item, self.ingroups))
254        name = self.item["name"]
255        typename = self.item["interface-type"]
256        kind = self.item["definition-kind"]
257        if kind == f"{typename}-only":
258            self.content.append(f"{typename} {name} {{")
259        elif kind == "typedef-only":
260            self.content.append(f"typedef {typename} {{")
261        else:
262            self.content.append(f"typedef {typename} {name} {{")
263        self.content.push_indent()
264        yield
265        self.content.pop_indent()
266        if kind == f"{typename}-only":
267            self.content.append("};")
268        else:
269            self.content.append(f"}} {name};")
270
271    def _generate(self) -> None:
272        self.content.add(f"/* Generated from spec:{self.item.uid} */")
273        _NODE_GENERATORS[self.item["interface-type"]](self)
274
275    def generate(self) -> None:
276        """ Generates a node to generate the node content. """
277        enabled_by = self.item["enabled-by"]
278        if not isinstance(enabled_by, bool) or not enabled_by:
279            mapper = _ItemLevelExpressionMapper(self.mapper)
280            self.content.add(f"#if {enabled_by_to_exp(enabled_by, mapper)}")
281            with self.content.indent():
282                self._generate()
283            self.content.add("#endif")
284        else:
285            self._generate()
286
287    def generate_compound(self) -> None:
288        """ Generates a compound (struct or union). """
289        with self._enum_struct_or_union():
290            for index, definition in enumerate(self.item["definition"]):
291                self.content.add(
292                    _add_definition(self, self.item, f"definition[{index}]",
293                                    definition, Node._get_compound_definition))
294
295    def generate_enum(self) -> None:
296        """ Generates an enum. """
297        with self._enum_struct_or_union():
298            enumerators: List[CContent] = []
299            for parent in self.item.parents("interface-enumerator"):
300                enumerator = self._get_description(parent, {})
301                enumerator.append(
302                    _add_definition(self, parent, "definition",
303                                    parent["definition"],
304                                    Node._get_enumerator_definition))
305                enumerators.append(enumerator)
306            for enumerator in enumerators[0:-1]:
307                enumerator.lines[-1] += ","
308                enumerator.append("")
309                self.content.append(enumerator)
310            try:
311                self.content.append(enumerators[-1])
312            except IndexError:
313                pass
314
315    def generate_define(self) -> None:
316        """ Generates a define. """
317        self._add_generic_definition(Node._get_define_definition)
318
319    def generate_forward_declaration(self) -> None:
320        """ Generates a forward declaration. """
321        self.content.append([
322            "", "/* Forward declaration */",
323            forward_declaration(self.item) + ";"
324        ])
325
326    def generate_function(self) -> None:
327        """ Generates a function. """
328        self._add_generic_definition(Node._get_function_definition)
329
330    def generate_group(self) -> None:
331        """ Generates a group. """
332        self.content.add_group(self.item["identifier"], self.item["name"],
333                               _get_group_identifiers(self.ingroups),
334                               self.substitute_text(self.item["brief"]),
335                               self.substitute_text(self.item["description"]))
336
337    def generate_macro(self) -> None:
338        """ Generates a macro. """
339        self._add_generic_definition(Node._get_macro_definition)
340
341    def _add_register_bits(self, group: str) -> _RegisterMemberContext:
342        ctx = _RegisterMemberContext({}, {}, collections.defaultdict(int),
343                                     collections.defaultdict(int))
344        for index, register in enumerate(self.item["registers"]):
345            name = register["name"]
346            group_ident = group + to_camel_case(name)
347            ctx.regs[name] = {}
348            width = register["width"]
349            assert width in [8, 16, 32, 64]
350            ctx.regs[name]["size"] = width // 8
351            ctx.regs[name]["type"] = f"uint{width}_t"
352            ctx.regs[name]["group"] = group_ident
353            brief = self.substitute_text(register["brief"])
354            with self.content.defgroup_block(group_ident, f"{brief} ({name})"):
355                self.content.add_brief_description(
356                    "This group contains register bit definitions.")
357                self.content.doxyfy(
358                    self.substitute_text(register["description"]))
359                self.content.add("@{")
360            for index_2, bits in enumerate(register["bits"]):
361                self.content.add(
362                    _add_definition(
363                        self, self.item, f"registers[{index}]/bits[{index_2}]",
364                        bits,
365                        functools.partial(Node._get_register_bits_definition,
366                                          reg_name=name)))
367            self.content.add_close_group()
368        return ctx
369
370    def _add_register_block_includes(self,
371                                     ctx: _RegisterMemberContext) -> None:
372        for link in self.item.links_to_parents("register-block-include"):
373            name = link["name"]
374            ctx.regs[name] = {}
375            ctx.regs[name]["size"] = link.item["register-block-size"]
376            ctx.regs[name]["type"] = link.item["name"]
377            ctx.regs[name]["group"] = link.item["identifier"]
378
379    def _get_register_member_info(self, ctx: _RegisterMemberContext) -> None:
380        offset = -1
381        for index, member in enumerate(self.item["definition"]):
382            assert member["offset"] > offset
383            offset = member["offset"]
384            default = [member["default"]] if member["default"] else []
385            for index_2, definition in enumerate(
386                    itertools.chain(default,
387                                    (variant["definition"]
388                                     for variant in member["variants"]))):
389                name, alias = _get_register_name(definition)
390                assert name.lower() != "reserved"
391                count = definition["count"]
392                if index_2 == 0:
393                    ctx.sizes[index] = ctx.regs[name]["size"] * count
394                else:
395                    assert ctx.sizes[index] == ctx.regs[name]["size"] * count
396                ctx.reg_counts[alias] += 1
397
398    def _add_register_defines(self, ctx: _RegisterMemberContext) -> None:
399        with self.content.doxygen_block():
400            self.content.add("@name Registers")
401            self.content.add_brief_description(
402                self.substitute_text(self.item["brief"]))
403            self.content.doxyfy(self.substitute_text(self.item["description"]))
404            self.content.add("@{")
405        for index, member in enumerate(self.item["definition"]):
406            self.content.add(
407                _add_definition(
408                    self, self.item, f"definition[{index}]", member,
409                    functools.partial(Node._get_register_define_definition,
410                                      ctx=ctx,
411                                      offset=member["offset"])))
412        self.content.add_close_group()
413
414    def _add_register_struct(self, ctx: _RegisterMemberContext,
415                             size: int) -> None:
416        with self.content.doxygen_block():
417            self.content.add_brief_description(
418                self.substitute_text(self.item["brief"]))
419            self.content.doxyfy(self.substitute_text(self.item["description"]))
420        self.content.append(f"typedef struct {self.item['name']} {{")
421        default_padding = min(*ctx.sizes.values(), 8)
422        offset = 0
423        with self.content.indent():
424            for index, member in enumerate(self.item["definition"]):
425                member_offset = member["offset"]
426                _add_register_padding(self.content, member_offset, offset,
427                                      default_padding)
428                self.content.add(
429                    _add_definition(
430                        self, self.item, f"definition[{index}]", member,
431                        functools.partial(Node._get_register_member_definition,
432                                          ctx=ctx)))
433                offset = member_offset + ctx.sizes[index]
434            assert offset <= size
435            _add_register_padding(self.content, size, offset, default_padding)
436        self.content.add(f"}} {self.item['name']};")
437
438    def _add_register_members(self, ctx: _RegisterMemberContext) -> None:
439        size = self.item["register-block-size"]
440        if size is None:
441            self._add_register_defines(ctx)
442        else:
443            self._add_register_struct(ctx, size)
444
445    def generate_register_block(self) -> None:
446        """ Generates a register block. """
447        self.header_file.add_includes(self.item.map("/c/if/uint32_t"))
448        for parent in self.item.parents("register-block-include"):
449            self.header_file.add_includes(parent)
450            self.header_file.add_dependency(self, parent)
451        group = self.item["identifier"]
452        name = self.item["register-block-group"]
453        with self.content.defgroup_block(group, name):
454            self.content.add_ingroup(_get_group_identifiers(self.ingroups))
455            self.content.add_brief_description(
456                f"This group contains the {name} interfaces.")
457            self.content.add("@{")
458        ctx = self._add_register_bits(group)
459        self._add_register_block_includes(ctx)
460        self._get_register_member_info(ctx)
461        self._add_register_members(ctx)
462        self.content.add_close_group()
463
464    def generate_typedef(self) -> None:
465        """ Generates a typedef. """
466        self._add_generic_definition(Node._get_typedef_definition)
467
468    def generate_variable(self) -> None:
469        """ Generates a variable. """
470        self._add_generic_definition(Node._get_variable_definition)
471
472    def substitute_code(self, text: str) -> str:
473        """
474        Performs a variable substitution on code using the item mapper of the
475        node.
476        """
477        if text:
478            with self.mapper.code():
479                return self.mapper.substitute(text.strip("\n"))
480        return text
481
482    def substitute_text(self,
483                        text: Optional[str],
484                        item: Optional[Item] = None) -> str:
485        """
486        Performs a variable substitution on a description using the item mapper
487        of the node.
488        """
489        if text:
490            return self.mapper.substitute(text.strip("\n"), item)
491        return ""
492
493    def _get_compound_definition(self, item: Item, definition: Any) -> Lines:
494        content = CContent()
495        content.add_description_block(
496            self.substitute_text(definition["brief"]),
497            self.substitute_text(definition["description"]))
498        kind = definition["kind"]
499        if kind == "member":
500            member = self.substitute_code(definition["definition"]) + ";"
501            content.append(member.split("\n"))
502        else:
503            content.append(f"{kind} {{")
504            content.gap = False
505            with content.indent():
506                for index, compound_member in enumerate(
507                        definition["definition"]):
508                    content.add(
509                        _add_definition(self, item, f"definition[{index}]",
510                                        compound_member,
511                                        Node._get_compound_definition))
512            name = definition["name"]
513            content.append(f"}} {name};")
514        return content.lines
515
516    def _get_enumerator_definition(self, item: Item, definition: Any) -> Lines:
517        name = item["name"]
518        if definition:
519            return f"{name} = {self.substitute_code(definition)}"
520        return f"{name}"
521
522    def _get_define_definition(self, item: Item, definition: Any) -> Lines:
523        name = item["name"]
524        value = self.substitute_code(definition)
525        if value:
526            return f"#define {name} {value}".split("\n")
527        return f"#define {name}"
528
529    def _get_function_definition(self, item: Item, definition: Any) -> Lines:
530        content = CContent()
531        name = item["name"]
532        attrs = self.substitute_code(definition["attributes"])
533        attrs = f"{attrs} " if attrs else ""
534        ret = self.substitute_code(definition["return"])
535        params = [
536            self.substitute_code(param) for param in definition["params"]
537        ]
538        body = definition["body"]
539        if body:
540            with content.function(f"{attrs}static inline {ret}", name, params):
541                content.add(self.substitute_code(body))
542        else:
543            content.declare_function(f"{attrs}{ret}", name, params)
544        return content.lines
545
546    def _get_macro_definition(self, item: Item, definition: Any) -> Lines:
547        name = item["name"]
548        params = [param["name"] for param in item["params"]]
549        if params:
550            param_line = " " + ", ".join(params) + " "
551        else:
552            param_line = ""
553        line = f"#define {name}({param_line})"
554        if len(line) > 79:
555            param_block = ", \\\n  ".join(params)
556            line = f"#define {name}( \\\n  {param_block} \\\n)"
557        body = definition["body"]
558        if not body:
559            return line
560        body_lines = self.substitute_code(body).split("\n")
561        if len(body_lines) == 1 and len(line + body_lines[0]) <= 79:
562            body = " "
563        else:
564            body = " \\\n  "
565        body += " \\\n  ".join(body_lines)
566        return line + body
567
568    def _get_register_bits_definition(self, _item: Item, definition: Any,
569                                      reg_name: str) -> Lines:
570        lines = []  # List[str]
571        prefix = self.item["register-prefix"]
572        if prefix is None:
573            prefix = self.item["name"]
574        prefix = f"{prefix}_{reg_name}_" if prefix else f"{reg_name}_"
575        for index, bit in enumerate(definition):
576            start = bit["start"]
577            width = bit["width"]
578            end = start + width
579            sfx = "ULL" if end > 32 else "U"
580            define = f"#define {prefix.upper()}{bit['name'].upper()}"
581            if index != 0:
582                lines.append("")
583            if width == 1:
584                val = 1 << start
585                lines.append(f"{define} {val:#x}{sfx}")
586            else:
587                mask = ((1 << width) - 1) << start
588                get = (1 << width) - 1
589                lines.extend([
590                    f"{define}_SHIFT {start}", f"{define}_MASK {mask:#x}{sfx}",
591                    f"{define}_GET( _reg ) \\",
592                    f"  ( ( ( _reg ) >> {start} ) & {get:#x}{sfx} )",
593                    f"{define}( _val ) ( ( _val ) << {start} )"
594                ])
595        return lines
596
597    def _get_register_define_definition(self, item: Item, definition: Any,
598                                        ctx: _RegisterMemberContext,
599                                        offset: int) -> Lines:
600        name, alias = _get_register_name(definition)
601        count = definition["count"]
602        assert count == 1
603        content = CContent()
604        with content.doxygen_block():
605            content.add(f"@brief See @ref {ctx.regs[name]['group']}.")
606        content.append(
607            f"#define {item['name'].upper()}_{alias.upper()} {offset:#x}")
608        return content.lines
609
610    def _get_register_member_definition(self, _item: Item, definition: Any,
611                                        ctx: _RegisterMemberContext) -> Lines:
612        name, alias = _get_register_name(definition)
613        count = definition["count"]
614        array = f"[ {count} ]" if count > 1 else ""
615        if ctx.reg_counts[alias] > 1:
616            index = ctx.reg_indices[alias]
617            ctx.reg_indices[alias] = index + 1
618            idx = f"_{index}"
619        else:
620            idx = ""
621        content = CContent()
622        with content.doxygen_block():
623            content.add(f"@brief See @ref {ctx.regs[name]['group']}.")
624        content.append(
625            f"{ctx.regs[name]['type']} {alias.lower()}{idx}{array};")
626        return content.lines
627
628    def _get_typedef_definition(self, _item: Item, definition: Any) -> Lines:
629        return f"typedef {self.substitute_code(definition)};"
630
631    def _get_variable_definition(self, _item: Item, definition: Any) -> Lines:
632        return f"extern {self.substitute_code(definition)};"
633
634    def _get_description(self, item: Item, ingroups: ItemMap) -> CContent:
635        content = CContent()
636        with content.doxygen_block():
637            content.add_ingroup(_get_group_identifiers(ingroups))
638            content.add_brief_description(self.substitute_text(item["brief"]))
639            if "params" in item:
640                content.add_param_description(item["params"],
641                                              self.substitute_text)
642            content.doxyfy(self.substitute_text(item["description"]))
643            ret = item.get("return", None)
644            if ret:
645                for retval in ret["return-values"]:
646                    content.wrap(self.substitute_text(retval["description"]),
647                                 initial_indent=self.substitute_text(
648                                     f"@retval {retval['value']} "))
649                content.wrap(self.substitute_text(ret["return"]),
650                             initial_indent="@return ")
651            content.add_paragraph("Notes", self.substitute_text(item["notes"]))
652            constraints = [
653                self.substitute_text(parent["text"], parent)
654                for parent in item.parents("constraint")
655                if parent.is_enabled(self.header_file.enabled)
656            ]
657            if constraints:
658                constraint_content = CContent()
659                target = _CONSTRAINT_TARGET[item.type]
660                constraint_content.add_list(
661                    constraints,
662                    f"The following constraints apply to {target}:")
663                content.add_paragraph("Constraints", constraint_content)
664        return content
665
666    def _add_generic_definition(self, get_lines: GetLines) -> None:
667        self.content.add(self._get_description(self.item, self.ingroups))
668        self.content.append(
669            _add_definition(self, self.item, "definition",
670                            self.item["definition"], get_lines))
671
672
673_NODE_GENERATORS = {
674    "enum": Node.generate_enum,
675    "define": Node.generate_define,
676    "forward-declaration": Node.generate_forward_declaration,
677    "function": Node.generate_function,
678    "group": Node.generate_group,
679    "macro": Node.generate_macro,
680    "register-block": Node.generate_register_block,
681    "struct": Node.generate_compound,
682    "typedef": Node.generate_typedef,
683    "union": Node.generate_compound,
684    "variable": Node.generate_variable,
685}
686
687
688def _is_ready_to_bubble(before: Node, after: Node) -> bool:
689    if after in before.dependents:
690        return False
691
692    # Move the groups towards the top of the header file
693    group = "interface/group"
694    if (before.item.type == group) ^ (after.item.type == group):
695        return after.item.type == group
696
697    # Move items with an explicit placement order towards the bottom of the
698    # file
699    if before.index and after.index:
700        return after.index < before.index
701    if after.index:
702        return False
703
704    return after < before
705
706
707def _bubble_sort(nodes: List[Node]) -> List[Node]:
708    node_count = len(nodes)
709    for i in range(node_count - 1):
710        for j in range(node_count - 1 - i):
711            if _is_ready_to_bubble(nodes[j], nodes[j + 1]):
712                nodes[j], nodes[j + 1] = nodes[j + 1], nodes[j]
713    return nodes
714
715
716class _HeaderFile:
717    """ A header file. """
718
719    def __init__(self, item: Item, enabled_by_defined: Dict[str, str],
720                 enabled: List[str]):
721        self._item = item
722        self._content = CContent()
723        self._content.register_license_and_copyrights_of_item(item)
724        self._ingroups = _get_ingroups(item)
725        self._includes: List[Item] = []
726        self._nodes: Dict[str, Node] = {}
727        self.enabled_by_defined = enabled_by_defined
728        self.enabled = enabled
729
730    def add_includes(self, item: Item) -> None:
731        """ Adds the includes of the item to the header file includes. """
732        for parent in item.parents("interface-placement"):
733            if parent.type == "interface/header-file":
734                self._includes.append(parent)
735
736    def _add_child(self, item: Item) -> None:
737        self._nodes[item.uid] = Node(self, item)
738        self._content.register_license_and_copyrights_of_item(item)
739
740    def add_dependency(self, node: Node, item: Item) -> None:
741        """
742        Adds a dependency from a node to another node identified by an item if
743        the item corresponds to a node and it is not a self reference.
744        """
745        if item.uid in self._nodes and item.uid != node.item.uid:
746            other = self._nodes[item.uid]
747            node.depends_on.add(other)
748            other.dependents.add(node)
749
750    def generate_nodes(self) -> None:
751        """ Generates all nodes of this header file. """
752        for child in self._item.children("interface-placement"):
753            self._add_child(child)
754        for node in self._nodes.values():
755            node.generate()
756
757    def _get_nodes_in_dependency_order(self) -> List[Node]:
758        """
759        Gets the nodes of this header file ordered according to node
760        dependencies and other criteria.
761
762        The nodes form a partially ordered set (poset).  The ordering is done
763        in two steps.  Firstly, a topological sort using Kahn's algorithm is
764        carried out.  Secondly, the nodes are sorted using a bubble sort which
765        takes node dependencies into account.  There are more efficient ways to
766        do this, however, if you experience run time problems due to this
767        method, then maybe you should reconsider your header file organization.
768        """
769        nodes_in_dependency_order: List[Node] = []
770
771        # Get incoming edge degrees for all nodes
772        in_degree: Dict[str, int] = {}
773        for node in self._nodes.values():
774            in_degree[node.item.uid] = len(node.dependents)
775
776        # Create a queue with all nodes with no incoming edges
777        queue: List[Node] = []
778        for node in sorted(self._nodes.values()):
779            if in_degree[node.item.uid] == 0:
780                queue.append(node)
781
782        # Topological sort
783        while queue:
784            node = queue.pop(0)
785            nodes_in_dependency_order.insert(0, node)
786
787            for other in sorted(node.depends_on):
788                in_degree[other.item.uid] -= 1
789                if in_degree[other.item.uid] == 0:
790                    queue.append(other)
791
792        return _bubble_sort(nodes_in_dependency_order)
793
794    def finalize(self) -> None:
795        """ Finalizes the header file. """
796        self._content.prepend_spdx_license_identifier()
797        with self._content.file_block():
798            self._content.add_ingroup(_get_group_identifiers(self._ingroups))
799            self._content.add_brief_description(self._item["brief"])
800        self._content.add_copyrights_and_licenses()
801        self._content.add_automatically_generated_warning()
802        self._content.add(f"/* Generated from spec:{self._item.uid} */")
803        with self._content.header_guard(self._item["path"]):
804            exp_mapper = _HeaderExpressionMapper(self._item,
805                                                 self.enabled_by_defined)
806            includes = [
807                CInclude(item["path"],
808                         enabled_by_to_exp(item["enabled-by"], exp_mapper))
809                for item in self._includes if item != self._item
810            ]
811            includes.extend([
812                CInclude(link.item["path"],
813                         enabled_by_to_exp(link["enabled-by"], exp_mapper))
814                for link in self._item.links_to_parents("interface-include")
815            ])
816            self._content.add_includes(includes)
817            with self._content.extern_c():
818                for node in self._get_nodes_in_dependency_order():
819                    self._content.add(node.content)
820
821    def write(self, domain_path: str) -> None:
822        """ Writes the header file. """
823        self._content.write(
824            os.path.join(domain_path, self._item["prefix"],
825                         self._item["path"]))
826
827
828def _generate_header_file(item: Item, domains: Dict[str, str],
829                          enabled_by_defined: Dict[str, str],
830                          enabled: List[str]) -> None:
831    domain = item.parent("interface-placement")
832    assert domain["interface-type"] == "domain"
833    domain_path = domains.get(domain.uid, None)
834    if domain_path is None:
835        return
836    header_file = _HeaderFile(item, enabled_by_defined, enabled)
837    header_file.generate_nodes()
838    header_file.finalize()
839    header_file.write(domain_path)
840
841
842def _gather_enabled_by_defined(item_level_interfaces: List[str],
843                               item_cache: ItemCache) -> Dict[str, str]:
844    enabled_by_defined: Dict[str, str] = {}
845    for uid in item_level_interfaces:
846        for child in item_cache[uid].children("interface-ingroup"):
847            if child.type == "interface/unspecified-define":
848                define = f"defined(${{{child.uid}:/name}})"
849                enabled_by_defined[child["name"]] = define
850    return enabled_by_defined
851
852
853def generate(config: dict, item_cache: ItemCache) -> None:
854    """
855    Generates header files according to the configuration.
856
857    :param config: A dictionary with configuration entries.
858    :param item_cache: The specification item cache containing the interfaces.
859    """
860    domains = config["domains"]
861    enabled = config["enabled"]
862    enabled_by_defined = _gather_enabled_by_defined(
863        config["item-level-interfaces"], item_cache)
864    for item in item_cache.all.values():
865        if item.type == "interface/header-file":
866            _generate_header_file(item, domains, enabled_by_defined, enabled)
Note: See TracBrowser for help on using the repository browser.