"""Deterministic text transformations.""" from __future__ import annotations import ast import codecs import operator import re _ARITHMETIC = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv, ast.Mod: operator.mod, ast.Pow: operator.pow, ast.USub: operator.neg, ast.UAdd: operator.pos, } _OPPOSITES = { "left": "right", "right": "left", "up": "down", "down": "up", "true": "false", "false": "true", "yes": "no", "no": "yes", "open": "closed", "closed": "open", } def _safe_arithmetic(expression: str) -> int | float: def evaluate(node): if isinstance(node, ast.Expression): return evaluate(node.body) if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in _ARITHMETIC: return _ARITHMETIC[type(node.op)](evaluate(node.left), evaluate(node.right)) if isinstance(node, ast.UnaryOp) and type(node.op) in _ARITHMETIC: return _ARITHMETIC[type(node.op)](evaluate(node.operand)) raise ValueError("Unsafe arithmetic expression") return evaluate(ast.parse(expression, mode="eval")) def solve_text_transformation(question: str) -> str | None: """Solve transformations only when the operation is unambiguous.""" reversed_question = question[::-1] lower_reversed = reversed_question.lower() if "write" in lower_reversed and "answer" in lower_reversed: # The reversed prompt itself contains the literal instruction and answer. quoted = re.findall(r'["“]([^"”]+)["”]', reversed_question) if quoted: literal = quoted[-1] if "opposite" in lower_reversed: return _OPPOSITES.get(literal.casefold()) return literal match = re.search(r"write\s+(?:the\s+)?(?:word\s+)?([a-z-]+)", lower_reversed) if match: return match.group(1) quoted = re.findall(r'["“]([^"”]+)["”]', question) source = quoted[0] if quoted else None lowered = question.lower() if source and re.search(r"\breverse\b", lowered): return source[::-1] if source and re.search(r"\buppercase\b", lowered): return source.upper() if source and re.search(r"\blowercase\b", lowered): return source.lower() if source and re.search(r"\balphabeti[sz]e\b", lowered): return " ".join(sorted(source.split(), key=str.casefold)) if source and re.search(r"\brot\s*-?13\b", lowered): return codecs.decode(source, "rot_13") if source and re.search(r"\bsort\b.*\bnumeric", lowered): numbers = re.findall(r"[-+]?\d+(?:\.\d+)?", source) return ", ".join(sorted(numbers, key=float)) arithmetic = re.search( r"(?:calculate|compute|evaluate|what is)\s+([\d\s+*/().%-]+)\??\s*$", question, re.IGNORECASE, ) if arithmetic: result = _safe_arithmetic(arithmetic.group(1).strip()) return ( str(int(result)) if isinstance(result, float) and result.is_integer() else str(result) ) extraction = re.search( r"extract\s+the\s+(\d+)(?:st|nd|rd|th)\s+word\s+from\s+[\"“]([^\"”]+)", question, re.IGNORECASE, ) if extraction: index = int(extraction.group(1)) - 1 words = extraction.group(2).split() return words[index] if 0 <= index < len(words) else None return None