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

Tree @master (Download .tar.gz)

javascript.py @masterraw · history · blame

"""
Backend for compiling ALPACA AST to Javascript.  Perhaps not complete.

"""

from alpaca.analysis import (
    get_class_map, find_nbhd_defn, BoundingBox, fit_bounding_box,
    get_defined_playfield, construct_representation_map
)


class Compiler(object):
    def __init__(self, alpaca, file, options=None):
        """alpaca is an ALPACA description in AST form.  file is a file-like
        object to which the compiled code will be written.

        """
        self.alpaca = alpaca
        self.file = file
        self.options = options

    def compile(self):
        bb = BoundingBox(0, 0, 0, 0)
        fit_bounding_box(self.alpaca, bb)
        self.file.write("""\
/*
 * This file was AUTOMATICALLY generated from an ALPACA description.
 * EDIT AT YOUR OWN RISK!
 */

function in_nbhd_pred(pf, x, y, pred, nbhd) {
  var count = 0;
  for (var i = 0; i < nbhd.length; i++) {
    if (pred(pf.get(x+nbhd[i][0], y+nbhd[i][1]))) {
      count++;
    }
  }
  return count;
}

function in_nbhd_eq(pf, x, y, stateId, nbhd) {
  return in_nbhd_pred(pf, x, y, function(x) { return x === stateId; }, nbhd);
}

function evolve_playfield(pf, new_pf) {
  pf.map(new_pf, evalState, %d, %d, %d, %d);
}
""" % (-1 * bb.max_dx, -1 * bb.max_dy, -1 * bb.min_dx, -1 * bb.min_dy,))

        # write the CA's load and dump mappers
        repr_map = construct_representation_map(self.alpaca)
        self.file.write("function loadMapper(c) {\n")
        for (char, state_id) in repr_map.items():
            self.file.write("  if (c === '%s') return '%s';\n" %
                            (char, state_id))
        self.file.write("};\n")

        self.file.write("function dumpMapper(s) {\n")
        for (char, state_id) in repr_map.items():
            self.file.write("  if (s === '%s') return '%s';\n" %
                            (state_id, char))
        self.file.write("};\n")

        class_map = get_class_map(self.alpaca)
        for (class_id, state_set) in class_map.items():
            self.file.write("function is_%s(st) {\n" % class_id)
            self.file.write("  return ");
            for state_id in state_set:
                self.file.write("(st === '%s') || " % state_id)
            self.file.write("0;\n}\n\n")
        for defn in self.alpaca.defns:
            if defn.type == 'ClassDefn':
                self.compile_class_defn(defn)
        for defn in self.alpaca.defns:
            if defn.type == 'StateDefn':
                self.compile_state_defn(defn)
        self.write_evalstate_function()
        pf = get_defined_playfield(self.alpaca)
        if pf is not None:
            self.file.write("var defaultCell = '%s';\n" % pf.default)
            self.file.write("var initialPlayfield = [{}];\n".format(
                ','.join("[%d, %d, '%s']" % (x, y, c) for (x, y, c) in pf.items())
            ))
        return True

    def write_evalstate_function(self):
        self.file.write("""\
function evalState(pf, x, y) {
  var stateId = pf.get(x, y);
""")
        for defn in self.alpaca.defns:
            if defn.type == 'StateDefn':
                self.file.write("""\
  if (stateId === '%s') return eval_%s(pf, x, y);
""" % (defn.id, defn.id));
        self.file.write('}\n')

    def compile_class_defn(self, defn):
        self.file.write("function evalClass_%s(pf, x, y, seen) {\nvar id;\nseen['%s'] = true;\n" % (defn.id, defn.id));
        for rule in defn.rules:
            dest = rule.state_ref
            expr = rule.expr
            self.file.write("if (")
            self.compile_expr(expr)
            self.file.write(") {\n  return ")
            self.compile_state_ref(dest)
            self.file.write(";\n}\n")
        for superclass in defn.classes:
            self.file.write("id = seen['%s'] ? undefined : evalClass_%s(pf, x, y, seen);\n" % (superclass.id, superclass.id))
            self.file.write("if (id !== undefined) return id;\n")
        self.file.write("return undefined;\n}\n\n")

    def compile_state_defn(self, defn):
        #char_repr = defn.children[0]
        self.file.write("function eval_%s(pf, x, y) {\nvar id;\n" % defn.id);
        for rule in defn.rules:
            dest = rule.state_ref
            expr = rule.expr
            self.file.write("if (")
            self.compile_expr(expr)
            self.file.write(") {\n  return ")
            self.compile_state_ref(dest)
            self.file.write(";\n}\n")
        for superclass in defn.classes:
            self.file.write("id = evalClass_%s(pf, x, y, {});\n" % superclass.id)
            self.file.write("if (id !== undefined) return id;\n")
        self.file.write("return '%s';\n}\n\n" % defn.id)

    def compile_state_ref(self, ref):
        # compare to eval_state_ref
        if ref.type == 'StateRefEq':
            self.file.write("'%s'" % ref.id)
        elif ref.type == 'StateRefRel':
            self.file.write("pf.get(x+%d,y+%d)" % (ref.value[0], ref.value[1]))
        else:
            raise NotImplementedError(repr(ref))

    def compile_relation(self, ref, ast):
        if ast.type == 'ClassDecl':
            self.file.write('is_%s(' % ast.id)
            self.compile_state_ref(ref)
            self.file.write(")")
        else:
            self.file.write('(')
            self.compile_state_ref(ref)
            self.file.write('===')
            self.compile_state_ref(ast)
            self.file.write(')')

    def compile_expr(self, expr):
        if expr.type == 'BoolOp':
            self.file.write('(')
            self.compile_expr(expr.lhs)
            self.file.write({
                'or': '||',
                'and': '&&',
                'xor': '!==',
            }[expr.op])
            self.compile_expr(expr.rhs)
            self.file.write(')')
        elif expr.type == 'Not':
            self.file.write('!(')
            self.compile_expr(expr.expr)
            self.file.write(')')
        elif expr.type == 'BoolLit':
            if expr.value == 'guess':
                self.file.write('(Math.random()<.5)')
            else:
                self.file.write(expr.value)
        elif expr.type == 'Relational':
            self.compile_relation(expr.lhs, expr.rhs)
        elif expr.type == 'Adjacency':
            count = expr.count
            rel = expr.rel
            nbhd = expr.nbhd
            if nbhd.type == 'NbhdRef':
                nbhd = find_nbhd_defn(self.alpaca, nbhd.id).children[0]
            assert nbhd.type == 'Neighbourhood'
            if rel.type == 'ClassDecl':
                self.file.write("(in_nbhd_pred(pf, x, y, is_%s" % rel.id)
            else:
                self.file.write('(in_nbhd_eq(pf, x, y, ')
                self.compile_state_ref(rel)
            self.file.write(', [')
            self.file.write(','.join(
                ['[%d,%d]' % child.value for child in nbhd.children]
            ))
            self.file.write(']) >= %d)' % int(count))
        else:
            raise NotImplementedError(repr(expr))