summaryrefslogblamecommitdiffstats
path: root/rtemsspec/interface.py
blob: 9bca070900b73effb87cf80fb18c4889a4792fce (plain) (tree)



























                                                                             
                                                             
 
                                                                      

                                                                           
                                                                            





                                               


                                         
                                            



                                               
                                                         
                                                           


                                            

                                                         

 



                                                                    



                                     




                                                                      

                                                          
                                                            
                                                  
                                                         


                                                                        

                                                       
                                                                          


                                                                           

                   



                                       
             
                                       
 

                                                                           

                                          

                                                          
                                                           

                                                           




                                                                            
 






                                                                          





                                                   
                                             
                                 
                                                  

 




                                                   
                                             
                                 

                                                           

 





                                                                       
                                             


                                                                        








                                                                            


                                                            




                                                                    
                               










                                                                     

                                   
                                                              






                                            
                                            





                                                      
                                                                         
                                
                                              
                                           













                                                                


                                                           

                                                              
                                            
                                                              






                                                                            



                                                       
                                                                        
                                 
                                                                            
                                                                               





                                                     
                                                       
                            
                                                                 
                                  

                                                                  






























                                                                     
                                                                          
                                                                     
                                                                            












                                                                   





                                                                             
                                    


                                                               
                                                
           

                                                                               
           


                                                           


                                                                             


                                                            

                                 
                                                                         




                                              

                                                        
                                
                                                                           

                                                                       




                                                                               
                           
                      
                                                                 


                                                                           
                           
                                                


                                                        

                                                                             
                            
                           




                                                                         
                

                                                                        
             

                                                       

                                                                          

                                                            



                                                      
                                              

                                                 


                                                               
                                                                 



                                                                    
                                          


                                                                             
                                                             

                                                                              
                                                            
 



                                                                          


                                                                              
                                

                                                                   

                                    
                                                   
                                                                             



                                                                     

                      
                                                                   
                                                                         
                            

                                                                















                                                             
                  
                          

                                                                       
                                  
                                                                   
                                            
                                               
                                                 
                                                    



                                                                          
                                                                
                                                       







                                                   

                                             
                                                          
                                                                   













                                                                          
                                     
                                                        
                                                   
                                                  































                                                                            
                                              





                                                  
                               
                                          
                                                       
                                        
                                                                             
                                                                    
                                                   
                                                           
                                                            


                                                                         
                                      



                                                                           
                                           


                                                                           
              
                                                



                                                                  
                                              
                                       
                            

                                                           

 

                                                                      


                                                      
                           
              



                                                       

 

                                                                    
                                 
                                                               
                                                                               







                                                                        
                                                  

                                                               
                             








                                                                              

                                                    
                                              
                                                                        
# 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 rtemsspec.content import CContent, CInclude, enabled_by_to_exp, \
    ExpressionMapper, get_value_double_colon, get_value_doxygen_function, \
    get_value_hash
from rtemsspec.items import Item, ItemCache, ItemGetValueContext, ItemMapper

ItemMap = Dict[str, Item]
Lines = Union[str, List[str]]
GetLines = Callable[["Node", Item, Any], Lines]


def _get_ingroups(item: Item) -> ItemMap:
    ingroups = {}  # type: ItemMap
    for link in item.links_to_parents():
        if link.role == "interface-ingroup":
            ingroups[link.item.uid] = link.item
    return ingroups


def _get_group_identifiers(groups: ItemMap) -> List[str]:
    return [item["identifier"] for item in groups.values()]


def _forward_declaration(item: Item) -> str:
    target = next(item.parents("interface-target"))
    return f"{target['interface-type']} {target['name']}"


def _get_value_forward_declaration(ctx: ItemGetValueContext) -> Any:
    return _forward_declaration(ctx.item)


