def visit_Constant(self, node: ast.Constant) -> None: try: f = FUNCTIONS[node.prop] except KeyError: raise CCLSymbolError(node, f'Property {node.prop} is not known.') if len(f.type.args) != 1 or f.type.args[0] != ObjectType.ATOM: raise CCLTypeError(node, f'Function {node.prop} is not a property.') if node.element not in ELEMENT_NAMES: raise CCLSymbolError(node, f'Element {node.element} not known.') self.global_table.define( ConstantSymbol(node.name, node, f, node.element))
def visit_EE(self, node: ast.EE) -> None: s1 = self.current_table.resolve(node.idx_row) s2 = self.current_table.resolve(node.idx_col) if s1 is not None or s2 is not None: raise CCLSymbolError( node, 'Index/indices for EE expression already defined.') table = SymbolTable(self.current_table) table.define(ObjectSymbol(node.idx_row, node, ObjectType.ATOM, None)) table.define(ObjectSymbol(node.idx_col, node, ObjectType.ATOM, None)) node.symbol_table = table self._iterating_over |= {node.idx_row, node.idx_col} self.current_table = table self.visit(node.diag) self.visit(node.off) self.visit(node.rhs) if {node.diag.result_type, node.off.result_type, node.rhs.result_type } != {NumericType.FLOAT}: raise CCLTypeError( node, f'EE expression has to have all parts with Float type.') self._iterating_over -= {node.idx_row, node.idx_col} assert self.current_table.parent is not None self.current_table = self.current_table.parent node.result_type = ArrayType(ObjectType.ATOM)
def visit_Function(self, node: ast.Function) -> None: # Functions have only one numerical argument def check_args( expected: Union[ObjectType, NumericType, ArrayType], given: Union[ObjectType, NumericType, ArrayType]) -> bool: if expected == given: return True if given == NumericType.INT and expected == NumericType.FLOAT: return True return False self.visit(node.arg) try: f = FUNCTIONS[node.name] except KeyError: raise CCLSymbolError(node, f'Function {node.name} is not known.') assert isinstance(node.arg.result_type, (ObjectType, NumericType, ArrayType)) assert isinstance(f.type.args[0], (ObjectType, NumericType, ArrayType)) if not check_args(f.type.args[0], node.arg.result_type): raise CCLTypeError( node.arg, f'Incompatible argument type for function {f.name}. ' f'Got {node.arg.result_type}, expected {f.type.args[0]}.') assert isinstance(f.type.return_type, (NumericType, ArrayType)) node.result_type = f.type.return_type
def visit_Property(self, node: ast.Property) -> None: try: f = FUNCTIONS[node.prop] except KeyError: raise CCLSymbolError(node, f'Function {node.prop} is not known.') self.global_table.define(FunctionSymbol(node.name, node, f))
def visit_Substitution(self, node: ast.Substitution) -> None: if isinstance(node.lhs, ast.Name): name = node.lhs.val indices: Tuple[ast.Name, ...] = tuple() if node.constraints is not None: raise CCLSymbolError( node, f'Substitution symbol {name} cannot have a constraint.') else: # ast.Subscript name = node.lhs.name.val indices = node.lhs.indices s = self.current_table.resolve(name) if s is None: ns = SubstitutionSymbol(name, node, indices) self.global_table.define(ns) ns.rules[node.constraints] = node.rhs else: if not isinstance(s, SubstitutionSymbol): raise CCLSymbolError( node, f'Symbol {s.name} already defined as something else.') if indices != s.indices: raise CCLSymbolError( node, f'Substitution symbol {s.name} has different indices defined.' ) if node.constraints in s.rules: raise CCLTypeError( node, f'Same constraint already defined for symbol {s.name}.') s.rules[node.constraints] = node.rhs used_names: Set[str] = set() if node.constraints is not None: used_names |= NameGetter().visit(node.constraints, self.global_table) used_names |= NameGetter().visit(node.rhs, self.global_table) for used_name in used_names: symbol = self.global_table.resolve(used_name) if isinstance(symbol, SubstitutionSymbol): raise CCLSymbolError( node, f'Cannot nest substitution {used_name} in another substitution {name}.' )
def check_substitutions_default(self) -> None: for symbol in self.global_table.symbols.values(): if isinstance(symbol, SubstitutionSymbol): if None not in symbol.rules: raise CCLSymbolError( symbol.def_node, f'No default option specified for Substitution symbol {symbol.name}.' )
def visit_Predicate(self, node: ast.Predicate) -> None: try: f = PREDICATES[node.name] except KeyError: raise CCLSymbolError(node, f'Predicate {node.name} not defined.') if len(f.type.args) != len(node.args): raise CCLSymbolError( node, f'Predicate {f.name} should have {len(f.type.args)} arguments ' f'but got {len(node.args)} instead.') for arg_type, arg in zip(f.type.args, node.args): if isinstance(arg_type, ObjectType): self.visit(arg) assert isinstance(arg, ast.Name) name = self._indices_mapping.get(arg.val, arg.val) if name not in self.iterating_over: raise CCLSymbolError( arg, f'Object {arg.val} not bound to ForEach or Sum.') if arg.result_type != arg_type: raise CCLTypeError( arg, f'Predicate\'s {node.name} argument is not {arg_type}.' ) elif arg_type == StringType: # Note that name would not have result_type set as it's really a String if not isinstance(arg, ast.Name): raise CCLTypeError( arg, f'Predicate {node.name} expected string argument.') elif isinstance(arg_type, NumericType): if not isinstance(arg.result_type, NumericType): raise CCLTypeError( arg, f'Predicate {node.name} expected numeric argument.') else: raise Exception('We should not get here!') if f.name == 'element' and str( node.args[1].val).lower() not in ELEMENT_NAMES: raise CCLTypeError(node.args[1], f'Unknown element {node.args[1].val}.')
def visit_ForEach(self, node: ast.ForEach) -> None: s = self.current_table.resolve(node.name.val) if s is not None: raise CCLSymbolError( node.name, f'Loop variable {node.name.val} already defined.') table = SymbolTable(self.current_table) atom_indices: Set[str] = set() if node.atom_indices is not None: i1, i2 = node.atom_indices s1 = self.current_table.resolve(i1) s2 = self.current_table.resolve(i2) if s1 is not None or s2 is not None: raise CCLSymbolError( node, f'Decomposition of bond symbol {node.name.val} used already defined names.' ) bonded_constraint = ast.Predicate( (node.line, node.column), 'bonded', ( ast.Name((node.line, node.column), i1), ast.Name((node.line, node.column), i2), )) table.define( ObjectSymbol(i1, node, ObjectType.ATOM, bonded_constraint)) table.define( ObjectSymbol(i2, node, ObjectType.ATOM, bonded_constraint)) atom_indices = {i1, i2} self._iterating_over |= atom_indices | {node.name.val} node.symbol_table = table table.define( ObjectSymbol(node.name.val, node, node.type, node.constraints)) self.current_table = table for statement in node.body: self.visit(statement) self._iterating_over -= atom_indices | {node.name.val} assert self.current_table.parent is not None self.current_table = self.current_table.parent
def visit_Sum(self, node: ast.Sum) -> None: s = self.current_table.resolve(node.name.val) if s is None: raise CCLSymbolError(node.name, f'Symbol {node.name.val} not defined.') node.name.result_type = s.symbol_type if not isinstance(s, ObjectSymbol): raise CCLSymbolError( node.name, f'Sum has to iterate over Atom or Bond not {s.symbol_type}.') self._iterating_over.add(node.name.val) if s.constraints is not None: self.visit(s.constraints) self.visit(node.expr) self._iterating_over.remove(node.name.val) node.result_type = node.expr.result_type
def visit_Name(self, node: ast.Name) -> None: name = self._indices_mapping.get(node.val, node.val) s = self.current_table.resolve(name) if s is not None: if isinstance(s, SubstitutionSymbol): self.visit(s.rules[None]) if s.symbol_type == ParameterType.COMMON: node.result_type = NumericType.FLOAT else: node.result_type = s.symbol_type else: raise CCLSymbolError(node, f'Symbol {name} not defined.')
def visit_Object(self, node: ast.Object) -> None: if node.atom_indices is not None: i1, i2 = node.atom_indices s1 = self.current_table.resolve(i1) s2 = self.current_table.resolve(i2) if s1 is not None or s2 is not None: raise CCLSymbolError( node, f'Decomposition of bond symbol {node.name} used already defined names.' ) self.global_table.define( ObjectSymbol(i1, node, ObjectType.ATOM, None)) self.global_table.define( ObjectSymbol(i2, node, ObjectType.ATOM, None)) self.global_table.define( ObjectSymbol(node.name, node, node.type, node.constraints))
def visit_For(self, node: ast.For) -> None: s = self.current_table.resolve(node.name.val) if s is not None: raise CCLSymbolError( node.name, f'Loop variable {node.name.val} already defined.') self._iterating_over.add(node.name.val) table = SymbolTable(self.current_table) node.symbol_table = table table.define(VariableSymbol(node.name.val, node, NumericType.INT)) self.current_table = table for statement in node.body: self.visit(statement) self._iterating_over.remove(node.name.val) assert self.current_table.parent is not None self.current_table = self.current_table.parent
def visit_Assign(self, node: ast.Assign) -> None: def check_types(lhs: CCLType, rhs: CCLType) -> bool: if isinstance(lhs, ArrayType) and isinstance(rhs, ArrayType): return lhs == rhs # Can assign number to all elements of the vector/matrix if isinstance(lhs, ArrayType) and isinstance(rhs, NumericType): return True if isinstance(lhs, NumericType) and isinstance(rhs, NumericType): # Cannot assign Float to Int, OK otherwise if lhs == NumericType.INT and rhs == NumericType.FLOAT: return False return True return False self.visit(node.rhs) rtype = node.rhs.result_type if not isinstance(rtype, (NumericType, ArrayType)): raise CCLTypeError( node.rhs, f'Only Numbers and Arrays can be assigned not {rtype}.') if isinstance(node.lhs, ast.Name): s = self.current_table.resolve(node.lhs.val) if s is not None: if s.name in self._iterating_over: raise CCLTypeError( node, f'Cannot assign to loop variable {s.name}.') if isinstance(s, SubstitutionSymbol): raise CCLSymbolError( node.lhs, f'Cannot assign to a substitution symbol {s.name}.') if isinstance(s, ParameterSymbol): raise CCLSymbolError( node.lhs, f'Cannot assign to a parameter symbol {s.name}.') symbol_type = s.symbol_type if check_types(symbol_type, rtype): node.lhs.result_type = symbol_type else: raise CCLTypeError( node, f'Cannot assign {rtype} to the variable {s.name} of type {symbol_type}.' ) else: # New symbols are defined at method scope self.symbol_table.define( VariableSymbol(node.lhs.val, node, rtype)) node.lhs.result_type = rtype elif isinstance(node.lhs, ast.Subscript): # ast.Subscript s = self.current_table.resolve(node.lhs.name.val) index_types = [] for idx in node.lhs.indices: self.visit(idx) index_types.append(idx.result_type) if idx.val not in self._iterating_over: raise CCLSymbolError( idx, f'Object {idx.val} not bound to any For/ForEach/Sum.') if s is not None: symbol_type = s.symbol_type # Check whether indices are correct if isinstance(s, SubstitutionSymbol): raise CCLSymbolError( node.lhs, f'Cannot assign to a substitution symbol {s.name}.') if not isinstance(symbol_type, ArrayType): raise CCLTypeError( node.lhs, f'Cannot assign to non-Array type {symbol_type}.') index_types = [] for idx in node.lhs.indices: self.visit(idx) index_types.append(idx.result_type) if symbol_type.indices != tuple(index_types): indices_str = ', '.join(str(i) for i in index_types) raise CCLTypeError( node.lhs, f'Cannot index Array of type {symbol_type} ' f'using index/indices of type(s) {indices_str}.') node.lhs.name.result_type = s.symbol_type else: if not all(isinstance(i, ObjectType) for i in index_types): raise CCLTypeError( node.lhs, f'Cannot index with something different than Atom or Bond.' ) # Hack for Mypy as it does not recognize the previous check it = cast(Sequence[ObjectType], index_types) self.symbol_table.define( VariableSymbol(node.lhs.name.val, node, ArrayType(*it))) node.lhs.name.result_type = ArrayType(*it) node.lhs.result_type = rtype else: raise Exception('Should not get here!')
def visit_Subscript(self, node: ast.Subscript) -> None: s = self.current_table.resolve(node.name.val) if not isinstance(s, SubstitutionSymbol): self.visit(node.name) symbol_type = node.name.result_type index_types_list = [] for idx in node.indices: self.visit(idx) index_types_list.append(idx.result_type) mapped_val = self._indices_mapping.get(idx.val, idx.val) if isinstance(idx.result_type, ObjectType) and \ mapped_val not in self.iterating_over: raise CCLSymbolError( idx, f'Object {mapped_val} not bound to any For/ForEach/Sum.') index_types = tuple(index_types_list) index_types_str = ', '.join(str(i) for i in index_types) if isinstance(s, ParameterSymbol): if symbol_type == ParameterType.ATOM and index_types != ( ObjectType.ATOM, ): raise CCLTypeError( node, f'Cannot index atom parameter with {index_types_str}.') if symbol_type == ParameterType.BOND: if index_types not in ((ObjectType.BOND, ), (ObjectType.ATOM, ObjectType.ATOM)): raise CCLTypeError( node, f'Cannot index bond parameter with {index_types_str}.') # Check whether two atoms are actually bonded, i.e., 'bonded' predicate exists if index_types == (ObjectType.ATOM, ObjectType.ATOM): s1 = self.current_table.resolve(node.indices[0].val) s2 = self.current_table.resolve(node.indices[1].val) assert s1 is not None and isinstance(s1, ObjectSymbol) assert s2 is not None and isinstance(s2, ObjectSymbol) idx1 = node.indices[0].val idx2 = node.indices[1].val results: Set[Optional[ast.ASTNode]] = {None} for c in (c for c in (s1.constraints, s2.constraints) if c is not None): # Search for either bonded(i, j) or bonded(j, i) results.add( ast.search_ast_element( c, ast.Predicate((-1, -1), 'bonded', (ast.Name( (-1, -1), idx1), ast.Name( (-1, -1), idx2))))) results.add( ast.search_ast_element( c, ast.Predicate((-1, -1), 'bonded', (ast.Name( (-1, -1), idx2), ast.Name( (-1, -1), idx1))))) if results == {None}: raise CCLSymbolError( node, f'Cannot index bond parameter by two non-bonded atoms.' ) if symbol_type == NumericType.FLOAT: raise CCLTypeError(node, f'Cannot index common parameter.') elif isinstance(s, VariableSymbol) and isinstance( symbol_type, ArrayType): if symbol_type.indices != index_types: raise CCLTypeError( node, f'Cannot index Array of type {symbol_type} ' f'using index/indices of type(s) {index_types_str}.') elif isinstance(s, FunctionSymbol): assert isinstance(symbol_type, FunctionType) if symbol_type.args != index_types: raise CCLTypeError( node, f'Cannot use function {s.function.name}: {s.function.type} ' f'with arguments of type(s) {index_types_str}') assert isinstance(s.function.type.return_type, (NumericType, ArrayType)) node.result_type = s.function.type.return_type return elif isinstance(s, SubstitutionSymbol): if len(s.indices) != len(index_types): raise CCLTypeError( node, f'Bad number of indices for {s.name}, got {len(index_types)}, ' f'expected {len(s.indices)}.') if not all(isinstance(t, ObjectType) for t in index_types): raise CCLTypeError( node, f'Substitution indices for symbol {s.name} must have type Atom or Bond.' ) self._indices_mapping.update( {si.val: ni.val for si, ni in zip(s.indices, node.indices)}) types = set() for constraint, expr in s.rules.items(): if constraint is not None: self.visit(constraint) self.visit(expr) types.add(expr.result_type) if len(types) > 1: raise CCLTypeError( node, f'All expressions within a substitution symbol {s.name} must have same type.' ) for si in s.indices: self._indices_mapping.pop(si.val) else: raise CCLTypeError( node, f'Cannot index type {symbol_type} with indices of type(s) {index_types_str}' ) # Return Float if not assigned already node.result_type = NumericType.FLOAT
def define(self, symbol: Symbol) -> None: if self.resolve(symbol.name): raise CCLSymbolError(symbol.def_node, f'Symbol {symbol.name} already defined.') self.symbols[symbol.name] = symbol