File size: 3,405 Bytes
9bed109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from __future__ import annotations

import re
from dataclasses import dataclass


@dataclass
class ParsedError:
    error_type: (
        str  # "AttributeError", "TypeError", "ImportError", "NameError", "SyntaxError", etc.
    )
    symbol: str | None  # Extracted symbol: "play_text", "ShowCreation", etc.
    invalid_arg: str | None  # For TypeError: what arg was wrong
    traceback_tail: str  # Last few lines of traceback
    line_number: int | None  # Extracted from "line XX" in traceback
    raw_message: str


def parse_render_error(error_logs: str) -> ParsedError:
    """Extract structured error info from Manim render logs."""

    # 1. Get the last line (the error message itself)
    lines = [line.strip() for line in error_logs.strip().split("\n") if line.strip()]
    if not lines:
        return ParsedError("UnknownError", None, None, "", None, error_logs)

    last_line = lines[-1]

    # 2. Extract Traceback Tail
    traceback_tail = "\n".join(lines[-5:]) if len(lines) >= 5 else "\n".join(lines)

    # 3. Extract Line Number
    line_match = re.search(r'File ".*", line (\d+)', error_logs)
    line_number = int(line_match.group(1)) if line_match else None

    # 4. Classification & Symbol Extraction
    error_type = "UnknownError"
    symbol = None
    invalid_arg = None

    # AttributeError: 'Scene' object has no attribute 'play_text'
    if "AttributeError" in last_line:
        error_type = "AttributeError"
        match = re.search(r"has no attribute '([^']+)'", last_line)
        if match:
            symbol = match.group(1)

    # NameError: name 'ShowCreation' is not defined
    elif "NameError" in last_line:
        error_type = "NameError"
        match = re.search(r"name '([^']+)' is not defined", last_line)
        if match:
            symbol = match.group(1)

    # TypeError: play() got an unexpected keyword argument 'run_time_extra'
    elif "TypeError" in last_line:
        error_type = "TypeError"
        # Function/Class match: "TypeError: play() got..."
        match_func = re.search(r"(?:TypeError:\s*)?(\w+)\(\)", last_line)
        if match_func:
            symbol = match_func.group(1)
        # Arg match
        match_arg = re.search(r"unexpected keyword argument '([^']+)'", last_line)
        if match_arg:
            invalid_arg = match_arg.group(1)

    # ImportError: cannot import name 'X' from 'Y'
    elif "ImportError" in last_line or "ModuleNotFoundError" in last_line:
        error_type = "ImportError"
        match = re.search(r"cannot import name '([^']+)'", last_line)
        if not match:
            match = re.search(r"No module named '([^']+)'", last_line)
        if match:
            symbol = match.group(1)

    # SyntaxError: invalid syntax (file, line N)
    elif "SyntaxError" in last_line:
        error_type = "SyntaxError"

    # LaTeX compilation error
    elif "LaTeX compilation" in error_logs or "Missing $" in error_logs:
        error_type = "LatexError"
        symbol = "LaTeX"

    # Generic catch-all for ErrorName: Message
    if error_type == "UnknownError":
        match = re.search(r"^(\w+Error):", last_line)
        if match:
            error_type = match.group(1)

    return ParsedError(
        error_type=error_type,
        symbol=symbol,
        invalid_arg=invalid_arg,
        traceback_tail=traceback_tail,
        line_number=line_number,
        raw_message=last_line,
    )