summaryrefslogblamecommitdiffstats
path: root/rtemsspec/interface.py
blob: ac124a6831d7b10a0d9153a202c5a54e361f2879 (plain) (tree)
1
2
3
4


                                                                        
                                                        





















                                                                             
                  
                                     

                
         

                                                                               
 
                                                                      

                                                                
                                                     
                                                                           

                                                                     
                                                                              





                                               
                                         
                          

                                                   


                   
                                                         
                                                           

 
                                   
 


                                     
                                 

                                                                             







                                                                     
                                                                         
                                                                      
                                                         
                                                                     
                                                         
                                                          
                                                      
                                                                 
                                            
                                                            
                                                  
                                                         
                                                  

                                                                        

                                                       

                                                             
                                                       
                                                      
                                                              
                                            
                                                                          
                                                                           
                                                                  
                                                           
                                                                      
                                                      

                   



                                       
             
                                       
 

                                                                       

                                          

                                                                   
                                                   
                 


                                                                              
 




                                                                          
                                                         
 

                                                   
 



                                                 
                                             
                                 
                                                  

 








































                                                                          
                                                   
 



                                                 
                                             
                                 

                                                           

 
                                                
 
                                                            

                                       
                               
 
                                             
                                                             

 






                                                                            


                                                                 


                                                            
                                                       
                                                                          



                                                                    
                               










                                                                     




























                                                                              
                      
                                        
                                           
                                        
                                         
                                                  
                                    
                                        


 

                                   

                                                  
                                                               

                                      
                                           

                                          
                                 
                                            
            

                                                 



                                                                             





                                                      
                                                                         
                                
                                              
                                           













                                                                
                                
                                                                      

                                                           

                                                              



                                                                       






                                                                            



                                                       
                                                                        
                                 
                                                                            
                                                                               



                                          
                                            

                                                                    
                                  

                                                               


















                                                                     
                                                







                                                                   
                                                                          
                                                                     

                                                                              




                                                                











                                                                          

                                                                               
                                                   
                                                                    









                                                                               
                                          





























                                                                             

















                                                                               




                                                                               
                                                     











                                                                               



                                                                              






                                                                         
















                                                                           
                                      
 







                                                                   





                                                                             
                                    


                                                               


                                                            
           

                                                                               
           
                

                                                                 


                                                                             


                                                            

                                 
                                                                         




                                              

                                                        
                                
                                                                           

                                                                       




                                                                               
                           
                      
                                                                 


                                                                           
                           
                                                


                                                        

                                                                             
                            
                           

                                                              




                                                                         
                
                                                                               
                                                       
             
                                                                   
                            

                                                                          

                                                            



                                                      
                                              

                                                 
                                                               

                                 
                       
                                                           



                                                                    
                                          

                          











                                                                         
                                                           



                                
                                                             

                                                  
                              


                                                          







                                                                           


                    


                                                                          









                                                                          

                                                                              















                                                                      
                                                                             
                                                             

                                                                              
                                                            
 



                                                                          
                                                                              
                                

                                                                   
                                                                     

                                          
                                                   
                                                                             



                                                                     
                                                                               


                                                            
                                                              


                                               
                                                      

                                            
                                                                    
                                                                        

                      
                                                                   
                                                                         
                            

                                                                








                                                             
                                                   






                                       











                                                                           
                                         


                    
                         










                                                               


                                                                         












                                              
                  
                          
 
                                                           
                                     
                         
                                  
                                                                   
                                            

                                         
                              
                              


                                                                          
                                                          



                                                       
                                             
 
                                             
                                                
                                                                   
 
                                                             
           

                                                                              

                                                                 


                                         
 
                                     
                                                        

                                                                
                                         




                                                                    







                                                                               
           
                                                  

                                                 
                                      
                                         
                                                           
 
                                                              
                              
                                                 

                                             





                                                     
                                                 


                                                  
 
                                                      
 






                                                                     
                               
                                          
                                                       
                                        
                                                                             
                                                                    
                                                   
                                                           
                                                                        
                                                            
                                                                          
                        


                                      


                                                                      

                                                               

                             


                                      

                                                                   
                                    
                                                                            
              
                                                



                                                                  
                                              
                                       
                            

                                                           

 
                                                              
                                                                               
                                               

                                               
                           
              
                                                     


                                  

 


                                                             
                                     


                                                                   

                                               








                                                                              
                               
                               
                                                                          
                                                                          
                                                              
