source: rtems-central/rtemsspec/interface.py @ 70430d6

Last change on this file since 70430d6 was 70430d6, checked in by Sebastian Huber <sebastian.huber@…>, on 09/10/20 at 14:03:37

interface: Add Doxygen links to config options

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