| 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") |
|
|
| |
| 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" |
| standards: Optional[List[str]] = None |
|
|
| 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 |
|
|
| @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""" |
| |
| 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() |
| |
| |
| 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 <stdio.h>\\nint main() { printf(\\"Hello\\\\n\\"); return 0; }", |
| "filename": "main.c", |
| "compiler": "gcc" |
| } |
| """ |
| try: |
| |
| 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") |
| |
| |
| 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": |
| |
| |
| |
| |
| try: |
| |
| 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 |
| |
| |
| 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: |
| |
| 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 |
| |
| |
| 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: |
| |
| compiler_cmd = request.compiler if request.compiler in ["gcc", "clang"] else "gcc" |
| |
| |
| extra_flags = ["-Wall", "-Wextra"] |
| if request.standards and "misra" in str(request.standards).lower(): |
| extra_flags.extend([ |
| "-pedantic", |
| "-Wconversion", |
| "-Wshadow", |
| "-Wstrict-prototypes" |
| ]) |
| |
| |
| 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 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)}" |
| |
| |
| 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) |