# SPDX-License-Identifier: BSD-2-Clause
""" This module provides functions for the generation of interfaces. """

# Copyright (C) 2020, 2021 embedded brains GmbH & Co. KG
#
# 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.

import collections
from contextlib import contextmanager
import functools
import itertools
import os
from typing import Any, Callable, Dict, Iterator, List, NamedTuple, Optional, \
    Union, Set, Tuple

from rtemsspec.content import CContent, CInclude, enabled_by_to_exp, \
    ExpressionMapper, forward_declaration, get_value_compound, \
    get_value_double_colon, get_value_doxygen_function, \
    get_value_doxygen_group, get_value_doxygen_ref, \
    get_value_forward_declaration, get_value_hash, get_value_header_file, \
    get_value_params, get_value_plural, get_value_unspecified_type, \
    to_camel_case
from rtemsspec.items import Item, ItemCache, ItemGetValueMap, ItemMapper, Link

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


def _get_ingroups(item: Item) -> ItemMap:
    ingroups: ItemMap = {}
    for group in item.parents("interface-ingroup"):
        ingroups[group.uid] = group
    return ingroups


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


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/struct/code:/name", get_value_compound)
        self.add_get_value("interface/union/code:/name", get_value_compound)
        self.add_get_value("interface/unspecified-struct/code:/name",
                           get_value_unspecified_type)
        self.add_get_value("interface/unspecified-struct/doc:/name",
                           get_value_unspecified_type)
        self.add_get_value("interface/unspecified-union/code:/name",
                           get_value_unspecified_type)
        self.add_get_value("interface/unspecified-union/doc:/name",
                           get_value_unspecified_type)
        self.add_get_value("glossary/term/doc:/plural", get_value_plural)
        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/function/doc:/params/name",
                           get_value_params)
        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/group/doc:/name",
                           get_value_doxygen_group)
        self.add_get_value("interface/header-file/doc:/path",
                           get_value_header_file)
        self.add_get_value("interface/macro/doc:/name",
                           get_value_doxygen_function)
        self.add_get_value("interface/macro/doc:/params/name",
                           get_value_params)
        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_doxygen_ref)
        self.add_get_value("interface/unspecified-function/doc:/name",
                           get_value_doxygen_function)

    @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_map(self, item: Item) -> ItemGetValueMap:
        if self._code_or_doc == "code" and item["type"] == "interface":
            node = self._node
            header_file = node.header_file
            if item["interface-type"] == "enumerator":
                for child in item.children("interface-enumerator"):
                    header_file.add_includes(child)
            else:
                header_file.add_includes(item)
            header_file.add_dependency(node, item)
        return self._get_value_map.get(f"{item.type}/{self._code_or_doc}", {})

    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.options[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)


def _filter_op_binary(op_name: str, options: Dict[str, str],
                      enabled_by: Any) -> Any:
    new_enabled_by = []
    for next_enabled_by in enabled_by:
        exp = _discard_non_options(options, next_enabled_by)
        if exp is not None:
            new_enabled_by.append(exp)
    if len(new_enabled_by) == 0:
        return None
    if len(new_enabled_by) == 1:
        return new_enabled_by[0]
    return {op_name: new_enabled_by}


def _filter_op_not(options: Dict[str, str], enabled_by: Any) -> Any:
    exp = _discard_non_options(options, enabled_by)
    if exp is None:
        return None
    return {"not": exp}


