gen_ir.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. #-------------------------------------------------------------------------------
  2. # Generate an intermediate representation of a clang AST dump.
  3. #-------------------------------------------------------------------------------
  4. import re, json, sys, subprocess
  5. def is_api_decl(decl, prefix):
  6. if 'name' in decl:
  7. return decl['name'].startswith(prefix)
  8. elif decl['kind'] == 'EnumDecl':
  9. # an anonymous enum, check if the items start with the prefix
  10. first = get_first_non_comment(decl['inner'])
  11. return first['name'].lower().startswith(prefix)
  12. else:
  13. return False
  14. def get_first_non_comment(items):
  15. return next(i for i in items if i['kind'] != 'FullComment')
  16. def strip_comments(items):
  17. return [i for i in items if i['kind'] != 'FullComment']
  18. def extract_comment(comment, source):
  19. return source[comment['range']['begin']['offset']:comment['range']['end']['offset']+1].rstrip()
  20. def is_dep_decl(decl, dep_prefixes):
  21. for prefix in dep_prefixes:
  22. if is_api_decl(decl, prefix):
  23. return True
  24. return False
  25. def dep_prefix(decl, dep_prefixes):
  26. for prefix in dep_prefixes:
  27. if is_api_decl(decl, prefix):
  28. return prefix
  29. return None
  30. def filter_types(str):
  31. return str.replace('_Bool', 'bool')
  32. def parse_struct(decl, source):
  33. outp = {}
  34. outp['kind'] = 'struct'
  35. outp['name'] = decl['name']
  36. outp['fields'] = []
  37. for item_decl in decl['inner']:
  38. if item_decl['kind'] == 'FullComment':
  39. outp['comment'] = extract_comment(item_decl, source)
  40. continue
  41. if item_decl['kind'] != 'FieldDecl':
  42. sys.exit(f"ERROR: Structs must only contain simple fields ({decl['name']})")
  43. item = {}
  44. if 'name' in item_decl:
  45. item['name'] = item_decl['name']
  46. item['type'] = filter_types(item_decl['type']['qualType'])
  47. outp['fields'].append(item)
  48. return outp
  49. def parse_enum(decl, source):
  50. outp = {}
  51. if 'name' in decl:
  52. outp['kind'] = 'enum'
  53. outp['name'] = decl['name']
  54. needs_value = False
  55. else:
  56. outp['kind'] = 'consts'
  57. needs_value = True
  58. outp['items'] = []
  59. for item_decl in decl['inner']:
  60. if item_decl['kind'] == 'FullComment':
  61. outp['comment'] = extract_comment(item_decl, source)
  62. continue
  63. if item_decl['kind'] == 'EnumConstantDecl':
  64. item = {}
  65. item['name'] = item_decl['name']
  66. if 'inner' in item_decl:
  67. exprs = strip_comments(item_decl['inner'])
  68. if len(exprs) > 0:
  69. const_expr = exprs[0]
  70. if const_expr['kind'] != 'ConstantExpr':
  71. sys.exit(f"ERROR: Enum values must be a ConstantExpr ({item_decl['name']}), is '{const_expr['kind']}'")
  72. if const_expr['valueCategory'] != 'rvalue' and const_expr['valueCategory'] != 'prvalue':
  73. sys.exit(f"ERROR: Enum value ConstantExpr must be 'rvalue' or 'prvalue' ({item_decl['name']}), is '{const_expr['valueCategory']}'")
  74. const_expr_inner = strip_comments(const_expr['inner'])
  75. if not ((len(const_expr_inner) == 1) and (const_expr_inner[0]['kind'] == 'IntegerLiteral')):
  76. sys.exit(f"ERROR: Enum value ConstantExpr must have exactly one IntegerLiteral ({item_decl['name']})")
  77. item['value'] = const_expr_inner[0]['value']
  78. if needs_value and 'value' not in item:
  79. sys.exit("ERROR: anonymous enum items require an explicit value")
  80. outp['items'].append(item)
  81. return outp
  82. def parse_func(decl, source):
  83. outp = {}
  84. outp['kind'] = 'func'
  85. outp['name'] = decl['name']
  86. outp['type'] = filter_types(decl['type']['qualType'])
  87. outp['params'] = []
  88. if 'inner' in decl:
  89. for param in decl['inner']:
  90. if param['kind'] == 'FullComment':
  91. outp['comment'] = extract_comment(param, source)
  92. continue
  93. if param['kind'] != 'ParmVarDecl':
  94. print(f" >> warning: ignoring func {decl['name']} (unsupported parameter type)")
  95. return None
  96. outp_param = {}
  97. outp_param['name'] = param['name']
  98. outp_param['type'] = filter_types(param['type']['qualType'])
  99. outp['params'].append(outp_param)
  100. return outp
  101. def parse_decl(decl, source):
  102. kind = decl['kind']
  103. if kind == 'RecordDecl':
  104. return parse_struct(decl, source)
  105. elif kind == 'EnumDecl':
  106. return parse_enum(decl, source)
  107. elif kind == 'FunctionDecl':
  108. return parse_func(decl, source)
  109. else:
  110. return None
  111. def clang(csrc_path, with_comments=False):
  112. cmd = ['clang', '-Xclang', '-ast-dump=json', "-c", csrc_path]
  113. if with_comments:
  114. cmd.append('-fparse-all-comments')
  115. return subprocess.check_output(cmd)
  116. def gen(header_path, source_path, module, main_prefix, dep_prefixes, with_comments=False):
  117. ast = clang(source_path, with_comments=with_comments)
  118. inp = json.loads(ast)
  119. outp = {}
  120. outp['module'] = module
  121. outp['prefix'] = main_prefix
  122. outp['dep_prefixes'] = dep_prefixes
  123. outp['decls'] = []
  124. # load string with original line endings (otherwise Clang's output ranges
  125. # for comments are off)
  126. # NOTE: that same problem might exist for non-ASCII characters,
  127. # so don't use those in header files!
  128. with open(header_path, mode='r', newline='') as f:
  129. source = f.read()
  130. first_comment = re.search(r"/\*(.*?)\*/", source, re.S).group(1)
  131. if first_comment and "Project URL" in first_comment:
  132. outp['comment'] = first_comment
  133. for decl in inp['inner']:
  134. is_dep = is_dep_decl(decl, dep_prefixes)
  135. if is_api_decl(decl, main_prefix) or is_dep:
  136. outp_decl = parse_decl(decl, source)
  137. if outp_decl is not None:
  138. outp_decl['is_dep'] = is_dep
  139. outp_decl['dep_prefix'] = dep_prefix(decl, dep_prefixes)
  140. outp['decls'].append(outp_decl)
  141. with open(f'{module}.json', 'w') as f:
  142. f.write(json.dumps(outp, indent=2));
  143. return outp