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

Last change on this file since bf90abb was bf90abb, checked in by Sebastian Huber <sebastian.huber@…>, on 11/06/20 at 17:50:28

interface: Substitute group descriptions

  • Property mode set to 100644
File size: 24.6 KB
Line 
1# SPDX-License-Identifier: BSD-2-Clause
2""" This module provides functions for the generation of interfaces. """
3
4# Copyright (C) 2020 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
27from contextlib import contextmanager
28import os
29from typing import Any, Callable, Dict, Iterator, List, Union, Set
30
31from rtemsspec.content import CContent, CInclude, enabled_by_to_exp, \
32    ExpressionMapper, get_value_double_colon, get_value_doxygen_function, \
33    get_value_hash
34from rtemsspec.items import Item, ItemCache, ItemGetValueContext, ItemMapper
35
36ItemMap = Dict[str, Item]
37Lines = Union[str, List[str]]
38GetLines = Callable[["Node", Item, Any], Lines]
39
40
41def _get_ingroups(item: Item) -> ItemMap:
42    ingroups = {}  # type: ItemMap
43    for group in item.parents("interface-ingroup"):
44        ingroups[group.uid] = group
45    return ingroups
46
47
48def _get_group_identifiers(groups: ItemMap) -> List[str]:
49    return [item["identifier"] for item in groups.values()]
50
51
52def _forward_declaration(item: Item) -> str:
53    target = item.parent("interface-target")
54    return f"{target['interface-type']} {target['name']}"
55
56
57def _get_value_forward_declaration(ctx: ItemGetValueContext) -> Any:
58    return _forward_declaration(ctx.item)
59
60
61class _InterfaceMapper(ItemMapper):
62    def __init__(self, node: "Node"):
63        super().__init__(node.item)
64        self._node = node
65        self._code_or_doc = "doc"
66        self.add_get_value("interface/forward-declaration:code:/name",
67                           _get_value_forward_declaration)
68        self.add_get_value("interface/forward-declaration:doc:/name",
69                           _get_value_forward_declaration)
70        self.add_get_value("interface/function:doc:/name",
71                           get_value_doxygen_function)
72        self.add_get_value("interface/enumerator:doc:/name",
73                           get_value_double_colon)
74        self.add_get_value("interface/typedef:doc:/name",
75                           get_value_double_colon)
76        self.add_get_value("interface/define:doc:/name", get_value_hash)
77        self.add_get_value("interface/enum:doc:/name", get_value_hash)
78        self.add_get_value("interface/macro:doc:/name",
79                           get_value_doxygen_function)
80        self.add_get_value("interface/variable:doc:/name", get_value_hash)
81        for opt in ["feature-enable", "feature", "initializer", "integer"]:
82            name = f"interface/appl-config-option/{opt}:doc:/name"
83            self.add_get_value(name, get_value_hash)
84        self.add_get_value("interface/unspecified-function:doc:/name",
85                           get_value_doxygen_function)
86
87    @contextmanager
88    def code(self) -> Iterator[None]:
89        """ Enables code mapping. """
90        code_or_doc = self._code_or_doc
91        self._code_or_doc = "code"
92        yield
93        self._code_or_doc = code_or_doc
94
95    def get_value(self, ctx: ItemGetValueContext) -> Any:
96        if self._code_or_doc == "code" and ctx.item["type"] == "interface":
97            node = self._node
98            header_file = node.header_file
99            if ctx.item["interface-type"] == "enumerator":
100                for child in ctx.item.children("interface-enumerator"):
101                    header_file.add_includes(child)
102            else:
103                header_file.add_includes(ctx.item)
104            header_file.add_dependency(node, ctx.item)
105        return super().get_value(
106            ItemGetValueContext(ctx.item, f"{self._code_or_doc}:{ctx.path}",
107                                ctx.value, ctx.key, ctx.index))
108
109    def enabled_by_to_defined(self, enabled_by: str) -> str:
110        """
111        Maps an item-level enabled-by attribute value to the corresponding
112        defined expression.
113        """
114        return self._node.header_file.enabled_by_defined[enabled_by]
115
116
117class _InterfaceExpressionMapper(ExpressionMapper):
118    def __init__(self, mapper: _InterfaceMapper):
119        super().__init__()
120        self._mapper = mapper
121
122    def map_symbol(self, symbol: str) -> str:
123        with self._mapper.code():
124            return self._mapper.substitute(symbol)
125
126
127class _ItemLevelExpressionMapper(ExpressionMapper):
128    def __init__(self, mapper: _InterfaceMapper):
129        super().__init__()
130        self._mapper = mapper
131
132    def map_symbol(self, symbol: str) -> str:
133        with self._mapper.code():
134            return self._mapper.substitute(
135                self._mapper.enabled_by_to_defined(symbol))
136
137
138class _HeaderExpressionMapper(ExpressionMapper):
139    def __init__(self, item: Item, enabled_by_defined: Dict[str, str]):
140        super().__init__()
141        self._mapper = ItemMapper(item)
142        self._enabled_by_defined = enabled_by_defined
143
144    def map_symbol(self, symbol: str) -> str:
145        return self._mapper.substitute(self._enabled_by_defined[symbol])
146
147
148def _add_definition(node: "Node", item: Item, prefix: str,
149                    value: Dict[str, Any], get_lines: GetLines) -> CContent:
150    content = CContent()
151    default = value["default"]
152    variants = value["variants"]
153    if variants:
154        ifelse = "#if "
155        with node.mapper.prefix(os.path.join(prefix, "variants")):
156            for variant in variants:
157                enabled_by = enabled_by_to_exp(
158                    variant["enabled-by"],
159                    _InterfaceExpressionMapper(node.mapper))
160                content.append(f"{ifelse}{enabled_by}")
161                with content.indent():
162                    content.append(get_lines(node, item,
163                                             variant["definition"]))
164                ifelse = "#elif "
165        if default is not None:
166            content.append("#else")
167            with node.mapper.prefix(os.path.join(prefix, "default")):
168                with content.indent():
169                    content.append(get_lines(node, item, default))
170        content.append("#endif")
171    else:
172        with node.mapper.prefix(os.path.join(prefix, "default")):
173            content.append(get_lines(node, item, default))
174    return content
175
176
177class Node:
178    """ Nodes of a header file. """
179
180    # pylint: disable=too-many-instance-attributes
181    def __init__(self, header_file: "_HeaderFile", item: Item):
182        self.header_file = header_file
183        self.item = item
184        self.ingroups = _get_ingroups(item)
185        self.dependents = set()  # type: Set["Node"]
186        self.depends_on = set()  # type: Set["Node"]
187        self.content = CContent()
188        self.mapper = _InterfaceMapper(self)
189        try:
190            group = item.child("placement-order")
191        except IndexError:
192            self.index = None
193        else:
194            self.index = (group.uid,
195                          list(group.parents("placement-order")).index(item))
196
197    def __lt__(self, other: "Node") -> bool:
198        return self.item.uid < other.item.uid
199
200    @contextmanager
201    def _enum_struct_or_union(self) -> Iterator[None]:
202        self.content.add(self._get_description(self.item, self.ingroups))
203        name = self.item["name"]
204        typename = self.item["interface-type"]
205        kind = self.item["definition-kind"]
206        if kind == f"{typename}-only":
207            self.content.append(f"{typename} {name} {{")
208        elif kind == "typedef-only":
209            self.content.append(f"typedef {typename} {{")
210        else:
211            self.content.append(f"typedef {typename} {name} {{")
212        self.content.push_indent()
213        yield
214        self.content.pop_indent()
215        if kind == f"{typename}-only":
216            self.content.append("};")
217        else:
218            self.content.append(f"}} {name};")
219
220    def _generate(self) -> None:
221        self.content.add(f"/* Generated from spec:{self.item.uid} */")
222        _NODE_GENERATORS[self.item["interface-type"]](self)
223
224    def generate(self) -> None:
225        """ Generates a node to generate the node content. """
226        enabled_by = self.item["enabled-by"]
227        if not isinstance(enabled_by, bool) or not enabled_by:
228            mapper = _ItemLevelExpressionMapper(self.mapper)
229            self.content.add(f"#if {enabled_by_to_exp(enabled_by, mapper)}")
230            with self.content.indent():
231                self._generate()
232            self.content.add("#endif")
233        else:
234            self._generate()
235
236    def generate_compound(self) -> None:
237        """ Generates a compound (struct or union). """
238        with self._enum_struct_or_union():
239            for index, definition in enumerate(self.item["definition"]):
240                self.content.add(
241                    _add_definition(self, self.item, f"definition[{index}]",
242                                    definition, Node._get_compound_definition))
243
244    def generate_enum(self) -> None:
245        """ Generates an enum. """
246        with self._enum_struct_or_union():
247            enumerators = []  # type: List[CContent]
248            for parent in self.item.parents("interface-enumerator"):
249                enumerator = self._get_description(parent, {})
250                enumerator.append(
251                    _add_definition(self, parent, "definition",
252                                    parent["definition"],
253                                    Node._get_enumerator_definition))
254                enumerators.append(enumerator)
255            for enumerator in enumerators[0:-1]:
256                enumerator.lines[-1] += ","
257                enumerator.append("")
258                self.content.append(enumerator)
259            try:
260                self.content.append(enumerators[-1])
261            except IndexError:
262                pass
263
264    def generate_define(self) -> None:
265        """ Generates a define. """
266        self._add_generic_definition(Node._get_define_definition)
267
268    def generate_forward_declaration(self) -> None:
269        """ Generates a forward declaration. """
270        self.content.append([
271            "", "/* Forward declaration */",
272            _forward_declaration(self.item) + ";"
273        ])
274
275    def generate_function(self) -> None:
276        """ Generates a function. """
277        self._add_generic_definition(Node._get_function_definition)
278
279    def generate_group(self) -> None:
280        """ Generates a group. """
281        for ingroup in self.ingroups.values():
282            self.header_file.add_dependency(self, ingroup)
283        self.content.add_group(self.item["identifier"], self.item["name"],
284                               _get_group_identifiers(self.ingroups),
285                               self.substitute_text(self.item["brief"]),
286                               self.substitute_text(self.item["description"]))
287
288    def generate_macro(self) -> None:
289        """ Generates a macro. """
290        self._add_generic_definition(Node._get_macro_definition)
291
292    def generate_typedef(self) -> None:
293        """ Generates a typedef. """
294        self._add_generic_definition(Node._get_typedef_definition)
295
296    def generate_variable(self) -> None:
297        """ Generates a variable. """
298        self._add_generic_definition(Node._get_variable_definition)
299
300    def substitute_code(self, text: str) -> str:
301        """
302        Performs a variable substitution on code using the item mapper of the
303        node.
304        """
305        if text:
306            with self.mapper.code():
307                return self.mapper.substitute(text.strip("\n"))
308        return text
309
310    def substitute_text(self, text: str) -> str:
311        """
312        Performs a variable substitution on a description using the item mapper
313        of the node.
314        """
315        if text:
316            return self.mapper.substitute(text.strip("\n"))
317        return text
318
319    def _get_compound_definition(self, item: Item, definition: Any) -> Lines:
320        content = CContent()
321        content.add_description_block(
322            self.substitute_text(definition["brief"]),
323            self.substitute_text(definition["description"]))
324        kind = definition["kind"]
325        if kind == "member":
326            member = self.substitute_code(definition["definition"]) + ";"
327            content.append(member.split("\n"))
328        else:
329            content.append(f"{kind} {{")
330            content.gap = False
331            with content.indent():
332                for index, compound_member in enumerate(
333                        definition["definition"]):
334                    content.add(
335                        _add_definition(self, item, f"definition[{index}]",
336                                        compound_member,
337                                        Node._get_compound_definition))
338            name = definition["name"]
339            content.append(f"}} {name};")
340        return content.lines
341
342    def _get_enumerator_definition(self, item: Item, definition: Any) -> Lines:
343        name = item["name"]
344        if definition:
345            return f"{name} = {self.substitute_code(definition)}"
346        return f"{name}"
347
348    def _get_define_definition(self, item: Item, definition: Any) -> Lines:
349        name = item["name"]
350        value = self.substitute_code(definition)
351        if value:
352            return f"#define {name} {value}".split("\n")
353        return f"#define {name}"
354
355    def _get_function_definition(self, item: Item, definition: Any) -> Lines:
356        content = CContent()
357        name = item["name"]
358        attrs = self.substitute_code(definition["attributes"])
359        attrs = f"{attrs} " if attrs else ""
360        ret = self.substitute_code(definition["return"])
361        params = [
362            self.substitute_code(param) for param in definition["params"]
363        ]
364        body = definition["body"]
365        if body:
366            with content.function(f"{attrs}static inline {ret}", name, params):
367                content.add(self.substitute_code(body))
368        else:
369            content.declare_function(f"{attrs}{ret}", name, params)
370        return content.lines
371
372    def _get_macro_definition(self, item: Item, definition: Any) -> Lines:
373        name = item["name"]
374        params = [param["name"] for param in item["params"]]
375        if params:
376            param_line = " " + ", ".join(params) + " "
377        else:
378            param_line = ""
379        line = f"#define {name}({param_line})"
380        if len(line) > 79:
381            param_block = ", \\\n  ".join(params)
382            line = f"#define {name}( \\\n  {param_block} \\\n)"
383        if not definition:
384            return line
385        body_lines = self.substitute_code(definition).split("\n")
386        if len(body_lines) == 1 and len(line + body_lines[0]) <= 79:
387            body = " "
388        else:
389            body = " \\\n  "
390        body += " \\\n  ".join(body_lines)
391        return line + body
392
393    def _get_typedef_definition(self, _item: Item, definition: Any) -> Lines:
394        return f"typedef {self.substitute_code(definition)};"
395
396    def _get_variable_definition(self, _item: Item, definition: Any) -> Lines:
397        return f"extern {self.substitute_code(definition)};"
398
399    def _get_description(self, item: Item, ingroups: ItemMap) -> CContent:
400        content = CContent()
401        with content.doxygen_block():
402            content.add_ingroup(_get_group_identifiers(ingroups))
403            content.add_brief_description(self.substitute_text(item["brief"]))
404            content.wrap(self.substitute_text(item["description"]))
405            content.wrap(self.substitute_text(item["notes"]))
406            if "params" in item:
407                content.add_param_description(item["params"],
408                                              self.substitute_text)
409            if "return" in item:
410                ret = item["return"]
411                for retval in ret["return-values"]:
412                    content.wrap(self.substitute_text(retval["description"]),
413                                 initial_indent=self.substitute_text(
414                                     f"@retval {retval['value']} "))
415                content.wrap(self.substitute_text(ret["return"]),
416                             initial_indent="@return ")
417        return content
418
419    def _add_generic_definition(self, get_lines: GetLines) -> None:
420        self.content.add(self._get_description(self.item, self.ingroups))
421        self.content.append(
422            _add_definition(self, self.item, "definition",
423                            self.item["definition"], get_lines))
424
425
426_NODE_GENERATORS = {
427    "enum": Node.generate_enum,
428    "define": Node.generate_define,
429    "forward-declaration": Node.generate_forward_declaration,
430    "function": Node.generate_function,
431    "group": Node.generate_group,
432    "macro": Node.generate_macro,
433    "struct": Node.generate_compound,
434    "typedef": Node.generate_typedef,
435    "union": Node.generate_compound,
436    "variable": Node.generate_variable,
437}
438
439
440def _is_ready_to_bubble(before: Node, after: Node) -> bool:
441    if after in before.dependents:
442        return False
443
444    # Move the groups towards the top of the header file
445    group = "interface/group"
446    if (before.item.type == group) ^ (after.item.type == group):
447        return after.item.type == group
448
449    # Move items with an explicit placement order towards the bottom of the
450    # file
451    if before.index and after.index:
452        return after.index < before.index
453    if after.index:
454        return False
455
456    return after < before
457
458
459def _bubble_sort(nodes: List[Node]) -> List[Node]:
460    node_count = len(nodes)
461    for i in range(node_count - 1):
462        for j in range(node_count - 1 - i):
463            if _is_ready_to_bubble(nodes[j], nodes[j + 1]):
464                nodes[j], nodes[j + 1] = nodes[j + 1], nodes[j]
465    return nodes
466
467
468class _HeaderFile:
469    """ A header file. """
470    def __init__(self, item: Item, enabled_by_defined: Dict[str, str]):
471        self._item = item
472        self._content = CContent()
473        self._content.register_license_and_copyrights_of_item(item)
474        self._ingroups = _get_ingroups(item)
475        self._includes = []  # type: List[Item]
476        self._nodes = {}  # type: Dict[str, Node]
477        self.enabled_by_defined = enabled_by_defined
478
479    def add_includes(self, item: Item) -> None:
480        """ Adds the includes of the item to the header file includes. """
481        for parent in item.parents("interface-placement"):
482            if parent.type == "interface/header-file":
483                self._includes.append(parent)
484
485    def _add_child(self, item: Item) -> None:
486        self._nodes[item.uid] = Node(self, item)
487        self._content.register_license_and_copyrights_of_item(item)
488
489    def add_dependency(self, node: Node, item: Item) -> None:
490        """
491        Adds a dependency from a node to another node identified by an item if
492        the item corresponds to a node and it is not a self reference.
493        """
494        if item.uid in self._nodes and item.uid != node.item.uid:
495            other = self._nodes[item.uid]
496            node.depends_on.add(other)
497            other.dependents.add(node)
498
499    def _resolve_ingroups(self, node: Node) -> None:
500        for ingroup in node.ingroups.values():
501            self.add_dependency(node, ingroup)
502
503    def generate_nodes(self) -> None:
504        """ Generates all nodes of this header file. """
505        for child in self._item.children("interface-placement"):
506            self._add_child(child)
507        for node in self._nodes.values():
508            self._resolve_ingroups(node)
509            node.generate()
510
511    def _get_nodes_in_dependency_order(self) -> List[Node]:
512        """
513        Gets the nodes of this header file ordered according to node
514        dependencies and other criteria.
515
516        The nodes form a partially ordered set (poset).  The ordering is done
517        in two steps.  Firstly, a topological sort using Kahn's algorithm is
518        carried out.  Secondly, the nodes are sorted using a bubble sort which
519        takes node dependencies into account.  There are more efficient ways to
520        do this, however, if you experience run time problems due to this
521        method, then maybe you should reconsider your header file organization.
522        """
523        nodes_in_dependency_order = []  # type: List[Node]
524
525        # Get incoming edge degrees for all nodes
526        in_degree = {}  # type: Dict[str, int]
527        for node in self._nodes.values():
528            in_degree[node.item.uid] = len(node.dependents)
529
530        # Create a queue with all nodes with no incoming edges
531        queue = []  # type: List[Node]
532        for node in self._nodes.values():
533            if in_degree[node.item.uid] == 0:
534                queue.append(node)
535
536        # Topological sort
537        while queue:
538            node = queue.pop(0)
539            nodes_in_dependency_order.insert(0, node)
540
541            for other in node.depends_on:
542                in_degree[other.item.uid] -= 1
543                if in_degree[other.item.uid] == 0:
544                    queue.append(other)
545
546        return _bubble_sort(nodes_in_dependency_order)
547
548    def finalize(self) -> None:
549        """ Finalizes the header file. """
550        self._content.prepend_spdx_license_identifier()
551        with self._content.file_block():
552            self._content.add_ingroup(_get_group_identifiers(self._ingroups))
553            self._content.add_brief_description(self._item["brief"])
554        self._content.add_copyrights_and_licenses()
555        self._content.add_automatically_generated_warning()
556        self._content.add(f"/* Generated from spec:{self._item.uid} */")
557        with self._content.header_guard(self._item["path"]):
558            exp_mapper = _HeaderExpressionMapper(self._item,
559                                                 self.enabled_by_defined)
560            includes = [
561                CInclude(item["path"],
562                         enabled_by_to_exp(item["enabled-by"], exp_mapper))
563                for item in self._includes if item != self._item
564            ]
565            includes.extend([
566                CInclude(link.item["path"],
567                         enabled_by_to_exp(link["enabled-by"], exp_mapper))
568                for link in self._item.links_to_parents()
569                if link.role == "interface-include"
570            ])
571            self._content.add_includes(includes)
572            with self._content.extern_c():
573                for node in self._get_nodes_in_dependency_order():
574                    self._content.add(node.content)
575
576    def write(self, domain_path: str) -> None:
577        """ Writes the header file. """
578        self._content.write(
579            os.path.join(domain_path, self._item["prefix"],
580                         self._item["path"]))
581
582
583def _generate_header_file(item: Item, domains: Dict[str, str],
584                          enabled_by_defined: Dict[str, str]) -> None:
585    domain = item.parent("interface-placement")
586    assert domain["interface-type"] == "domain"
587    domain_path = domains.get(domain.uid, None)
588    if domain_path is None:
589        return
590    header_file = _HeaderFile(item, enabled_by_defined)
591    header_file.generate_nodes()
592    header_file.finalize()
593    header_file.write(domain_path)
594
595
596def _gather_enabled_by_defined(item_level_interfaces: List[str],
597                               item_cache: ItemCache) -> Dict[str, str]:
598    enabled_by_defined = {}  # type: Dict[str, str]
599    for uid in item_level_interfaces:
600        for child in item_cache[uid].children("interface-placement"):
601            define = f"defined(${{{child.uid}:/name}})"
602            enabled_by_defined[child["name"]] = define
603    return enabled_by_defined
604
605
606def generate(config: dict, item_cache: ItemCache) -> None:
607    """
608    Generates header files according to the configuration.
609
610    :param config: A dictionary with configuration entries.
611    :param item_cache: The specification item cache containing the interfaces.
612    """
613    domains = config["domains"]
614    enabled_by_defined = _gather_enabled_by_defined(
615        config["item-level-interfaces"], item_cache)
616    for item in item_cache.all.values():
617        if item.type == "interface/header-file":
618            _generate_header_file(item, domains, enabled_by_defined)
Note: See TracBrowser for help on using the repository browser.