You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
411 lines
16 KiB
411 lines
16 KiB
#!/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")
|
|
|