class _InterfaceMapper(ItemMapper):
    def __init__(self, node: "Node"):
        super().__init__(node.item)
        self._node = node
        self._code_or_doc = "doc"
        self.add_get_value("interface/forward-declaration:code:/name",
                           _get_value_forward_declaration)
        self.add_get_value("interface/forward-declaration:doc:/name",
                           _get_value_forward_declaration)
        self.add_get_value("interface/function:doc:/name",
                           get_value_doxygen_function)
        self.add_get_value("interface/enumerator:doc:/name",
                           get_value_double_colon)
        self.add_get_value("interface/typedef:doc:/name",
                           get_value_double_colon)
        self.add_get_value("interface/define:doc:/name", get_value_hash)
        self.add_get_value("interface/enum:doc:/name", get_value_hash)
        self.add_get_value("interface/macro:doc:/name",
                           get_value_doxygen_function)
        self.add_get_value("interface/variable:doc:/name", get_value_hash)
        for opt in ["feature-enable", "feature", "initializer", "integer"]:
            name = f"interface/appl-config-option/{opt}:doc:/name"
            self.add_get_value(name, get_value_hash)

    @contextmanager
    def code(self) -> Iterator[None]:
        """ Enables code mapping. """
        code_or_doc = self._code_or_doc
        self._code_or_doc = "code"
        yield
        self._code_or_doc = code_or_doc

    def get_value(self, ctx: ItemGetValueContext) -> Any:
        if self._code_or_doc == "code" and ctx.item["type"] == "interface":
            node = self._node
            header_file = node.header_file
            if ctx.item["interface-type"] == "enumerator":
                for link in ctx.item.links_to_children():
                    if link.role == "interface-enumerator":
                        header_file.add_includes(link.item)
            else:
                header_file.add_includes(ctx.item)
            header_file.add_potential_edge(node, ctx.item)
        return super().get_value(
            ItemGetValueContext(ctx.item, f"{self._code_or_doc}:{ctx.path}",
                                ctx.value, ctx.key, ctx.index))

    def enabled_by_to_defined(self, enabled_by: str) -> str:
        """
        Maps an item-level enabled-by attribute value to the corresponding
        defined expression.
        """
        return self._node.header_file.enabled_by_defined[enabled_by]


class _InterfaceExpressionMapper(ExpressionMapper):
    def __init__(self, mapper: _InterfaceMapper):
        super().__init__()
        self._mapper = mapper

    def map_symbol(self, symbol: str) -> str:
        with self._mapper.code():
            return self._mapper.substitute(symbol)


class _ItemLevelExpressionMapper(ExpressionMapper):
    def __init__(self, mapper: _InterfaceMapper):
        super().__init__()
        self._mapper = mapper

    def map_symbol(self, symbol: str) -> str:
        with self._mapper.code():
            return self._mapper.substitute(
                self._mapper.enabled_by_to_defined(symbol))


