def program(self, program: Node, env: Optional[Env] = None) -> Tuple[Env, Node]: """Transpile a program to YOLOL.""" assert program.kind == 'program' logger.debug('transpiling program') if env is None: env = Env() yolol = Node(kind='program') statements = [line.children[0] for line in program.children] for statement in statements: if statement.kind == 'import_group': env = self.import_group(env, statement) elif statement.kind == 'export': env = self.export(env, statement) elif statement.kind.startswith('let'): # type: ignore env, line = self.let(env, statement) yolol.append_child(line) elif statement.kind.startswith('def'): # type: ignore env = self.define(env, statement) elif statement.kind == 'using': env = self.using(env, statement) elif statement.kind == 'comment': pass else: raise AssertionError('unexpected statement kind: {}'.format( statement.kind)) return env, yolol
def assign(self, index: int) -> Tuple[List[Node], 'Number']: """Generate YOLOL assignment statements.""" ident = '{}{}'.format(Number.PREFIX, index) var = Node(kind='variable', value=ident) expr = self.evaluate() asn = Node(kind='assignment', children=[var, expr]) return [asn], Number(ident)
def assign(self, num_index: int) -> Tuple[Node, 'SimpleNumber']: """Generate a YOLOL assignment statement.""" ident = 'n{}'.format(num_index) var = Node(kind='variable', value=ident) expr = self.evaluate() asn = Node(kind='assignment', children=[var, expr]) return asn, SimpleNumber(ident)
def paste_in(self, sn): """ Paste this object in sn """ Node.paste_in(self, sn) sn.set_state(self.get_state()) if self.get_sps() is not None: sn.set_sps(self.get_sps().copy()) else: sn.set_sps(None)
def exp_zero(expr: Node) -> Tuple[Node, bool]: """Reduce (0^n) to (0) and (n^0) to (1) for (n!=0).""" if not Transform.is_binary_expr(expr): return expr, False elif expr.kind == 'exp' and expr.children[0].value == 0 and expr.children[1].value != 0: return Node(kind='number', value='0'), True elif expr.kind == 'exp' and expr.children[0].value != 0 and expr.children[1].value == 0: return Node(kind='number', value='1'), True else: return expr, False
def mul_zero(expr: Node) -> Tuple[Node, bool]: """Reduce (0*n) and (n*0) to (0).""" if not Transform.is_binary_expr(expr): return expr, False elif expr.kind == 'mul' and expr.children[0].value == 0: return Node(kind='number', value='0'), True elif expr.kind == 'mul' and expr.children[1].value == 0: return Node(kind='number', value='0'), True else: return expr, False
def assign(self, index: int) -> Tuple[List[Node], 'Vector']: """Generate YOLOL assignment statements.""" assignments = [] nums = [] expressions = [n.evaluate() for n in self.nums] for i, expr in enumerate(expressions): ident = '{}{}_e{}'.format(Vector.PREFIX, index, i) var = Node(kind='variable', value=ident) asn = Node(kind='assignment', children=[var, expr]) assignments.append(asn) nums.append(Number(ident)) return assignments, Vector(nums)
def assign(self, vec_index: int) -> Tuple[List[Node], 'SimpleVector']: """Generate YOLOL assignment statements.""" assignments = [] snums = [] expressions = [sn.evaluate() for sn in self.snums] for i, expr in enumerate(expressions): ident = 'v{}e{}'.format(vec_index, i) var = Node(kind='variable', value=ident) asn = Node(kind='assignment', children=[var, expr]) assignments.append(asn) snums.append(SimpleNumber(ident)) return assignments, SimpleVector(snums)
def evaluate(self) -> Node: """Generate a YOLOL expression.""" if type(self.initial) == str: node = Node(kind='variable', value=self.initial) else: node = Node(kind='number', value=self.initial) for op, *args in self.queue: if len(args) == 0: node = Node(kind=op, children=[node]) elif len(args) == 1: node = Node(kind=op, children=[node, args[0].evaluate()]) else: raise AssertionError( 'unrecognized item in queue: {}, {}'.format(op, args)) return node
def __init__(self): Node.__init__(self) self.__state = 's' #Represents the actual status of the SpriteNode (it's a letter for it's SpriteScheduler (cf SpriteScheduler) self.sps = None #SpriteScheduler-> if it's None hit boxes will be shown instead of the sprite self.animation_speed = 0.05 #Speed of animation (number of frames a single frame stays) self.animation_step = 0.0 #Count for the animation self.set_mapping( "Flat" ) #Way to show the image : Flat : extended // Repeatx : Repeted along x self.x_offset = 0 self.y_offset = 0 self.mult_offset = 1
def assign(self, index: int) -> Tuple[List[Node], 'Matrix']: """Generate YOLOL assignment statements.""" assignments = [] vecs = [] for i, v in enumerate(self.vecs): nums = [] for j, n in enumerate(v.nums): expr = n.evaluate() ident = '{}{}_r{}c{}'.format(Matrix.PREFIX, index, i, j) var = Node(kind='variable', value=ident) asn = Node(kind='assignment', children=[var, expr]) assignments.append(asn) nums.append(Number(ident)) vecs.append(Vector(nums)) return assignments, Matrix(vecs)
def assign(self, mat_index: int) -> Tuple[List[Node], 'SimpleMatrix']: """Generate YOLOL assignment statements.""" assignments = [] svecs = [] for i, sv in enumerate(self.svecs): snums = [] for j, sn in enumerate(sv.snums): expr = sn.evaluate() ident = 'm{}r{}c{}'.format(mat_index, i, j) var = Node(kind='variable', value=ident) asn = Node(kind='assignment', children=[var, expr]) assignments.append(asn) snums.append(SimpleNumber(ident)) svecs.append(SimpleVector(snums)) return assignments, SimpleMatrix(svecs)
def use_library(ident: str, parser, root: str) -> Sequence[Node]: """Use definitions from a library.""" logger.debug('using definitions from library - {}'.format(ident)) matches = list(Path(root).glob('**/{}.lib.yovec'.format(ident))) # type: ignore if len(matches) == 0: raise YovecError('library not found: {}'.format(ident)) if len(matches) > 1: raise YovecError('multiple files found for library {}: {}'.format(ident, [str(p) for p in matches])) try: with open(matches[0]) as f: text = f.read() except IOError as e: raise YovecError('unable to load library {}: {}'.format(ident, str(e))) try: program = Node.from_tree(parser.parse(text)) except Exception as e: raise YovecError('failed to parse library {}: {}'.format(ident, str(e))) statements = [line.children[0] for line in program.children] definitions = [] for statement in statements: if statement.kind == 'comment': continue elif statement.kind.startswith('def_'): # type: ignore definitions.append(statement) else: raise YovecError('invalid statement in library {}: {}'.format(ident, statement)) return definitions
def run_yovec(source: str, root: str, no_elim: bool, no_reduce: bool, no_mangle: bool, ast: bool, cylon: bool) -> str: """Run Yovec.""" try: parser = Lark(YOVEC_EBNF, start='program') # type: ignore yovec = Node.from_tree(parser.parse(source)) except Exception as e: raise YovecError('Parse error: {}'.format(str(e))) try: transpiler = Transpiler(parser, root) # type: ignore env, yolol = transpiler.program(yovec) yolol, imported, exported = resolve_aliases(env, yolol) except YovecError as e: raise YovecError('Transpilation error: {}\n\n{}'.format(str(e), Context.format())) try: if not no_reduce: yolol = reduce_expressions(yolol) if not no_elim: yolol = eliminate_dead_code(yolol, exported) # type: ignore if not no_mangle: yolol = mangle_names(yolol, imported, exported) # type: ignore except YovecError as e: raise YovecError('Optimization error: {}\n\n{}'.format(str(e), Context.format())) if ast: return yolol.pretty() elif cylon: return yolol_to_cylon(yolol) else: return yolol_to_text(yolol)
def reduce_expressions(program: Node) -> Node: """Reduce expressions in a YOLOL program.""" assert program.kind == 'program' logger.debug('reducing expressions') clone = program.clone() while _propagate_constants(clone) or _fold_constants(clone): pass return clone
def exp_one(expr: Node) -> Tuple[Node, bool]: """Reduce (1^n) to (1) and (n^1) to (n).""" if not Transform.is_binary_expr(expr): return expr, False elif expr.kind == 'exp' and expr.children[0].value == 1: return Node(kind='number', value='1'), True elif expr.kind == 'exp' and expr.children[1].value == 1: return expr.children[0], True else: return expr, False
def mangle_names(program: Node, imported: Sequence[str], exported: Sequence[str]) -> Node: """Mangle names in a YOLOL program.""" assert program.kind == 'program' logger.debug('mangling names') clone = program.clone() pool = Pool([*imported, *exported]) variables = clone.find(lambda node: node.kind == 'variable') for var in variables: var.value = pool.replace(var.value) # type: ignore return clone
def _fold_constants(program: Node) -> bool: """Fold constants in a program. Returns True if constants were folded. """ assert program.kind == 'program' assignments = program.find(lambda node: node.kind == 'assignment') for asn in assignments: if _fold_assignment(asn): return True return False
def __init__(self, ident: str, params: Sequence[Node], return_type: str, body: Node): self.arity = len(params) self.param_types = [p.children[0].value for p in params] self.param_idents = [p.children[1].value for p in params] self.return_type = return_type self.body = body if len(set(self.param_idents)) < len(self.param_idents): raise YovecError('duplicate parameters') variables = body.find(lambda node: node.kind == 'variable') for var in variables: if var.value not in self.param_idents: raise YovecError('undefined variable: {}'.format(var.value)) calls = body.find(lambda node: node.kind == 'call') for call in calls: if call.children[0].value == ident: raise YovecError('recursion is not allowed')
def resolve_aliases(env: Env, program: Node) -> Tuple[Node, Set[str], Set[str]]: """Resolve aliases to their targets in a YOLOL program.""" assert program.kind == 'program' logger.debug('resolving aliases') clone = program.clone() imported = set() exported = set() for alias, target in env.imports.items(): variables = clone.find( lambda node: node.kind == 'variable' and node.value == alias) for var in variables: var.value = target imported.add(target) for alias, target in env.exports.items(): var, index = env.var(alias) if type(var) == Number: num_prefix = '{}{}'.format(Number.PREFIX, index) num_variables = clone.find(lambda node: node.kind == 'variable' and node.value.startswith(num_prefix)) for var in num_variables: var.value = var.value.replace(num_prefix, target) # type: ignore exported.add(var.value) elif type(var) == Vector: vec_prefix = '{}{}'.format(Vector.PREFIX, index) vec_variables = clone.find(lambda node: node.kind == 'variable' and node.value.startswith(vec_prefix)) for var in vec_variables: var.value = var.value.replace(vec_prefix, target) # type: ignore exported.add(var.value) elif type(var) == Matrix: mat_prefix = '{}{}'.format(Matrix.PREFIX, index) mat_variables = clone.find(lambda node: node.kind == 'variable' and node.value.startswith(mat_prefix)) for var in mat_variables: var.value = var.value.replace(mat_prefix, target) # type: ignore exported.add(var.value) else: raise AssertionError('unexpected variable type: {}'.format( type(var))) return clone, imported, exported
def _propagate_constants(program: Node) -> bool: """Propagate constants in a program. Returns True if a constant was propagated. """ assert program.kind == 'program' variables = program.find(lambda node: node.kind == 'variable') for var in variables: replacement, delta = _propagate_var(program, var) if delta: var.parent.replace_child(var, replacement) # type: ignore return True return False
def binary_op(expr: Node) -> Tuple[Node, bool]: """Reduce a binary operation.""" if not Transform.is_binary_expr(expr): return expr, False elif expr.children[0].kind == 'number' and expr.children[1].kind == 'number': try: left = Decimal(expr.children[0].value) right = Decimal(expr.children[1].value) result = str(left.binary(expr.kind, right)) # type: ignore return Node(kind='number', value=result), True except ArithmeticError: raise YovecError('failed to fold constants in binary expression') else: return expr, False
def _fold_assignment(assignment: Node) -> bool: """Fold constants in an assignment. Returns True if constants were folded.""" assert assignment.kind == 'assignment' numbers = assignment.find(lambda node: node.kind == 'number') for num in numbers: expr = num.parent for transform in Transform().functions: replacement, delta = transform(expr) if delta: expr.parent.replace_child(expr, replacement) # type: ignore return True return False
def _graph_deps(program: Node) -> Dict[str, Set[str]]: """Graph variable dependencies.""" assert program.kind == 'program' logger.debug('graphing variable dependencies') graph = {} assignments = program.find(lambda node: node.kind == 'assignment') for asn in assignments: variable = asn.children[0] expr = asn.children[1] unique = { v.value for v in expr.find(lambda node: node.kind == 'variable') } graph[variable.value] = unique return graph
def _remove_dead(program: Node, alive: Set[str]) -> Node: """Remove dead assignments.""" assert program.kind == 'program' logger.debug('removing dead assignments') clone = program.clone() assignments = clone.find(lambda node: node.kind == 'assignment') for asn in assignments: if asn.children[0].value not in alive: assert asn.parent is not None asn.parent.remove_child(asn) # type: ignore lines = clone.find(lambda node: node.kind == 'line') for line in lines: if len(line.children) == 0: assert line.parent is not None line.parent.remove_child(line) # type: ignore return clone
def let(self, env: Env, let: Node) -> Tuple[Env, Node]: """Transpile a let statement to YOLOL.""" assert let.kind.startswith('let') # type: ignore logger.debug('transpiling let statement - {}'.format(let)) ident = let.children[0].value expr = let.children[1] if let.kind == 'let_num': env, value = self.nexpr(env, expr) elif let.kind == 'let_vec': env, value = self.vexpr(env, expr) elif let.kind == 'let_mat': env, value = self.mexpr(env, expr) else: raise AssertionError('unexpected let kind: {}'.format(let.kind)) env, assignments = env.let(ident, value) line = Node(kind='line', children=assignments) return env, line
def main(infile, outfile, ast=False): with open('grammar/yovec.ebnf') as f: grammar = f.read() parser = Lark(grammar, start='program') with open(infile) as f: raw_program = f.read() yovec_program = Node.from_tree(parser.parse(raw_program)) yolol_program = transpile_yovec(yovec_program) if ast: output = yolol_program.pretty() else: output = format_yolol(yolol_program) if outfile == '': print(output) else: with open(outfile, mode='w') as f: f.write(output)
def _propagate_var(program: Node, var: Node) -> Tuple[Node, bool]: """Propagate constants in a variable. Returns True if a constant was propagated. """ if var.parent.kind == 'assignment' and var.parent.children.index(var) == 0: #$ type: ignore # Found "A" in "let A = expr" return var, False assignments = program.find(lambda node: node.kind == 'assignment' and node.children[0].value == var.value) if len(assignments) == 0: # Found external variable; cannot propagate return var, False expr = assignments[0].children[1].clone() if expr.kind == 'variable': # Found assignment of single variable return expr, True elif len(expr.find(lambda node: node.kind == 'variable')) == 0: # Found assignment of constant expression return expr, True else: return var, False
def yolol_to_text(program: Node) -> str: """Format a YOLOL program as text.""" assert program.kind == 'program' assignments = program.find(lambda node: node.kind == 'assignment') formatted = [_format_assignment(a) for a in assignments] text = '' curr = [] for f in formatted: line = ' '.join(curr) if len(f) > 70: stderr.write('Warning: line exceeds 70 characters\n') text += '{}\n{}\n'.format(line, f) curr = [] elif len(line) + len(' ') + len(f) > 70: text += '{}\n'.format(line) curr = [f] else: curr.append(f) if len(curr) > 0: text += '{}\n'.format(' '.join(curr)) return text.strip('\n')