source: rtems-central/rtemsqual/interface.py @ 348596b

Last change on this file since 348596b was 348596b, checked in by Sebastian Huber <sebastian.huber@…>, on 05/11/20 at 07:57:57

content: Add CInclude

Support enabled-by attribute for C includes.

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