git @ Cat's Eye Technologies ALPACA / master src / alpaca / eval.py
master

Tree @master (Download .tar.gz)

eval.py @masterraw · history · blame

"""
Direct evaluator of ALPACA AST nodes.

"""

from alpaca.analysis import (
    find_state_defn, find_class_defn, find_nbhd_defn, state_defn_is_a,
    BoundingBox, fit_bounding_box,
)

import random

def eval_state_ref(playfield, x, y, ast):
    if ast.type == 'StateRefEq':
        return ast.id
    elif ast.type == 'StateRefRel':
        return playfield.get(x + ast.value[0], y + ast.value[1])
    else:
        raise NotImplementedError


def eval_relation(alpaca, playfield, x, y, state_id, ast, verbose=False):
    """state_id is the ID of a state (possibly from the playfield)
    that we want to check.  ast is either a StateRef or a ClassDecl.
    Returns true iff the state_id satisfies the StateRef or ClassDecl.

    """
    if ast.type == 'ClassDecl':
        class_id = ast.id
        state_ast = find_state_defn(alpaca, state_id)
        result = state_defn_is_a(alpaca, state_ast, class_id, verbose=verbose)
        if verbose:
            print(" => checking if {} is_a {}: {}".format(state_id, class_id, result))
        return result
    elif ast.type in ('StateRefEq', 'StateRefRel'):
        pf_state_id = eval_state_ref(playfield, x, y, ast)
        return state_id == pf_state_id


def eval_expr(alpaca, playfield, x, y, ast, verbose=False):
    """Given a playfield and a position within it, and a boolean expression,
    return what the expression evaluates to at that position.
    
    """
    if ast.type == 'BoolOp':
        lhs = eval_expr(alpaca, playfield, x, y, ast.lhs, verbose=verbose)
        rhs = eval_expr(alpaca, playfield, x, y, ast.rhs, verbose=verbose)
        op = ast.op
        if op == 'and':
            return lhs and rhs
        elif op == 'or':
            return lhs or rhs
        elif op == 'xor':
            return lhs != rhs
    elif ast.type == 'Not':
        return not eval_expr(alpaca, playfield, x, y, ast.expr, verbose=verbose)
    elif ast.type == 'Adjacency':
        rel = ast.rel
        nbhd = ast.nbhd
        if nbhd.type == 'NbhdRef':
            nbhd = find_nbhd_defn(alpaca, nbhd.id).children[0]
        assert nbhd.type == 'Neighbourhood'
        count = 0
        for (dx, dy) in set([node.value for node in nbhd.children]):
            pf_state_id = playfield.get(x + dx, y + dy)
            if eval_relation(alpaca, playfield, x, y, pf_state_id, rel, verbose=verbose):
                count += 1
        #print("(%d,%d) has %d neighbours that are %r" % (x, y, count, rel))
        return count >= int(ast.count)
    elif ast.type == 'Relational':
        state_id = eval_state_ref(playfield, x, y, ast.lhs)
        rel = ast.rhs
        return eval_relation(alpaca, playfield, x, y, state_id, rel, verbose=verbose)
    elif ast.type == 'BoolLit':
        if ast.value == 'true':
            return True
        elif ast.value == 'false':
            return False
        elif ast.value == 'guess':
            return bool(random.getrandbits(1))
        else:
            raise NotImplementedError(repr(ast))
    else:
        raise NotImplementedError(repr(ast))


def eval_rules(alpaca, playfield, x, y, rules, verbose=False):
    """Given a playfield and a position within it, and a set of rules,
    return the "to" state for the rule that applies.

    If no rule applies, None is returned.

    """
    for rule in rules:
        assert rule.type == 'Rule'
        s = rule.state_ref
        e = rule.expr
        if eval_expr(alpaca, playfield, x, y, e, verbose=verbose):
            return eval_state_ref(playfield, x, y, s)
    return None


# TODO: encapsulate this in an object to keep some state
# and reduce the number of recomputations each time
def evolve_playfield(playfield, new_pf, alpaca, verbose=False):
    if playfield.min_y is None:
        return
    bb = BoundingBox(0, 0, 0, 0)
    fit_bounding_box(alpaca, bb)
    y = playfield.min_y - bb.max_dy
    while y <= playfield.max_y - bb.min_dy:
        x = playfield.min_x - bb.max_dx
        while x <= playfield.max_x - bb.min_dx:
            state_id = playfield.get(x, y)
            if verbose:
                print("state at (%d,%d): %s" % (x, y, state_id))
            state_ast = find_state_defn(alpaca, state_id)
            if verbose:
                print(" => %r" % state_ast)
            classes = state_ast.classes
            rules = state_ast.rules
            new_state_id = apply_rules(alpaca, playfield, x, y, rules, classes, verbose=verbose)
            if new_state_id is None:
                new_state_id = state_id
            if verbose:
                print("new state: %s" % new_state_id)
            new_pf.set(x, y, new_state_id)
            x += 1
        y += 1


def apply_rules(alpaca, playfield, x, y, rules, class_decls, checked_classes=None, verbose=False):
    """Given a set of rules and a set of superclasses (for a given state or
    class which is not given), try the rules; if none of them apply,
    recursively apply this function with the rules and superclasses for each
    given superclass.

    """
    if checked_classes is None:
        checked_classes = set()
    new_state_id = eval_rules(alpaca, playfield, x, y, rules, verbose=verbose)
    if new_state_id is not None:
        return new_state_id
    for class_decl in class_decls:
        assert class_decl.type == 'ClassDecl'
        class_id = class_decl.id
        if class_id in checked_classes:
            continue
        class_ast = find_class_defn(alpaca, class_id)
        rules = class_ast.rules
        classes = class_ast.classes
        checked_classes.add(class_id)
        new_state_id = apply_rules(
            alpaca, playfield, x, y, rules, classes,
            checked_classes=checked_classes, verbose=verbose
        )
        if new_state_id is not None:
            return new_state_id
    return None