diff --git a/epicardium/api/genapi.py b/epicardium/api/genapi.py index 725020e55e0c4f54636da98ed62c6de46b269820..c83358b50311eabc6a2bfa06b794c9eaf7954cb4 100644 --- a/epicardium/api/genapi.py +++ b/epicardium/api/genapi.py @@ -1,8 +1,79 @@ import argparse -import contextlib import os import re import subprocess +import sys + + +MATCH_EXPANSION = re.compile( + r"__GENERATE_API \$ __GEN_ID_(?P<id>\w+) \$ (?P<decl>.*?) \$", + re.DOTALL | re.MULTILINE, +) + +MATCH_DECLARATION = re.compile( + r"^(?P<typename>.*?)\s*\((?P<args>.*)\)$", + re.DOTALL, +) + +MATCH_TYPENAME = re.compile( + r"^(?P<type>(?:const )?(?:struct )?\w+(?:\s+|\*+))(?P<name>\w+)$", +) + + +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_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, + }) + + return declarations def main(): @@ -20,169 +91,123 @@ def main(): ) 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 = """\ + # 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, - ) + header=os.path.relpath(args.header) + ) - args_matcher = re.compile(r"(?P<type>(?:const )?\w+(?:\*+|\s+))(?P<name>\w+),") + # Evaluate the preprocessor + source = subprocess.check_output( + ["gcc", "-E", "-"], input=api_src.encode() + ).decode() - # Open output files - f_client = cx.enter_context(open(args.client, "w")) - f_server = cx.enter_context(open(args.server, "w")) + declarations = parse_declarations(source) + fmt_header = { + "header": os.path.basename(args.header) + } - print("""\ + # Generate Client {{{ + with open(args.client, "w") as f_client: + tmp = """\ #include <stdio.h> -#include "{}" +#include "{header}" #include "api/caller.h" -""".format( - os.path.basename(args.header) - ), file=f_client) +""" + f_client.write(tmp.format(**fmt_header)) - print("""\ -#include <stdio.h> -#include "{}" + for decl in declarations: + decl["total_size"] = sizeof(decl["args"]) + tmp = """\ -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}) +{return_type} {name}({args_str}) {{ - const int size = {total_size}; - void*buffer; + 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, - ) + 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)) 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); + tmp = """\ + + return *({return_type}*)_api_call_transact(buffer); }} -""".format( - id=api_id, - ret=api_return, - ), - file=f_client, - ) +""" + f_client.write(tmp.format(**decl)) + # END: Generate Client }}} + + # Generate Dispatcher {{{ + with open(args.server, "w") as f_dispatcher: + tmp = """\ +#include <stdio.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)) else: - print( - """ - _api_call_transact(buffer); + 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 */ + printf("Error: API function %x is unknown!!\\n", id); + break; + }} }} -""".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) +""" + f_dispatcher.write(tmp.format(**fmt_header)) + # END: Generate Dispatcher }}} if __name__ == "__main__":