def apply_eval()

in runtool/runtool/transformations.py [0:0]


def apply_eval(node: dict, locals: dict) -> Any:
    """
    Evaluates the expression in `node["$eval"]` recursively then returns
    the result.

    The text in `node["$eval"]` can contain some keywords which `apply_eval`
    can use to preprocess the text before `eval` is run on the text.
    The following keywords are supported in the text:

    - `$`       is used to reference data in the `locals` parameters
    - `$trial`  references an experiment which is defined during runtime
                (see `apply_trial`)

    Example of when `$` is used:

    >>> apply_eval({"$eval": "2 + 5 * $.value"}, {"value": 2})
    12

    >>> apply_eval({"$eval": "$.my_key.split()"}, {"my_key": "some string"})
    ['some', 'string']

    Example of when `$trial` is used. Since we do not yet know what the `$trial`
    should resolve to we cannot calculate the value.
    Note::

        $trial gets renamed to __trial__ here as this function is a
        preprocessing step to `apply_trial`.

    >>> apply_eval({"$eval": "$trial.algorithm.some_value * 2"}, {})
    {'$eval': '__trial__.algorithm.some_value * 2'}

    Parameters
    ----------
    node
        The node which should be processed.
    locals:
        The local variables available for when calling eval.
    Returns
    -------
    Any
        The transformed node.
    """
    if not (isinstance(node, dict) and "$eval" in node):
        return node

    assert len(node) == 1, "$eval needs to be only value"
    text = str(node["$eval"])
    text = text.replace("$trial", "__trial__")

    # matches any parts of the text which is similar to this:
    # $.somestring.somotherstring[0]['a_key']["some_key"]
    regex = r"""
        (\$                         # match string starting with $ and followed by:
            (?:
                \[[\d]+\]|          # digits enclosed in [] i.e. $[0]
                \[\"[\w_\d$]+\"\]|  # words or digits in "[]" i.e. $["0"]
                \[\'[\w_\d$]+\'\]|  # words or digits in '[]' i.e. $['0']
                \.[\w_\d]+          # words or digits prepended with a dot, i.e. $.hello
            )+
        )
    """

    # replace any matched substrings of the text with whatever the
    # substrings pointed to in the locals parameter
    for match in re.finditer(regex, text, flags=re.VERBOSE):
        path, value = recurse_eval(match[0].lstrip("$."), locals, apply_eval)
        path = f"$.{path}"
        if isinstance(value, dict) and "$eval" in value:
            text = text.replace(path, f"({value['$eval']})")
        elif type(value) is str:
            text = text.replace(path, f"'{value}'")
        else:
            text = text.replace(path, str(value))

    try:
        # continue recursion as to handle any $eval nodes
        # generated after evaluating the current node.
        return apply_eval(evaluate(text, locals), locals)
    except NameError as error:
        if "__trial__" in str(error):
            node["$eval"] = text
            return node
        else:
            raise error