rabukasim / compiler /parser_semantics.py
trioskosmos's picture
Upload folder using huggingface_hub
463f868 verified
from __future__ import annotations
import re
from typing import Any, Dict, List
from engine.models.ability import Condition, ConditionType
from .parser_lexer import StructuralLexer
from .parser_patterns import (
CONDITION_SEMANTIC_SPECIAL_CASES,
CONDITION_TRUE_ALIASES,
IGNORED_CONDITIONS,
KEYWORD_CONDITIONS,
)
def parse_pseudocode_conditions(parser: Any, text: str) -> List[Condition]:
conditions: List[Condition] = []
stripped_text = text.strip()
if stripped_text.upper().startswith("OR(") and stripped_text.endswith(")"):
inner = stripped_text[3:-1].strip()
clauses = []
for clause_text in StructuralLexer.split_respecting_nesting(inner, delimiter=","):
clause_text = clause_text.strip()
if not clause_text:
continue
parsed_clause = parse_pseudocode_conditions(parser, clause_text)
if parsed_clause:
clauses.append(parser._serialize_condition_clause(parsed_clause[0]))
if clauses:
return [Condition(ConditionType.NONE, {"raw_cond": "OR", "clauses": clauses})]
top_level_or_parts = StructuralLexer.split_respecting_nesting(text, delimiter=" OR ")
if len(top_level_or_parts) > 1:
clauses = []
for clause_text in top_level_or_parts:
clause_text = clause_text.strip()
if not clause_text:
continue
parsed_clause = parse_pseudocode_conditions(parser, clause_text)
if parsed_clause:
clauses.append(parser._serialize_condition_clause(parsed_clause[0]))
if clauses:
return [Condition(ConditionType.NONE, {"raw_cond": "OR", "clauses": clauses})]
parts = StructuralLexer.split_respecting_nesting(text, delimiter=",", extra_delimiters=[";"])
for part in parts:
if not part:
continue
negated = part.startswith("NOT ") or part.startswith("NOT_")
name_part = part[4:] if negated else part
if not negated and name_part.startswith("!"):
negated = True
name_part = name_part[1:]
params: Dict[str, Any] = {}
for brace_block in re.findall(r"\{([^{}]*)\}", name_part):
params.update(parser._parse_pseudocode_params("{" + brace_block + "}"))
name_part_no_braces = re.sub(r"\s*\{[^{}]*\}", "", name_part).strip()
match = re.match(r"(\w+)(?:\((.*?)\))?", name_part_no_braces)
if not match:
continue
name = match.group(1).upper()
val_in_parens = match.group(2)
if val_in_parens:
value_parts = [vp.strip() for vp in val_in_parens.split(",")]
for value_part in value_parts:
value_upper = value_part.upper()
if value_upper.startswith("UNIT_"):
params["unit"] = value_part[5:]
elif value_upper.startswith("GROUP_"):
params["group"] = value_part[6:]
elif value_upper in [
"STAGE",
"HAND",
"DISCARD",
"ENERGY",
"SUCCESS_LIVE",
"LIVE_ZONE",
"SUCCESS_PILE",
]:
params["zone"] = value_upper
elif "=" in value_part:
key, value = [s.strip().strip('"').strip("'") for s in value_part.split("=", 1)]
params[key.lower()] = value
elif ":" in value_part:
key, value = [s.strip().strip('"').strip("'") for s in value_part.split(":", 1)]
params[key.lower()] = value
else:
params["val"] = value_part
if "->" in name_part_no_braces:
arrow_pos = name_part_no_braces.find("->")
target_part = name_part_no_braces[arrow_pos + 2 :].strip()
target_word = target_part.split()[0].strip().upper()
if target_word:
params["target"] = target_word
remaining_part = name_part_no_braces[len(match.group(0)) :].strip()
if "->" in remaining_part:
remaining_part = remaining_part[: remaining_part.find("->")].strip()
if remaining_part:
comparison_match = re.match(r"(>=|<=|>|<|=)\s*[\"']?(.*?)[\"']?$", remaining_part)
if comparison_match:
op_map = {">=": "GE", "<=": "LE", ">": "GT", "<": "LT", "=": "EQ"}
params["comparison"] = op_map.get(comparison_match.group(1), "GE")
params["val"] = comparison_match.group(2)
else:
equals_match = re.search(r"=\s*[\"']?(.*?)[\"']?$", remaining_part)
if equals_match:
params["val"] = equals_match.group(1)
params["raw_cond"] = name
is_negated = negated
if name == "PLAYER_CENTER_COST_GT_OPPONENT_CENTER_COST":
name = "SYNC_COST"
params["area"] = "CENTER"
params["comparison"] = "GT"
params["val"] = "0"
elif name == "OPPONENT_CENTER_COST_GT_PLAYER_CENTER_COST":
name = "SYNC_COST"
params["area"] = "CENTER"
params["comparison"] = "LT"
params["val"] = "0"
elif name == "HEARTS_COUNT" and "OPPONENT" in str(params.get("val", "")).upper():
name = "HEART_LEAD"
params["target"] = "opponent"
params["val"] = "0"
if name in IGNORED_CONDITIONS:
conditions.append(Condition(ConditionType.NONE, params, is_negated=is_negated))
continue
if name in KEYWORD_CONDITIONS:
params["keyword"] = KEYWORD_CONDITIONS[name]
conditions.append(Condition(ConditionType.HAS_KEYWORD, params, is_negated=is_negated))
continue
if name.startswith("MATCH_") or name.startswith("DID_ACTIVATE_"):
params["keyword"] = name
conditions.append(Condition(ConditionType.HAS_KEYWORD, params, is_negated=is_negated))
continue
if name in ["COUNT_SUCCESS_LIVES", "COUNT_SUCCESS_LIVE", "COUNT_CARDS"]:
zone_value = str(params.get("zone") or "").upper()
if name == "COUNT_CARDS" and zone_value not in ["SUCCESS_PILE", "SUCCESS_LIVE"]:
pass
else:
if "target" not in params:
target_value = str(params.get("PLAYER", params.get("val", "self"))).upper()
params["target"] = "opponent" if target_value in {"OPPONENT", "1"} else "self"
if "PLAYER" in params:
del params["PLAYER"]
if "val" in params and str(params["val"]).upper() in ["PLAYER", "OPPONENT"]:
del params["val"]
if "COUNT" in params:
params["value"] = params["COUNT"]
params["comparison"] = "EQ"
del params["COUNT"]
conditions.append(Condition(ConditionType.COUNT_SUCCESS_LIVE, params, is_negated=is_negated))
continue
if name in CONDITION_TRUE_ALIASES:
canonical_name, extra_params = CONDITION_TRUE_ALIASES[name]
try:
condition_type = ConditionType[canonical_name]
except KeyError:
condition_type = ConditionType.NONE
conditions.append(Condition(condition_type, {**params, **extra_params}, is_negated=is_negated))
continue
if name in CONDITION_SEMANTIC_SPECIAL_CASES:
canonical_name, extra_params = CONDITION_SEMANTIC_SPECIAL_CASES[name]
try:
condition_type = ConditionType[canonical_name]
except KeyError:
condition_type = ConditionType.NONE
for key, value in extra_params.items():
if key not in params:
params[key] = value
if name == "NOT_MOVED_THIS_TURN":
is_negated = True
conditions.append(Condition(condition_type, params, is_negated=is_negated))
continue
if name in {"AREA_IN", "AREA"}:
value = str(params.get("val", "")).upper().strip('"')
if value == "CENTER" or params.get("zone") == "CENTER" or params.get("area") == "CENTER":
condition_type = ConditionType.IS_CENTER
else:
condition_type = ConditionType.AREA_CHECK
params["keyword"] = "AREA_CHECK"
area_map = {"LEFT_SIDE": 0, "LEFT": 0, "RIGHT_SIDE": 2, "RIGHT": 2}
if value in area_map:
params["value"] = area_map[value]
conditions.append(Condition(condition_type, params, is_negated=is_negated))
continue
if name == "COST_LEAD" and params.get("area") == "CENTER":
params["zone"] = "CENTER_STAGE"
del params["area"]
if name == "REVEALED_CONTAINS":
if "TYPE_LIVE" in params:
params["value"] = "live"
if "TYPE_MEMBER" in params:
params["value"] = "member"
if name == "SELECT_CARD" and str(params.get("val", "")).upper() == "REVEALED_CARD":
filter_str = str(params.get("FILTER") or params.get("filter") or "").upper()
if "TYPE_LIVE" in filter_str:
params["card_type"] = "live"
conditions.append(Condition(ConditionType.TYPE_CHECK, params, is_negated=is_negated))
continue
if "TYPE_MEMBER" in filter_str:
params["card_type"] = "member"
conditions.append(Condition(ConditionType.TYPE_CHECK, params, is_negated=is_negated))
continue
condition_type = getattr(ConditionType, name, ConditionType.NONE)
conditions.append(Condition(condition_type, params, is_negated=is_negated))
return conditions
def looks_like_condition_instruction(text: str) -> bool:
"""Return True if the instruction text should be routed through the condition parser."""
stripped = text.strip()
if not stripped:
return False
upper = stripped.upper()
if upper.startswith("CONDITION:") or upper.startswith("OR("):
return True
name_match = re.match(r"^([\w_]+)", stripped)
if not name_match:
return False
name = name_match.group(1).upper()
if (
name in CONDITION_TRUE_ALIASES
or name in CONDITION_SEMANTIC_SPECIAL_CASES
or name in KEYWORD_CONDITIONS
or name in IGNORED_CONDITIONS
or hasattr(ConditionType, name)
or name.startswith("MATCH_")
or name.startswith("DID_ACTIVATE_")
or name in {
"AREA",
"AREA_IN",
"REVEALED_CONTAINS",
"SELECT_CARD",
"PLAYER_CENTER_COST_GT_OPPONENT_CENTER_COST",
"OPPONENT_CENTER_COST_GT_PLAYER_CENTER_COST",
"HEARTS_COUNT",
"COUNT_SUCCESS_LIVES",
"COUNT_SUCCESS_LIVE",
"COUNT_CARDS",
}
):
return True
return False
__all__ = ["parse_pseudocode_conditions", "looks_like_condition_instruction"]