summaryrefslogtreecommitdiffstats
path: root/rtemsqual/interface.py
diff options
context:
space:
mode:
authorSebastian Huber <sebastian.huber@embedded-brains.de>2020-04-28 09:31:39 +0200
committerSebastian Huber <sebastian.huber@embedded-brains.de>2020-05-28 10:34:46 +0200
commitddbc8f717afdf85355bd6d8c8aea85c3b7826571 (patch)
treeab5392e883b889d1f240fffb6462a4f90f5da867 /rtemsqual/interface.py
parentcontent: Rework API (diff)
downloadrtems-central-ddbc8f717afdf85355bd6d8c8aea85c3b7826571.tar.bz2
interface: New module
Diffstat (limited to 'rtemsqual/interface.py')
-rw-r--r--rtemsqual/interface.py475
1 files changed, 475 insertions, 0 deletions
diff --git a/rtemsqual/interface.py b/rtemsqual/interface.py
new file mode 100644
index 00000000..732d2d03
--- /dev/null
+++ b/rtemsqual/interface.py
@@ -0,0 +1,475 @@
+# SPDX-License-Identifier: BSD-2-Clause
+""" This module provides functions for the generation of interfaces. """
+
+# Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from contextlib import contextmanager
+import os
+from typing import Any, Callable, Dict, Iterator, List, Union
+
+from rtemsqual.content import CContent
+from rtemsqual.items import Item, ItemCache, ItemMapper
+
+ItemMap = Dict[str, Item]
+Lines = Union[str, List[str]]
+GetLines = Callable[["Node", Item, Any], Lines]
+
+
+def _designator(name: str) -> str:
+ return name.replace(" ", "")
+
+
+def _get_ingroups(item: Item) -> ItemMap:
+ ingroups = {} # type: ItemMap
+ for link in item.links_to_parents():
+ if link["role"] == "ingroup":
+ ingroups[link.item.uid] = link.item
+ return ingroups
+
+
+def _ingroups_to_designators(ingroups: ItemMap) -> List[str]:
+ return [_designator(item["group-name"]) for item in ingroups.values()]
+
+
+def _forward_declaration(item: Item) -> str:
+ target = item.map(item["interface-target"])
+ return target["interface-type"] + " " + target["interface-name"]
+
+
+def _add_definition(node: "Node", item: Item, prefix: str,
+ value: Dict[str, Any], get_lines: GetLines) -> CContent:
+ content = CContent()
+ default = value["default"]
+ variants = value["variants"]
+ if variants:
+ ifelse = "#if "
+ with node.mapper.prefix(os.path.join(prefix, "variants")):
+ for variant in variants:
+ enabled_by = variant["enabled-by"].strip()
+ content.append(f"{ifelse}{enabled_by}")
+ with content.indent():
+ content.append(get_lines(node, item,
+ variant["definition"]))
+ ifelse = "#elif "
+ if default:
+ content.append("#else")
+ with node.mapper.prefix(os.path.join(prefix, "default")):
+ with content.indent():
+ content.append(get_lines(node, item, default))
+ content.append("#endif")
+ else:
+ with node.mapper.prefix(os.path.join(prefix, "default")):
+ content.append(get_lines(node, item, default))
+ return content
+
+
+def _get_description(item: Item, ingroups: ItemMap) -> CContent:
+ content = CContent()
+ with content.doxygen_block():
+ content.add_ingroup(_ingroups_to_designators(ingroups))
+ content.add_brief_description(item["interface-brief"])
+ content.add(content.wrap(item["interface-description"]))
+ if "interface-params" in item:
+ for param in item["interface-params"]:
+ content.add(
+ content.wrap(param["name"] + " " + param["description"],
+ intro=_PARAM[param["dir"]]))
+ if "interface-return" in item:
+ ret = item["interface-return"]
+ for retval in ret["return-values"]:
+ val = retval["value"]
+ intro = f"@retval {val} "
+ content.add(content.wrap(retval["description"], intro=intro))
+ content.add(content.wrap(ret["return"], intro="@return "))
+ return content
+
+
+class InterfaceMapper(ItemMapper):
+ """ Interface mapper. """
+ def __init__(self, node: "Node"):
+ super().__init__(node.item)
+ self._node = node
+
+ def __getitem__(self, identifier):
+ item, value = self.map(identifier)
+ if item["type"] == "interface":
+ node = self._node
+ header_file = node.header_file
+ if item["interface-type"] == "enumerator":
+ for link in item.links_to_children():
+ if link["role"] == "enumerator":
+ header_file.add_includes(link.item)
+ else:
+ header_file.add_includes(item)
+ header_file.add_potential_edge(node, item)
+ return value
+
+ def interface_name(self, item: Item, _value: Any):
+ """ Returns the interface name of a forward declaration. """
+ # pylint: disable=no-self-use
+ return _forward_declaration(item)
+
+
+_PARAM = {
+ None: "@param ",
+ "in": "@param[in] ",
+ "out": "@param[out] ",
+ "inout": "@param[in,out] ",
+}
+
+
+class Node:
+ """ Nodes of a header file. """
+ def __init__(self, header_file: "HeaderFile", item: Item,
+ ingroups: ItemMap):
+ self.header_file = header_file
+ self.item = item
+ self.ingroups = ingroups
+ self.in_edges = {} # type: ItemMap
+ self.out_edges = {} # type: ItemMap
+ self.content = CContent()
+ self.mapper = InterfaceMapper(self)
+
+ def __lt__(self, other: "Node") -> bool:
+ return self.item.uid < other.item.uid
+
+ @contextmanager
+ def _enum_struct_or_union(self) -> Iterator[None]:
+ self.content.add(_get_description(self.item, self.ingroups))
+ name = self.item["interface-name"]
+ typename = self.item["interface-type"]
+ kind = self.item["interface-definition-kind"]
+ if kind == f"{typename}-only":
+ self.content.append(f"{typename} {name} {{")
+ elif kind == "typedef-only":
+ self.content.append(f"typedef {typename} {{")
+ else:
+ self.content.append(f"typedef {typename} {name} {{")
+ self.content.push_indent()
+ yield
+ self.content.pop_indent()
+ if kind == f"{typename}-only":
+ self.content.append("};")
+ else:
+ self.content.append(f"}} {name};")
+
+ def generate(self) -> None:
+ """ Generates a node to generate the node content. """
+ _NODE_GENERATORS[self.item["interface-type"]](self)
+
+ def generate_compound(self) -> None:
+ """ Generates a compound (struct or union). """
+ with self._enum_struct_or_union():
+ self.content.append(
+ _add_definition(self, self.item, "interface-definition",
+ self.item["interface-definition"],
+ Node._get_compound_definition))
+
+ def generate_enum(self) -> None:
+ """ Generates an enum. """
+ with self._enum_struct_or_union():
+ enumerators = [] # type: List[CContent]
+ for link in self.item.links_to_parents():
+ if link["role"] != "enumerator":
+ continue
+ enumerator = _get_description(link.item, {})
+ enumerator.append(
+ _add_definition(self, link.item, "interface-definition",
+ link.item["interface-definition"],
+ Node._get_enumerator_definition))
+ enumerators.append(enumerator)
+ for enumerator in enumerators[0:-1]:
+ enumerator.lines[-1] += ","
+ enumerator.append("")
+ self.content.append(enumerator)
+ try:
+ self.content.append(enumerators[-1])
+ except IndexError:
+ pass
+
+ def generate_define(self) -> None:
+ """ Generates a define. """
+ self._add_generic_definition(Node._get_define_definition)
+
+ def generate_forward_declaration(self) -> None:
+ """ Generates a forward declaration. """
+ self.content.append([
+ "", "/* Forward declaration */",
+ _forward_declaration(self.item) + ";"
+ ])
+
+ def generate_function(self) -> None:
+ """ Generates a function. """
+ self._add_generic_definition(Node._get_function_definition)
+
+ def generate_group(self) -> None:
+ """ Generates a group. """
+ self.header_file.add_ingroup(self.item)
+ for ingroup in self.ingroups.values():
+ self.header_file.add_potential_edge(self, ingroup)
+ name = self.item["group-name"]
+ self.content.add_group(_designator(name), name,
+ _ingroups_to_designators(self.ingroups),
+ self.item["group-brief"],
+ self.item["group-description"])
+
+ def generate_macro(self) -> None:
+ """ Generates a macro. """
+ self._add_generic_definition(Node._get_macro_definition)
+
+ def generate_typedef(self) -> None:
+ """ Generates a typedef. """
+ self._add_generic_definition(Node._get_typedef_definition)
+
+ def generate_variable(self) -> None:
+ """ Generates a variable. """
+ self._add_generic_definition(Node._get_variable_definition)
+
+ def substitute(self, text: str) -> str:
+ """
+ Performs a variable substitution using the item mapper of the node.
+ """
+ return self.mapper.substitute(text.strip("\n"))
+
+ def _get_compound_definition(self, item: Item, definition: Any) -> Lines:
+ content = CContent()
+ with content.doxygen_block():
+ content.add_brief_description(definition["brief"])
+ content.add(content.wrap(definition["description"]))
+ kind = definition["kind"]
+ if kind == "member":
+ member = self.substitute(definition["definition"]) + ";"
+ content.append(member.split("\n"))
+ else:
+ content.append(f"{kind} {{")
+ content.gap = False
+ with content.indent():
+ index = 0
+ for compound_member in definition["members"]:
+ content.add(
+ _add_definition(self, item, f"members[{index}]",
+ compound_member,
+ Node._get_compound_definition))
+ index += 1
+ name = definition["name"]
+ content.append(f"}} {name};")
+ return content.lines
+
+ def _get_enumerator_definition(self, item: Item, definition: Any) -> Lines:
+ name = item["interface-name"]
+ if definition:
+ return f"{name} = {self.substitute(definition)}"
+ return f"{name}"
+
+ def _get_define_definition(self, item: Item, definition: Any) -> Lines:
+ name = item["interface-name"]
+ return f"#define {name} {self.substitute(definition)}".split("\n")
+
+ def _get_function_definition(self, item: Item, definition: Any) -> Lines:
+ ret = self.substitute(definition["return"])
+ if "body" in definition:
+ ret = "static inline " + ret
+ name = item["interface-name"]
+ space = "" if ret.endswith("*") else " "
+ params = [self.substitute(param) for param in definition["params"]]
+ param_line = ", ".join(params)
+ line = f"{ret}{space}{name}({param_line})"
+ if len(line) > 79:
+ param_block = ",\n ".join(params)
+ line = f"{ret}{space}{name}(\n {param_block}\n)"
+ if "body" in definition:
+ body = self.substitute("\n ".join(
+ definition["body"].strip("\n").split("\n")))
+ line = f"""{line}
+{{
+ {body}
+}}"""
+ else:
+ line += ";"
+ return line
+
+ def _get_macro_definition(self, item: Item, definition: Any) -> Lines:
+ name = item["interface-name"]
+ params = [param["name"] for param in item["interface-params"]]
+ param_line = ", ".join(params)
+ line = f"#define {name}({param_line}) "
+ if len(line) > 79:
+ param_block = ", \\\n ".join(params)
+ line = f"#define {name}( \\\n {param_block} \\\n) "
+ body = self.substitute(" \\\n ".join(
+ definition.strip("\n").split("\n")))
+ return line + body
+
+ def _get_typedef_definition(self, _item: Item, definition: Any) -> Lines:
+ return f"typedef {self.substitute(definition)};"
+
+ def _get_variable_definition(self, _item: Item, definition: Any) -> Lines:
+ return f"extern {self.substitute(definition)};"
+
+ def _add_generic_definition(self, get_lines: GetLines) -> None:
+ self.content.add(_get_description(self.item, self.ingroups))
+ self.content.append(
+ _add_definition(self, self.item, "interface-definition",
+ self.item["interface-definition"], get_lines))
+
+
+_NODE_GENERATORS = {
+ "enum": Node.generate_enum,
+ "define": Node.generate_define,
+ "forward-declaration": Node.generate_forward_declaration,
+ "function": Node.generate_function,
+ "group": Node.generate_group,
+ "macro": Node.generate_macro,
+ "struct": Node.generate_compound,
+ "typedef": Node.generate_typedef,
+ "union": Node.generate_compound,
+ "variable": Node.generate_variable,
+}
+
+
+class HeaderFile:
+ """ A header file. """
+ def __init__(self):
+ self._content = CContent()
+ self._ingroups = {} # type: ItemMap
+ self._includes = [] # type: List[Item]
+ self._nodes = {} # type: Dict[str, Node]
+
+ def add_includes(self, item: Item) -> None:
+ """ Adds the includes of the item to the header file includes. """
+ for link in item.links_to_parents():
+ if link["role"] == "include":
+ self._includes.append(link.item)
+
+ def add_ingroup(self, item: Item) -> None:
+ """ Adds an ingroup to the header file. """
+ self._ingroups[item.uid] = item
+
+ def _add_child(self, item: Item) -> None:
+ ingroups = _get_ingroups(item)
+ self._ingroups.update(ingroups)
+ self._nodes[item.uid] = Node(self, item, ingroups)
+ item.register_license_and_copyrights(self._content)
+
+ def add_potential_edge(self, node: Node, item: Item) -> None:
+ """
+ Adds a potential edge from a node to another node identified by an
+ item.
+ """
+ if item.uid in self._nodes and item.uid != node.item.uid:
+ node.out_edges[item.uid] = item
+ self._nodes[item.uid].in_edges[node.item.uid] = node.item
+
+ def _resolve_ingroups(self, node: Node) -> None:
+ for ingroup in node.ingroups.values():
+ self.add_potential_edge(node, ingroup)
+
+ def generate_nodes(self, item: Item) -> None:
+ """ Generates all nodes of this header file. """
+ for link in item.links_to_children():
+ if link["role"] == "include":
+ self._add_child(link.item)
+ for node in self._nodes.values():
+ self._resolve_ingroups(node)
+ node.generate()
+
+ def _get_nodes_in_dependency_order(self) -> List[Node]:
+ """
+ Gets the nodes of this header file ordered according to node
+ dependencies and UIDs.
+
+ Performs a topological sort using Kahn's algorithm.
+ """
+ nodes_in_dependency_order = [] # type: List[Node]
+
+ # Get incoming edge degrees for all nodes
+ in_degree = {} # type: Dict[str, int]
+ for node in self._nodes.values():
+ in_degree[node.item.uid] = len(node.in_edges)
+
+ # Create a queue with all nodes with no incoming edges sorted by UID
+ queue = [] # type: List[Node]
+ for node in self._nodes.values():
+ if in_degree[node.item.uid] == 0:
+ queue.append(node)
+ queue.sort(reverse=True)
+
+ # Topological sort
+ while queue:
+ node = queue.pop(0)
+ nodes_in_dependency_order.insert(0, node)
+
+ # Sort by UID
+ for uid in sorted(node.out_edges.keys()):
+ in_degree[uid] -= 1
+ if in_degree[uid] == 0:
+ queue.append(self._nodes[uid])
+
+ return nodes_in_dependency_order
+
+ def finalize(self, item: Item) -> None:
+ """ Finalizes the header file. """
+ self._content.add_spdx_license_identifier()
+ with self._content.file_block():
+ self._content.add_ingroup(_ingroups_to_designators(self._ingroups))
+ self._content.add_copyrights_and_licenses()
+ with self._content.header_guard(item["path-include"]):
+ self._content.add_includes(
+ [inc["path-include"] for inc in self._includes if inc != item])
+ with self._content.extern_c():
+ for node in self._get_nodes_in_dependency_order():
+ self._content.add(node.content)
+
+ def write(self, filename: str) -> None:
+ """ Writes the header file. """
+ self._content.write(filename)
+
+
+def _generate_header_file(item: Item, domains: Dict[str, str]) -> None:
+ dom = item["path-domain"]
+ if dom not in domains:
+ return
+ header_file = HeaderFile()
+ header_file.generate_nodes(item)
+ header_file.finalize(item)
+ header_file.write(
+ os.path.join(domains[dom], item["path-prefix"], item["path-include"]))
+
+
+def _visit_header_files(item: Item, domains: Dict[str, str]) -> None:
+ for child in item.children():
+ _visit_header_files(child, domains)
+ if item["type"] == "interface" and item["interface-type"] == "header-file":
+ _generate_header_file(item, domains)
+
+
+def generate(config: dict, item_cache: ItemCache) -> None:
+ """
+ Generates header files according to the configuration.
+
+ :param config: A dictionary with configuration entries.
+ :param item_cache: The specification item cache containing the interfaces.
+ """
+ for item in item_cache.top_level.values():
+ _visit_header_files(item, config["path-domains"])