Skip to content
Snippets Groups Projects
code_randomizer.py 4.78 KiB
Newer Older
Nicolas M. Thiéry's avatar
Nicolas M. Thiéry committed
import random
import re
from typing import Dict, Tuple

from jupyter_exercizer_helpers import RANDOM_INT, RANDOM_CHOICE

STRING_QUOTE = '"'
VECTOR_OPEN = "{"
VECTOR_CLOSE = "}"


def_regexp = {
    "C++": r"CONST\s+(\w+)\s+=\s+(.*?);?\s*$",
    "python": r"(\w+):\s+CONST\s+=\s+(.*?);?\s*$"
}
for language in ['C++11', 'C++14', 'C++17']:
    def_regexp[language] = def_regexp['C++']


def to_language(value):
    """
    Return `value` as a string representing a C++ constant

    .. TODO:: make the output language configurable

        >>> to_language(3)
        '3'
        >>> to_language("alice")
        '"alice"'
        >>> to_language([1,2,3])
        '{1, 2, 3}'
        >>> to_language([[1,2],["alice", "bob"]])
        '{{1, 2}, {"alice", "bob"}}'
    """
    if isinstance(value, str):
        if value == "REF":
            return "&"
        if value == "VAL":
            return ""
        return STRING_QUOTE + value + STRING_QUOTE
    elif isinstance(value, list):
        return VECTOR_OPEN + ", ".join(to_language(v) for v in value) + VECTOR_CLOSE
    else:
        return str(value)


def RANDOM_VECTOR(n, generator, *args):
    r"""
    Return a random vector of length `n` and whose elements are
    generated by calling `generator(*args)`

    This return a random vector of integers of length 5, with
    elements between 1 and 3:

        >>> RANDOM_VECTOR(5, RANDOM_INT, 1, 3) # doctest: +SKIP
        [3, 1, 2, 1, 3]

        >>> RANDOM_VECTOR(5, RANDOM_INT, 1, 1)
        [1, 1, 1, 1, 1]
    """
    return [generator(*args) for i in range(n)]


def RANDOM_VALOUREF():
    r"""
    pas sur que ce soit la meilleur des methodes....
    """
    return str(random.choice(["REF", "VAL"]))


locals = {
    "RANDOM_INT": RANDOM_INT,
    "RANDOM_CHOICE": RANDOM_CHOICE,
    "RANDOM_VECTOR": RANDOM_VECTOR,
    "RANDOM_VALOUREF": RANDOM_VALOUREF,
}

test_code = """CONST N = RANDOM_INT(3,3);
CONST M = RANDOM_INT(4,4);
CONST V = RANDOM_VECTOR(N, RANDOM_INT, 5, 5);
CONST VV = RANDOM_VECTOR(N, RANDOM_VECTOR, M, RANDOM_INT, 1, 1);
int main () {
    int a = N + M;
    vector<int> v = V;
    vector<vector<int>> vv = VV;
}"""


class Randomizer:
    def __init__(self, language='C++'):
        consts = {}
        consts["R"], consts["S"], consts["T"] = random.sample("rst", 3)
        consts["X"], consts["Y"], consts["Z"] = random.sample("xyz", 3)
        consts["I"], consts["J"], consts["K"], consts["N"] = random.sample("ijkn", 4)
        consts["PLUSOUMOINS"] = str(random.choice(["+", "-"]))
        consts["NAME"] = str(
            random.choice(
                ["Alexandre", "Yasmine", "Albert", "Alice", "Antoine", "Anna"]
            )
        )
        self.consts = consts

        self.def_regexp = re.compile(def_regexp[language])

    def randomize(self, text: str, is_code: bool = True) -> str:
        result = []
        for line in text.splitlines():
            pattern = re.compile(r"\b(" + "|".join(self.consts.keys()) + r")\b")
            if is_code:
                match = re.match(self.def_regexp, line)
            else:
                match = None
            if match:
                # Define new constant
                variable, value = match.groups()
                # Substitutes all constants in the value
                value = pattern.sub(lambda i: self.consts[i.group()], value)
                # Evaluates the value in a context containing all the RANDOM_*
                # functions
                self.consts[variable] = to_language(eval(value, {}, locals))
            else:
                # Substitutes all constants
                line = pattern.sub(lambda i: self.consts[i.group()], line)
                result.append(line)
        return "\n".join(result)


def randomize_code(code: str) -> Tuple[str, Dict]:
    r"""
    Randomize the given code

    Examples:

        >>> import random
        >>> random.seed(0)
        >>> randomize_code("int XX=3;")[0]
        'int XX=3;'
        >>> randomize_code("int X=3; int Y=4; int Z=5;")[0]
        'int z=3; int x=4; int y=5;'
        >>> randomize_code("I, J, K, N")[0]
        'n, k, j, i'
        >>> randomize_code("int X=1;\nint Y=2;")[0]
        'int z=1;\nint y=2;'

        >>> print(test_code)
        CONST N = RANDOM_INT(3,3);
        CONST M = RANDOM_INT(4,4);
        CONST V = RANDOM_VECTOR(N, RANDOM_INT, 5, 5);
        CONST VV = RANDOM_VECTOR(N, RANDOM_VECTOR, M, RANDOM_INT, 1, 1);
        int main () {
            int a = N + M;
            vector<int> v = V;
            vector<vector<int>> vv = VV;
        }
        >>> print(randomize_code(test_code)[0])
        int main () {
            int a = 3 + 4;
            vector<int> v = {5, 5, 5};
            vector<vector<int>> vv = {{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};
        }
    """
    randomizer = Randomizer()
    code = randomizer.randomize(code)
    return code, randomizer.consts