source: rtems-central/rtemsqual/interface.py @ 05246b3

Last change on this file since 05246b3 was 05246b3, checked in by Sebastian Huber <sebastian.huber@…>, on May 11, 2020 at 7:00:49 AM

items: Add and use Link.role

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