import sys import os import re # ──────── Interpreter State ──────── class Frame: def __init__(self, function_name, locals=None): self.function_name = function_name self.locals = locals or {} class InterpreterState: def __init__(self): self.stack = [] # Call stack self.functions = {} # name → AST def push_frame(self, name): self.stack.append(Frame(name)) def pop_frame(self): return self.stack.pop() def current_frame(self): return self.stack[-1] if self.stack else None def debug_bt(self): print("\nBacktrace:") for i, frame in enumerate(reversed(self.stack)): print(f" #{i} {frame.function_name}") print() def debug_vars(self): frame = self.current_frame() print("\nVariables:") if frame: for k, v in frame.locals.items(): print(f" {k} = {v}") else: print(" No active frame.") print() # ──────── C Parser (toy) ──────── def parse_c_file(filename, included=None): if included is None: included = set() abs_path = os.path.abspath(filename) if abs_path in included: return {} included.add(abs_path) try: with open(abs_path) as f: lines = f.readlines() except FileNotFoundError: print(f"Error: File '{filename}' not found.") return {} ast = {} current_fn = None body = [] for line in lines: line = line.strip() if line.startswith("#include"): match = re.match(r'#include\s+"(.+?)"', line) if match: included_ast = parse_c_file(os.path.join(os.path.dirname(abs_path), match.group(1)), included) ast.update(included_ast) elif line.startswith("void"): fn_match = re.match(r'void\s+(\w+)\s*\(\s*\)\s*{', line) if fn_match: if current_fn: ast[current_fn] = body body = [] current_fn = fn_match.group(1) elif line == "}": if current_fn: ast[current_fn] = body current_fn = None body = [] elif re.match(r'int\s+\w+\s*=\s*\d+;', line): var_match = re.match(r'int\s+(\w+)\s*=\s*(\d+);', line) body.append(('decl', var_match.group(1), int(var_match.group(2)))) elif re.match(r'printf\(".*",\s*\w+\);', line): print_match = re.match(r'printf\(".*",\s*(\w+)\);', line) body.append(('print', print_match.group(1))) elif re.match(r'\w+\(\);', line): call_match = re.match(r'(\w+)\(\);', line) body.append(('call', call_match.group(1))) return ast # ──────── Interpreter Executor ──────── class Interpreter: def __init__(self, state): self.state = state def run_function(self, name): if name not in self.state.functions: print(f"Function '{name}' not defined.") return self.state.push_frame(name) body = self.state.functions[name] for stmt in body: self.exec_stmt(stmt) self.state.pop_frame() def exec_stmt(self, stmt): frame = self.state.current_frame() if stmt[0] == 'decl': _, var, val = stmt frame.locals[var] = val elif stmt[0] == 'print': var = stmt[1] print(frame.locals.get(var, 'undefined')) elif stmt[0] == 'call': self.run_function(stmt[1]) # ──────── Debugger CLI ──────── def debugger_loop(state, interp): print("Mini C Interpreter Debugger") print("Commands: run, bt, vars, quit") while True: cmd = input("(debug) ").strip() if cmd == "run": interp.run_function("main") elif cmd == "bt": state.debug_bt() elif cmd == "vars": state.debug_vars() elif cmd == "quit": break else: print("Unknown command. Use: run, bt, vars, quit") # ──────── Entry Point ──────── def main(): if len(sys.argv) < 2: print("Usage: python c_interpreter.py ") return filename = sys.argv[1] ast = parse_c_file(filename) if not ast: print("No functions parsed.") return state = InterpreterState() state.functions = ast interp = Interpreter(state) debugger_loop(state, interp) if __name__ == "__main__": main()