source: rtems-central/rtemsspec/interface.py @ 4ad78f2

Last change on this file since 4ad78f2 was 4ad78f2, checked in by Sebastian Huber <sebastian.huber@…>, on 07/24/20 at 10:25:31

spec: Add header file brief descriptions

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