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

Last change on this file since ddbc8f7 was ddbc8f7, checked in by Sebastian Huber <sebastian.huber@…>, on 04/28/20 at 07:31:39

interface: New module

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