#!/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 | struct | enum ") 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 ") 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 ") 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 ") 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")