avciTheProgrammer's picture
Update app.py
0425e1e verified
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 <stdio.h>\\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)