source: rtems-central/rtemsqual/interface.py @ aab2f5e

Last change on this file since aab2f5e was aab2f5e, checked in by Sebastian Huber <sebastian.huber@…>, on 05/06/20 at 08:13:03

items: Rework item mapper

Rename Item.get() in Item.get_by_key_path() since it may conflict with a
future dict-like get() method.

In Item.get_by_key_path() first call the get_value() callable to get the
value. This allows a context-sensitive formatting of values.

  • Property mode set to 100644
File size: 18.3 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, Optional, Union
30
31from rtemsqual.content import CContent
32from rtemsqual.items import Item, ItemCache, ItemMapper
33
34ItemMap = Dict[str, Item]
35Lines = Union[str, List[str]]
36GetLines = Callable[["Node", Item, Any], Lines]
37
38
39def _designator(name: str) -> str:
40    return name.replace(" ", "")
41
42
43def _get_ingroups(item: Item) -> ItemMap:
44    ingroups = {}  # type: ItemMap
45    for link in item.links_to_parents():
46        if link["role"] == "ingroup":
47            ingroups[link.item.uid] = link.item
48    return ingroups
49
50
51def _ingroups_to_designators(ingroups: ItemMap) -> List[str]:
52    return [_designator(item["group-name"]) for item in ingroups.values()]
53
54
55def _forward_declaration(item: Item) -> str:
56    target = item.map(item["interface-target"])
57    return target["interface-type"] + " " + target["interface-name"]
58
59
60def _add_definition(node: "Node", item: Item, prefix: str,
61                    value: Dict[str, Any], get_lines: GetLines) -> CContent:
62    content = CContent()
63    default = value["default"]
64    variants = value["variants"]
65    if variants:
66        ifelse = "#if "
67        with node.mapper.prefix(os.path.join(prefix, "variants")):
68            for variant in variants:
69                enabled_by = variant["enabled-by"].strip()
70                content.append(f"{ifelse}{enabled_by}")
71                with content.indent():
72                    content.append(get_lines(node, item,
73                                             variant["definition"]))
74                ifelse = "#elif "
75        if default:
76            content.append("#else")
77            with node.mapper.prefix(os.path.join(prefix, "default")):
78                with content.indent():
79                    content.append(get_lines(node, item, default))
80        content.append("#endif")
81    else:
82        with node.mapper.prefix(os.path.join(prefix, "default")):
83            content.append(get_lines(node, item, default))
84    return content
85
86
87def _get_description(item: Item, ingroups: ItemMap) -> CContent:
88    content = CContent()
89    with content.doxygen_block():
90        content.add_ingroup(_ingroups_to_designators(ingroups))
91        content.add_brief_description(item["interface-brief"])
92        content.add(content.wrap(item["interface-description"]))
93        if "interface-params" in item:
94            for param in item["interface-params"]:
95                content.add(
96                    content.wrap(param["name"] + " " + param["description"],
97                                 intro=_PARAM[param["dir"]]))
98        if "interface-return" in item:
99            ret = item["interface-return"]
100            for retval in ret["return-values"]:
101                val = retval["value"]
102                intro = f"@retval {val} "
103                content.add(content.wrap(retval["description"], intro=intro))
104            content.add(content.wrap(ret["return"], intro="@return "))
105    return content
106
107
108class InterfaceMapper(ItemMapper):
109    """ Interface mapper. """
110    def __init__(self, node: "Node"):
111        super().__init__(node.item)
112        self._node = node
113
114    def __getitem__(self, identifier):
115        item, value = self.map(identifier)
116        if item["type"] == "interface":
117            node = self._node
118            header_file = node.header_file
119            if item["interface-type"] == "enumerator":
120                for link in item.links_to_children():
121                    if link["role"] == "enumerator":
122                        header_file.add_includes(link.item)
123            else:
124                header_file.add_includes(item)
125            header_file.add_potential_edge(node, item)
126        return value
127
128    def get_value(self, item: Item, _path: str, _value: Any, key: str,
129                  _index: Optional[int]) -> Any:
130        """ Gets a value by key and optional index. """
131        # pylint: disable=no-self-use
132        if key == "interface-name" and item["type"] == "interface" and item[
133                "interface-type"] == "forward-declaration":
134            return _forward_declaration(item)
135        raise KeyError
136
137
138_PARAM = {
139    None: "@param ",
140    "in": "@param[in] ",
141    "out": "@param[out] ",
142    "inout": "@param[in,out] ",
143}
144
145
146class Node:
147    """ Nodes of a header file. """
148    def __init__(self, header_file: "HeaderFile", item: Item,
149                 ingroups: ItemMap):
150        self.header_file = header_file
151        self.item = item
152        self.ingroups = ingroups
153        self.in_edges = {}  # type: ItemMap
154        self.out_edges = {}  # type: ItemMap
155        self.content = CContent()
156        self.mapper = InterfaceMapper(self)
157
158    def __lt__(self, other: "Node") -> bool:
159        return self.item.uid < other.item.uid
160
161    @contextmanager
162    def _enum_struct_or_union(self) -> Iterator[None]:
163        self.content.add(_get_description(self.item, self.ingroups))
164        name = self.item["interface-name"]
165        typename = self.item["interface-type"]
166        kind = self.item["interface-definition-kind"]
167        if kind == f"{typename}-only":
168            self.content.append(f"{typename} {name} {{")
169        elif kind == "typedef-only":
170            self.content.append(f"typedef {typename} {{")
171        else:
172            self.content.append(f"typedef {typename} {name} {{")
173        self.content.push_indent()
174        yield
175        self.content.pop_indent()
176        if kind == f"{typename}-only":
177            self.content.append("};")
178        else:
179            self.content.append(f"}} {name};")
180
181    def generate(self) -> None:
182        """ Generates a node to generate the node content. """
183        _NODE_GENERATORS[self.item["interface-type"]](self)
184
185    def generate_compound(self) -> None:
186        """ Generates a compound (struct or union). """
187        with self._enum_struct_or_union():
188            self.content.append(
189                _add_definition(self, self.item, "interface-definition",
190                                self.item["interface-definition"],
191                                Node._get_compound_definition))
192
193    def generate_enum(self) -> None:
194        """ Generates an enum. """
195        with self._enum_struct_or_union():
196            enumerators = []  # type: List[CContent]
197            for link in self.item.links_to_parents():
198                if link["role"] != "enumerator":
199                    continue
200                enumerator = _get_description(link.item, {})
201                enumerator.append(
202                    _add_definition(self, link.item, "interface-definition",
203                                    link.item["interface-definition"],
204                                    Node._get_enumerator_definition))
205                enumerators.append(enumerator)
206            for enumerator in enumerators[0:-1]:
207                enumerator.lines[-1] += ","
208                enumerator.append("")
209                self.content.append(enumerator)
210            try:
211                self.content.append(enumerators[-1])
212            except IndexError:
213                pass
214
215    def generate_define(self) -> None:
216        """ Generates a define. """
217        self._add_generic_definition(Node._get_define_definition)
218
219    def generate_forward_declaration(self) -> None:
220        """ Generates a forward declaration. """
221        self.content.append([
222            "", "/* Forward declaration */",
223            _forward_declaration(self.item) + ";"
224        ])
225
226    def generate_function(self) -> None:
227        """ Generates a function. """
228        self._add_generic_definition(Node._get_function_definition)
229
230    def generate_group(self) -> None:
231        """ Generates a group. """
232        self.header_file.add_ingroup(self.item)
233        for ingroup in self.ingroups.values():
234            self.header_file.add_potential_edge(self, ingroup)
235        name = self.item["group-name"]
236        self.content.add_group(_designator(name), name,
237                               _ingroups_to_designators(self.ingroups),
238                               self.item["group-brief"],
239                               self.item["group-description"])
240
241    def generate_macro(self) -> None:
242        """ Generates a macro. """
243        self._add_generic_definition(Node._get_macro_definition)
244
245    def generate_typedef(self) -> None:
246        """ Generates a typedef. """
247        self._add_generic_definition(Node._get_typedef_definition)
248
249    def generate_variable(self) -> None:
250        """ Generates a variable. """
251        self._add_generic_definition(Node._get_variable_definition)
252
253    def substitute(self, text: str) -> str:
254        """
255        Performs a variable substitution using the item mapper of the node.
256        """
257        return self.mapper.substitute(text.strip("\n"))
258
259    def _get_compound_definition(self, item: Item, definition: Any) -> Lines:
260        content = CContent()
261        with content.doxygen_block():
262            content.add_brief_description(definition["brief"])
263            content.add(content.wrap(definition["description"]))
264        kind = definition["kind"]
265        if kind == "member":
266            member = self.substitute(definition["definition"]) + ";"
267            content.append(member.split("\n"))
268        else:
269            content.append(f"{kind} {{")
270            content.gap = False
271            with content.indent():
272                index = 0
273                for compound_member in definition["definition"]:
274                    content.add(
275                        _add_definition(self, item, f"definition[{index}]",
276                                        compound_member,
277                                        Node._get_compound_definition))
278                    index += 1
279            name = definition["name"]
280            content.append(f"}} {name};")
281        return content.lines
282
283    def _get_enumerator_definition(self, item: Item, definition: Any) -> Lines:
284        name = item["interface-name"]
285        if definition:
286            return f"{name} = {self.substitute(definition)}"
287        return f"{name}"
288
289    def _get_define_definition(self, item: Item, definition: Any) -> Lines:
290        name = item["interface-name"]
291        return f"#define {name} {self.substitute(definition)}".split("\n")
292
293    def _get_function_definition(self, item: Item, definition: Any) -> Lines:
294        ret = self.substitute(definition["return"])
295        if "body" in definition:
296            ret = "static inline " + ret
297        name = item["interface-name"]
298        space = "" if ret.endswith("*") else " "
299        params = [self.substitute(param) for param in definition["params"]]
300        param_line = ", ".join(params)
301        line = f"{ret}{space}{name}({param_line})"
302        if len(line) > 79:
303            param_block = ",\n  ".join(params)
304            line = f"{ret}{space}{name}(\n  {param_block}\n)"
305        if "body" in definition:
306            body = self.substitute("\n  ".join(
307                definition["body"].strip("\n").split("\n")))
308            line = f"""{line}
309{{
310  {body}
311}}"""
312        else:
313            line += ";"
314        return line
315
316    def _get_macro_definition(self, item: Item, definition: Any) -> Lines:
317        name = item["interface-name"]
318        params = [param["name"] for param in item["interface-params"]]
319        param_line = ", ".join(params)
320        line = f"#define {name}({param_line}) "
321        if len(line) > 79:
322            param_block = ", \\\n  ".join(params)
323            line = f"#define {name}( \\\n  {param_block} \\\n) "
324        body = self.substitute(" \\\n  ".join(
325            definition.strip("\n").split("\n")))
326        return line + body
327
328    def _get_typedef_definition(self, _item: Item, definition: Any) -> Lines:
329        return f"typedef {self.substitute(definition)};"
330
331    def _get_variable_definition(self, _item: Item, definition: Any) -> Lines:
332        return f"extern {self.substitute(definition)};"
333
334    def _add_generic_definition(self, get_lines: GetLines) -> None:
335        self.content.add(_get_description(self.item, self.ingroups))
336        self.content.append(
337            _add_definition(self, self.item, "interface-definition",
338                            self.item["interface-definition"], get_lines))
339
340
341_NODE_GENERATORS = {
342    "enum": Node.generate_enum,
343    "define": Node.generate_define,
344    "forward-declaration": Node.generate_forward_declaration,
345    "function": Node.generate_function,
346    "group": Node.generate_group,
347    "macro": Node.generate_macro,
348    "struct": Node.generate_compound,
349    "typedef": Node.generate_typedef,
350    "union": Node.generate_compound,
351    "variable": Node.generate_variable,
352}
353
354
355class HeaderFile:
356    """ A header file. """
357    def __init__(self):
358        self._content = CContent()
359        self._ingroups = {}  # type: ItemMap
360        self._includes = []  # type: List[Item]
361        self._nodes = {}  # type: Dict[str, Node]
362
363    def add_includes(self, item: Item) -> None:
364        """ Adds the includes of the item to the header file includes. """
365        for link in item.links_to_parents():
366            if link["role"] == "include":
367                self._includes.append(link.item)
368
369    def add_ingroup(self, item: Item) -> None:
370        """ Adds an ingroup to the header file. """
371        self._ingroups[item.uid] = item
372
373    def _add_child(self, item: Item) -> None:
374        ingroups = _get_ingroups(item)
375        self._ingroups.update(ingroups)
376        self._nodes[item.uid] = Node(self, item, ingroups)
377        item.register_license_and_copyrights(self._content)
378
379    def add_potential_edge(self, node: Node, item: Item) -> None:
380        """
381        Adds a potential edge from a node to another node identified by an
382        item.
383        """
384        if item.uid in self._nodes and item.uid != node.item.uid:
385            node.out_edges[item.uid] = item
386            self._nodes[item.uid].in_edges[node.item.uid] = node.item
387
388    def _resolve_ingroups(self, node: Node) -> None:
389        for ingroup in node.ingroups.values():
390            self.add_potential_edge(node, ingroup)
391
392    def generate_nodes(self, item: Item) -> None:
393        """ Generates all nodes of this header file. """
394        for link in item.links_to_children():
395            if link["role"] == "include":
396                self._add_child(link.item)
397        for node in self._nodes.values():
398            self._resolve_ingroups(node)
399            node.generate()
400
401    def _get_nodes_in_dependency_order(self) -> List[Node]:
402        """
403        Gets the nodes of this header file ordered according to node
404        dependencies and UIDs.
405
406        Performs a topological sort using Kahn's algorithm.
407        """
408        nodes_in_dependency_order = []  # type: List[Node]
409
410        # Get incoming edge degrees for all nodes
411        in_degree = {}  # type: Dict[str, int]
412        for node in self._nodes.values():
413            in_degree[node.item.uid] = len(node.in_edges)
414
415        # Create a queue with all nodes with no incoming edges sorted by UID
416        queue = []  # type: List[Node]
417        for node in self._nodes.values():
418            if in_degree[node.item.uid] == 0:
419                queue.append(node)
420        queue.sort(reverse=True)
421
422        # Topological sort
423        while queue:
424            node = queue.pop(0)
425            nodes_in_dependency_order.insert(0, node)
426
427            # Sort by UID
428            for uid in sorted(node.out_edges.keys()):
429                in_degree[uid] -= 1
430                if in_degree[uid] == 0:
431                    queue.append(self._nodes[uid])
432
433        return nodes_in_dependency_order
434
435    def finalize(self, item: Item) -> None:
436        """ Finalizes the header file. """
437        self._content.add_spdx_license_identifier()
438        with self._content.file_block():
439            self._content.add_ingroup(_ingroups_to_designators(self._ingroups))
440        self._content.add_copyrights_and_licenses()
441        with self._content.header_guard(item["path-include"]):
442            self._content.add_includes(
443                [inc["path-include"] for inc in self._includes if inc != item])
444            with self._content.extern_c():
445                for node in self._get_nodes_in_dependency_order():
446                    self._content.add(node.content)
447
448    def write(self, filename: str) -> None:
449        """ Writes the header file. """
450        self._content.write(filename)
451
452
453def _generate_header_file(item: Item, domains: Dict[str, str]) -> None:
454    dom = item["path-domain"]
455    if dom not in domains:
456        return
457    header_file = HeaderFile()
458    header_file.generate_nodes(item)
459    header_file.finalize(item)
460    header_file.write(
461        os.path.join(domains[dom], item["path-prefix"], item["path-include"]))
462
463
464def _visit_header_files(item: Item, domains: Dict[str, str]) -> None:
465    for child in item.children():
466        _visit_header_files(child, domains)
467    if item["type"] == "interface" and item["interface-type"] == "header-file":
468        _generate_header_file(item, domains)
469
470
471def generate(config: dict, item_cache: ItemCache) -> None:
472    """
473    Generates header files according to the configuration.
474
475    :param config: A dictionary with configuration entries.
476    :param item_cache: The specification item cache containing the interfaces.
477    """
478    for item in item_cache.top_level.values():
479        _visit_header_files(item, config["path-domains"])
Note: See TracBrowser for help on using the repository browser.