myrmidon / python /src /server /services /lean /compiler_service.py
tek Atrust
chore(deploy): build monolithic server for Hugging Face
d5ef46f
Raw
History Blame Contribute Delete
3.72 kB
# python/src/server/services/lean/compiler_service.py
import os
import re
import subprocess
from typing import Any
from ...config.logfire_config import get_logger
logger = get_logger(__name__)
class LeanCompilerService:
"""Service to physical build/compile Lean 4 files and parse errors."""
def __init__(self, lean_proofs_dir: str | None = None):
if lean_proofs_dir:
self.lean_proofs_dir = lean_proofs_dir
else:
# Fallback path finding relative to this file
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", ".."))
self.lean_proofs_dir = os.path.join(base_dir, "lean_proofs")
def run_build(self) -> dict[str, Any]:
"""Runs `lake build` in the lean_proofs directory."""
if not os.path.exists(self.lean_proofs_dir):
return {
"success": False,
"error": f"lean_proofs directory not found at: {self.lean_proofs_dir}",
"errors": []
}
try:
logger.info(f"Running 'lake build' in {self.lean_proofs_dir}")
# Use lake build inside subprocess
result = subprocess.run(
["lake", "build"],
cwd=self.lean_proofs_dir,
capture_output=True,
text=True,
check=False
)
success = result.returncode == 0
stdout_err = result.stdout + "\n" + result.stderr
parsed_errors = self.parse_lake_errors(stdout_err)
return {
"success": success,
"returncode": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr,
"errors": parsed_errors
}
except Exception as e:
logger.error(f"Failed to execute lake build: {e}")
return {
"success": False,
"error": str(e),
"errors": []
}
def parse_lake_errors(self, output: str) -> list[dict[str, Any]]:
"""Parses standard Lean 4 error output into a list of structured JSON errors.
Example output format:
/path/to/file.lean:line:col: error: message description
"""
errors = []
# Pattern to capture filename, line, col, and message
# Format: path/to/file.lean:line:col: error: message
pattern = re.compile(r"([^\s\n]+.lean):(\d+):(\d+):\s*error:\s*(.*)", re.MULTILINE)
# We also need to capture multi-line messages until the next file path or empty line
lines = output.splitlines()
current_error = None
for line in lines:
match = pattern.search(line)
if match:
if current_error:
errors.append(current_error)
file_path = match.group(1)
line_num = int(match.group(2))
col_num = int(match.group(3))
msg = match.group(4).strip()
current_error = {
"file": file_path,
"line": line_num,
"column": col_num,
"message": msg
}
elif current_error and line.strip():
# Append to current multiline message
current_error["message"] = str(current_error["message"]) + "\n" + line.strip()
elif current_error and not line.strip():
errors.append(current_error)
current_error = None
if current_error:
errors.append(current_error)
return errors
lean_compiler_service = LeanCompilerService()