Skip to content
Snippets Groups Projects
genapi.py 7.51 KiB
Newer Older
  • Learn to ignore specific revisions
  • import argparse
    import os
    import re
    import subprocess
    
    MATCH_API_EXPANSION = re.compile(
    
        r"__GENERATE_API \$ __GEN_ID_(?P<id>\w+) \$ (?P<decl>.*?) \$",
    
        re.DOTALL | re.MULTILINE,
    )
    
    
    MATCH_ISR_EXPANSION = re.compile(
        r"__GENERATE_API_ISR \$ __GEN_ID_(?P<id>\w+) \$ (?P<isr>.*?) \$",
        re.DOTALL | re.MULTILINE,
    )
    
    
    MATCH_DECLARATION = re.compile(r"^(?P<typename>.*?)\s*\((?P<args>.*)\)$", re.DOTALL)
    
        r"^(?P<type>(?:const )?(?:struct |enum |union )?\w+[*\s]+)(?P<name>\w+)$"
    
    rahix's avatar
    rahix committed
    def sizeof(args):
        """Return a string that describes the size of a list of arguments."""
        return " + ".join(a["sizeof"] for a in args) if args != [] else "0"
    
    
    
    def bailout(message, *args, **kwargs):
        fmt = "\x1B[31;1mError\x1B[0m: {}"
        print(fmt.format(message.format(*args, **kwargs)), file=sys.stderr)
        sys.exit(1)
    
    
    
    def parse_declarations(source):
        """Parse all declarations in the given source."""
        declarations = []
    
        for exp in MATCH_API_EXPANSION.finditer(source):
    
            id = exp.group("id")
            decl = MATCH_DECLARATION.match(exp.group("decl"))
    
            if decl is None:
                bailout("Error in declaration '{}'", exp.group("decl"))
    
            typename = MATCH_TYPENAME.match(decl.group("typename"))
    
            args = []
            args_str = decl.group("args")
    
    
            if typename is None:
                bailout("Error in declaration '{}'", exp.group("decl"))
    
    
            # Parse arguments
    
            for arg_str in map(str.strip, args_str.split(",")):
                if arg_str in ["void", ""]:
                    continue
    
                arg = MATCH_TYPENAME.match(arg_str.strip())
    
                if arg is None:
                    bailout("Failed to parse argument '{}'", arg_str.strip())
    
                args.append(
                    {
                        "type": arg.group("type").strip(),
                        "name": arg.group("name"),
                        "sizeof": "sizeof({})".format(arg.group("type").strip()),
                        "offset": sizeof(args),
                    }
                )
    
            declarations.append(
                {
                    "id": id,
                    "return_type": typename.group("type").strip(),
                    "name": typename.group("name"),
                    "args": args,
                    "args_str": args_str,
                }
            )
    
    def parse_interrupts(source):
        """Parse all isr declarations in the given source."""
        interrupts = []
        for exp in MATCH_ISR_EXPANSION.finditer(source):
            id = exp.group("id")
            isr = exp.group("isr")
    
    
            interrupts.append({"id": id, "isr": isr})
    
    
        return interrupts
    
    
    
    def main():
        parser = argparse.ArgumentParser(
            description="Generate the API stubs from a header file."
        )
        parser.add_argument(
            "-H", "--header", required=True, help="The header to base the definitions on."
        )
        parser.add_argument(
            "-c", "--client", required=True, help="The output client-side c source file."
        )
        parser.add_argument(
            "-s", "--server", required=True, help="The output server-side c source file."
        )
        args = parser.parse_args()
    
    
        # Run the preprocessor on the header file to get the API definitions.
        #
        # For this, we first need a source to include the header which contains
        # an alternative definition of the `API` macro that marks definitions in
        # a way we can find later on.
        api_src = """\
    
    #define API(id, def) __GENERATE_API $ __GEN_ID_##id $ def $
    
    #define API_ISR(id, isr) __GENERATE_API_ISR $ __GEN_ID_##id $ isr $
    
    #include "{header}"
    """.format(
    
            header=os.path.relpath(args.header)
        )
    
        # Evaluate the preprocessor
        source = subprocess.check_output(
    
            ["gcc", "-E", "-P", "-"], input=api_src.encode()
    
        declarations = parse_declarations(source)
    
        interrupts = parse_interrupts(source)
    
    
        fmt_header = {"header": os.path.basename(args.header)}
    
        # Generate Client {{{
        with open(args.client, "w") as f_client:
            tmp = """\
    
    #include <stdio.h>
    
    
    #define API_ISR(id, isr)
    
    #include "{header}"
    
    #include "api/caller.h"
    
    """
            f_client.write(tmp.format(**fmt_header))
    
            for decl in declarations:
                decl["total_size"] = sizeof(decl["args"])
                tmp = """\
    
    
    /* Autogenerated stub for {id} */
    
    {return_type} {name}({args_str})
    
            const int size = {total_size};
            void*buffer;
    
            buffer = _api_call_start({id}, size);
            /* TODO: Check if buffer is not NULL */
    
    """
                f_client.write(tmp.format(**decl))
    
                for i, arg in enumerate(decl["args"]):
                    tmp = """\
            *({type}*)(buffer + {offset}) = {name};
    """
                    f_client.write(tmp.format(**arg))
    
                if decl["return_type"] == "void":
                    # Don't return if return type is void
                    tmp = """\
    
            _api_call_transact(buffer);
    }}
    """
                    f_client.write(tmp.format(**decl))
    
                    tmp = """\
    
            return *({return_type}*)_api_call_transact(buffer);
    
    """
                    f_client.write(tmp.format(**decl))
    
    
            tmp = """\
    
    
    /* Weakly linked stubs for ISRs */
    """
            f_client.write(tmp)
    
            for isr in interrupts:
                tmp = """\
    void {isr}(api_int_id_t id)
            __attribute__((weak, alias("__epic_isr_default_handler")));
    """
                f_client.write(tmp.format(**isr))
    
            tmp = """\
    
    /* Default handler stub */
    __attribute__((weak)) void epic_isr_default_handler(api_int_id_t id)
    {
            ;
    }
    
    /*
     * This function is needed because aliasing the weak default handler will
     * lead to issues.
     */
    void __epic_isr_default_handler(api_int_id_t id)
    {
            epic_isr_default_handler(id);
    }
    
    /*
     * __dispatch_isr() will be called from the actual isr which was triggered
     * by core 0.  It will then call the appropriate isr.
     */
    void __dispatch_isr(api_int_id_t id)
    {
            switch (id) {
    """
            f_client.write(tmp)
    
            for isr in interrupts:
                tmp = """\
            case {id}:
                    {isr}(id);
                    break;
    """
                f_client.write(tmp.format(**isr))
    
            tmp = """\
            default:
                    epic_isr_default_handler(id);
                    break;
            }
    }
    """
            f_client.write(tmp)
    
        # END: Generate Client }}}
    
        # Generate Dispatcher {{{
        with open(args.server, "w") as f_dispatcher:
            tmp = """\
    
    #include "modules/log.h"
    
    #include "{header}"
    
    void __api_dispatch_call(uint32_t id, void*buffer)
    {{
            switch (id) {{
    """
            f_dispatcher.write(tmp.format(**fmt_header))
    
            for decl in declarations:
                if decl["return_type"] == "void":
                    tmp = """\
            case {id}:
                    {name}("""
                    f_dispatcher.write(tmp.format(**decl))
    
                    tmp = """\
            case {id}:
                    *(({return_type}*)buffer) = {name}("""
                    f_dispatcher.write(tmp.format(**decl))
    
                for i, arg in enumerate(decl["args"]):
                    arg["comma"] = "" if i == 0 else ","
                    tmp = """{comma}
                            *({type}*)(buffer + {offset})"""
                    f_dispatcher.write(tmp.format(**arg))
    
                tmp = """
                    );
                    break;
    """
                f_dispatcher.write(tmp.format(**decl))
    
            tmp = """\
            default:
                    /* TODO: Better error handling */
    
                    LOG_ERR("api-dispatcher", "API function 0x%lx is unknown!!", id);
    
    """
            f_dispatcher.write(tmp.format(**fmt_header))
        # END: Generate Dispatcher }}}
    
    
    
    if __name__ == "__main__":
        main()