Skip to content
Snippets Groups Projects
genapi.py 5.33 KiB
Newer Older
import argparse
import contextlib
import os
import re
import subprocess


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()

    with contextlib.ExitStack() as cx:
        # 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 $
#include "{header}"
""".format(
            header=os.path.relpath(args.header)
        )

        # Evaluate the preprocessor
        source = subprocess.check_output(
            ["gcc", "-E", "-"], input=api_src.encode()
        ).decode()

        # Parse the header for API definitions
        matcher = re.compile(
            r"__GENERATE_API \$ __GEN_ID_(?P<id>\w+) \$ (?P<type>\w+(?:\*+|\s+))(?P<decl>.+?)\((?P<args>.*?)\) \$",
            re.DOTALL | re.MULTILINE,
        )

        args_matcher = re.compile(r"(?P<type>(?:const )?\w+(?:\*+|\s+))(?P<name>\w+),")

        # Open output files
        f_client = cx.enter_context(open(args.client, "w"))
        f_server = cx.enter_context(open(args.server, "w"))

        print("""\
#include <stdio.h>

#include "{}"
#include "api/caller.h"
""".format(
            os.path.basename(args.header)
        ), file=f_client)

        print("""\
#include <stdio.h>
#include "{}"

void __api_dispatch_call(uint32_t id, void*buffer)
{{
    switch (id) {{""".format(
            os.path.basename(args.header)
        ), file=f_server)

        for match in matcher.finditer(source):
            api_id = match.group("id")
            api_return = match.group("type").strip()
            api_decl = match.group("decl")
            api_args = match.group("args")

            api_args_names = []
            api_args_types = []
            api_args_sizes = []

            # Destructure args
            for match in args_matcher.finditer(api_args + ","):
                arg_type = match.group("type").strip()
                arg_name = match.group("name")

                api_args_names.append(arg_name)
                api_args_types.append(arg_type)
                api_args_sizes.append("sizeof({})".format(arg_type))

            print(
                """\
/* Autogenerated stub for {id} */
{ret} {cdecl}({cargs})
{{
    const int size = {total_size};
    void*buffer;

    buffer = _api_call_start({id}, size);
    /* TODO: Check if buffer is no NULL */
""".format(
                    id=api_id,
                    ret=api_return,
                    cdecl=api_decl,
                    cargs=api_args,
                    total_size=" + ".join(api_args_sizes) if api_args_sizes != [] else "0",
                ),
                file=f_client,
            )

            if api_return != "void":
                print("""\
    case {id}:
        *(({ret}*)buffer) = {cdecl}(""".format(id=api_id, ret=api_return, cdecl=api_decl),
                    file=f_server,
                )
            else:
                print("""\
    case {id}:
        {cdecl}(""".format(id=api_id, ret=api_return, cdecl=api_decl),
                    file=f_server,
                )

            for i, (arg, ty) in enumerate(zip(api_args_names, api_args_types)):
                print(
                    """    *({type}*)(buffer + {offset}) = {arg};""".format(
                        type=ty,
                        offset=" + ".join(api_args_sizes[:i]) if i > 0 else "0",
                        arg=arg,
                    ),
                    file=f_client,
                )

                if i != 0:
                    print(",", file=f_server)

                print(
                    """\
            *({type}*)(buffer + {offset})""".format(
                        type=ty,
                        offset=" + ".join(api_args_sizes[:i]) if i > 0 else "0",
                    ),
                    file=f_server,
                    end="",
                )

            print("""
        );
        break;""".format(
                    cdecl=api_decl,
                    args=", ".join(api_args_names),
                ),
                file=f_server,
            )

            if api_return != "void":
                print(
                """
    return *({ret}*)_api_call_transact(buffer);
}}
""".format(
                        id=api_id,
                        ret=api_return,
                    ),
                    file=f_client,
                )
            else:
                print(
                """
    _api_call_transact(buffer);
}}
""".format(
                        id=api_id,
                    ),
                    file=f_client,
                )

        print("""\
    default:
        printf("Error: API function %x is unknown!!\\n", {id});
        break;
    }}
}}""".format(
            id=api_id,
        ), file=f_server)


if __name__ == "__main__":
    main()