from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel import subprocess import os import tempfile import shutil from typing import Optional, List, Dict import xml.etree.ElementTree as ET app = FastAPI(title="C Compiler API - PC-Lint Compatible") # Configure CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class CodeRequest(BaseModel): code: str filename: str = "main.c" compiler: str = "gcc" # gcc, clang, or pclint standards: Optional[List[str]] = None # For MISRA standards class LintMessage(BaseModel): file: str line: int type: str code: str desc: str class CompileResponse(BaseModel): success: bool output: str errors: str executable_output: str = "" messages: Optional[List[LintMessage]] = None # For PC-lint XML output @app.get("/") def read_root(): """API information endpoint""" return { "message": "C Compiler API is running", "version": "2.0", "endpoints": { "/compile": "POST - Compile C code", "/health": "GET - Health check", "/docs": "GET - API documentation" }, "supported_compilers": ["gcc", "clang", "pclint"], "features": [ "Standard C compilation", "PC-lint static analysis", "MISRA C/C++ checking", "XML output parsing" ] } @app.get("/health") def health_check(): """Health check endpoint""" # Check if compilers are available compilers_status = {} for compiler in ["gcc", "clang", "splint"]: try: result = subprocess.run( [compiler, "--version"], capture_output=True, timeout=2 ) compilers_status[compiler] = "available" if result.returncode == 0 else "not available" except: compilers_status[compiler] = "not available" return { "status": "healthy", "service": "C Compiler API", "compilers": compilers_status } def parse_xml_lint_output(xml_file: str) -> List[Dict]: """Parse PC-lint XML output format""" messages = [] try: if not os.path.exists(xml_file) or os.path.getsize(xml_file) == 0: return messages tree = ET.parse(xml_file) root = tree.getroot() # Parse messages from XML for msg in root.findall('.//message'): messages.append({ "file": msg.find('file').text if msg.find('file') is not None else "", "line": int(msg.find('line').text) if msg.find('line') is not None and msg.find('line').text else 0, "type": msg.find('type').text if msg.find('type') is not None else "unknown", "code": msg.find('code').text if msg.find('code') is not None else "", "desc": msg.find('desc').text if msg.find('desc') is not None else "" }) except Exception as e: print(f"XML parsing error: {e}") return messages @app.post("/compile", response_model=CompileResponse) async def compile_code(request: CodeRequest): """ Compile and execute C code Supports: - gcc: Standard GNU C compiler - clang: LLVM C compiler - pclint: PC-lint Plus static analysis (using splint alternative) Example request: { "code": "#include \\nint main() { printf(\\"Hello\\\\n\\"); return 0; }", "filename": "main.c", "compiler": "gcc" } """ try: # Create a temporary directory temp_dir = tempfile.mkdtemp() source_file = os.path.join(temp_dir, request.filename) output_file = os.path.join(temp_dir, "output") xml_output = os.path.join(temp_dir, "lint_report.xml") # Write the code to a file with open(source_file, 'w') as f: f.write(request.code) compile_output = "" compile_errors = "" executable_output = "" success = False lint_messages = None if request.compiler == "pclint": # PC-lint static analysis # Note: Using splint as PC-lint alternative (PC-lint is proprietary) # If you have PC-lint, modify this section to use: pclp64 or lint-nt try: # Try using splint first (open-source alternative) result = subprocess.run( ["splint", "+quiet", source_file], capture_output=True, text=True, timeout=10, cwd=temp_dir ) compile_output = "PC-lint analysis (using Splint alternative)\n" + result.stdout compile_errors = result.stderr # Parse output into structured messages lint_messages = [] for line in (result.stdout + result.stderr).split('\n'): if line.strip() and ':' in line: parts = line.split(':', 3) if len(parts) >= 3: lint_messages.append({ "file": parts[0] if len(parts) > 0 else "", "line": int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 0, "type": "warning", "code": "", "desc": parts[-1].strip() if len(parts) > 2 else "" }) success = result.returncode == 0 except FileNotFoundError: # Fallback to GCC with strict warnings (MISRA-like) result = subprocess.run( [ "gcc", "-Wall", "-Wextra", "-pedantic", "-Wconversion", "-Wshadow", "-Wstrict-prototypes", "-fsyntax-only", source_file ], capture_output=True, text=True, timeout=10, cwd=temp_dir ) compile_output = "PC-lint not available. Using GCC with strict warnings.\n" + result.stdout compile_errors = result.stderr success = result.returncode == 0 # Parse GCC warnings into messages lint_messages = [] for line in compile_errors.split('\n'): if ':' in line and ('warning' in line or 'error' in line): lint_messages.append({ "file": source_file, "line": 0, "type": "warning" if "warning" in line else "error", "code": "", "desc": line.strip() }) else: # Standard GCC or Clang compilation compiler_cmd = request.compiler if request.compiler in ["gcc", "clang"] else "gcc" # Additional flags for MISRA-like checking extra_flags = ["-Wall", "-Wextra"] if request.standards and "misra" in str(request.standards).lower(): extra_flags.extend([ "-pedantic", "-Wconversion", "-Wshadow", "-Wstrict-prototypes" ]) # Compile the code compile_result = subprocess.run( [compiler_cmd, source_file, "-o", output_file] + extra_flags, capture_output=True, text=True, timeout=10, cwd=temp_dir ) compile_output = compile_result.stdout compile_errors = compile_result.stderr # If compilation successful, run the executable if compile_result.returncode == 0: success = True try: run_result = subprocess.run( [output_file], capture_output=True, text=True, timeout=5, cwd=temp_dir ) executable_output = run_result.stdout if run_result.stderr: executable_output += f"\nStderr: {run_result.stderr}" except subprocess.TimeoutExpired: executable_output = "Program execution timed out" except Exception as e: executable_output = f"Execution error: {str(e)}" # Cleanup shutil.rmtree(temp_dir, ignore_errors=True) return CompileResponse( success=success, output=compile_output, errors=compile_errors, executable_output=executable_output, messages=lint_messages ) except subprocess.TimeoutExpired: raise HTTPException(status_code=408, detail="Compilation timed out") except Exception as e: raise HTTPException(status_code=500, detail=f"Error: {str(e)}") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7860)