class _HeaderExpressionMapper(ExpressionMapper):
    def __init__(self, item: Item, enabled_by_defined: Dict[str, str]):
        super().__init__()
        self._mapper = ItemMapper(item)
        self._enabled_by_defined = enabled_by_defined

    def map_symbol(self, symbol: str) -> str:
        return self._mapper.substitute(self._enabled_by_defined[symbol])


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 = enabled_by_to_exp(
                    variant["enabled-by"],
                    _InterfaceExpressionMapper(node.mapper))
                content.append(f"{ifelse}{enabled_by}")
                with content.indent():
                    content.append(get_lines(node, item,
                                             variant["definition"]))
                ifelse = "#elif "
        if default is not None:
            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


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(self._get_description(self.item, self.ingroups))
        name = self.item["name"]
        typename = self.item["interface-type"]
        kind = self.item["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:
        _NODE_GENERATORS[self.item["interface-type"]](self)

    def generate(self) -> None:
        """ Generates a node to generate the node content. """
        enabled_by = self.item["enabled-by"]
        if not isinstance(enabled_by, bool) or not enabled_by:
            mapper = _ItemLevelExpressionMapper(self.mapper)
            self.content.add(f"#if {enabled_by_to_exp(enabled_by, mapper)}")
            with self.content.indent():
                self._generate()
            self.content.add("#endif")
        else:
            self._generate()

    def generate_compound(self) -> None:
        """ Generates a compound (struct or union). """
        with self._enum_struct_or_union():
            for index, definition in enumerate(self.item["definition"]):
                self.content.add(
                    _add_definition(self, self.item, f"definition[{index}]",
                                    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 != "interface-enumerator":
                    continue
                enumerator = self._get_description(link.item, {})
                enumerator.append(
                    _add_definition(self, link.item, "definition",
                                    link.item["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)
        self.content.add_group(self.item["identifier"], self.item["name"],
                               _get_group_identifiers(self.ingroups),
                               self.item["brief"], self.item["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_code(self, text: str) -> str:
        """
        Performs a variable substitution on code using the item mapper of the
        node.
        """
        if text:
            with self.mapper.code():
                return self.mapper.substitute(text.strip("\n"))
        return text

    def substitute_text(self, text: str) -> str:
        """
        Performs a variable substitution on a description using the item mapper
        of the node.
        """
        if text:
            return self.mapper.substitute(text.strip("\n"))
        return text

    def _get_compound_definition(self, item: Item, definition: Any) -> Lines:
        content = CContent()
        content.add_description_block(
            self.substitute_text(definition["brief"]),
            self.substitute_text(definition["description"]))
        kind = definition["kind"]
        if kind == "member":
            member = self.substitute_code(definition["definition"]) + ";"
            content.append(member.split("\n"))
        else:
            content.append(f"{kind} {{")
            content.gap = False
            with content.indent():
                for index, compound_member in enumerate(
                        definition["definition"]):
                    content.add(
                        _add_definition(self, item, f"definition[{index}]",
                                        compound_member,
                                        Node._get_compound_definition))
            name = definition["name"]
            content.append(f"}} {name};")
        return content.lines

    def _get_enumerator_definition(self, item: Item, definition: Any) -> Lines:
        name = item["name"]
        if definition:
            return f"{name} = {self.substitute_code(definition)}"
        return f"{name}"

    def _get_define_definition(self, item: Item, definition: Any) -> Lines:
        name = item["name"]
        value = self.substitute_code(definition)
        if value:
            return f"#define {name} {value}".split("\n")
        return f"#define {name}"

    def _get_function_definition(self, item: Item, definition: Any) -> Lines:
        content = CContent()
        name = item["name"]
        ret = self.substitute_code(definition["return"])
        params = [
            self.substitute_code(param) for param in definition["params"]
        ]
        body = definition["body"]
        if body:
            with content.function("static inline " + ret, name, params):
                content.add(self.substitute_code(body))
        else:
            content.declare_function(ret, name, params)
        return content.lines

    def _get_macro_definition(self, item: Item, definition: Any) -> Lines:
        name = item["name"]
        params = [param["name"] for param in item["params"]]
        if params:
            param_line = " " + ", ".join(params) + " "
        else:
            param_line = ""
        line = f"#define {name}({param_line})"
        if len(line) > 79:
            param_block = ", \\\n  ".join(params)
            line = f"#define {name}( \\\n  {param_block} \\\n)"
        if not definition:
            return line
        body_lines = self.substitute_code(definition).split("\n")
        if len(body_lines) == 1 and len(line + body_lines[0]) <= 79:
            body = " "
        else:
            body = " \\\n  "
        body += " \\\n  ".join(body_lines)
        return line + body

    def _get_typedef_definition(self, _item: Item, definition: Any) -> Lines:
        return f"typedef {self.substitute_code(definition)};"

    def _get_variable_definition(self, _item: Item, definition: Any) -> Lines:
        return f"extern {self.substitute_code(definition)};"

    def _get_description(self, item: Item, ingroups: ItemMap) -> CContent:
        content = CContent()
        with content.doxygen_block():
            content.add_ingroup(_get_group_identifiers(ingroups))
            content.add_brief_description(self.substitute_text(item["brief"]))
            content.wrap(self.substitute_text(item["description"]))
            content.wrap(self.substitute_text(item["notes"]))
            if "params" in item:
                content.add_param_description(item["params"],
                                              self.substitute_text)
            if "return" in item:
                ret = item["return"]
                for retval in ret["return-values"]:
                    content.wrap(self.substitute_text(retval["description"]),
                                 initial_indent=self.substitute_text(
                                     f"@retval {retval['value']} "))
                content.wrap(self.substitute_text(ret["return"]),
                             initial_indent="@return ")
        return content

    def _add_generic_definition(self, get_lines: GetLines) -> None:
        self.content.add(self._get_description(self.item, self.ingroups))
        self.content.append(
            _add_definition(self, self.item, "definition",
                            self.item["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, item: Item, enabled_by_defined: Dict[str, str]):
        self._item = item
        self._content = CContent()
        self._content.register_license_and_copyrights_of_item(item)
        self._ingroups = {}  # type: ItemMap
        self._includes = []  # type: List[Item]
        self._nodes = {}  # type: Dict[str, Node]
        self.enabled_by_defined = enabled_by_defined

    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 == "interface-placement" and link.item[
                    "interface-type"] == "header-file":
                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)
        if item["interface-type"] != "group":
            self._ingroups.update(ingroups)
        self._nodes[item.uid] = Node(self, item, ingroups)
        self._content.register_license_and_copyrights_of_item(item)

    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) -> None:
        """ Generates all nodes of this header file. """
        for link in self._item.links_to_children():
            if link.role == "interface-placement":
                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):
                in_degree[uid] -= 1
                if in_degree[uid] == 0:
                    queue.append(self._nodes[uid])

        return nodes_in_dependency_order

    def finalize(self) -> None:
        """ Finalizes the header file. """
        self._content.prepend_spdx_license_identifier()
        with self._content.file_block():
            self._content.add_ingroup(_get_group_identifiers(self._ingroups))
            self._content.add_brief_description(self._item["brief"])
        self._content.add_copyrights_and_licenses()
        self._content.add_automatically_generated_warning()
        with self._content.header_guard(self._item["path"]):
            exp_mapper = _HeaderExpressionMapper(self._item,
                                                 self.enabled_by_defined)
            includes = [
                CInclude(item["path"],
                         enabled_by_to_exp(item["enabled-by"], exp_mapper))
                for item in self._includes if item != self._item
            ]
            includes.extend([
                CInclude(link.item["path"],
                         enabled_by_to_exp(link["enabled-by"], exp_mapper))
                for link in self._item.links_to_parents()
                if link.role == "interface-include"
            ])
            self._content.add_includes(includes)
            with self._content.extern_c():
                for node in self._get_nodes_in_dependency_order():
                    self._content.add(node.content)

    def write(self, domain_path: str) -> None:
        """ Writes the header file. """
        self._content.write(
            os.path.join(domain_path, self._item["prefix"],
                         self._item["path"]))


def _generate_header_file(item: Item, domains: Dict[str, str],
                          enabled_by_defined: Dict[str, str]) -> None:
    domain = next(item.parents("interface-placement"))
    assert domain["interface-type"] == "domain"
    domain_path = domains.get(domain.uid, None)
    if domain_path is None:
        return
    header_file = _HeaderFile(item, enabled_by_defined)
    header_file.generate_nodes()
    header_file.finalize()
    header_file.write(domain_path)


def _visit_header_files(item: Item, domains: Dict[str, str],
                        enabled_by_defined: Dict[str, str]) -> None:
    for child in item.children():
        _visit_header_files(child, domains, enabled_by_defined)
    if item["type"] == "interface" and item["interface-type"] == "header-file":
        _generate_header_file(item, domains, enabled_by_defined)


def _gather_enabled_by_defined(item_level_interfaces: List[str],
                               item_cache: ItemCache) -> Dict[str, str]:
    enabled_by_defined = {}  # type: Dict[str, str]
    for uid in item_level_interfaces:
        for link in item_cache[uid].links_to_children():
            if link.role == "interface-placement":
                define = f"defined(${{{link.item.uid}:/name}})"
                enabled_by_defined[link.item["name"]] = define
    return enabled_by_defined


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.
    """
    enabled_by_defined = _gather_enabled_by_defined(
        config["item-level-interfaces"], item_cache)
    for item in item_cache.top_level.values():
        _visit_header_files(item, config["domains"], enabled_by_defined)