Fix c_util script: implement proper C code navigation (toc, func, struct, enum commands)
This commit is contained in:
1 parent
277ab872bf
commit
e3e205ca10
3 files changed
+474
No files matched your search
@@ -0,0 +1,411 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import re
|
||||
|
||||
c_keywords = set([
|
||||
'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do',
|
||||
'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if',
|
||||
'int', 'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static',
|
||||
'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while',
|
||||
'_Alignas', '_Alignof', '_Atomic', '_Bool', '_Complex', '_Generic', '_Imaginary',
|
||||
'_Noreturn', '_Static_assert', '_Thread_local'
|
||||
])
|
||||
|
||||
def read_filelist(filename='filelist.txt'):
|
||||
with open(filename, 'r') as f:
|
||||
return [line.strip() for line in f if line.strip()]
|
||||
|
||||
def load_files(files):
|
||||
contents = {}
|
||||
for f in files:
|
||||
with open(f, 'r') as fd:
|
||||
contents[f] = fd.read().splitlines()
|
||||
return contents
|
||||
|
||||
def extract_functions_and_structs(lines):
|
||||
functions = []
|
||||
structs = []
|
||||
enums = []
|
||||
i = 0
|
||||
n = len(lines)
|
||||
multi_comment = False
|
||||
extern_c_level = 0 # Track extern "C" nesting level
|
||||
pre_lines = []
|
||||
|
||||
def skip_multi_comment(line):
|
||||
nonlocal multi_comment
|
||||
if multi_comment:
|
||||
if '*/' in line:
|
||||
multi_comment = False
|
||||
return True
|
||||
if '/*' in line and '*/' not in line:
|
||||
multi_comment = True
|
||||
return True
|
||||
return False
|
||||
|
||||
while i < n:
|
||||
line = lines[i]
|
||||
stripped = line.strip()
|
||||
|
||||
if skip_multi_comment(line):
|
||||
pre_lines.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if stripped.startswith('//') or not stripped or stripped.startswith('#'):
|
||||
pre_lines.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Skip closing braces
|
||||
if stripped.startswith('}'):
|
||||
pre_lines = []
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Check for struct/enum/union declarations
|
||||
is_typedef = stripped.startswith('typedef ')
|
||||
is_struct = stripped.startswith('struct ')
|
||||
is_enum = stripped.startswith('enum ')
|
||||
is_union = stripped.startswith('union ')
|
||||
is_typedef_struct = stripped.startswith('typedef struct')
|
||||
is_typedef_enum = stripped.startswith('typedef enum')
|
||||
is_typedef_union = stripped.startswith('typedef union')
|
||||
|
||||
if is_typedef_struct or is_typedef_enum or is_typedef_union or is_struct or is_enum or is_union:
|
||||
decl_lines = [line]
|
||||
start_line = i
|
||||
already_processed = False # Flag to skip brace counting if already processed
|
||||
i += 1
|
||||
|
||||
# Collect declaration until we find { or ;
|
||||
while i < n:
|
||||
line = lines[i]
|
||||
stripped_cont = line.strip()
|
||||
|
||||
if skip_multi_comment(line):
|
||||
decl_lines.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if stripped_cont.startswith('//') or stripped_cont.startswith('#'):
|
||||
break
|
||||
|
||||
decl_lines.append(line)
|
||||
|
||||
# Check if we hit the opening brace
|
||||
if '{' in stripped_cont:
|
||||
break
|
||||
|
||||
# Check if it's a forward declaration or end of typedef
|
||||
# Forward declaration: "typedef enum name;" - no { before
|
||||
# End of typedef: "} name;" - has { before and } in current line
|
||||
if stripped_cont.endswith(';'):
|
||||
has_brace = any('{' in l for l in decl_lines)
|
||||
if not has_brace:
|
||||
# Forward declaration, skip
|
||||
pre_lines = []
|
||||
i += 1
|
||||
break
|
||||
elif '}' in stripped_cont:
|
||||
# End of typedef (e.g., "} debug_level_t;")
|
||||
# Don't need to count braces, already have the complete declaration
|
||||
end_line = i
|
||||
decl_text = ' '.join([l.strip() for l in decl_lines])
|
||||
|
||||
# Extract name
|
||||
close_brace = decl_text.rfind('}')
|
||||
semi = decl_text.find(';', close_brace)
|
||||
if semi > close_brace:
|
||||
name = decl_text[close_brace + 1:semi].strip()
|
||||
else:
|
||||
name = decl_text[close_brace + 1:].strip()
|
||||
|
||||
if is_typedef_enum:
|
||||
enums.append({'name': name, 'pre': pre_lines[:], 'start': start_line, 'end': end_line, 'line_count': end_line - start_line + 1})
|
||||
elif is_typedef_struct or is_typedef_union:
|
||||
structs.append({'name': name, 'pre': pre_lines[:], 'start': start_line, 'end': end_line, 'line_count': end_line - start_line + 1})
|
||||
|
||||
pre_lines = []
|
||||
i += 1
|
||||
already_processed = True
|
||||
break
|
||||
|
||||
i += 1
|
||||
|
||||
# Skip brace counting if we already processed this typedef (found "} name;")
|
||||
if already_processed:
|
||||
pre_lines = []
|
||||
continue
|
||||
|
||||
if i >= n or not any('{' in l for l in decl_lines):
|
||||
pre_lines = []
|
||||
continue
|
||||
|
||||
# Now we're inside the struct/enum/union body, find the closing }
|
||||
brace_level = 1
|
||||
while i < n and brace_level > 0:
|
||||
i += 1
|
||||
if i >= n:
|
||||
break
|
||||
line = lines[i]
|
||||
decl_lines.append(line)
|
||||
stripped_line = line.strip()
|
||||
|
||||
if skip_multi_comment(line):
|
||||
continue
|
||||
|
||||
# Skip preprocessor directives when counting braces
|
||||
# Check for # after stripping leading whitespace (handles indented #define)
|
||||
if stripped_line.startswith('#'):
|
||||
continue
|
||||
|
||||
# Skip extern "C" { blocks (C++ linkage)
|
||||
if 'extern "C"' in stripped_line or stripped_line.startswith('extern'):
|
||||
if '{' in stripped_line:
|
||||
extern_c_level += 1
|
||||
continue
|
||||
|
||||
# Skip closing brace of extern "C" block
|
||||
if extern_c_level > 0 and stripped_line == '}':
|
||||
extern_c_level -= 1
|
||||
continue
|
||||
|
||||
# If we're inside extern "C", skip counting braces
|
||||
if extern_c_level > 0:
|
||||
continue
|
||||
|
||||
# Skip do { ... } while(0) patterns in macros
|
||||
if stripped_line.startswith('do {'):
|
||||
continue
|
||||
if stripped_line.startswith('}') and 'while(0)' in stripped_line:
|
||||
continue
|
||||
|
||||
brace_level += line.count('{') - line.count('}')
|
||||
|
||||
# Check if there's a name after } (for typedef) or if it's a simple struct
|
||||
end_line = i
|
||||
decl_text = ' '.join([l.strip() for l in decl_lines])
|
||||
|
||||
# Extract name and determine type
|
||||
if is_typedef_struct or is_typedef_enum or is_typedef_union:
|
||||
# Name is after the closing brace and before ;
|
||||
# Format: typedef struct { ... } name;
|
||||
close_brace = decl_text.rfind('}')
|
||||
semi = decl_text.find(';', close_brace)
|
||||
if semi > close_brace:
|
||||
name = decl_text[close_brace + 1:semi].strip()
|
||||
else:
|
||||
name = decl_text[close_brace + 1:].strip()
|
||||
|
||||
if is_typedef_enum:
|
||||
item_type = 'enum'
|
||||
enums.append({'name': name, 'pre': pre_lines[:], 'start': start_line, 'end': end_line, 'line_count': end_line - start_line + 1})
|
||||
else:
|
||||
item_type = 'struct'
|
||||
structs.append({'name': name, 'pre': pre_lines[:], 'start': start_line, 'end': end_line, 'line_count': end_line - start_line + 1})
|
||||
else:
|
||||
# Simple struct/enum/union name { ... };
|
||||
# Name is between keyword and {
|
||||
keyword = 'struct' if is_struct else ('enum' if is_enum else 'union')
|
||||
keyword_pos = decl_text.find(keyword)
|
||||
brace_pos = decl_text.find('{', keyword_pos)
|
||||
name = decl_text[keyword_pos + len(keyword):brace_pos].strip()
|
||||
|
||||
# Skip anonymous structs (no name) - they are local variables, not declarations
|
||||
if not name:
|
||||
pre_lines = []
|
||||
continue
|
||||
|
||||
if is_enum:
|
||||
enums.append({'name': name, 'pre': pre_lines[:], 'start': start_line, 'end': end_line, 'line_count': end_line - start_line + 1})
|
||||
else:
|
||||
structs.append({'name': name, 'pre': pre_lines[:], 'start': start_line, 'end': end_line, 'line_count': end_line - start_line + 1})
|
||||
|
||||
pre_lines = []
|
||||
continue
|
||||
|
||||
# Handle function declarations
|
||||
if '(' in stripped and not stripped.startswith('typedef'):
|
||||
decl_lines = [line]
|
||||
start_line = i
|
||||
i += 1
|
||||
paren_level = stripped.count('(') - stripped.count(')')
|
||||
|
||||
while i < n and paren_level > 0:
|
||||
line = lines[i]
|
||||
stripped_cont = line.strip()
|
||||
|
||||
if skip_multi_comment(line):
|
||||
decl_lines.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if stripped_cont.startswith('//') or stripped_cont.startswith('#'):
|
||||
break
|
||||
|
||||
decl_lines.append(line)
|
||||
paren_level += stripped_cont.count('(') - stripped_cont.count(')')
|
||||
|
||||
# Check if we closed all parens and found opening brace on same line
|
||||
if paren_level == 0 and '{' in stripped_cont:
|
||||
break
|
||||
|
||||
i += 1
|
||||
|
||||
# Check if current or next line starts with {
|
||||
has_brace = False
|
||||
if i < n:
|
||||
current_line = lines[i].strip()
|
||||
if '{' in current_line:
|
||||
has_brace = True
|
||||
elif i + 1 < n and lines[i + 1].strip().startswith('{'):
|
||||
i += 1
|
||||
has_brace = True
|
||||
decl_lines.append(lines[i])
|
||||
i += 1
|
||||
|
||||
if has_brace:
|
||||
decl_lines.append(lines[i])
|
||||
i += 1
|
||||
|
||||
# Find function end
|
||||
brace_level = 1
|
||||
while i < n and brace_level > 0:
|
||||
line = lines[i]
|
||||
decl_lines.append(line)
|
||||
|
||||
if skip_multi_comment(line):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
brace_level += line.count('{') - line.count('}')
|
||||
i += 1
|
||||
|
||||
end_line = i - 1
|
||||
|
||||
# Parse function signature
|
||||
decl_text = ' '.join([l.strip() for l in decl_lines])
|
||||
paren_start = decl_text.find('(')
|
||||
paren_end = decl_text.rfind(')', 0, decl_text.find('{'))
|
||||
|
||||
if paren_start > 0 and paren_end > paren_start:
|
||||
args = decl_text[paren_start + 1:paren_end].strip()
|
||||
before_paren = decl_text[:paren_start].strip()
|
||||
parts = before_paren.rsplit(maxsplit=1)
|
||||
|
||||
if len(parts) == 2:
|
||||
ret_type = parts[0]
|
||||
name = parts[1]
|
||||
else:
|
||||
ret_type = 'void'
|
||||
name = parts[0] if parts else 'unknown'
|
||||
|
||||
if name not in c_keywords and name.isidentifier():
|
||||
functions.append({
|
||||
'type': 'function',
|
||||
'name': name,
|
||||
'args': args,
|
||||
'ret': ret_type,
|
||||
'pre': pre_lines[:],
|
||||
'start': start_line,
|
||||
'end': end_line,
|
||||
'line_count': end_line - start_line + 1
|
||||
})
|
||||
|
||||
pre_lines = []
|
||||
continue
|
||||
|
||||
# Not a declaration we care about
|
||||
pre_lines = []
|
||||
i += 1
|
||||
|
||||
return functions, structs, enums
|
||||
|
||||
if __name__ == "__main__":
|
||||
files = read_filelist()
|
||||
contents = load_files(files)
|
||||
project_functions = {}
|
||||
project_structs = {}
|
||||
project_enums = {}
|
||||
for f, lines in contents.items():
|
||||
funcs, strs, enums = extract_functions_and_structs(lines)
|
||||
project_functions[f] = funcs
|
||||
project_structs[f] = strs
|
||||
project_enums[f] = enums
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python script.py toc | func <file> <funcname> | struct <file> <structname> | enum <file> <enumname>")
|
||||
sys.exit(1)
|
||||
cmd = sys.argv[1]
|
||||
if cmd == 'toc':
|
||||
for f in files:
|
||||
print(f"File: {f}")
|
||||
funcs = project_functions.get(f, [])
|
||||
strs = project_structs.get(f, [])
|
||||
enums = project_enums.get(f, [])
|
||||
if funcs:
|
||||
for func in funcs:
|
||||
print(f"Function: {func['ret']} {func['name']}({func['args']}) - {func['line_count']} lines")
|
||||
if strs:
|
||||
for s in strs:
|
||||
print(f"Struct: {s['name']} - {s['line_count']} lines")
|
||||
if enums:
|
||||
for e in enums:
|
||||
print(f"Enum: {e['name']} - {e['line_count']} lines")
|
||||
elif cmd == 'func':
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: python script.py func <file> <funcname>")
|
||||
sys.exit(1)
|
||||
file = sys.argv[2]
|
||||
funcname = sys.argv[3]
|
||||
found = False
|
||||
if file in project_functions:
|
||||
for func in project_functions[file]:
|
||||
if func['name'] == funcname:
|
||||
lines = contents[file]
|
||||
pre = func['pre']
|
||||
body = lines[func['start']:func['end'] + 1]
|
||||
print('\n'.join(pre + body))
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
print("Function not found")
|
||||
elif cmd == 'struct':
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: python script.py struct <file> <structname>")
|
||||
sys.exit(1)
|
||||
file = sys.argv[2]
|
||||
structname = sys.argv[3]
|
||||
found = False
|
||||
if file in project_structs:
|
||||
for strct in project_structs[file]:
|
||||
if strct['name'] == structname:
|
||||
lines = contents[file]
|
||||
pre = strct['pre']
|
||||
body = lines[strct['start']:strct['end'] + 1]
|
||||
print('\n'.join(pre + body))
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
print("Struct not found")
|
||||
elif cmd == 'enum':
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: python script.py enum <file> <enumname>")
|
||||
sys.exit(1)
|
||||
file = sys.argv[2]
|
||||
enumname = sys.argv[3]
|
||||
found = False
|
||||
if file in project_enums:
|
||||
for enm in project_enums[file]:
|
||||
if enm['name'] == enumname:
|
||||
lines = contents[file]
|
||||
pre = enm['pre']
|
||||
body = lines[enm['start']:enm['end'] + 1]
|
||||
print('\n'.join(pre + body))
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
print("Enum not found")
|
||||
else:
|
||||
print("Unknown command")
|
||||
Reference in new issue
Block a user