_FILTER_OP = {
    "and": functools.partial(_filter_op_binary, "and"),
    "not": _filter_op_not,
    "or": functools.partial(_filter_op_binary, "or")
}


def _discard_non_options(options: Dict[str, str], enabled_by: Any) -> Any:
    if isinstance(enabled_by, bool):
        return enabled_by
    if isinstance(enabled_by, list):
        return _filter_op_binary("or", options, enabled_by)
    if isinstance(enabled_by, dict):
        key, value = next(iter(enabled_by.items()))
        return _FILTER_OP[key](options, value)  # type: ignore
    if enabled_by in options:
        return enabled_by
    return None


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, options: Dict[str, str]):
        super().__init__()
        self._mapper = ItemMapper(item)
        self._options = options

    def map_symbol(self, symbol: str) -> str:
        return self._mapper.substitute(self._options[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 "
        for index, variant in enumerate(variants):
            prefix_2 = os.path.join(prefix, f"variants[{index}]")
            with node.mapper.prefix(prefix_2):
                enabled_by = enabled_by_to_exp(
                    variant["enabled-by"],
                    _InterfaceExpressionMapper(node.mapper))
                content.append(f"{ifelse}{enabled_by}")
            with node.mapper.prefix(os.path.join(prefix_2, "definition")):
                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 _RegisterMemberContext(NamedTuple):
    sizes: Dict[int, int]
    regs: Dict[str, Any]
    reg_counts: Dict[str, int]
    reg_indices: Dict[str, int]


def _add_register_padding(content: CContent, new_offset: int, old_offset: int,
                          default_padding: int) -> None:
    delta = new_offset - old_offset
    if delta > 0:
        padding = default_padding
        while delta % padding != 0:
            padding //= 2
        count = delta // padding
        array = f"[ {count} ]" if count > 1 else ""
        content.add(f"uint{padding * 8}_t "
                    f"reserved_{old_offset:x}_{new_offset:x}{array};")


def _get_register_name(definition: Dict[str, Any]) -> Tuple[str, str]:
    name = definition["name"]
    try:
        name, alias = name.split(":")
    except ValueError:
        alias = name
    return name, alias


_CONSTRAINT_TARGET = {
    "interface/define": "this constant",
    "interface/function": "this directive",
    "interface/macro": "this directive",
    "interface/struct": "this structure",
    "interface/typedef": "functions of this type",
    "interface/union": "this union",
    "interface/variable": "this object",
}


class Node:
    """ Nodes of a header file. """

    # pylint: disable=too-many-instance-attributes
    def __init__(self, header_file: "_HeaderFile", item: Item):
        self.header_file = header_file
        self.item = item
        self.ingroups = _get_ingroups(item)
        self.dependents: Set[Node] = set()
        self.depends_on: Set[Node] = set()
        self.content = CContent()
        self.mapper = _InterfaceMapper(self)
        try:
            group = item.child("placement-order")
        except IndexError:
            self.index = None
        else:
            self.index = (group.uid,
                          list(group.parents("placement-order")).index(item))

    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:
        self.content.add(f"/* Generated from spec:{self.item.uid} */")
        _NODE_GENERATORS[self.item["interface-type"]](self)

    def generate(self) -> None:
        """ Generates a node to generate the node content. """
        enabled_by = _discard_non_options(self.header_file.options,
                                          self.item["enabled-by"])
        if enabled_by is not None and (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: List[CContent] = []
            for parent in self.item.parents("interface-enumerator"):
                enumerator = self._get_description(parent, {})
                enumerator.append(
                    _add_definition(self, parent, "definition",
                                    parent["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.content.add_group(self.item["identifier"], self.item["name"],
                               _get_group_identifiers(self.ingroups),
                               self.substitute_text(self.item["brief"]),
                               self.substitute_text(self.item["description"]))

    def generate_macro(self) -> None:
        """ Generates a macro. """
        self._add_generic_definition(Node._get_macro_definition)

    def _add_register_bits(self, group: str) -> _RegisterMemberContext:
        ctx = _RegisterMemberContext({}, {}, collections.defaultdict(int),
                                     collections.defaultdict(int))
        for index, register in enumerate(self.item["registers"]):
            name = register["name"]
            group_ident = group + to_camel_case(name)
            ctx.regs[name] = {}
            width = register["width"]
            assert width in [8, 16, 32, 64]
            ctx.regs[name]["size"] = width // 8
            ctx.regs[name]["type"] = f"uint{width}_t"
            ctx.regs[name]["group"] = group_ident
            brief = self.substitute_text(register["brief"])
            with self.content.defgroup_block(group_ident, f"{brief} ({name})"):
                self.content.add_brief_description(
                    "This group contains register bit definitions.")
                self.content.doxyfy(
                    self.substitute_text(register["description"]))
                self.content.add("@{")
            for index_2, bits in enumerate(register["bits"]):
                self.content.add(
                    _add_definition(
                        self, self.item, f"registers[{index}]/bits[{index_2}]",
                        bits,
                        functools.partial(Node._get_register_bits_definition,
                                          reg_name=name)))
            self.content.add_close_group()
        return ctx

    def _add_register_block_includes(self,
                                     ctx: _RegisterMemberContext) -> None:
        for link in self.item.links_to_parents("register-block-include"):
            name = link["name"]
            ctx.regs[name] = {}
            ctx.regs[name]["size"] = link.item["register-block-size"]
            ctx.regs[name]["type"] = link.item["name"]
            ctx.regs[name]["group"] = link.item["identifier"]

    def _get_register_member_info(self, ctx: _RegisterMemberContext) -> None:
        offset = -1
        for index, member in enumerate(self.item["definition"]):
            assert member["offset"] > offset
            offset = member["offset"]
            default = [member["default"]] if member["default"] else []
            for index_2, definition in enumerate(
                    itertools.chain(default,
                                    (variant["definition"]
                                     for variant in member["variants"]))):
                name, alias = _get_register_name(definition)
                assert name.lower() != "reserved"
                count = definition["count"]
                if index_2 == 0:
                    ctx.sizes[index] = ctx.regs[name]["size"] * count
                else:
                    assert ctx.sizes[index] == ctx.regs[name]["size"] * count
                ctx.reg_counts[alias] += 1

    def _add_register_defines(self, ctx: _RegisterMemberContext) -> None:
        with self.content.doxygen_block():
            self.content.add("@name Registers")
            self.content.add_brief_description(
                self.substitute_text(self.item["brief"]))
            self.content.doxyfy(self.substitute_text(self.item["description"]))
            self.content.add("@{")
        for index, member in enumerate(self.item["definition"]):
            self.content.add(
                _add_definition(
                    self, self.item, f"definition[{index}]", member,
                    functools.partial(Node._get_register_define_definition,
                                      ctx=ctx,
                                      offset=member["offset"])))
        self.content.add_close_group()

    def _add_register_struct(self, ctx: _RegisterMemberContext,
                             size: int) -> None:
        with self.content.doxygen_block():
            self.content.add_brief_description(
                self.substitute_text(self.item["brief"]))
            self.content.doxyfy(self.substitute_text(self.item["description"]))
        self.content.append(f"typedef struct {self.item['name']} {{")
        default_padding = min(*ctx.sizes.values(), 8)
        offset = 0
        with self.content.indent():
            for index, member in enumerate(self.item["definition"]):
                member_offset = member["offset"]
                _add_register_padding(self.content, member_offset, offset,
                                      default_padding)
                self.content.add(
                    _add_definition(
                        self, self.item, f"definition[{index}]", member,
                        functools.partial(Node._get_register_member_definition,
                                          ctx=ctx)))
                offset = member_offset + ctx.sizes[index]
            assert offset <= size
            _add_register_padding(self.content, size, offset, default_padding)
        self.content.add(f"}} {self.item['name']};")

    def _add_register_members(self, ctx: _RegisterMemberContext) -> None:
        size = self.item["register-block-size"]
        if size is None:
            self._add_register_defines(ctx)
        else:
            self._add_register_struct(ctx, size)

    def generate_register_block(self) -> None:
        """ Generates a register block. """
        self.header_file.add_includes(self.item.map("/c/if/uint32_t"))
        for parent in self.item.parents("register-block-include"):
            self.header_file.add_includes(parent)
            self.header_file.add_dependency(self, parent)
        group = self.item["identifier"]
        name = self.item["register-block-group"]
        with self.content.defgroup_block(group, name):
            self.content.add_ingroup(_get_group_identifiers(self.ingroups))
            self.content.add_brief_description(
                f"This group contains the {name} interfaces.")
            self.content.add("@{")
        ctx = self._add_register_bits(group)
        self._add_register_block_includes(ctx)
        self._get_register_member_info(ctx)
        self._add_register_members(ctx)
        self.content.add_close_group()

    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: Optional[str],
                        item: Optional[Item] = None) -> str:
        """
        Performs a variable substitution on a description using the item mapper
        of the node.
        """
        if text:
            return self.mapper.substitute(text.strip("\n"), item)
        return ""

    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"]
        attrs = self.substitute_code(definition["attributes"])
        attrs = f"{attrs} " if attrs else ""
        ret = self.substitute_code(definition["return"])
        params = [
            self.substitute_code(param) for param in definition["params"]
        ]
        body = definition["body"]
        if body:
            with content.function(f"{attrs}static inline {ret}", name, params):
                content.add(self.substitute_code(body))
        else:
            content.declare_function(f"{attrs}{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)"
        body = definition["body"]
        if not body:
            return line
        body_lines = self.substitute_code(body).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_register_bits_definition(self, _item: Item, definition: Any,
                                      reg_name: str) -> Lines:
        lines = []  # List[str]
        prefix = self.item["register-prefix"]
        if prefix is None:
            prefix = self.item["name"]
        prefix = f"{prefix}_{reg_name}_" if prefix else f"{reg_name}_"
        for index, bit in enumerate(definition):
            start = bit["start"]
            width = bit["width"]
            end = start + width
            sfx = "ULL" if end > 32 else "U"
            base = f"{prefix.upper()}{bit['name'].upper()}"
            if index != 0:
                lines.append("")
            if width == 1:
                val = 1 << start
                lines.append(f"#define {base} {val:#x}{sfx}")
            else:
                mask = ((1 << width) - 1) << start
                lines.extend([
                    f"#define {base}_SHIFT {start}",
                    f"#define {base}_MASK {mask:#x}{sfx}",
                    f"#define {base}_GET( _reg ) \\",
                    f"  ( ( ( _reg ) & {base}_MASK ) >> \\",
                    f"    {base}_SHIFT )",
                    f"#define {base}_SET( _reg, _val ) \\",
                    f"  ( ( ( _reg ) & ~{base}_MASK ) | \\",
                    f"    ( ( ( _val ) << {base}_SHIFT ) & \\",
                    f"      {base}_MASK ) )", f"#define {base}( _val ) \\",
                    f"  ( ( ( _val ) << {base}_SHIFT ) & \\",
                    f"    {base}_MASK )"
                ])
        return lines

    def _get_register_define_definition(self, item: Item, definition: Any,
                                        ctx: _RegisterMemberContext,
                                        offset: int) -> Lines:
        name, alias = _get_register_name(definition)
        count = definition["count"]
        assert count == 1
        content = CContent()
        with content.doxygen_block():
            content.add(f"@brief See @ref {ctx.regs[name]['group']}.")
        content.append(
            f"#define {item['name'].upper()}_{alias.upper()} {offset:#x}")
        return content.lines

    def _get_register_member_definition(self, _item: Item, definition: Any,
                                        ctx: _RegisterMemberContext) -> Lines:
        name, alias = _get_register_name(definition)
        count = definition["count"]
        array = f"[ {count} ]" if count > 1 else ""
        if ctx.reg_counts[alias] > 1:
            index = ctx.reg_indices[alias]
            ctx.reg_indices[alias] = index + 1
            idx = f"_{index}"
        else:
            idx = ""
        content = CContent()
        with content.doxygen_block():
            content.add(f"@brief See @ref {ctx.regs[name]['group']}.")
        content.append(
            f"{ctx.regs[name]['type']} {alias.lower()}{idx}{array};")
        return content.lines

    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"]))
            if "params" in item:
                content.add_param_description(item["params"],
                                              self.substitute_text)
            content.doxyfy(self.substitute_text(item["description"]))
            ret = item.get("return", None)
            if ret:
                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 ")
            content.add_paragraph("Notes", self.substitute_text(item["notes"]))
            constraints = [
                self.substitute_text(parent["text"], parent)
                for parent in item.parents("constraint")
                if parent.is_enabled(self.header_file.enabled)
            ]
            if constraints:
                constraint_content = CContent()
                target = _CONSTRAINT_TARGET[item.type]
                constraint_content.add_list(
                    constraints,
                    f"The following constraints apply to {target}:")
                content.add_paragraph("Constraints", constraint_content)
        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,
    "register-block": Node.generate_register_block,
    "struct": Node.generate_compound,
    "typedef": Node.generate_typedef,
    "union": Node.generate_compound,
    "variable": Node.generate_variable,
}


def _is_ready_to_bubble(before: Node, after: Node) -> bool:
    if after in before.dependents:
        return False

    # Move the groups towards the top of the header file
    group = "interface/group"
    if (before.item.type == group) ^ (after.item.type == group):
        return after.item.type == group

    # Move items with an explicit placement order towards the bottom of the
    # file
    if before.index and after.index:
        return after.index < before.index
    if after.index:
        return False

    return after < before


def _bubble_sort(nodes: List[Node]) -> List[Node]:
    node_count = len(nodes)
    for i in range(node_count - 1):
        for j in range(node_count - 1 - i):
            if _is_ready_to_bubble(nodes[j], nodes[j + 1]):
                nodes[j], nodes[j + 1] = nodes[j + 1], nodes[j]
    return nodes


def _merge_enabled_by(options: Dict[str, str], link: Link) -> Any:
    enabled_by = _discard_non_options(options, link["enabled-by"])
    enabled_by_2 = _discard_non_options(options, link.item["enabled-by"])
    if enabled_by == enabled_by_2:
        return enabled_by
    if isinstance(enabled_by, bool):
        if enabled_by:
            return enabled_by_2
        return False
    if isinstance(enabled_by_2, bool):
        if enabled_by_2:
            return enabled_by
        return False
    return {"and": [enabled_by, enabled_by_2]}


class _HeaderFile:
    """ A header file. """

    def __init__(self, item: Item, options: Dict[str, str],
                 enabled: List[str]):
        self._item = item
        self._content = CContent()
        self._content.register_license_and_copyrights_of_item(item)
        self._ingroups = _get_ingroups(item)
        self._includes: List[Item] = []
        self._nodes: Dict[str, Node] = {}
        self.options = options
        self.enabled = enabled

    def add_includes(self, item: Item) -> None:
        """ Adds the includes of the item to the header file includes. """
        for parent in item.parents("interface-placement"):
            if parent.type in [
                    "interface/header-file",
                    "interface/unspecified-header-file"
            ]:
                self._includes.append(parent)

    def _add_child(self, item: Item) -> None:
        self._nodes[item.uid] = Node(self, item)
        self._content.register_license_and_copyrights_of_item(item)

    def add_dependency(self, node: Node, item: Item) -> None:
        """
        Adds a dependency from a node to another node identified by an item if
        the item corresponds to a node and it is not a self reference.
        """
        if item.uid in self._nodes and item.uid != node.item.uid:
            other = self._nodes[item.uid]
            node.depends_on.add(other)
            other.dependents.add(node)

    def generate_nodes(self) -> None:
        """ Generates all nodes of this header file. """
        for child in self._item.children("interface-placement"):
            self._add_child(child)
        for node in self._nodes.values():
            node.generate()

    def _get_nodes_in_dependency_order(self) -> List[Node]:
        """
        Gets the nodes of this header file ordered according to node
        dependencies and other criteria.

        The nodes form a partially ordered set (poset).  The ordering is done
        in two steps.  Firstly, a topological sort using Kahn's algorithm is
        carried out.  Secondly, the nodes are sorted using a bubble sort which
        takes node dependencies into account.  There are more efficient ways to
        do this, however, if you experience run time problems due to this
        method, then maybe you should reconsider your header file organization.
        """
        nodes_in_dependency_order: List[Node] = []

        # Get incoming edge degrees for all nodes
        in_degree: Dict[str, int] = {}
        for node in self._nodes.values():
            in_degree[node.item.uid] = len(node.dependents)

        # Create a queue with all nodes with no incoming edges
        queue: List[Node] = []
        for node in sorted(self._nodes.values()):
            if in_degree[node.item.uid] == 0:
                queue.append(node)

        # Topological sort
        while queue:
            node = queue.pop(0)
            nodes_in_dependency_order.insert(0, node)

            for other in sorted(node.depends_on):
                in_degree[other.item.uid] -= 1
                if in_degree[other.item.uid] == 0:
                    queue.append(other)

        return _bubble_sort(nodes_in_dependency_order)

    def _combine_enabled_by(self, enabled_by: Any) -> Any:
        enabled_by_2 = _discard_non_options(self.options,
                                            self._item["enabled-by"])
        if enabled_by == enabled_by_2 or enabled_by is None:
            return True
        return enabled_by

    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()
        self._content.add(f"/* Generated from spec:{self._item.uid} */")
        with self._content.header_guard(self._item["path"]):
            exp_mapper = _HeaderExpressionMapper(self._item, self.options)
            includes = [
                CInclude(
                    item["path"],
                    enabled_by_to_exp(
                        self._combine_enabled_by(
                            _discard_non_options(self.options,
                                                 item["enabled-by"])),
                        exp_mapper)) for item in self._includes
                if item != self._item
            ]
            includes.extend([
                CInclude(
                    link.item["path"],
                    enabled_by_to_exp(
                        self._combine_enabled_by(
                            _merge_enabled_by(self.options, link)),
                        exp_mapper))
                for link in self._item.links_to_parents("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],
                          options: Dict[str, str], enabled: List[str]) -> None:
    domain = item.parent("interface-placement")
    assert domain["interface-type"] == "domain"
    domain_path = domains.get(domain.uid, None)
    if domain_path is None:
        return
    header_file = _HeaderFile(item, options, enabled)
    header_file.generate_nodes()
    header_file.finalize()
    header_file.write(domain_path)


def _gather_options(item_level_interfaces: List[str],
                    item_cache: ItemCache) -> Dict[str, str]:
    options: Dict[str, str] = {}
    for uid in item_level_interfaces:
        for child in item_cache[uid].children("interface-ingroup"):
            if child.type == "interface/unspecified-define":
                define = f"defined(${{{child.uid}:/name}})"
                options[child["name"]] = define
    return options


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.
    """
    domains = config["domains"]
    enabled = config["enabled"]
    options = _gather_options(config["item-level-interfaces"], item_cache)
    for item in item_cache.items_by_type.get("interface/header-file", []):
        _generate_header_file(item, domains, options, enabled)