from fastapi import FastAPI, Request, Form from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates import sys import io import json app = FastAPI() # Mount static files (for CSS, JS) app.mount("/static", StaticFiles(directory="static"), name="static") templates = Jinja2Templates(directory="templates") def run_code_with_trace(code: str): trace_data = [] stdout_capture = io.StringIO() # ✅ Safely serialize objects (avoid JSON TypeError) def safe_serialize(obj): try: json.dumps(obj) return obj except (TypeError, OverflowError): return str(obj) # ✅ Tracer function def tracer(frame, event, arg): if event == "line": locals_serialized = {k: safe_serialize(v) for k, v in frame.f_locals.items()} trace_data.append({ "line": frame.f_lineno, "locals": locals_serialized }) return tracer # Redirect stdout temporarily (if user uses print) sys.settrace(tracer) sys.stdout = stdout_capture try: safe_globals = {"__builtins__": {"print": print, "range": range, "len": len}} exec(code, safe_globals) except Exception as e: trace_data.append({"error": str(e)}) finally: sys.settrace(None) sys.stdout = sys.__stdout__ # reset stdout output = stdout_capture.getvalue() stdout_capture.close() return trace_data, output @app.get("/", response_class=HTMLResponse) async def index(request: Request): return templates.TemplateResponse("index.html", {"request": request}) @app.post("/run", response_class=HTMLResponse) async def run_code(request: Request, code: str = Form(...)): trace, output = run_code_with_trace(code) return templates.TemplateResponse( "index.html", { "request": request, "trace_json": json.dumps(trace), "output": output, "code": code } )