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 call(self, args: Sequence[Node]) -> Node: """Call a macro with arguments.""" if len(args) != self.arity: raise YovecError('expected {} arguments, but got {}'.format( self.arity, len(args))) for i, arg in enumerate(args): type_ = self.param_types[i] if type_ == 'number' and not is_nexpr(arg.kind): # type: ignore raise YovecError( 'expected argument to be number expression, but got {}'. format(arg.kind)) elif type_ == 'vector' and not is_vexpr(arg.kind): # type: ignore raise YovecError( 'expected argument to be vector expression, but got {}'. format(arg.kind)) elif type_ == 'matrix' and not is_mexpr(arg.kind): # type: ignore raise YovecError( 'expected argument to be matrix expression, but got {}'. format(arg.kind)) clone = self.body.clone() variables = clone.find(lambda node: node.kind == 'variable') for var in variables: index = self.param_idents.index(var.value) var.parent.replace_child(var, args[index]) # type: ignore return clone
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 import_(self, alias: str, target: str) -> 'Env': """Import an alias to a target.""" logger.debug('importing alias with target - {}, {}'.format(alias, target)) if alias in self.imports: raise YovecError('cannot redefine existing import: {}'.format(alias)) elif target in self.imports.values() or target in self.exports.values(): raise YovecError('conflicting import target: {}'.format(target)) clone = deepcopy(self) clone._imports[alias] = target return clone
def define(self, ident: str, macro: Macro) -> 'Env': """Define a macro.""" logger.debug('defining macro - {}'.format(ident)) if ident in self.macros: raise YovecError('cannot redefine existing macro: {}'.format(ident)) elif ident in self.variables: raise YovecError('conflict between macro and variable: {}'.format(ident)) clone = deepcopy(self) clone._macros[ident] = macro return clone
def var(self, ident: str, expect: Any = None) -> Union[NumVar, VecVar]: """Get a variable from the environment.""" try: v = self.variables[ident] except KeyError: raise YovecError('undefined variable: {}'.format(ident)) if expect is not None and type(v) != expect: raise YovecError( 'expected variable to have type {}, but got {}'.format( expect, type(v))) return v
def set_alias(self, alias: str, target: str) -> 'Env': """Set an alias.""" if alias in self.aliases: raise YovecError( 'cannot redefine existing alias: {}'.format(alias)) if target in self.aliases.values(): raise YovecError('conflicting alias target: {}'.format(target)) if set(alias) in set(ascii_uppercase + '_') and alias not in self.variables: raise YovecError( 'cannot export undefined variable: {}'.format(alias)) clone = deepcopy(self) clone.aliases[alias] = target return clone
def elem(self, row_index: int, col_index: int) -> Number: """Get a matrix element by index.""" try: return self.vecs[row_index].nums[col_index] except IndexError: raise YovecError('element indices {}, {} are out of range'.format( row_index, col_index))
def target(self, alias: str) -> str: """Get the target of an alias.""" if alias in self.imports: return self.imports[alias] elif alias in self.exports: return self.exports[alias] else: raise YovecError('undefined alias: {}'.format(alias))
def apply(self, op: str, other: 'Matrix') -> 'Matrix': """Apply a binary operation to two matrices.""" if self._rows != other._rows or self._cols != other._cols: raise YovecError( 'cannot apply operation {} to matrices of different sizes'. format(op)) return Matrix( [lv.apply(op, rv) for lv, rv in zip(self.vecs, other.vecs)])
def set_num(self, ident: str, num_index: int, sn: SimpleNumber) -> 'Env': """Set a number variable.""" if ident in self.variables: raise YovecError( 'cannot redefine existing variable: {}'.format(ident)) clone = deepcopy(self) clone.variables[ident] = NumVar(index=num_index, sn=sn) return clone
def set_vec(self, ident: str, vec_index: int, sv: SimpleVector) -> 'Env': """Set a vector variable.""" if ident in self.variables: raise YovecError( 'cannot redefine existing variable: {}'.format(ident)) clone = deepcopy(self) clone.variables[ident] = VecVar(index=vec_index, sv=sv) return clone
def set_mat(self, ident: str, mat_index: int, sm: SimpleMatrix) -> 'Env': """Set a matrix variable.""" if ident in self.variables: raise YovecError( 'cannot redefine existing variable: {}'.format(ident)) clone = deepcopy(self) clone.variables[ident] = MatVar(index=mat_index, sm=sm) return clone
def matbinary(self, op: str, other: 'Matrix') -> 'Matrix': """Apply a binary operation to two matrices.""" if self._rows != other._rows or self._cols != other._cols: raise YovecError( 'cannot apply operation {} to matrices of different sizes'. format(op)) return Matrix([ v.vecbinary(op.replace('mat_', 'vec_'), other.vecs[i]) for i, v in enumerate(self.vecs) ])
def __init__(self, vecs: List[Vector]): assert len(vecs) > 0 if len(vecs) > 1: for v in vecs[1:]: if v.length != vecs[0].length: raise YovecError( 'all vectors in a matrix must have the same length') self.vecs = vecs self._rows = len(self.vecs) self._cols = self.vecs[0].length
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 __init__(self, svecs: List[SimpleVector]): assert len(svecs) > 0 if len(svecs) > 1: n = svecs[0].length for sv in svecs[1:]: if sv.length != n: raise YovecError( 'all vectors in a matrix must have the same length') self.svecs = svecs self._rows = len(self.svecs) self._cols = self.svecs[0].length
def let(self, ident: str, value: Value) -> Tuple['Env', List[Node]]: """Assign a value to a variable.""" logger.debug('assigning variable - {}'.format(ident)) if ident in self.variables: raise YovecError('cannot redefine existing variable: {}'.format(ident)) elif ident in self.macros: raise YovecError('conflict between macro and variable: {}'.format(ident)) if type(value) == Number: index = self._num_index self._num_index += 1 elif type(value) == Vector: index = self._vec_index self._vec_index += 1 elif type(value) == Matrix: index = self._mat_index self._mat_index += 1 else: raise AssertionError('unexpected value type: {}'.format(type(value))) assignments, value = value.assign(index) clone = deepcopy(self) clone._variables[ident] = (value, index) return clone, assignments
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 matmul(self, other: 'Matrix') -> 'Matrix': """Multiply two matrices.""" if self._cols != other._rows: raise YovecError('cannot mulitply matrices with mismatching sizes') vecs = [] for i in range(self._rows): nums = [] for j in range(other._cols): n = Number(0) for k in range(other._rows): n = n.binary( 'add', self.vecs[i].nums[k].binary('mul', other.vecs[k].nums[j])) nums.append(n) vecs.append(Vector(nums)) return Matrix(vecs)
def matmul(self, other: 'SimpleMatrix') -> 'SimpleMatrix': """Multiply two simple matrices.""" if self._cols != other._rows: raise YovecError('cannot mulitply matrices with mismatching sizes') svecs = [] for i in range(self._rows): snums = [] for j in range(other._cols): sn = SimpleNumber(0) for k in range(other._rows): sn = sn.binary( 'add', self.svecs[i].snums[k].binary('mul', other.svecs[k].snums[j])) snums.append(sn) svecs.append(SimpleVector(snums)) return SimpleMatrix(svecs)
def elem(self, index: int) -> Number: """Get a vector element by index.""" try: return self.nums[index] except IndexError: raise YovecError('element index {} is out of range'.format(index))
def alias(self, ident: str) -> str: """Get an alias from the environment.""" try: return self.aliases[ident] except KeyError: raise YovecError('undefined alias: {}'.format(ident))
def vecbinary(self, op: str, other: 'SimpleVector') -> 'SimpleVector': """Apply a binary operation to two simple vectors.""" if self.length != other.length: raise YovecError('cannot apply operation "{}" to vectors of different lengths'.format(op)) return SimpleVector([sn.binary(op.strip('vec_'), other.snums[i]) for i, sn in enumerate(self.snums)])
def row(self, index: int) -> Vector: """Get a matrix row by index.""" try: return self.vecs[index] except IndexError: raise YovecError('row index {} is out of range'.format(index))
def col(self, index: int) -> Vector: """Get a matrix column by index.""" try: return Vector([v.nums[index] for v in self.vecs]) except IndexError: raise YovecError('column index {} is out of range'.format(index))
def vecbinary(self, op: str, other: 'Vector') -> 'Vector': """Apply a binary operation to two vectors.""" if self.length != other.length: raise YovecError('cannot apply operation {} to vectors of different lengths'.format(op)) return Vector([n.binary(op.strip('vec_'), other.nums[i]) for i, n in enumerate(self.nums)])
def macro(self, ident: str) -> Macro: """Get a macro.""" try: return self.macros[ident] except KeyError: raise YovecError('undefined macro: {}'.format(ident))
def var(self, ident: str) -> Tuple[Value, int]: "Get the value of a variable." try: return self.variables[ident] except KeyError: raise YovecError('undefined variable: {}'.format(ident))
def apply(self, op: str, other: 'Vector') -> 'Vector': """Apply a binary operation to two vectors.""" if self.length != other.length: raise YovecError('cannot apply operation {} to vectors of different lengths'.format(op)) return Vector([ln.binary(op, rn) for ln, rn in zip(self.nums, other.nums)])