Spaces:
Runtime error
Runtime error
File size: 4,931 Bytes
2803d7e | 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 | from __future__ import annotations
import re
from dataclasses import dataclass
from agents.master.base import SUPPORTED_DIRECTIONS
_TOKEN_RE = re.compile(r"^[a-z0-9]+(?: [a-z0-9]+)*$")
_BANNED_OBJECT_TOKENS = {"a", "an", "the"}
@dataclass(frozen=True)
class CliCommandAst:
kind: str
normalized_command: str
arguments: tuple[str, ...] = ()
@dataclass(frozen=True)
class CliCommandParseResult:
valid: bool
normalized_command: str | None = None
ast: CliCommandAst | None = None
error: str | None = None
def parse_cli_command(raw_command: str) -> CliCommandParseResult:
normalized = normalize_cli_command(raw_command)
if not normalized:
return CliCommandParseResult(valid=False, error="Command must not be empty.")
if normalized in {"look", "inventory", "wait"}:
return _ok(normalized, normalized)
if normalized in SUPPORTED_DIRECTIONS:
return _ok("move", f"go {normalized}", normalized)
if normalized.startswith("go "):
direction = normalized[3:].strip()
if direction in SUPPORTED_DIRECTIONS:
return _ok("move", f"go {direction}", direction)
return CliCommandParseResult(valid=False, error="Unknown direction.")
if match := re.fullmatch(r"look in (?P<object>.+)", normalized):
object_text = match.group("object").strip()
return _object_result("look_in", normalized, object_text)
if match := re.fullmatch(r"take (?P<object>.+) from (?P<source>.+)", normalized):
return _two_object_result("take_from", normalized, match.group("object"), match.group("source"))
one_target_patterns = {
"open": r"open (?P<object>.+)",
"read": r"read (?P<object>.+)",
"talk": r"talk (?P<object>.+)",
"examine": r"examine (?P<object>.+)",
}
for kind, pattern in one_target_patterns.items():
if match := re.fullmatch(pattern, normalized):
object_text = match.group("object").strip()
return _object_result(kind, normalized, object_text)
if match := re.fullmatch(r"take (?P<object>.+)", normalized):
object_text = match.group("object").strip()
return _object_result("take", normalized, object_text)
if match := re.fullmatch(r"unlock (?P<object>.+) with (?P<tool>.+)", normalized):
return _two_object_result("unlock", normalized, match.group("object"), match.group("tool"))
if match := re.fullmatch(r"use (?P<object>.+) on (?P<target>.+)", normalized):
return _two_object_result("use", normalized, match.group("object"), match.group("target"))
if match := re.fullmatch(r"combine (?P<object>.+) with (?P<target>.+)", normalized):
return _two_object_result("combine", normalized, match.group("object"), match.group("target"))
if match := re.fullmatch(r"give (?P<object>.+) to (?P<target>.+)", normalized):
return _two_object_result("give", normalized, match.group("object"), match.group("target"))
if match := re.fullmatch(r"submit (?P<answer>[a-z0-9]+(?: [a-z0-9]+)*)", normalized):
answer = match.group("answer").strip()
return _ok("submit", normalized, answer)
return CliCommandParseResult(valid=False, error="Command does not match the strict CLI grammar.")
def normalize_cli_command(raw_command: str) -> str:
return re.sub(r"\s+", " ", raw_command.strip().lower())
def _object_result(kind: str, normalized_command: str, object_text: str) -> CliCommandParseResult:
object_error = _validate_object_text(object_text)
if object_error is not None:
return CliCommandParseResult(valid=False, error=object_error)
return _ok(kind, normalized_command, object_text)
def _two_object_result(kind: str, normalized_command: str, first: str, second: str) -> CliCommandParseResult:
first_error = _validate_object_text(first)
if first_error is not None:
return CliCommandParseResult(valid=False, error=first_error)
second_error = _validate_object_text(second)
if second_error is not None:
return CliCommandParseResult(valid=False, error=second_error)
return _ok(kind, normalized_command, first.strip(), second.strip())
def _validate_object_text(value: str) -> str | None:
candidate = value.strip()
if not candidate:
return "Command target must not be empty."
if not _TOKEN_RE.fullmatch(candidate):
return "Command targets must use lowercase letters, numbers, and spaces only."
if any(token in _BANNED_OBJECT_TOKENS for token in candidate.split()):
return "Strict CLI commands must use exact parser-safe object names without articles."
return None
def _ok(kind: str, normalized_command: str, *arguments: str) -> CliCommandParseResult:
return CliCommandParseResult(
valid=True,
normalized_command=normalized_command,
ast=CliCommandAst(kind=kind, normalized_command=normalized_command, arguments=arguments),
)
|