c/tools/codec-generator/find_specs.py (81 lines of code) (raw):
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())