source: rtems-central/rtemsspec/interface.py @ e165b7d

Last change on this file since e165b7d was e165b7d, checked in by Sebastian Huber <sebastian.huber@…>, on Dec 1, 2020 at 11:07:29 AM

items: Enable custom get value for lists

  • Property mode set to 100644
File size: 24.5 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, Set
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, \
35    ItemGetValueMap, ItemMapper
36
37ItemMap = Dict[str, Item]
38Lines = Union[str, List[str]]
39GetLines = Callable[["Node", Item, Any], Lines]
40
41
42def _get_ingroups(item: Item) -> ItemMap:
43    ingroups = {}  # type: ItemMap
44    for group in item.parents("interface-ingroup"):
45        ingroups[group.uid] = group
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 = item.parent("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",
80                           get_value_doxygen_function)
81        self.add_get_value("interface/variable/doc:/name", get_value_hash)
82        for opt in ["feature-enable", "feature", "initializer", "integer"]:
83            name = f"interface/appl-config-option/{opt}/doc:/name"
84            self.add_get_value(name, get_value_hash)
85        self.add_get_value("interface/unspecified-function/doc:/name",
86                           get_value_doxygen_function)
87
88    @contextmanager
89    def code(self) -> Iterator[None]:
90        """ Enables code mapping. """
91        code_or_doc = self._code_or_doc
92        self._code_or_doc = "code"
93        yield
94        self._code_or_doc = code_or_doc
95
96    def get_value_map(self, item: Item) -> ItemGetValueMap:
97        if self._code_or_doc == "code" and item["type"] == "interface":
98            node = self._node
99            header_file = node.header_file
100            if item["interface-type"] == "enumerator":
101                for child in item.children("interface-enumerator"):
102                    header_file.add_includes(child)
103            else:
104                header_file.add_includes(item)
105            header_file.add_dependency(node, item)
106        return self._get_value_map.get(f"{item.type}/{self._code_or_doc}", {})
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
179    # pylint: disable=too-many-instance-attributes
180    def __init__(self, header_file: "_HeaderFile", item: Item):
181        self.header_file = header_file
182        self.item = item
183        self.ingroups = _get_ingroups(item)
184        self.dependents = set()  # type: Set["Node"]
185        self.depends_on = set()  # type: Set["Node"]
186        self.content = CContent()
187        self.mapper = _InterfaceMapper(self)
188        try:
189            group = item.child("placement-order")
190        except IndexError:
191            self.index = None
192        else:
193            self.index = (group.uid,
194                          list(group.parents("placement-order")).index(item))
195
196    def __lt__(self, other: "Node") -> bool:
197        return self.item.uid < other.item.uid
198
199    @contextmanager
200    def _enum_struct_or_union(self) -> Iterator[None]:
201        self.content.add(self._get_description(self.item, self.ingroups))
202        name = self.item["name"]
203        typename = self.item["interface-type"]
204        kind = self.item["definition-kind"]
205        if kind == f"{typename}-only":
206            self.content.append(f"{typename} {name} {{")
207        elif kind == "typedef-only":
208            self.content.append(f"typedef {typename} {{")
209        else:
210            self.content.append(f"typedef {typename} {name} {{")
211        self.content.push_indent()
212        yield
213        self.content.pop_indent()
214        if kind == f"{typename}-only":
215            self.content.append("};")
216        else:
217            self.content.append(f"}} {name};")
218
219    def _generate(self) -> None:
220        self.content.add(f"/* Generated from spec:{self.item.uid} */")
221        _NODE_GENERATORS[self.item["interface-type"]](self)
222
223    def generate(self) -> None:
224        """ Generates a node to generate the node content. """
225        enabled_by = self.item["enabled-by"]
226        if not isinstance(enabled_by, bool) or not enabled_by:
227            mapper = _ItemLevelExpressionMapper(self.mapper)
228            self.content.add(f"#if {enabled_by_to_exp(enabled_by, mapper)}")
229            with self.content.indent():
230                self._generate()
231            self.content.add("#endif")
232        else:
233            self._generate()
234
235    def generate_compound(self) -> None:
236        """ Generates a compound (struct or union). """
237        with self._enum_struct_or_union():
238            for index, definition in enumerate(self.item["definition"]):
239                self.content.add(
240                    _add_definition(self, self.item, f"definition[{index}]",
241                                    definition, Node._get_compound_definition))
242
243    def generate_enum(self) -> None:
244        """ Generates an enum. """
245        with self._enum_struct_or_union():
246            enumerators = []  # type: List[CContent]
247            for parent in self.item.parents("interface-enumerator"):
248                enumerator = self._get_description(parent, {})
249                enumerator.append(
250                    _add_definition(self, parent, "definition",
251                                    parent["definition"],
252                                    Node._get_enumerator_definition))
253                enumerators.append(enumerator)
254            for enumerator in enumerators[0:-1]:
255                enumerator.lines[-1] += ","
256                enumerator.append("")
257                self.content.append(enumerator)
258            try:
259                self.content.append(enumerators[-1])
260            except IndexError:
261                pass
262
263    def generate_define(self) -> None:
264        """ Generates a define. """
265        self._add_generic_definition(Node._get_define_definition)
266
267    def generate_forward_declaration(self) -> None:
268        """ Generates a forward declaration. """
269        self.content.append([
270            "", "/* Forward declaration */",
271            _forward_declaration(self.item) + ";"
272        ])
273
274    def generate_function(self) -> None:
275        """ Generates a function. """
276        self._add_generic_definition(Node._get_function_definition)
277
278    def generate_group(self) -> None:
279        """ Generates a group. """
280        for ingroup in self.ingroups.values():
281            self.header_file.add_dependency(self, ingroup)
282        self.content.add_group(self.item["identifier"], self.item["name"],
283                               _get_group_identifiers(self.ingroups),
284                               self.substitute_text(self.item["brief"]),
285                               self.substitute_text(self.item["description"]))
286
287    def generate_macro(self) -> None:
288        """ Generates a macro. """
289        self._add_generic_definition(Node._get_macro_definition)
290
291    def generate_typedef(self) -> None:
292        """ Generates a typedef. """
293        self._add_generic_definition(Node._get_typedef_definition)
294
295    def generate_variable(self) -> None:
296        """ Generates a variable. """
297        self._add_generic_definition(Node._get_variable_definition)
298
299    def substitute_code(self, text: str) -> str:
300        """
301        Performs a variable substitution on code using the item mapper of the
302        node.
303        """
304        if text:
305            with self.mapper.code():
306                return self.mapper.substitute(text.strip("\n"))
307        return text
308
309    def substitute_text(self, text: str) -> str:
310        """
311        Performs a variable substitution on a description using the item mapper
312        of the node.
313        """
314        if text:
315            return self.mapper.substitute(text.strip("\n"))
316        return text
317
318    def _get_compound_definition(self, item: Item, definition: Any) -> Lines:
319        content = CContent()
320        content.add_description_block(
321            self.substitute_text(definition["brief"]),
322            self.substitute_text(definition["description"]))
323        kind = definition["kind"]
324        if kind == "member":
325            member = self.substitute_code(definition["definition"]) + ";"
326            content.append(member.split("\n"))
327        else:
328            content.append(f"{kind} {{")
329            content.gap = False
330            with content.indent():
331                for index, compound_member in enumerate(
332                        definition["definition"]):
333                    content.add(
334                        _add_definition(self, item, f"definition[{index}]",
335                                        compound_member,
336                                        Node._get_compound_definition))
337            name = definition["name"]
338            content.append(f"}} {name};")
339        return content.lines
340
341    def _get_enumerator_definition(self, item: Item, definition: Any) -> Lines:
342        name = item["name"]
343        if definition:
344            return f"{name} = {self.substitute_code(definition)}"
345        return f"{name}"
346
347    def _get_define_definition(self, item: Item, definition: Any) -> Lines:
348        name = item["name"]
349        value = self.substitute_code(definition)
350        if value:
351            return f"#define {name} {value}".split("\n")
352        return f"#define {name}"
353
354    def _get_function_definition(self, item: Item, definition: Any) -> Lines:
355        content = CContent()
356        name = item["name"]
357        attrs = self.substitute_code(definition["attributes"])
358        attrs = f"{attrs} " if attrs else ""
359        ret = self.substitute_code(definition["return"])
360        params = [
361            self.substitute_code(param) for param in definition["params"]
362        ]
363        body = definition["body"]
364        if body:
365            with content.function(f"{attrs}static inline {ret}", name, params):
366                content.add(self.substitute_code(body))
367        else:
368            content.declare_function(f"{attrs}{ret}", name, params)
369        return content.lines
370
371    def _get_macro_definition(self, item: Item, definition: Any) -> Lines:
372        name = item["name"]
373        params = [param["name"] for param in item["params"]]
374        if params:
375            param_line = " " + ", ".join(params) + " "
376        else:
377            param_line = ""
378        line = f"#define {name}({param_line})"
379        if len(line) > 79:
380            param_block = ", \\\n  ".join(params)
381            line = f"#define {name}( \\\n  {param_block} \\\n)"
382        if not definition:
383            return line
384        body_lines = self.substitute_code(definition).split("\n")
385        if len(body_lines) == 1 and len(line + body_lines[0]) <= 79:
386            body = " "
387        else:
388            body = " \\\n  "
389        body += " \\\n  ".join(body_lines)
390        return line + body
391
392    def _get_typedef_definition(self, _item: Item, definition: Any) -> Lines:
393        return f"typedef {self.substitute_code(definition)};"
394
395    def _get_variable_definition(self, _item: Item, definition: Any) -> Lines:
396        return f"extern {self.substitute_code(definition)};"
397
398    def _get_description(self, item: Item, ingroups: ItemMap) -> CContent:
399        content = CContent()
400        with content.doxygen_block():
401            content.add_ingroup(_get_group_identifiers(ingroups))
402            content.add_brief_description(self.substitute_text(item["brief"]))
403            content.wrap(self.substitute_text(item["description"]))
404            content.wrap(self.substitute_text(item["notes"]))
405            if "params" in item:
406                content.add_param_description(item["params"],
407                                              self.substitute_text)
408            if "return" in item:
409                ret = item["return"]
410                for retval in ret["return-values"]:
411                    content.wrap(self.substitute_text(retval["description"]),
412                                 initial_indent=self.substitute_text(
413                                     f"@retval {retval['value']} "))
414                content.wrap(self.substitute_text(ret["return"]),
415                             initial_indent="@return ")
416        return content
417
418    def _add_generic_definition(self, get_lines: GetLines) -> None:
419        self.content.add(self._get_description(self.item, self.ingroups))
420        self.content.append(
421            _add_definition(self, self.item, "definition",
422                            self.item["definition"], get_lines))
423
424
425_NODE_GENERATORS = {
426    "enum": Node.generate_enum,
427    "define": Node.generate_define,
428    "forward-declaration": Node.generate_forward_declaration,
429    "function": Node.generate_function,
430    "group": Node.generate_group,
431    "macro": Node.generate_macro,
432    "struct": Node.generate_compound,
433    "typedef": Node.generate_typedef,
434    "union": Node.generate_compound,
435    "variable": Node.generate_variable,
436}
437
438
439def _is_ready_to_bubble(before: Node, after: Node) -> bool:
440    if after in before.dependents:
441        return False
442
443    # Move the groups towards the top of the header file
444    group = "interface/group"
445    if (before.item.type == group) ^ (after.item.type == group):
446        return after.item.type == group
447
448    # Move items with an explicit placement order towards the bottom of the
449    # file
450    if before.index and after.index:
451        return after.index < before.index
452    if after.index:
453        return False
454
455    return after < before
456
457
458def _bubble_sort(nodes: List[Node]) -> List[Node]:
459    node_count = len(nodes)
460    for i in range(node_count - 1):
461        for j in range(node_count - 1 - i):
462            if _is_ready_to_bubble(nodes[j], nodes[j + 1]):
463                nodes[j], nodes[j + 1] = nodes[j + 1], nodes[j]
464    return nodes
465
466
467class _HeaderFile:
468    """ A header file. """
469    def __init__(self, item: Item, enabled_by_defined: Dict[str, str]):
470        self._item = item
471        self._content = CContent()
472        self._content.register_license_and_copyrights_of_item(item)
473        self._ingroups = _get_ingroups(item)
474        self._includes = []  # type: List[Item]
475        self._nodes = {}  # type: Dict[str, Node]
476        self.enabled_by_defined = enabled_by_defined
477
478    def add_includes(self, item: Item) -> None:
479        """ Adds the includes of the item to the header file includes. """
480        for parent in item.parents("interface-placement"):
481            if parent.type == "interface/header-file":
482                self._includes.append(parent)
483
484    def _add_child(self, item: Item) -> None:
485        self._nodes[item.uid] = Node(self, item)
486        self._content.register_license_and_copyrights_of_item(item)
487
488    def add_dependency(self, node: Node, item: Item) -> None:
489        """
490        Adds a dependency from a node to another node identified by an item if
491        the item corresponds to a node and it is not a self reference.
492        """
493        if item.uid in self._nodes and item.uid != node.item.uid:
494            other = self._nodes[item.uid]
495            node.depends_on.add(other)
496            other.dependents.add(node)
497
498    def _resolve_ingroups(self, node: Node) -> None:
499        for ingroup in node.ingroups.values():
500            self.add_dependency(node, ingroup)
501
502    def generate_nodes(self) -> None:
503        """ Generates all nodes of this header file. """
504        for child in self._item.children("interface-placement"):
505            self._add_child(child)
506        for node in self._nodes.values():
507            self._resolve_ingroups(node)
508            node.generate()
509
510    def _get_nodes_in_dependency_order(self) -> List[Node]:
511        """
512        Gets the nodes of this header file ordered according to node
513        dependencies and other criteria.
514
515        The nodes form a partially ordered set (poset).  The ordering is done
516        in two steps.  Firstly, a topological sort using Kahn's algorithm is
517        carried out.  Secondly, the nodes are sorted using a bubble sort which
518        takes node dependencies into account.  There are more efficient ways to
519        do this, however, if you experience run time problems due to this
520        method, then maybe you should reconsider your header file organization.
521        """
522        nodes_in_dependency_order = []  # type: List[Node]
523
524        # Get incoming edge degrees for all nodes
525        in_degree = {}  # type: Dict[str, int]
526        for node in self._nodes.values():
527            in_degree[node.item.uid] = len(node.dependents)
528
529        # Create a queue with all nodes with no incoming edges
530        queue = []  # type: List[Node]
531        for node in self._nodes.values():
532            if in_degree[node.item.uid] == 0:
533                queue.append(node)
534
535        # Topological sort
536        while queue:
537            node = queue.pop(0)
538            nodes_in_dependency_order.insert(0, node)
539
540            for other in node.depends_on:
541                in_degree[other.item.uid] -= 1
542                if in_degree[other.item.uid] == 0:
543                    queue.append(other)
544
545        return _bubble_sort(nodes_in_dependency_order)
546
547    def finalize(self) -> None:
548        """ Finalizes the header file. """
549        self._content.prepend_spdx_license_identifier()
550        with self._content.file_block():
551            self._content.add_ingroup(_get_group_identifiers(self._ingroups))
552            self._content.add_brief_description(self._item["brief"])
553        self._content.add_copyrights_and_licenses()
554        self._content.add_automatically_generated_warning()
555        self._content.add(f"/* Generated from spec:{self._item.uid} */")
556        with self._content.header_guard(self._item["path"]):
557            exp_mapper = _HeaderExpressionMapper(self._item,
558                                                 self.enabled_by_defined)
559            includes = [
560                CInclude(item["path"],
561                         enabled_by_to_exp(item["enabled-by"], exp_mapper))
562                for item in self._includes if item != self._item
563            ]
564            includes.extend([
565                CInclude(link.item["path"],
566                         enabled_by_to_exp(link["enabled-by"], exp_mapper))
567                for link in self._item.links_to_parents()
568                if link.role == "interface-include"
569            ])
570            self._content.add_includes(includes)
571            with self._content.extern_c():
572                for node in self._get_nodes_in_dependency_order():
573                    self._content.add(node.content)
574
575    def write(self, domain_path: str) -> None:
576        """ Writes the header file. """
577        self._content.write(
578            os.path.join(domain_path, self._item["prefix"],
579                         self._item["path"]))
580
581
582def _generate_header_file(item: Item, domains: Dict[str, str],
583                          enabled_by_defined: Dict[str, str]) -> None:
584    domain = item.parent("interface-placement")
585    assert domain["interface-type"] == "domain"
586    domain_path = domains.get(domain.uid, None)
587    if domain_path is None:
588        return
589    header_file = _HeaderFile(item, enabled_by_defined)
590    header_file.generate_nodes()
591    header_file.finalize()
592    header_file.write(domain_path)
593
594
595def _gather_enabled_by_defined(item_level_interfaces: List[str],
596                               item_cache: ItemCache) -> Dict[str, str]:
597    enabled_by_defined = {}  # type: Dict[str, str]
598    for uid in item_level_interfaces:
599        for child in item_cache[uid].children("interface-placement"):
600            define = f"defined(${{{child.uid}:/name}})"
601            enabled_by_defined[child["name"]] = define
602    return enabled_by_defined
603
604
605def generate(config: dict, item_cache: ItemCache) -> None:
606    """
607    Generates header files according to the configuration.
608
609    :param config: A dictionary with configuration entries.
610    :param item_cache: The specification item cache containing the interfaces.
611    """
612    domains = config["domains"]
613    enabled_by_defined = _gather_enabled_by_defined(
614        config["item-level-interfaces"], item_cache)
615    for item in item_cache.all.values():
616        if item.type == "interface/header-file":
617            _generate_header_file(item, domains, enabled_by_defined)
Note: See TracBrowser for help on using the repository browser.