From 9d94f87e43c6f5676c947269a86a5abfde0972fc Mon Sep 17 00:00:00 2001
From: ch3 <ch3@mailbox.org>
Date: Sun, 16 Jun 2019 02:47:01 +0200
Subject: [PATCH] refact(genapi): Try to increase readability

---
 hw-tests/api-demo/genapi.py | 312 +++++++++++++++++++++---------------
 1 file changed, 179 insertions(+), 133 deletions(-)

diff --git a/hw-tests/api-demo/genapi.py b/hw-tests/api-demo/genapi.py
index 40cf1099..5f8eb5bc 100644
--- a/hw-tests/api-demo/genapi.py
+++ b/hw-tests/api-demo/genapi.py
@@ -4,6 +4,155 @@ import os
 import re
 import subprocess
 
+api_src_fmt = """\
+#define API(id, def) __GENERATE_API $ __GEN_ID_##id $ def $
+#include "{header}"
+"""
+
+
+"""Generated Client file"""
+api_func_start_fmt = """\
+/* Autogenerated stub for {id} */
+void {cdecl}({cargs})
+{{
+    const int size = {total_size};
+    void*buffer;
+
+    buffer = api_call_start({id}, size);
+    /* TODO: Check if buffer is no NULL */
+"""
+
+serialise_arg_fmt = """    *({type}*)(buffer + {offset}) = {arg};"""
+
+bother_dispatcher_fmt = """
+    printf("Sending call {id}\\nBUF: ");
+    for (int i = 0; i < size; i++) {{
+        printf("0x%02x ", ((char*)buffer)[i]);
+    }}
+    printf("\\n");
+
+    api_call_bother_dispatcher(buffer);
+}}
+"""
+
+
+"""Generated Service file"""
+api_dispatch_call_start = """\
+void __api_dispatch_call(uint32_t id, void*buffer)
+{
+    switch (id) {"""
+
+dispatch_case_fmt = """\
+        case {id}:
+            {cdecl}("""
+
+deserialise_arg_fmt = """\
+                *({type}*)(buffer + {offset})"""
+
+insert_switch_case_break_str = """
+            );
+            break;"""
+
+switch_add_default_fmt = """\
+        default:
+            printf("Error: API function %x is unknown!!\\n", {id});
+        break;
+    }}
+}}"""
+
+def api_func_iter (header:str):
+    # Parse the header for API definitions
+    matcher = re.compile(
+        r"__GENERATE_API \$ __GEN_ID_(?P<id>\w+) \$ void (?P<decl>.+?)\((?P<args>.*?)\) \$",
+        re.DOTALL | re.MULTILINE,
+    )
+
+    # 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 = api_src_fmt.format(
+        header=os.path.relpath(header)
+    )
+
+    # Evaluate the preprocessor
+    source = subprocess.check_output(
+        ["gcc", "-E", "-"], input=api_src.encode()
+    ).decode()
+
+    return matcher.finditer(source)
+
+
+def include_header (header:str, file):
+    print('#include "{}"\n'.format(
+        os.path.basename(header)
+    ), file=file)
+
+
+def destructure_args (api_args:str):
+    api_args_names = []
+    api_args_types = []
+    api_args_sizes = []
+
+    args_matcher = re.compile(r"(?P<type>\w+(?:\*+|\s+))(?P<name>\w+),")
+
+    # Destructure args
+    for arg in args_matcher.finditer(api_args + ","):
+        arg_type = arg.group("type").strip()
+        arg_name = arg.group("name")
+
+        api_args_names.append(arg_name)
+        api_args_types.append(arg_type)
+        api_args_sizes.append("sizeof({})".format(arg_type))
+
+    return (api_args_names, api_args_types, api_args_sizes)
+
+
+def serialise_arg (ty:str, api_args_sizes:list, arg_idx:int, arg:str, file):
+    print(
+        serialise_arg_fmt.format(
+            type=ty,
+            offset=" + ".join(api_args_sizes[:arg_idx]) if arg_idx > 0 else "0",
+            arg=arg,
+        ),
+        file=file,
+    )
+
+def deserialise_arg (ty:str, api_args_sizes:list, arg_idx:int, file):
+    if arg_idx != 0:
+        print(",", file=file)
+
+    print(
+        deserialise_arg_fmt.format(
+            type=ty,
+            offset=" + ".join(api_args_sizes[:arg_idx]) if arg_idx > 0 else "0",
+        ),
+        file=file,
+        end="",
+    )
+
+
+def insert_switch_case_break (file):
+    print(insert_switch_case_break_str,
+            file=file,
+    )
+
+
+def bother_dispatcher (api_id:str, file):
+    print(bother_dispatcher_fmt.format(
+            id=api_id
+        ),
+        file=file,
+    )
+
+
+def switch_add_default (api_id:str, file):
+    print(switch_add_default_fmt.format(
+        id=api_id,
+    ), file=file)
+
+
 
 def main():
     parser = argparse.ArgumentParser(
@@ -20,148 +169,45 @@ 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 = """\
-#define API(id, def) __GENERATE_API $ __GEN_ID_##id $ def $
-#include "{header}"
-""".format(
-            header=os.path.relpath(args.header)
-        )
+    # Open output files
+    cx = contextlib.ExitStack()
+    f_client = cx.enter_context(open(args.client, "w"))
+    f_server = cx.enter_context(open(args.server, "w"))
 
-        # Evaluate the preprocessor
-        source = subprocess.check_output(
-            ["gcc", "-E", "-"], input=api_src.encode()
-        ).decode()
+    include_header (args.header, f_client)
+    include_header (args.header, f_server)
 
-        # Parse the header for API definitions
-        matcher = re.compile(
-            r"__GENERATE_API \$ __GEN_ID_(?P<id>\w+) \$ void (?P<decl>.+?)\((?P<args>.*?)\) \$",
-            re.DOTALL | re.MULTILINE,
-        )
+    print(api_dispatch_call_start, file=f_server)
 
-        args_matcher = re.compile(r"(?P<type>\w+(?:\*+|\s+))(?P<name>\w+),")
+    for api_func in api_func_iter (args.header):
+        api_id = api_func.group("id")
+        api_decl = api_func.group("decl")
+        api_args = api_func.group("args")
 
-        # Open output files
-        f_client = cx.enter_context(open(args.client, "w"))
-        f_server = cx.enter_context(open(args.server, "w"))
+        (api_args_names, api_args_types, api_args_sizes) = \
+                destructure_args (api_args)
 
-        print('#include "{}"\n'.format(
-            os.path.basename(args.header)
-        ), file=f_client)
+        print(api_func_start_fmt.format(
+                id=api_id,
+                cdecl=api_decl,
+                cargs=api_args,
+                total_size=" + ".join(api_args_sizes),
+            ),
+            file=f_client,
+        )
 
-        print("""\
-#include "{}"
+        print(dispatch_case_fmt.format(id=api_id, cdecl=api_decl),
+            file=f_server,
+        )
 
-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_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} */
-void {cdecl}({cargs})
-{{
-    const int size = {total_size};
-    void*buffer;
+        for i, (arg, ty) in enumerate(zip(api_args_names, api_args_types)):
+            serialise_arg (ty, api_args_sizes, i, arg, f_client)
+            deserialise_arg (ty, api_args_sizes, i, f_server)
 
-    buffer = api_call_start({id}, size);
-    /* TODO: Check if buffer is no NULL */
-""".format(
-                    id=api_id,
-                    cdecl=api_decl,
-                    cargs=api_args,
-                    total_size=" + ".join(api_args_sizes),
-                ),
-                file=f_client,
-            )
-
-            print("""\
-    case {id}:
-        {cdecl}(""".format(id=api_id, 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,
-            )
-
-            print(
-                """
-    printf("Sending call {id}\\nBUF: ");
-    for (int i = 0; i < size; i++) {{
-        printf("0x%02x ", ((char*)buffer)[i]);
-    }}
-    printf("\\n");
+        insert_switch_case_break (f_server)
+        bother_dispatcher (api_id, f_client)
 
-    api_call_bother_dispatcher(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)
+    switch_add_default (api_id, f_server)
 
 
 if __name__ == "__main__":
-- 
GitLab