Spaces:
Running on Zero
Running on Zero
File size: 5,239 Bytes
777ea0e 635e6fb 777ea0e | 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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | """Validate GDScript produced by the model using gdtoolkit (Scony's parser).
Pure-Python, CPU-only, Godot-4 (GDScript 2.0). Checks SYNTAX (gdparse) and STYLE
(gdlint); it does NOT check runtime/scene semantics (node paths, types against a
real project) — that needs the Godot engine.
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
_FENCE_RE = re.compile(r"```(?:gdscript|gd|godot)?\s*\n(.*?)```", re.S | re.I)
@dataclass
class BlockResult:
code: str
ok: bool # parses (valid syntax)
error: str = "" # syntax error message (if any)
lint: list[str] = field(default_factory=list) # style/lint warnings
formatted: str = "" # gdformat output (if available)
def extract_gdscript_blocks(text: str) -> list[str]:
"""Pull fenced GDScript blocks from a model answer."""
blocks = [m.group(1).strip() for m in _FENCE_RE.finditer(text or "")]
return [b for b in blocks if b]
# ---------------------------------------------------------------------------
# gdtoolkit wrappers (imported lazily so the module loads even if absent)
# ---------------------------------------------------------------------------
def _parse(code: str) -> tuple[bool, str]:
try:
from gdtoolkit.parser import parser
except Exception as e: # gdtoolkit not installed
return True, f"(parser unavailable: {e})"
try:
parser.parse(code, gather_metadata=False)
return True, ""
except TypeError:
# older/newer signature without gather_metadata
try:
parser.parse(code)
return True, ""
except Exception as e:
return False, _fmt_err(e)
except Exception as e:
return False, _fmt_err(e)
def _fmt_err(e: Exception) -> str:
line = getattr(e, "line", None)
col = getattr(e, "column", None)
msg = str(e).strip().splitlines()[0] if str(e).strip() else type(e).__name__
if line is not None:
return f"line {line}:{col or 0}: {msg}"
return msg
def _lint(code: str) -> list[str]:
try:
from gdtoolkit.linter import lint_code
except Exception:
return []
try:
problems = lint_code(code)
except Exception:
return []
out = []
for p in problems:
line = getattr(p, "line", "?")
name = getattr(p, "name", "")
desc = getattr(p, "description", str(p))
out.append(f"line {line}: {desc}" + (f" [{name}]" if name else ""))
return out
def _format(code: str) -> str:
try:
from gdtoolkit.formatter import format_code
return format_code(code, max_line_length=100)
except Exception:
return ""
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
def validate_code(code: str) -> BlockResult:
ok, err = _parse(code)
return BlockResult(
code=code, ok=ok, error=err,
lint=_lint(code) if ok else [],
formatted=_format(code) if ok else "",
)
def validate_answer(answer: str) -> list[BlockResult]:
return [validate_code(b) for b in extract_gdscript_blocks(answer)]
def gdscript_block_spans(text: str) -> list[tuple[str, int, int]]:
"""Each fenced GDScript block as (stripped_code, match_start, match_end), in
document order. The span covers the whole ```...``` fence so a caller can
splice a corrected block back in place of the original."""
out: list[tuple[str, int, int]] = []
for m in _FENCE_RE.finditer(text or ""):
code = m.group(1).strip()
if code:
out.append((code, m.start(), m.end()))
return out
def first_gdscript_block(text: str) -> str:
"""First fenced GDScript block (stripped), or '' if none — used to pull the
corrected code out of a fix generation."""
blocks = extract_gdscript_blocks(text)
return blocks[0] if blocks else ""
def render_report(results: list[BlockResult]) -> str:
"""Markdown summary for the UI."""
if not results:
return "_No GDScript code blocks detected to validate._"
lines = []
for i, r in enumerate(results, 1):
if r.ok:
badge = "✅ **valid GDScript** (syntax OK)"
if r.lint:
badge += f" · {len(r.lint)} lint note(s)"
else:
badge = f"❌ **syntax error** — {r.error}"
lines.append(f"**Block {i}:** {badge}")
for w in r.lint[:5]:
lines.append(f"- ⚠ {w}")
return "\n".join(lines)
def first_syntax_error(results: list[BlockResult]) -> tuple[str, str] | None:
"""Return (code, error) of the first block that failed to parse, else None."""
for r in results:
if not r.ok:
return r.code, r.error
return None
if __name__ == "__main__":
good = "extends Node\n\n@export var speed: float = 5.0\n\nfunc _ready() -> void:\n\tprint(speed)\n"
bad = "extends Node\n\nfunc _ready(\n\tprint('oops')\n"
for label, code in (("GOOD", good), ("BAD", bad)):
r = validate_code(code)
print(f"== {label} == ok={r.ok} error={r.error!r} lint={r.lint}")
|