From 543bd5021dc56793d2a2f1689dfdbe6eadf05d86 Mon Sep 17 00:00:00 2001 From: Alexander Arvidsson <2972103+AlexanderArvidsson@users.noreply.github.com> Date: Sun, 5 Jan 2025 09:44:17 +0100 Subject: bindgen: Initial doc comments for Odin --- bindgen/gen_ir.py | 88 +++++++++++++++++++++++++++++++++++------------------ bindgen/gen_odin.py | 25 ++++++++++++++- 2 files changed, 82 insertions(+), 31 deletions(-) (limited to 'bindgen') diff --git a/bindgen/gen_ir.py b/bindgen/gen_ir.py index 0089ae28..558392ae 100644 --- a/bindgen/gen_ir.py +++ b/bindgen/gen_ir.py @@ -8,10 +8,20 @@ def is_api_decl(decl, prefix): return decl['name'].startswith(prefix) elif decl['kind'] == 'EnumDecl': # an anonymous enum, check if the items start with the prefix - return decl['inner'][0]['name'].lower().startswith(prefix) + first = get_first_non_comment(decl['inner']) + return first['name'].lower().startswith(prefix) else: return False +def get_first_non_comment(items): + return next(i for i in items if i['kind'] != 'FullComment') + +def strip_comments(items): + return [i for i in items if i['kind'] != 'FullComment'] + +def extract_comment(comment, source): + return source[comment['range']['begin']['offset']:comment['range']['end']['offset']] + def is_dep_decl(decl, dep_prefixes): for prefix in dep_prefixes: if is_api_decl(decl, prefix): @@ -27,12 +37,16 @@ def dep_prefix(decl, dep_prefixes): def filter_types(str): return str.replace('_Bool', 'bool') -def parse_struct(decl): +def parse_struct(decl, source): outp = {} outp['kind'] = 'struct' outp['name'] = decl['name'] outp['fields'] = [] for item_decl in decl['inner']: + if item_decl['kind'] == 'FullComment': + outp['comment'] = extract_comment(item_decl, source) + outp['comment_multiline'] = '\n' in outp['comment'] + continue if item_decl['kind'] != 'FieldDecl': sys.exit(f"ERROR: Structs must only contain simple fields ({decl['name']})") item = {} @@ -42,7 +56,7 @@ def parse_struct(decl): outp['fields'].append(item) return outp -def parse_enum(decl): +def parse_enum(decl, source): outp = {} if 'name' in decl: outp['kind'] = 'enum' @@ -53,24 +67,31 @@ def parse_enum(decl): needs_value = True outp['items'] = [] for item_decl in decl['inner']: + if item_decl['kind'] == 'FullComment': + outp['comment'] = extract_comment(item_decl, source) + outp['comment_multiline'] = '\n' in outp['comment'] + continue if item_decl['kind'] == 'EnumConstantDecl': item = {} item['name'] = item_decl['name'] if 'inner' in item_decl: - const_expr = item_decl['inner'][0] - if const_expr['kind'] != 'ConstantExpr': - sys.exit(f"ERROR: Enum values must be a ConstantExpr ({item_decl['name']}), is '{const_expr['kind']}'") - if const_expr['valueCategory'] != 'rvalue' and const_expr['valueCategory'] != 'prvalue': - sys.exit(f"ERROR: Enum value ConstantExpr must be 'rvalue' or 'prvalue' ({item_decl['name']}), is '{const_expr['valueCategory']}'") - if not ((len(const_expr['inner']) == 1) and (const_expr['inner'][0]['kind'] == 'IntegerLiteral')): - sys.exit(f"ERROR: Enum value ConstantExpr must have exactly one IntegerLiteral ({item_decl['name']})") - item['value'] = const_expr['inner'][0]['value'] + exprs = strip_comments(item_decl['inner']) + if len(exprs) > 0: + const_expr = exprs[0] + if const_expr['kind'] != 'ConstantExpr': + sys.exit(f"ERROR: Enum values must be a ConstantExpr ({item_decl['name']}), is '{const_expr['kind']}'") + if const_expr['valueCategory'] != 'rvalue' and const_expr['valueCategory'] != 'prvalue': + sys.exit(f"ERROR: Enum value ConstantExpr must be 'rvalue' or 'prvalue' ({item_decl['name']}), is '{const_expr['valueCategory']}'") + const_expr_inner = strip_comments(const_expr['inner']) + if not ((len(const_expr_inner) == 1) and (const_expr_inner[0]['kind'] == 'IntegerLiteral')): + sys.exit(f"ERROR: Enum value ConstantExpr must have exactly one IntegerLiteral ({item_decl['name']})") + item['value'] = const_expr_inner[0]['value'] if needs_value and 'value' not in item: - sys.exit(f"ERROR: anonymous enum items require an explicit value") + sys.exit("ERROR: anonymous enum items require an explicit value") outp['items'].append(item) return outp -def parse_func(decl): +def parse_func(decl, source): outp = {} outp['kind'] = 'func' outp['name'] = decl['name'] @@ -78,6 +99,10 @@ def parse_func(decl): outp['params'] = [] if 'inner' in decl: for param in decl['inner']: + if param['kind'] == 'FullComment': + outp['comment'] = extract_comment(param, source) + outp['comment_multiline'] = '\n' in outp['comment'] + continue if param['kind'] != 'ParmVarDecl': print(f" >> warning: ignoring func {decl['name']} (unsupported parameter type)") return None @@ -87,38 +112,41 @@ def parse_func(decl): outp['params'].append(outp_param) return outp -def parse_decl(decl): +def parse_decl(decl, source): kind = decl['kind'] if kind == 'RecordDecl': - return parse_struct(decl) + return parse_struct(decl, source) elif kind == 'EnumDecl': - return parse_enum(decl) + return parse_enum(decl, source) elif kind == 'FunctionDecl': - return parse_func(decl) + return parse_func(decl, source) else: return None -def clang(csrc_path): - cmd = ['clang', '-Xclang', '-ast-dump=json', '-c' ] - cmd.append(csrc_path) +def clang(csrc_path, with_comments=False): + cmd = ['clang', '-Xclang', '-ast-dump=json', "-c", csrc_path] + if with_comments: + cmd.append('-fparse-all-comments') return subprocess.check_output(cmd) -def gen(header_path, source_path, module, main_prefix, dep_prefixes): - ast = clang(source_path) +def gen(header_path, source_path, module, main_prefix, dep_prefixes, with_comments=False): + ast = clang(source_path, with_comments=with_comments) inp = json.loads(ast) outp = {} outp['module'] = module outp['prefix'] = main_prefix outp['dep_prefixes'] = dep_prefixes outp['decls'] = [] - for decl in inp['inner']: - is_dep = is_dep_decl(decl, dep_prefixes) - if is_api_decl(decl, main_prefix) or is_dep: - outp_decl = parse_decl(decl) - if outp_decl is not None: - outp_decl['is_dep'] = is_dep - outp_decl['dep_prefix'] = dep_prefix(decl, dep_prefixes) - outp['decls'].append(outp_decl) + with open(header_path, 'r') as f: + source = f.read() + for decl in inp['inner']: + is_dep = is_dep_decl(decl, dep_prefixes) + if is_api_decl(decl, main_prefix) or is_dep: + outp_decl = parse_decl(decl, source) + if outp_decl is not None: + outp_decl['is_dep'] = is_dep + outp_decl['dep_prefix'] = dep_prefix(decl, dep_prefixes) + outp['decls'].append(outp_decl) with open(f'{module}.json', 'w') as f: f.write(json.dumps(outp, indent=2)); return outp diff --git a/bindgen/gen_odin.py b/bindgen/gen_odin.py index 497d756c..8b2b7798 100644 --- a/bindgen/gen_odin.py +++ b/bindgen/gen_odin.py @@ -21,6 +21,7 @@ module_names = { 'sdtx_': 'debugtext', 'sshape_': 'shape', 'sglue_': 'glue', + 'simgui_': 'imgui', } system_libs = { @@ -76,6 +77,7 @@ c_source_names = { 'sdtx_': 'sokol_debugtext.c', 'sshape_': 'sokol_shape.c', 'sglue_': 'sokol_glue.c', + 'simgui_': 'sokol_imgui.c', } ignores = [ @@ -420,6 +422,13 @@ def gen_c_imports(inp, c_prefix, prefix): args = funcdecl_args_c(decl, prefix) res_type = funcdecl_result_c(decl, prefix) res_str = '' if res_type == '' else f'-> {res_type}' + if decl.get('comment'): + if decl.get('comment_multiline'): + l(" /*") + l(" " + " ".join(decl['comment'].splitlines(True))) + l(" */") + else: + l(" // " + decl['comment'].strip()) # Need to special case sapp_sg to avoid Odin's context keyword if c_prefix == "sapp_sg": l(f' @(link_name="{decl["name"]}")') @@ -438,6 +447,13 @@ def gen_consts(decl, prefix): def gen_struct(decl, prefix): c_struct_name = check_override(decl['name']) struct_name = as_struct_or_enum_type(c_struct_name, prefix) + if decl.get('comment'): + if decl.get('comment_multiline'): + l("/*") + l(decl["comment"]) + l("*/") + else: + l("// " + decl['comment'].strip()) l(f'{struct_name} :: struct {{') for field in decl['fields']: field_name = check_override(field['name']) @@ -452,6 +468,13 @@ def gen_struct(decl, prefix): def gen_enum(decl, prefix): enum_name = check_override(decl['name']) + if decl.get('comment'): + if decl.get('comment_multiline'): + l("/*") + l(decl["comment"]) + l("*/") + else: + l("// " + decl['comment'].strip()) l(f'{as_struct_or_enum_type(enum_name, prefix)} :: enum i32 {{') for item in decl['items']: item_name = as_enum_item_name(check_override(item['name'])) @@ -529,7 +552,7 @@ def gen(c_header_path, c_prefix, dep_c_prefixes): shutil.copyfile(c_header_path, f'{c_root}/{os.path.basename(c_header_path)}') csource_path = get_csource_path(c_prefix) module_name = module_names[c_prefix] - ir = gen_ir.gen(c_header_path, csource_path, module_name, c_prefix, dep_c_prefixes) + ir = gen_ir.gen(c_header_path, csource_path, module_name, c_prefix, dep_c_prefixes, with_comments=True) gen_module(ir, c_prefix, dep_c_prefixes) with open(f"{module_root}/{ir['module']}/{ir['module']}.odin", 'w', newline='\n') as f_outp: f_outp.write(out_lines) -- cgit v1.2.3