genapi.py 3.56 KB
Newer Older
Rahix's avatar
Rahix committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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<decl>.+?)\((?P<args>.*?)\) \$",
            re.DOTALL | re.MULTILINE,
        )

        args_matcher = re.compile(r"(?P<type>\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"))

Rahix's avatar
Rahix committed
53
54
55
56
57
58
        print('#include "{}"\n'.format(
            os.path.basename(args.header)
        ), file=f_client)
        print('#include "{}"\n'.format(
            os.path.basename(args.header)
        ), file=f_server)
Rahix's avatar
Rahix committed
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

        for match in matcher.finditer(source):
            api_id = match.group("id")
            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} */
{cdecl}({cargs}) {{
    const int size = {total_size};
    unsigned char buffer[size];
""".format(
                    id=api_id,
                    cdecl=api_decl,
                    cargs=api_args,
                    total_size=" + ".join(api_args_sizes),
Rahix's avatar
Rahix committed
89
90
                ),
                file=f_client,
Rahix's avatar
Rahix committed
91
92
93
94
95
96
97
98
            )

            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,
Rahix's avatar
Rahix committed
99
100
                    ),
                    file=f_client,
Rahix's avatar
Rahix committed
101
102
103
104
105
106
107
108
109
110
111
112
                )

            print(
                """
    printf("{id}: ");
    for (int i = 0; i < size; i++) {{
        printf("0x%02x ", buffer[i]);
    }}
    printf("\\n");
}}
""".format(
                    id=api_id
Rahix's avatar
Rahix committed
113
114
                ),
                file=f_client,
Rahix's avatar
Rahix committed
115
116
117
118
119
            )


if __name__ == "__main__":
    main()