File size: 3,719 Bytes
c641d5f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""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