This is an automated email from the ASF dual-hosted git repository.

astitcher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-proton.git

commit bbe7ab103682d9398d14d3f893ec8f8793a8a009
Author: Andrew Stitcher <[email protected]>
AuthorDate: Wed Jun 23 15:03:18 2021 -0400

    PROTON-2451: Added tool to generate code for codec
    
    This generates code for both emitting and consuming AMQP types as
    defined by an input json file.
    
    There is also a simple scanner to generate the json file by scanning
    the source C code. This scanner will become useless when all the code
    that uses scan/fill formats has been removed from the source base as
    there will be nothing left to find!
---
 c/CMakeLists.txt                      |  56 +++-
 c/tools/codec-generator/Pipfile       |  12 +
 c/tools/codec-generator/find_specs.py | 103 +++++++
 c/tools/codec-generator/generate.py   | 556 ++++++++++++++++++++++++++++++++++
 c/tools/codec-generator/specs.json    |  49 +++
 5 files changed, 769 insertions(+), 7 deletions(-)

diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt
index 580f83f..4ffe079 100644
--- a/c/CMakeLists.txt
+++ b/c/CMakeLists.txt
@@ -64,22 +64,61 @@ include_directories (
   )
 
 add_custom_command (
-  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/src/encodings.h
-  COMMAND ${PN_ENV_SCRIPT} PYTHONPATH=${CMAKE_SOURCE_DIR}/tools/python 
${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/src/encodings.h.py > 
${CMAKE_CURRENT_BINARY_DIR}/src/encodings.h
-  DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/encodings.h.py
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/src/encodings.h
+  COMMAND
+    ${PN_ENV_SCRIPT} PYTHONPATH=${CMAKE_SOURCE_DIR}/tools/python 
${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/src/encodings.h.py > 
${CMAKE_CURRENT_BINARY_DIR}/src/encodings.h
+  DEPENDS
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/encodings.h.py
   )
 
 add_custom_command (
-  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/src/protocol.h
-  COMMAND ${PN_ENV_SCRIPT} PYTHONPATH=${CMAKE_SOURCE_DIR}/tools/python 
${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/src/protocol.h.py > 
${CMAKE_CURRENT_BINARY_DIR}/src/protocol.h
-  DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/protocol.h.py
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/src/protocol.h
+  COMMAND
+    ${PN_ENV_SCRIPT} PYTHONPATH=${CMAKE_SOURCE_DIR}/tools/python 
${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/src/protocol.h.py > 
${CMAKE_CURRENT_BINARY_DIR}/src/protocol.h
+  DEPENDS
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/protocol.h.py
+  )
+
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_generators.c
+    ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_generators.h
+  COMMAND
+    ${Python_EXECUTABLE} 
${CMAKE_CURRENT_SOURCE_DIR}/tools/codec-generator/generate.py --emit -i 
${CMAKE_CURRENT_SOURCE_DIR}/tools/codec-generator/specs.json -o 
${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_generators
+  DEPENDS
+    ${CMAKE_CURRENT_SOURCE_DIR}/tools/codec-generator/generate.py
+    ${CMAKE_CURRENT_SOURCE_DIR}/tools/codec-generator/specs.json
+  )
+
+add_custom_command(
+  OUTPUT
+    ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_consumers.c
+    ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_consumers.h
+  COMMAND
+    ${Python_EXECUTABLE} 
${CMAKE_CURRENT_SOURCE_DIR}/tools/codec-generator/generate.py --consume -i 
${CMAKE_CURRENT_SOURCE_DIR}/tools/codec-generator/specs.json -o 
${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_consumers
+  DEPENDS
+    ${CMAKE_CURRENT_SOURCE_DIR}/tools/codec-generator/generate.py
+    ${CMAKE_CURRENT_SOURCE_DIR}/tools/codec-generator/specs.json
   )
 
 add_custom_target(
   generated_c_files
-  DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/src/protocol.h 
${CMAKE_CURRENT_BINARY_DIR}/src/encodings.h
+  DEPENDS
+    ${CMAKE_CURRENT_BINARY_DIR}/src/protocol.h
+    ${CMAKE_CURRENT_BINARY_DIR}/src/encodings.h
+    ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_generators.c
+    ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_generators.h
+    ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_consumers.c
+    ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_consumers.h
   )
 
+option (ENABLE_GENERATED_CODEC "Use automatically generated codec code" ON)
+if (NOT ENABLE_GENERATED_CODEC)
+  add_compile_definitions(GENERATE_CODEC_CODE)
+endif ()
+
 file (GLOB_RECURSE source_files "src/*.h" "src/*.c" "src/*.cpp")
 
 foreach (sfile ${source_files})
@@ -241,6 +280,9 @@ set (qpid-proton-core
   src/core/autodetect.c
   src/core/transport.c
   src/core/message.c
+
+  ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_generators.c
+  ${CMAKE_CURRENT_BINARY_DIR}/src/core/frame_consumers.c
   )
 
 set (qpid-proton-include-generated
diff --git a/c/tools/codec-generator/Pipfile b/c/tools/codec-generator/Pipfile
new file mode 100644
index 0000000..dbda1e8
--- /dev/null
+++ b/c/tools/codec-generator/Pipfile
@@ -0,0 +1,12 @@
+[[source]]
+name = "pypi"
+url = "https://pypi.org/simple";
+verify_ssl = true
+
+[dev-packages]
+
+[packages]
+pycparser = "*"
+
+[requires]
+python_version = "3.7"
diff --git a/c/tools/codec-generator/find_specs.py 
b/c/tools/codec-generator/find_specs.py
new file mode 100644
index 0000000..15fcb6e
--- /dev/null
+++ b/c/tools/codec-generator/find_specs.py
@@ -0,0 +1,103 @@
+import argparse
+import json
+import os
+import re
+import sys
+
+from pycparser import c_ast, parse_file
+
+from typing import Any, Dict, List, Set, Tuple, Union
+
+
+def strip_quotes(arg: str) -> str:
+    if arg[0] != '"' or arg[-1:] != '"':
+            raise Exception(arg)
+    return arg[1:-1]
+
+
+def find_function_calls(file: str, name: str, includes: List[str], defines: 
List[str]) -> List[List[Any]]:
+    class FillFinder(c_ast.NodeVisitor):
+        def __init__(self):
+            self._name = name
+            self.result = []
+
+        def visit_FuncCall(self, node):
+            if node.name.name == self._name:
+                r = []
+                for e in node.args.exprs:
+                    r.append(e)
+                self.result.append(r)
+
+    include_args = [f'-I{d}' for d in includes]
+    define_args = [f'-D{d}' for d in defines]
+    ast = parse_file(file, use_cpp=True,
+                     cpp_args=[
+                         *include_args,
+                         *define_args
+                     ])
+    ff = FillFinder()
+    ff.visit(ast)
+    return ff.result
+
+
+c_defines = ['GENERATE_CODEC_CODE',
+             'NDEBUG',
+             '__attribute__(X)=',
+             '__asm__(X)=',
+             '__inline=',
+             '__extension__=',
+             '__restrict=',
+             '__builtin_va_list=int']
+
+
+def find_fill_specs(c_includes, pn_source):
+    amqp_calls = find_function_calls(os.path.join(pn_source, 
'c/src/core/transport.c'), 'pn_fill_performative',
+                                     c_includes, c_defines)
+    sasl_calls = find_function_calls(os.path.join(pn_source, 
'c/src/sasl/sasl.c'), 'pn_fill_performative',
+                                     c_includes, c_defines)
+    message_calls = find_function_calls(os.path.join(pn_source, 
'c/src/core/message.c'), 'pn_data_fill',
+                                        c_includes, c_defines)
+    fill_spec_args = [c[1] for c in amqp_calls]
+    fill_spec_args += [c[1] for c in sasl_calls]
+    fill_spec_args += [c[1] for c in message_calls]
+    fill_specs: Set[str] = \
+        {strip_quotes(e.value) for e in fill_spec_args if type(e) is 
c_ast.Constant and e.type == 'string'}
+    return fill_specs
+
+
+def find_scan_specs(c_includes, pn_source):
+    amqp_calls = find_function_calls(os.path.join(pn_source, 
'c/src/core/transport.c'), 'pn_data_scan',
+                                     c_includes, c_defines)
+    message_calls = find_function_calls(os.path.join(pn_source, 
'c/src/core/message.c'),'pn_data_scan',
+                                        c_includes, c_defines)
+    scan_spec_args = [c[1] for c in amqp_calls]
+    scan_spec_args += [c[1] for c in message_calls]
+    # Only try to generate code for constant strings
+    scan_specs: Set[str] = \
+        {strip_quotes(e.value) for e in scan_spec_args if type(e) is 
c_ast.Constant and e.type == 'string'}
+    return scan_specs
+
+
+def main():
+    argparser = argparse.ArgumentParser(description='Find scan/fill specs in 
proton code for code generation')
+    argparser.add_argument('-o', '--output', help='output json file', 
type=str, required=True)
+    argparser.add_argument('-s', '--source', help='proton source directory', 
type=str, required=True)
+    argparser.add_argument('-b', '--build', help='proton build directory', 
type=str, required=True)
+
+    args = argparser.parse_args()
+
+    pn_source = args.source
+    pn_build = args.build
+    json_filename = args.output
+
+    c_includes = [f'{os.path.join(pn_build, "c/include")}',
+                  f'{os.path.join(pn_build, "c/src")}']
+    fill_specs = find_fill_specs(c_includes, pn_source)
+    scan_specs = find_scan_specs(c_includes, pn_source)
+    with open(json_filename, 'w') as file:
+        json.dump({'fill_specs': sorted(list(fill_specs)), 'scan_specs': 
sorted(list(scan_specs))}, file, indent=2)
+
+
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())
diff --git a/c/tools/codec-generator/generate.py 
b/c/tools/codec-generator/generate.py
new file mode 100644
index 0000000..fb9f68f
--- /dev/null
+++ b/c/tools/codec-generator/generate.py
@@ -0,0 +1,556 @@
+import argparse
+import itertools
+import json
+import os
+import re
+import sys
+
+from typing import Any, Dict, List, Set, Tuple, Union
+
+
+class ParseError(Exception):
+    def __init__(self, error):
+        super().__init__(error)
+
+
+indent_size = 4
+
+
+class ASTNode:
+    def __init__(self, function_suffix: str, types: List[str], consume_types: 
Union[List[str], None] = None):
+        self.function_suffix = function_suffix
+        self.types = types
+        self.consume_types = consume_types
+        self.count_args = len(self.types)
+        self.count_consume_args = len(self.consume_types) if 
self.consume_types else len(self.types)
+
+    @staticmethod
+    def mk_indent(indent: int):
+        return " "*indent*indent_size
+
+    @staticmethod
+    def mk_funcall(name: str, args: List[str]):
+        return f'{name}({", ".join(args)})'
+
+    def gen_emit_code(self, prefix: List[str], first_arg: int, indent: int) -> 
List[str]:
+        return 
[f'{self.mk_indent(indent)}{self.mk_funcall("emit_"+self.function_suffix, 
prefix + self.gen_args(first_arg))};']
+
+    def gen_params(self, first_arg: int) -> List[Tuple[str, str]]:
+        return [(f'arg{i+first_arg}', self.types[i]) for i in 
range(len(self.types))]
+
+    def gen_args(self, first_arg: int) -> List[str]:
+        return [f'arg{i+first_arg}' for i in range(len(self.types))]
+
+    def gen_consume_code(self, prefix: List[str], first_arg: int, indent: int) 
-> List[str]:
+        return 
[f'{self.mk_indent(indent)}{self.mk_funcall("consume_"+self.function_suffix, 
prefix + self.gen_consume_args(first_arg))};']
+
+    @staticmethod
+    def add_pointer(type: str) -> str:
+        return f'{type}*'
+
+    def gen_consume_params(self, first_arg: int) -> List[Tuple[str, str]]:
+        if self.consume_types:
+            return [(f'arg{i + first_arg}', self.consume_types[i]) for i in 
range(len(self.consume_types))]
+        else:
+            return [(f'arg{i + first_arg}', self.add_pointer(self.types[i])) 
for i in range(len(self.types))]
+
+    def gen_consume_args(self, first_arg: int) -> List[str]:
+        if self.consume_types:
+            return [f'arg{i+first_arg}' for i in 
range(len(self.consume_types))]
+        else:
+            return [f'arg{i+first_arg}' for i in range(len(self.types))]
+
+    def __repr__(self) -> str:
+        return f'<{self.function_suffix}: {self.types}>'
+
+
+class NullNode(ASTNode):
+    def __init__(self, function_suffix: str):
+        super().__init__(function_suffix, [])
+
+    def gen_emit_code(self, prefix: List[str], first_arg: int, indent: int) -> 
List[str]:
+        return 
[f'{self.mk_indent(indent)}{self.mk_funcall("emit_"+self.function_suffix, 
prefix)};']
+
+    def gen_params(self, first_arg: int) -> List[Tuple[str, str]]:
+        return []
+
+
+class ListNode(ASTNode):
+    def __init__(self, name: str, l: List[ASTNode], types: List[str]):
+        self.list: List[ASTNode] = l
+        super().__init__(name, types)
+        self.count_args += sum([i.count_args for i in l])
+        self.count_consume_args += sum([i.count_consume_args for i in l])
+
+    def gen_params_list(self, first_arg: int) -> List[Tuple[str, str]]:
+        params = []
+        arg = first_arg
+        for n in self.list:
+            r = n.gen_params(arg)
+            arg += len(r)
+            params.append(r)
+        return [i for i in itertools.chain(*params)]
+
+    def gen_params(self, first_arg: int) -> List[Tuple[str, str]]:
+        args = super().gen_params(first_arg)
+        return [
+            *args,
+            *self.gen_params_list(first_arg + len(args))
+        ]
+
+    def gen_consume_params_list(self, first_arg: int) -> List[Tuple[str, str]]:
+        params = []
+        arg = first_arg
+        for n in self.list:
+            r = n.gen_consume_params(arg)
+            arg += len(r)
+            params.append(r)
+        return [i for i in itertools.chain(*params)]
+
+    def gen_consume_params(self, first_arg: int) -> List[Tuple[str, str]]:
+        args = super().gen_consume_params(first_arg)
+        return [
+            *args,
+            *self.gen_consume_params_list(first_arg + len(args))
+        ]
+
+    def gen_emit_code(self, prefix: List[str], first_arg: int, indent: int) -> 
List[str]:
+        lines = []
+        arg = first_arg
+        for n in self.list:
+            lines.append(n.gen_emit_code(prefix, arg, indent))
+            arg += n.count_args
+        return [i for i in itertools.chain(*lines)]
+
+    def gen_consume_code(self, prefix: List[str], first_arg: int, indent: int) 
-> List[str]:
+        lines = []
+        arg = first_arg
+        for n in self.list:
+            lines.append(n.gen_consume_code(prefix, arg, indent+1))
+            arg += n.count_consume_args
+        return [
+            f'{self.mk_indent(indent)}{{',
+            f'{self.mk_indent(indent+1)}pni_consumer_t subconsumer;',
+            f'{self.mk_indent(indent+1)}uint32_t count;',
+            f'{self.mk_indent(indent+1)}{self.mk_funcall("consume_list", 
prefix+["&subconsumer", "&count"])};',
+            f'{self.mk_indent(indent+1)}pni_consumer_t consumer = 
subconsumer;',
+            *[i for i in itertools.chain(*lines)],
+            f'{self.mk_indent(indent+1)}{self.mk_funcall("consume_end_list", 
prefix)};',
+            f'{self.mk_indent(indent)}}}'
+        ]
+
+    def __repr__(self) -> str:
+        nl = "\n    "
+        return f'''<{self.function_suffix}: {self.types}
+    {nl.join([repr(x) for x in self.list])}
+>'''
+
+
+class DescListNode(ListNode):
+    def __init__(self, l: List[ASTNode]):
+        super().__init__('described_list', l, ['uint64_t'])
+
+    def gen_emit_code(self, prefix: List[str], first_arg: int, indent: int) -> 
List[str]:
+        args = self.gen_args(first_arg)
+        return [
+            f'{self.mk_indent(indent)}emit_descriptor({", 
".join(prefix+args)});',
+            f'{self.mk_indent(indent)}for (bool small_encoding = true; ; 
small_encoding = false) {{',
+            f'{self.mk_indent(indent+1)}pni_compound_context c = '
+            f'{self.mk_funcall("emit_list", prefix+["small_encoding", 
"true"])};',
+            f'{self.mk_indent(indent+1)}pni_compound_context compound = c;',
+            *super().gen_emit_code(prefix, first_arg+len(args), indent + 1),
+            f'{self.mk_indent(indent+1)}{self.mk_funcall("emit_end_list", 
prefix+["small_encoding"])};',
+            f'{self.mk_indent(indent+1)}if (encode_succeeded({", 
".join(prefix)})) break;',
+            f'{self.mk_indent(indent)}}}',
+        ]
+
+
+class DescListIgnoreTypeNode(ListNode):
+    def __init__(self, l: List[ASTNode]):
+        super().__init__('described_unknown_list', l, [])
+
+    def gen_consume_code(self, prefix: List[str], first_arg: int, indent: int) 
-> List[str]:
+        return [
+            f'{self.mk_indent(indent)}{{',
+            f'{self.mk_indent(indent+1)}pni_consumer_t subconsumer;',
+            f'{self.mk_indent(indent+1)}uint64_t dummy;',
+            f'{self.mk_indent(indent+1)}{self.mk_funcall("consume_descriptor", 
prefix+["&subconsumer", "&dummy"])};',
+            f'{self.mk_indent(indent+1)}pni_consumer_t consumer = 
subconsumer;',
+            *super().gen_consume_code(prefix, first_arg, indent + 1),
+            f'{self.mk_indent(indent)}}}',
+        ]
+
+
+class ArrayNode(ListNode):
+    def __init__(self, l: List[ASTNode]):
+        self.list: List[ASTNode] = l
+        super().__init__('array', l, ['pn_type_t'])
+
+    def gen_emit_code(self, prefix: List[str], first_arg: int, indent: int) -> 
List[str]:
+        args = self.gen_args(first_arg)
+        return [
+            f'{self.mk_indent(indent)}for (bool small_encoding = true; ; 
small_encoding = false) {{',
+            f'{self.mk_indent(indent+1)}pni_compound_context c = '
+            f'{self.mk_funcall("emit_array", 
prefix+["small_encoding"]+args)};',
+            f'{self.mk_indent(indent+1)}pni_compound_context compound = c;',
+            *super().gen_emit_code(prefix, first_arg+len(args), indent + 1),
+            f'{self.mk_indent(indent+1)}{self.mk_funcall("emit_end_array", 
prefix+["small_encoding"])};',
+            f'{self.mk_indent(indent+1)}if (encode_succeeded({", 
".join(prefix)})) break;',
+            f'{self.mk_indent(indent)}}}',
+        ]
+
+
+class OptionNode(ASTNode):
+    def __init__(self, i: ASTNode):
+        self.option: ASTNode = i
+        super().__init__('option', ['bool'])
+        self.count_args += i.count_args
+        self.count_consume_args += i.count_consume_args
+
+    def gen_emit_code(self, prefix: List[str], first_arg: int, indent: int) -> 
List[str]:
+        arg = f'arg{first_arg}'
+        return [
+            f'{self.mk_indent(indent)}if ({arg}) {{',
+            *self.option.gen_emit_code(prefix, first_arg+1, indent + 1),
+            f'{self.mk_indent(indent)}}} else {{',
+            *NullNode("null").gen_emit_code(prefix, 0, indent + 1),
+            f'{self.mk_indent(indent)}}}'
+        ]
+
+    def gen_params(self, first_arg: int) -> List[Tuple[str, str]]:
+        args = super().gen_params(first_arg)
+        return [
+            *args,
+            *self.option.gen_params(first_arg + len(args))
+        ]
+
+    def gen_consume_code(self, prefix: List[str], first_arg: int, indent: int) 
-> List[str]:
+        arg = f'arg{first_arg}'
+        return [
+            f'{self.mk_indent(indent)}*{arg} = 
{self.option.gen_consume_code(prefix, first_arg+1, 0)[0]};'
+        ]
+
+    def gen_consume_params(self, first_arg: int) -> List[Tuple[str, str]]:
+        args = super().gen_consume_params(first_arg)
+        return [
+            *args,
+            *self.option.gen_consume_params(first_arg + len(args))
+        ]
+
+
+class EmptyNode(ASTNode):
+    def __init__(self):
+        super().__init__('empty_frame', [])
+
+    def gen_emit_code(self, prefix: List[str], first_arg: int, indent: int) -> 
List[str]:
+        return []
+
+    def gen_params(self, first_arg: int) -> List[Tuple[str, str]]:
+        return []
+
+
+def expect_char(format: str, char: str) -> Tuple[bool, str]:
+    return format[0] == char, format[1:]
+
+
+def parse_list(format: str) -> Tuple[List[ASTNode], str]:
+    rest = format
+    list = []
+    while not rest[0] == ']':
+        i, rest, = parse_item(rest)
+        list.append(i)
+    return list, rest
+
+
+def parse_item(format: str) -> Tuple[ASTNode, str]:
+    if len(format) == 0:
+        return EmptyNode(), ''
+    if format.startswith('DL['):
+        l, rest = parse_list(format[3:])
+        b, rest = expect_char(rest, ']')
+        if not b:
+            raise ParseError(format)
+        return DescListNode(l), rest
+    elif format.startswith('D.['):
+        l, rest = parse_list(format[3:])
+        b, rest = expect_char(rest, ']')
+        if not b:
+            raise ParseError(format)
+        return DescListIgnoreTypeNode(l), rest
+    elif format.startswith('D?LR'):
+        return ASTNode('described_maybe_type_raw', ['bool', 'uint64_t', 
'pn_bytes_t'], consume_types=['bool*', 'uint64_t*', 'pn_bytes_t*']), format[4:]
+    elif format.startswith('D?L?.'):
+        return ASTNode('described_maybe_type_maybe_anything', ['bool', 
'uint64_t', 'bool'], consume_types=['bool*', 'uint64_t*', 'bool*']), format[4:]
+    elif format.startswith('DLC'):
+        return ASTNode('described_type_copy', ['uint64_t', 'pn_data_t*'], 
consume_types=['uint64_t*', 'pn_data_t*']), format[3:]
+    elif format.startswith('DL.'):
+        return ASTNode('described_type_anything', ['uint64_t']), format[3:]
+    elif format.startswith('D?L.'):
+        return ASTNode('described_maybe_type_anything', ['bool', 'uint64_t']), 
format[3:]
+    elif format.startswith('D..'):
+        return NullNode('described_anything'), format[3:]
+    elif format.startswith('D.C'):
+        return ASTNode('described_copy', ['pn_data_t*'], 
consume_types=['pn_data_t*']), format[3:]
+    elif format.startswith('@T['):
+        l, rest = parse_list(format[3:])
+        b, rest = expect_char(rest, ']')
+        if not b:
+            raise ParseError(format)
+        return ArrayNode(l), rest
+    elif format.startswith('?'):
+        i, rest = parse_item(format[1:])
+        return OptionNode(i), rest
+    elif format.startswith('*s'):
+        return ASTNode('counted_symbols', ['size_t', 'char**']), format[2:]
+    elif format.startswith('.'):
+        return NullNode('anything'), format[1:]
+    elif format.startswith('s'):
+        return ASTNode('symbol', ['const char*'], 
consume_types=['pn_bytes_t*']), format[1:]
+    elif format.startswith('S'):
+        return ASTNode('string', ['const char*'], 
consume_types=['pn_bytes_t*']), format[1:]
+    elif format.startswith('C'):
+        return ASTNode('copy', ['pn_data_t*'], consume_types=['pn_data_t*']), 
format[1:]
+    elif format.startswith('I'):
+        return ASTNode('uint', ['uint32_t']), format[1:]
+    elif format.startswith('H'):
+        return ASTNode('ushort', ['uint16_t']), format[1:]
+    elif format.startswith('n'):
+        return NullNode('null'), format[1:]
+    elif format.startswith('R'):
+        return ASTNode('raw', ['pn_bytes_t'], consume_types=['pn_bytes_t*']), 
format[1:]
+    elif format.startswith('M'):
+        return ASTNode('multiple', ['pn_data_t*']), format[1:]
+    elif format.startswith('o'):
+        return ASTNode('bool', ['bool']), format[1:]
+    elif format.startswith('B'):
+        return ASTNode('ubyte', ['uint8_t']), format[1:]
+    elif format.startswith('L'):
+        return ASTNode('ulong', ['uint64_t']), format[1:]
+    elif format.startswith('t'):
+        return ASTNode('timestamp', ['pn_timestamp_t']), format[1:]
+    elif format.startswith('z'):
+        return ASTNode('binaryornull', ['size_t', 'const char*'], 
consume_types=['pn_bytes_t*']), format[1:]
+    elif format.startswith('Z'):
+        return ASTNode('binarynonull', ['size_t', 'const char*'], 
consume_types=['pn_bytes_t*']), format[1:]
+    else:
+        raise ParseError(format)
+
+
+# Need to translate '@[]*?.' to legal identifier characters
+# These will be fairly arbitrary and just need to avoid already used codes
+def make_legal_identifier(s: str) -> str:
+    subs = {'@': 'A', '[': 'E', ']': 'e', '{': 'F', '}': 'f', '*': 'j', '.': 
'q', '?': 'Q'}
+    r = ''
+    for c in s:
+        if c in subs:
+            r += subs[c]
+        else:
+            r += c
+    return r
+
+
+def consume_function(name_prefix: str, scan_spec: str, prefix_args: 
List[Tuple[str, str]]) -> Tuple[str, List[str]]:
+    p, _ = parse_item(scan_spec)
+    params = p.gen_consume_params(0)
+    function_name = name_prefix + '_' + make_legal_identifier(scan_spec)
+    param_list = ', '.join([t+' '+arg for arg, t in prefix_args+params])
+    function_spec = f'size_t {function_name}({param_list})'
+
+    function_decl = f'{function_spec};'
+
+    prefix_params = [a for (a, _) in prefix_args]
+    function_defn = [
+        f'{function_spec}',
+        '{',
+        f'{p.mk_indent(1)}pni_consumer_t consumer = 
make_consumer_from_bytes({", ".join(prefix_params)});',
+        *p.gen_consume_code(['&consumer'], 0, 1),
+        f'{p.mk_indent(1)}return consumer.position;',
+        '}',
+        ''
+    ]
+    return function_decl, function_defn
+
+
+def emit_function(name_prefix: str, fill_spec: str, prefix_args: 
List[Tuple[str, str]]) -> Tuple[str, List[str]]:
+    p, _ = parse_item(fill_spec)
+    params = p.gen_params(0)
+    function_name = name_prefix + '_' + make_legal_identifier(fill_spec)
+    param_list = ', '.join([t+' '+arg for arg, t in prefix_args+params])
+    function_spec = f'pn_bytes_t {function_name}({param_list})'
+
+    bytes_args = [('bytes', 'char*'), ('size', 'size_t')]
+    bytes_function_name = name_prefix + '_bytes_' + 
make_legal_identifier(fill_spec)
+    bytes_param_list = ', '.join([t+' '+arg for arg, t in bytes_args+params])
+    bytes_function_spec = f'size_t {bytes_function_name}({bytes_param_list})'
+
+    inner_function_name = name_prefix + '_inner_' + 
make_legal_identifier(fill_spec)
+    inner_param_list = [('emitter', 'pni_emitter_t*')]+params
+    inner_params = ', '.join([t+' '+arg for arg, t in inner_param_list])
+    inner_function_spec = f'bool {inner_function_name}({inner_params})'
+
+    function_decl = f'{function_spec};\n{bytes_function_spec};'
+
+    prefix_params = [a for (a, _) in prefix_args]
+    args = [a for (a, _) in params]
+    if type(p) is EmptyNode:
+        function_defn = [
+            f'{function_spec}',
+            '{',
+            f'{p.mk_indent(1)}pni_emitter_t emitter = make_emitter({", 
".join(prefix_params)});',
+            f'{p.mk_indent(1)}return make_return(emitter);',
+            '}',
+            ''
+        ]
+    else:
+        function_defn = [
+            f'{inner_function_spec}',
+            '{',
+            f'{p.mk_indent(1)}pni_compound_context compound = 
make_compound();',
+            *p.gen_emit_code(['emitter', '&compound'], 0, 1),
+            f'{p.mk_indent(1)}return resize_required(emitter);',
+            '}',
+            '',
+            f'{function_spec}',
+            '{',
+            f'{p.mk_indent(1)}do {{',
+            f'{p.mk_indent(2)}pni_emitter_t emitter = 
make_emitter_from_buffer({", ".join(prefix_params)});',
+            f'{p.mk_indent(2)}if ({p.mk_funcall(inner_function_name, 
["&emitter", *args])}) {{',
+            f'{p.mk_indent(3)}{p.mk_funcall("size_buffer_to_emitter", 
[*prefix_params, "&emitter"])};',
+            f'{p.mk_indent(3)}continue;',
+            f'{p.mk_indent(2)}}}',
+            f'{p.mk_indent(2)}return make_bytes_from_emitter(emitter);',
+            f'{p.mk_indent(1)}}} while (true);',
+            f'{p.mk_indent(1)}/*Unreachable*/',
+            '}',
+            ''
+            f'{bytes_function_spec}',
+            '{',
+            f'{p.mk_indent(1)}pni_emitter_t emitter = 
make_emitter_from_bytes((pn_rwbytes_t){{.size=size, .start=bytes}});',
+            f'{p.mk_indent(1)}{p.mk_funcall(inner_function_name, ["&emitter", 
*args])};',
+            f'{p.mk_indent(1)}return make_bytes_from_emitter(emitter).size;',
+            '}',
+            ''
+        ]
+    return function_decl, function_defn
+
+
+prefix_emit_header = """
+#include "proton/codec.h"
+#include "buffer.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+"""
+
+prefix_consume_header = """
+#include "proton/codec.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+"""
+
+
+def output_header(decls: Dict[str, str], prefix: str, file=None):
+    # Output declarations
+    print(prefix, file=file)
+    for fill_spec, decl in sorted(decls.items()):
+        print(
+            f'/* {fill_spec} */\n'
+            f'{decl}',
+            file=file
+        )
+
+
+prefix_emit_implementation = """
+#include "core/emitters.h"
+"""
+
+prefix_consume_implementation = """
+#include "core/consumers.h"
+"""
+
+
+def output_implementation(defns: Dict[str, List[str]], prefix: str, 
header=None, file=None):
+    # Output implementations
+    if header:
+        print(f'#include "{header}"', file=file)
+
+    print(prefix, file=file)
+    for fill_spec, defn in sorted(defns.items()):
+        printable_defn = '\n'.join(defn)
+        print(
+            f'/* {fill_spec} */\n'
+            f'{printable_defn}',
+            file=file
+        )
+
+
+def emit(fill_specs, decl_filename, impl_filename):
+    decls: Dict[str, str] = {}
+    defns: Dict[str, List[str]] = {}
+    for fill_spec in fill_specs:
+        decl, defn = emit_function('pn_amqp_encode', fill_spec, [('buffer', 
'pn_buffer_t*')])
+        decls[fill_spec] = decl
+        defns[fill_spec] = defn
+    if decl_filename and impl_filename:
+        with open(decl_filename, 'w') as dfile:
+            output_header(decls, prefix_emit_header, file=dfile)
+        with open(impl_filename, 'w') as ifile:
+            output_implementation(defns, prefix_emit_implementation, 
header=os.path.basename(decl_filename), file=ifile)
+    else:
+        output_header(decls, prefix_emit_header)
+        output_implementation(defns, prefix_emit_implementation)
+
+
+def consume(scan_specs, decl_filename, impl_filename):
+
+    decls: Dict[str, str] = {}
+    defns: Dict[str, List[str]] = {}
+    for scan_spec in scan_specs:
+        decl, defn = consume_function('pn_amqp_decode', scan_spec, [('bytes', 
'pn_bytes_t')])
+        decls[scan_spec] = decl
+        defns[scan_spec] = defn
+    if decl_filename and impl_filename:
+        with open(decl_filename, 'w') as dfile:
+            output_header(decls, prefix_consume_header, file=dfile)
+        with open(impl_filename, 'w') as ifile:
+            output_implementation(defns, prefix_consume_implementation, 
header=os.path.basename(decl_filename), file=ifile)
+    else:
+        output_header(decls, prefix_consume_header)
+        output_implementation(defns, prefix_consume_implementation)
+
+
+def main():
+    argparser = argparse.ArgumentParser(description='Generate AMQP codec in C 
for data scan/fill function calls')
+    argparser.add_argument('-i', '--input_specs', help='json file with specs 
to generate codec output code', type=str, required=True)
+    argparser.add_argument('-o', '--output-base', help='basename for output 
code', type=str)
+    group = argparser.add_mutually_exclusive_group(required=True)
+    group.add_argument('-e', '--emit', help='generate code to emit amqp', 
action='store_true')
+    group.add_argument('-c', '--consume', help='generate code to consume 
amqp', action='store_true')
+
+    args = argparser.parse_args()
+
+    if args.output_base:
+        decl_filename = args.output_base + '.h'
+        impl_filename = args.output_base + '.c'
+    else:
+        decl_filename = None
+        impl_filename = None
+
+    with open(args.input_specs, 'r') as file:
+        jsonfile = json.load(file)
+        if args.emit:
+            fill_specs = jsonfile['fill_specs']
+            emit(fill_specs, decl_filename, impl_filename)
+        elif args.consume:
+            scan_specs = jsonfile['scan_specs']
+            consume(scan_specs, decl_filename, impl_filename)
+
+
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())
diff --git a/c/tools/codec-generator/specs.json 
b/c/tools/codec-generator/specs.json
new file mode 100644
index 0000000..562e74d
--- /dev/null
+++ b/c/tools/codec-generator/specs.json
@@ -0,0 +1,49 @@
+{
+  "fill_specs": [
+    "DLC",
+    "DL[?DL[sSC]]",
+    "DL[?HIII]",
+    "DL[?IIII?I?I?In?o]",
+    "DL[?o?B?I?o?I]",
+    "DL[@T[*s]]",
+    "DL[Bz]",
+    "DL[CzSSSCss?t?tS?IS]",
+    "DL[I?o?DL[sSC]]",
+    "DL[IIzI?o?on?DLC?o?o?o]",
+    "DL[SIoBB?DL[SIsIoC?sCnCC]DL[C]nnI]",
+    "DL[SIoBB?DL[SIsIoC?sCnMM]?DL[SIsIoCM]nnILnnC]",
+    "DL[SS?I?H?InnMMC]",
+    "DL[S]",
+    "DL[Z]",
+    "DL[oI?I?o?DL[]]",
+    "DL[oIn?o?DLC]",
+    "DL[szS]"
+  ],
+  "scan_specs": [
+    "D.C",
+    "D.[.....D..D.[.....CC]]",
+    "D.[.....D..D.[C]...]",
+    "D.[.....D..DL....]",
+    "D.[.....D.[.....C.C.CC]]",
+    "D.[?HI]",
+    "D.[?IIII?I?II.o]",
+    "D.[?S?S?I?HI..CCC]",
+    "D.[CzSSSCssttSIS]",
+    "D.[I?Iz.?oo.D?LRooo]",
+    "D.[IoR]",
+    "D.[sSC]",
+    "D.[D.[sSC]]",
+    "D.[SIo?B?BD.[SIsIo.s]D.[SIsIo]..IL..?C]",
+    "D.[o?BIoI]",
+    "D.[oI?IoR]",
+    "D.[?o?oC]",
+    "D.[?I?L]",
+    "D.[sz]",
+    "D.[s]",
+    "D.[z]",
+    "D.[Bz]",
+    "D.[R]",
+    "D?L.",
+    "D?L?."
+  ]
+}

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to