source: rtems-central/rtemsqual/interface.py @ 02c58ef

Last change on this file since 02c58ef was 02c58ef, checked in by Sebastian Huber <sebastian.huber@…>, on 05/10/20 at 17:58:25

Rename path attributes

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