import json
import os
import sys
import re
import cffi
from logging import info, error, warning
from enum import Enum
from typing import Dict, List, Any
class ImpellerAPI:
def __init__(self, header_without_comments: str) -> None:
self.enums: Dict[str, List[str]] = parse_enums_from_header(header_without_comments)
self.handles: Dict[str, str] = parses_handles_from_header(header_without_comments)
self.functions: Dict[str, Dict[str, Any]] = parse_functions_from_header(header_without_comments)
self.pod_structs: Dict[str, Dict[str, Any]] = parses_structs_from_header(header_without_comments)
types = set()
for struct in self.pod_structs.values():
for field in struct['fields']:
types.add(field['ty'])
for function in self.functions.values():
for arg in function['args']:
types.add(arg['ty'])
types.add(function['return_ty']['ty'])
for handle in self.handles.keys():
types.discard(handle)
for enum in self.enums.keys():
types.discard(enum)
for struct in self.pod_structs.keys():
types.discard(f'const {struct}*')
types.discard(f'{struct}*')
types.discard(f'{struct}')
self.types = sorted(types)
def read_impeller_header_to_str():
impeller_header_path = "impeller.h"
with open(impeller_header_path, "r") as impeller_header:
return impeller_header.read()
def strip_line_comments_from_header_contents(header_contents: str):
header_without_comments = re.sub(r"[^/]//(?!/).*", "", header_contents)
return header_without_comments
def parse_enums_from_header(header_without_comments):
enum_pattern = r"enum\s+(\w+)\s*{([\w\s,]+)}"
enums = re.findall(enum_pattern, header_without_comments)
def parse_enum_values(enum_values: str):
"parses enum values from block"
variant_pattern = r"(\w+)"
variants_list = re.findall(variant_pattern, enum_values)
return [value.strip() for value in variants_list]
return {enum[0].strip(): parse_enum_values(enum[1]) for enum in enums}
def parses_handles_from_header(header_without_comments):
opaque_struct_pattern = r"(^///.*(?:\n///.*)*)?\nIMPELLER_DEFINE_HANDLE\((\w+)\);"
opaque_structs = re.findall(opaque_struct_pattern, header_without_comments, re.MULTILINE)
return {struct[1].strip(): struct[0].strip() for struct in opaque_structs}
def parses_structs_from_header(header_without_comments) -> Dict[str, Dict[str, Any]]:
"assumes that there's no comments in header"
pod_struct_pattern = r"(///.*(?:\n///.*)*)?\ntypedef struct\s+(\w+)\s*{([^}]+)}"
pod_structs = re.findall(pod_struct_pattern, header_without_comments)
structs = {}
for struct_match in pod_structs:
name = struct_match[1].strip()
doc = struct_match[0].strip()
fields = []
fields_match = struct_match[2].strip()
if fields_match:
for field_match in fields_match.split(';'):
if field_match:
field = parse_var_ty_declaration(field_match)
fields.append(field)
structs[name] = {
"doc": doc,
"fields": fields
}
return structs
def parse_var_ty_declaration(var_ty_declaration: str) -> Dict[str, Any]:
var_ty_pattern = r"(.*?)\s+((?:IMPELLER_NULLABLE|IMPELLER_NONNULL))?\s*(\w+)(\[\d+\])?$"
var_ty_match = re.match(var_ty_pattern, var_ty_declaration.strip())
if var_ty_match is None:
raise ValueError(f"var_ty_declaration is not in correct format: {var_ty_declaration}")
name = var_ty_match.group(3).strip()
ty = ty=var_ty_match.group(1).strip()
nonnull=var_ty_match.group(2) == "IMPELLER_NONNULL"
array_match = var_ty_match.group(4)
array_size = int(array_match.strip('[]')) if array_match else 0
result = {
"name": name,
"ty": ty,
}
if nonnull:
result["nonnull"] = nonnull
if array_size:
result["array_size"] = array_size
return result
def parse_functions_from_header(header_without_comments) -> Dict[str, Dict[str, Any]]:
"assumes that there's no comments in header"
function_pattern = r"(///.*(?:\n///.*)*)?\nIMPELLER_EXPORT\s+(IMPELLER_NODISCARD\s+)?(\w+\s+)(IMPELLER_NULLABLE|IMPELLER_NONNULL)?\s*(\w+\s*)\(([^)]*)\);"
functions = re.findall(function_pattern, header_without_comments)
result = {}
for function in functions:
name = function[4].strip()
return_ty = {
"ty": function[2].strip(),}
if function[1]:
return_ty["nodiscard"] = True
if function[3].strip() == "IMPELLER_NONNULL":
return_ty["nonnull"] = True
args = []
args_match = function[5].strip()
if args_match:
for arg_match in args_match.split(','):
if arg_match:
arg = parse_var_ty_declaration(arg_match)
args.append(arg)
result[name] = {
"return_ty": return_ty,
"args": args
}
return result
def generate_impeller_api(impeller_header_contents: str) -> ImpellerAPI:
header_without_comments = strip_line_comments_from_header_contents(impeller_header_contents)
return ImpellerAPI(header_without_comments)
def fake_main():
impeller_header_contents = read_impeller_header_to_str()
api_json = generate_impeller_api(impeller_header_contents)
with open('impeller_api.json', 'w') as f:
f.write(json.dumps(api_json.__dict__, indent=2, ))
if __name__ == "__main__":
fake_main()