source: rtems-central/rtemsspec/interface.py @ ea3eadf

Last change on this file since ea3eadf was ea3eadf, checked in by Sebastian Huber <sebastian.huber@…>, on 03/15/23 at 07:39:35

interface: Improve register bit field macros

Update #4828.

  • Property mode set to 100644
File size: 35.5 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            base = f"{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 {base} {val:#x}{sfx}")
586            else:
587                mask = ((1 << width) - 1) << start
588                lines.extend([
589                    f"#define {base}_SHIFT {start}",
590                    f"#define {base}_MASK {mask:#x}{sfx}",
591                    f"#define {base}_GET( _reg ) \\",
592                    f"  ( ( ( _reg ) & {base}_MASK ) >> {base}_SHIFT )",
593                    f"#define {base}( _val ) \\",
594                    f"  ( ( _val ) << {base}_SHIFT )"
595                ])
596        return lines
597
598    def _get_register_define_definition(self, item: Item, definition: Any,
599                                        ctx: _RegisterMemberContext,
600                                        offset: int) -> Lines:
601        name, alias = _get_register_name(definition)
602        count = definition["count"]
603        assert count == 1
604        content = CContent()
605        with content.doxygen_block():
606            content.add(f"@brief See @ref {ctx.regs[name]['group']}.")
607        content.append(
608            f"#define {item['name'].upper()}_{alias.upper()} {offset:#x}")
609        return content.lines
610
611    def _get_register_member_definition(self, _item: Item, definition: Any,
612                                        ctx: _RegisterMemberContext) -> Lines:
613        name, alias = _get_register_name(definition)
614        count = definition["count"]
615        array = f"[ {count} ]" if count > 1 else ""
616        if ctx.reg_counts[alias] > 1:
617            index = ctx.reg_indices[alias]
618            ctx.reg_indices[alias] = index + 1
619            idx = f"_{index}"
620        else:
621            idx = ""
622        content = CContent()
623        with content.doxygen_block():
624            content.add(f"@brief See @ref {ctx.regs[name]['group']}.")
625        content.append(
626            f"{ctx.regs[name]['type']} {alias.lower()}{idx}{array};")
627        return content.lines
628
629    def _get_typedef_definition(self, _item: Item, definition: Any) -> Lines:
630        return f"typedef {self.substitute_code(definition)};"
631
632    def _get_variable_definition(self, _item: Item, definition: Any) -> Lines:
633        return f"extern {self.substitute_code(definition)};"
634
635    def _get_description(self, item: Item, ingroups: ItemMap) -> CContent:
636        content = CContent()
637        with content.doxygen_block():
638            content.add_ingroup(_get_group_identifiers(ingroups))
639            content.add_brief_description(self.substitute_text(item["brief"]))
640            if "params" in item:
641                content.add_param_description(item["params"],
642                                              self.substitute_text)
643            content.doxyfy(self.substitute_text(item["description"]))
644            ret = item.get("return", None)
645            if ret:
646                for retval in ret["return-values"]:
647                    content.wrap(self.substitute_text(retval["description"]),
648                                 initial_indent=self.substitute_text(
649                                     f"@retval {retval['value']} "))
650                content.wrap(self.substitute_text(ret["return"]),
651                             initial_indent="@return ")
652            content.add_paragraph("Notes", self.substitute_text(item["notes"]))
653            constraints = [
654                self.substitute_text(parent["text"], parent)
655                for parent in item.parents("constraint")
656                if parent.is_enabled(self.header_file.enabled)
657            ]
658            if constraints:
659                constraint_content = CContent()
660                target = _CONSTRAINT_TARGET[item.type]
661                constraint_content.add_list(
662                    constraints,
663                    f"The following constraints apply to {target}:")
664                content.add_paragraph("Constraints", constraint_content)
665        return content
666
667    def _add_generic_definition(self, get_lines: GetLines) -> None:
668        self.content.add(self._get_description(self.item, self.ingroups))
669        self.content.append(
670            _add_definition(self, self.item, "definition",
671                            self.item["definition"], get_lines))
672
673
674_NODE_GENERATORS = {
675    "enum": Node.generate_enum,
676    "define": Node.generate_define,
677    "forward-declaration": Node.generate_forward_declaration,
678    "function": Node.generate_function,
679    "group": Node.generate_group,
680    "macro": Node.generate_macro,
681    "register-block": Node.generate_register_block,
682    "struct": Node.generate_compound,
683    "typedef": Node.generate_typedef,
684    "union": Node.generate_compound,
685    "variable": Node.generate_variable,
686}
687
688
689def _is_ready_to_bubble(before: Node, after: Node) -> bool:
690    if after in before.dependents:
691        return False
692
693    # Move the groups towards the top of the header file
694    group = "interface/group"
695    if (before.item.type == group) ^ (after.item.type == group):
696        return after.item.type == group
697
698    # Move items with an explicit placement order towards the bottom of the
699    # file
700    if before.index and after.index:
701        return after.index < before.index
702    if after.index:
703        return False
704
705    return after < before
706
707
708def _bubble_sort(nodes: List[Node]) -> List[Node]:
709    node_count = len(nodes)
710    for i in range(node_count - 1):
711        for j in range(node_count - 1 - i):
712            if _is_ready_to_bubble(nodes[j], nodes[j + 1]):
713                nodes[j], nodes[j + 1] = nodes[j + 1], nodes[j]
714    return nodes
715
716
717class _HeaderFile:
718    """ A header file. """
719
720    def __init__(self, item: Item, enabled_by_defined: Dict[str, str],
721                 enabled: List[str]):
722        self._item = item
723        self._content = CContent()
724        self._content.register_license_and_copyrights_of_item(item)
725        self._ingroups = _get_ingroups(item)
726        self._includes: List[Item] = []
727        self._nodes: Dict[str, Node] = {}
728        self.enabled_by_defined = enabled_by_defined
729        self.enabled = enabled
730
731    def add_includes(self, item: Item) -> None:
732        """ Adds the includes of the item to the header file includes. """
733        for parent in item.parents("interface-placement"):
734            if parent.type == "interface/header-file":
735                self._includes.append(parent)
736
737    def _add_child(self, item: Item) -> None:
738        self._nodes[item.uid] = Node(self, item)
739        self._content.register_license_and_copyrights_of_item(item)
740
741    def add_dependency(self, node: Node, item: Item) -> None:
742        """
743        Adds a dependency from a node to another node identified by an item if
744        the item corresponds to a node and it is not a self reference.
745        """
746        if item.uid in self._nodes and item.uid != node.item.uid:
747            other = self._nodes[item.uid]
748            node.depends_on.add(other)
749            other.dependents.add(node)
750
751    def generate_nodes(self) -> None:
752        """ Generates all nodes of this header file. """
753        for child in self._item.children("interface-placement"):
754            self._add_child(child)
755        for node in self._nodes.values():
756            node.generate()
757
758    def _get_nodes_in_dependency_order(self) -> List[Node]:
759        """
760        Gets the nodes of this header file ordered according to node
761        dependencies and other criteria.
762
763        The nodes form a partially ordered set (poset).  The ordering is done
764        in two steps.  Firstly, a topological sort using Kahn's algorithm is
765        carried out.  Secondly, the nodes are sorted using a bubble sort which
766        takes node dependencies into account.  There are more efficient ways to
767        do this, however, if you experience run time problems due to this
768        method, then maybe you should reconsider your header file organization.
769        """
770        nodes_in_dependency_order: List[Node] = []
771
772        # Get incoming edge degrees for all nodes
773        in_degree: Dict[str, int] = {}
774        for node in self._nodes.values():
775            in_degree[node.item.uid] = len(node.dependents)
776
777        # Create a queue with all nodes with no incoming edges
778        queue: List[Node] = []
779        for node in sorted(self._nodes.values()):
780            if in_degree[node.item.uid] == 0:
781                queue.append(node)
782
783        # Topological sort
784        while queue:
785            node = queue.pop(0)
786            nodes_in_dependency_order.insert(0, node)
787
788            for other in sorted(node.depends_on):
789                in_degree[other.item.uid] -= 1
790                if in_degree[other.item.uid] == 0:
791                    queue.append(other)
792
793        return _bubble_sort(nodes_in_dependency_order)
794
795    def finalize(self) -> None:
796        """ Finalizes the header file. """
797        self._content.prepend_spdx_license_identifier()
798        with self._content.file_block():
799            self._content.add_ingroup(_get_group_identifiers(self._ingroups))
800            self._content.add_brief_description(self._item["brief"])
801        self._content.add_copyrights_and_licenses()
802        self._content.add_automatically_generated_warning()
803        self._content.add(f"/* Generated from spec:{self._item.uid} */")
804        with self._content.header_guard(self._item["path"]):
805            exp_mapper = _HeaderExpressionMapper(self._item,
806                                                 self.enabled_by_defined)
807            includes = [
808                CInclude(item["path"],
809                         enabled_by_to_exp(item["enabled-by"], exp_mapper))
810                for item in self._includes if item != self._item
811            ]
812            includes.extend([
813                CInclude(link.item["path"],
814                         enabled_by_to_exp(link["enabled-by"], exp_mapper))
815                for link in self._item.links_to_parents("interface-include")
816            ])
817            self._content.add_includes(includes)
818            with self._content.extern_c():
819                for node in self._get_nodes_in_dependency_order():
820                    self._content.add(node.content)
821
822    def write(self, domain_path: str) -> None:
823        """ Writes the header file. """
824        self._content.write(
825            os.path.join(domain_path, self._item["prefix"],
826                         self._item["path"]))
827
828
829def _generate_header_file(item: Item, domains: Dict[str, str],
830                          enabled_by_defined: Dict[str, str],
831                          enabled: List[str]) -> None:
832    domain = item.parent("interface-placement")
833    assert domain["interface-type"] == "domain"
834    domain_path = domains.get(domain.uid, None)
835    if domain_path is None:
836        return
837    header_file = _HeaderFile(item, enabled_by_defined, enabled)
838    header_file.generate_nodes()
839    header_file.finalize()
840    header_file.write(domain_path)
841
842
843def _gather_enabled_by_defined(item_level_interfaces: List[str],
844                               item_cache: ItemCache) -> Dict[str, str]:
845    enabled_by_defined: Dict[str, str] = {}
846    for uid in item_level_interfaces:
847        for child in item_cache[uid].children("interface-ingroup"):
848            if child.type == "interface/unspecified-define":
849                define = f"defined(${{{child.uid}:/name}})"
850                enabled_by_defined[child["name"]] = define
851    return enabled_by_defined
852
853
854def generate(config: dict, item_cache: ItemCache) -> None:
855    """
856    Generates header files according to the configuration.
857
858    :param config: A dictionary with configuration entries.
859    :param item_cache: The specification item cache containing the interfaces.
860    """
861    domains = config["domains"]
862    enabled = config["enabled"]
863    enabled_by_defined = _gather_enabled_by_defined(
864        config["item-level-interfaces"], item_cache)
865    for item in item_cache.all.values():
866        if item.type == "interface/header-file":
867            _generate_header_file(item, domains, enabled_by_defined, enabled)
Note: See TracBrowser for help on using the repository browser.