Просмотр исходного кода

bindgen: Initial doc comments for Odin

Alexander Arvidsson 8 месяцев назад
Родитель
Сommit
543bd5021d
2 измененных файлов с 82 добавлено и 31 удалено
  1. 58 30
      bindgen/gen_ir.py
  2. 24 1
      bindgen/gen_odin.py

+ 58 - 30
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

+ 24 - 1
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)