| |
|
|
| 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: |
| |
| 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}") |
| |
| 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 = re.compile(r"([^\s\n]+.lean):(\d+):(\d+):\s*error:\s*(.*)", re.MULTILINE) |
|
|
| |
| 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(): |
| |
| 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() |
|
|