def binary_op(self, node): op = node.op left = self.visit(node.left) right = self.visit(node.right) if hasFunction(self, userdef_binary_str(op, left, right)): return self.builder.call( self.module.get_global(userdef_binary_str(op, left, right)), (left, right), "binop") elif op == CAST: return cast_ops(self, left, right, node) elif op in (IS, IS_NOT): return is_ops(self, op, left, right, node) elif isinstance(left.type, ir.IntType) and isinstance( right.type, ir.IntType): return int_ops(self, op, left, right, node) elif type(left.type) in NUM_TYPES and type(right.type) in NUM_TYPES: if isinstance(left.type, ir.IntType): left = cast_ops(self, left, right.type, node) elif isinstance(right.type, ir.IntType): right = cast_ops(self, right, left.type, node) return float_ops(self, op, left, right, node) elif is_enum(left.type) and is_enum(right.type): return enum_ops(self, op, left, right, node) else: error('file={} line={}: Unknown operator {} for {} and {}'.format( self.file_name, node.line_num, op, node.left, node.right))
def visit_funccall(self, node): func_name = node.name func = self.search_scopes(func_name) parameters = None parameter_defaults = None if isinstance(func, (ClassSymbol, EnumSymbol)): parameters = func.fields parameter_defaults = func.fields else: parameters = func.parameters parameter_defaults = func.parameter_defaults for x, param in enumerate(parameters.values()): if x < len(node.arguments): var = self.visit(node.arguments[x]) param_ss = self.search_scopes(param.value) # TODO: Hacky stuff, first line is made to bypass checks for first-class functions, to fix if not isinstance(var, FuncSymbol) and (var.type is not None and not types_compatible(var, param_ss) and (param_ss != self.search_scopes(ANY) and param.value != var.name and param.value != var.type.name)): raise TypeError # TODO: Make this an actual error else: func_param_keys = list(parameters.keys()) if func_param_keys[x] not in node.named_arguments.keys() and func_param_keys[x] not in parameter_defaults.keys(): error('file={} line={}: Missing arguments to function: {}'.format(self.file_name, node.line_num, repr(func_name))) else: if func_param_keys[x] in node.named_arguments.keys(): if not types_compatible(param.value, self.visit(node.named_arguments[func_param_keys[x]]).name): raise TypeError if func is None: error('file={} line={}: Name Error: {}'.format(self.file_name, node.line_num, repr(func_name))) else: func.accessed = True return func.type
def visit_dotaccess(self, node): obj = self.search_scopes(node.obj) obj.accessed = True if isinstance(obj, EnumSymbol): return obj elif node.field not in obj.type.fields: error('file={} line={}: Invalid property {} of variable {}'.format( self.file_name, node.line_num, node.field, node.obj)) return self.visit(obj.type.fields[node.field])
def visit_incrementassign(self, node): left = self.visit(node.left) left_type = self.infer_type(left) any_type = self.search_scopes(ANY) if left_type in (self.search_scopes(DOUBLE), self.search_scopes(FLOAT), self.search_scopes(INT)) \ or left_type is any_type: return left_type else: error('file={} line={}: Things that should not be happening ARE happening (fix this message)'.format(self.file_name, node.line_num))
def visit_var(self, node): var_name = node.value val = self.search_scopes(var_name) if val is None: error('file={} line={}: Name Error: {}'.format(self.file_name, node.line_num, repr(var_name))) else: if not val.val_assigned: error('file={} line={}: {} is being accessed before it was defined'.format(self.file_name, node.line_num, var_name)) val.accessed = True return val
def visit_opassign(self, node): left = self.visit(node.left) right = self.visit(node.right) left_type = self.infer_type(left) right_type = self.infer_type(right) any_type = self.search_scopes(ANY) if types_compatible(left_type, right_type) or left_type is any_type or right_type is any_type: return left_type else: error('file={} line={}: Things that should not be happening ARE happening (fix this message)'.format(self.file_name, node.line_num))
def find_until(self, to_find, until): num = 0 x = None while x != until: num += 1 x = self.lexer.preview_token(num).value if x == to_find: return True elif x == EOF: error('file={} line={} Syntax Error: expected {}'.format(self.file_name, self.line_num, to_find)) return False
def visit_range(self, node): left = self.visit(node.left) right = self.visit(node.right) left_type = self.infer_type(left) right_type = self.infer_type(right) any_type = self.search_scopes(ANY) if left_type in (self.search_scopes(INT), self.search_scopes(DOUBLE), self.search_scopes(FLOAT)) and right_type in (self.search_scopes(INT), self.search_scopes(DOUBLE), self.search_scopes(FLOAT)): return self.search_scopes(LIST), left_type elif right_type is left_type or left_type is any_type or right_type is any_type: return self.search_scopes(LIST), left_type else: error('file={} line={}: Please don\'t do what you just did there ever again. It bad (fix this message)'.format(self.file_name, node.line_num))
def visit_binop(self, node): if node.op == CAST or node.op in (IS, IS_NOT): self.visit(node.left) if node.right.value not in TYPES and not isinstance(self.search_scopes(node.right.value), (EnumSymbol, ClassSymbol)): error('file={} line={}: type expected for operation {}, got {} : {}'.format(self.file_name, node.line_num, node.op, node.left, node.right)) return self.infer_type(self.visit(node.right)) else: left = self.visit(node.left) right = self.visit(node.right) left_type = self.infer_type(left) right_type = self.infer_type(right) any_type = self.search_scopes(ANY) if types_compatible(left_type, right_type) or left_type is any_type or right_type is any_type: return left_type else: error('file={} line={}: types do not match for operation {}, got {} : {}'.format(self.file_name, node.line_num, node.op, left, right))
def visit_anonymousfunc(self, node): func_type = self.search_scopes(node.return_type.value) self.new_scope() for k, v in node.parameters.items(): var_type = self.search_scopes(v.value) if var_type is self.search_scopes(FUNC): sym = FuncSymbol(k, v.func_ret_type, None, None) else: sym = VarSymbol(k, var_type) sym.val_assigned = True self.define(sym.name, sym) func_symbol = FuncSymbol(ANON, func_type, node.parameters, node.body) return_var_type = self.visit(func_symbol.body) return_var_type = list(flatten(return_var_type)) for ret_type in return_var_type: if self.infer_type(ret_type) is not func_type: error('file={} line={}: The actual return type does not match the declared return type'.format(self.file_name, node.line_num)) self.drop_top_scope() return func_symbol
def unary_op(self, node): op = node.op expr = self.visit(node.expr) if hasFunction(self, userdef_unary_str(op, expr)) and \ self.current_function.name != userdef_unary_str(op, expr): return self.builder.call( self.module.get_global(userdef_unary_str(op, expr)), [expr], "unop") elif op == MINUS: if isinstance(expr.type, ir.IntType): return self.builder.neg(expr) elif isinstance(expr.type, (ir.FloatType, ir.DoubleType)): return self.builder.fsub(ir.Constant(ir.DoubleType(), 0), expr) elif op == NOT: if isinstance(expr.type, ir.IntType) and str( expr.type).split("i")[1] == '1': return self.builder.not_(expr) elif op == BINARY_ONES_COMPLIMENT: if isinstance(expr.type, ir.IntType): return self.builder.not_(expr) else: error('file={} line={}: Unknown operator {} for {}'.format( self.file_name, node.line_num, op, expr))
def visit_externfuncdecl(self, node): func_name = node.name func_type = self.search_scopes(node.return_type.value) if self.search_scopes(func_name) is not None: error('file={} line={}: Cannot redefine a declared function: {}'.format(self.file_name, node.line_num, func_name)) if func_type and func_type.name == FUNC: func_type.func = FuncSymbol(ANON, self.visit(node.return_type.func_ret_type), node.parameters, node.body, node.parameter_defaults) self.define(func_name, FuncSymbol(func_name, func_type, node.parameters, None)) self.new_scope() if node.varargs: varargs_type = self.search_scopes(LIST) varargs_type.type = node.varargs[1].value varargs = CollectionSymbol(node.varargs[0], varargs_type, self.search_scopes(node.varargs[1].value)) varargs.val_assigned = True self.define(varargs.name, varargs) for k, v in node.parameters.items(): var_type = self.search_scopes(v.value) if var_type is self.search_scopes(FUNC): sym = FuncSymbol(k, v.func_ret_type, None, None) elif isinstance(var_type, TypeSymbol): var_type.accessed = True if var_type.type is self.search_scopes(FUNC): sym = FuncSymbol(k, var_type.type.return_type, None, None) else: raise NotImplementedError else: sym = VarSymbol(k, var_type) sym.val_assigned = True self.define(sym.name, sym) func_symbol = FuncSymbol(func_name, func_type, node.parameters, None) self.define(func_name, func_symbol, 1) self.drop_top_scope()
def visit_collectionaccess(self, node): collection = self.search_scopes(node.collection.value) collection.accessed = True if isinstance(node.key, Var): key = self.infer_type(node.key.value) else: key = self.visit(node.key) if collection.type is self.search_scopes(LIST) or collection.type is self.search_scopes(TUPLE) or collection.type is self.search_scopes(SET): if key is not self.search_scopes(INT) and key.type is not self.search_scopes(INT): error('file={} line={}: Something something error... huh? (fix this message)'.format(self.file_name, node.line_num)) return collection.item_types elif collection.type is self.search_scopes(DICT) or collection.type is self.search_scopes(ENUM): if key is not self.search_scopes(STR) and key.type is not self.search_scopes(STR): error('file={} line={}: Dude....... don\'t (fix this message)'.format(self.file_name, node.line_num)) return self.search_scopes(ANY) else: error('file={} line={}: WHY? (fix this message)'.format(self.file_name, node.line_num))
def visit_funcdecl(self, node): func_name = node.name func_type = self.search_scopes(node.return_type.value) if self.search_scopes(func_name) is not None: error('file={} line={}: Cannot redefine a declared function: {}'.format(self.file_name, node.line_num, func_name)) if func_type and func_type.name == FUNC: func_type.func = FuncSymbol(ANON, self.visit(node.return_type.func_ret_type), node.parameters, node.body, node.parameter_defaults) self.define(func_name, FuncSymbol(func_name, func_type, node.parameters, node.body, node.parameter_defaults)) self.new_scope() if node.varargs: varargs_type = self.search_scopes(LIST) varargs_type.type = node.varargs[1].value varargs = CollectionSymbol(node.varargs[0], varargs_type, self.search_scopes(node.varargs[1].value)) varargs.val_assigned = True self.define(varargs.name, varargs) for k, v in node.parameters.items(): var_type = self.search_scopes(v.value) if var_type is self.search_scopes(FUNC): sym = FuncSymbol(k, v.func_ret_type, v.func_params, None) elif isinstance(var_type, TypeSymbol): var_type.accessed = True if var_type.type is self.search_scopes(FUNC): sym = FuncSymbol(k, var_type.type.return_type, v.func_params, None) else: raise NotImplementedError else: sym = VarSymbol(k, var_type) sym.val_assigned = True self.define(sym.name, sym) return_types = self.visit(node.body) return_types = list(flatten(return_types)) if self.return_flag: self.return_flag = False for ret_type in return_types: infered_type = self.infer_type(ret_type) if infered_type is not func_type and not types_compatible(infered_type, func_type): error('file={} line={}: The actual return type does not match the declared return type: {}'.format(self.file_name, node.line_num, func_name)) elif func_type is not None: error('file={} line={}: No return value was specified for function: {}'.format(self.file_name, node.line_num, func_name)) func_symbol = FuncSymbol(func_name, func_type, node.parameters, node.body, node.parameter_defaults) self.define(func_name, func_symbol, 1) self.drop_top_scope()
def function_declaration(self): op_func = False extern_func = False self.eat_value(DEF) if self.current_token.value == LPAREN: name = ANON elif self.current_token.value == OPERATOR: self.eat_value(OPERATOR) op_func = True name = self.next_token() elif self.current_token.value == EXTERN: self.eat_value(EXTERN) extern_func = True name = self.next_token() else: name = self.next_token() self.eat_value(LPAREN) params = OrderedDict() param_defaults = {} vararg = None while self.current_token.value != RPAREN: param_name = self.current_token.value self.eat_type(NAME) if self.current_token.value == COLON: self.eat_value(COLON) param_type = self.type_spec() else: param_type = self.variable(self.current_token) params[param_name] = param_type if self.current_token.value != RPAREN: if self.current_token.value == ASSIGN: if extern_func: error("Extern functions cannot have defaults") self.eat_value(ASSIGN) param_defaults[param_name] = self.expr() if self.current_token.value == ELLIPSIS: key, value = params.popitem() if not vararg: vararg = [] vararg.append(key) vararg.append(value) self.eat_value(ELLIPSIS) break if self.current_token.value != RPAREN: self.eat_value(COMMA) self.eat_value(RPAREN) if self.current_token.value != ARROW: return_type = Void() else: self.eat_value(ARROW) if self.current_token.value == VOID: return_type = Void() self.next_token() else: return_type = self.type_spec() if extern_func: return ExternFuncDecl(name.value, return_type, params, self.line_num, vararg) self.eat_type(NEWLINE) self.indent_level += 1 stmts = self.compound_statement() self.indent_level -= 1 if name == ANON: return AnonymousFunc(return_type, params, stmts, self.line_num, param_defaults, vararg) if op_func: if len(params) not in (1, 2): # TODO: move this to type checker error("Operators can either be unary or binary, and the number of parameters do not match") name.value = OPERATOR + '.' + name.value for param in params: type_name = str(type_map[str(params[param].value)]) if str(params[param].value) in type_map else str(params[param].value) name.value += '.' + type_name return FuncDecl(name.value, return_type, params, stmts, self.line_num, param_defaults, vararg)
def visit_assign(self, node): # TODO clean up this mess of a function collection_type = None field_assignment = None collection_assignment = None if isinstance(node.left, VarDecl): var_name = node.left.value.value value = self.infer_type(node.left.type) value.accessed = True if isinstance(node.right, Collection) or isinstance(node.right, Range): _, collection_type = self.visit(node.right) if value.name in (TUPLE, LIST) and (not isinstance(node.right, Range) and node.right.type != value.name): error('file={} line={}: Contradicting {}-{} declaration'.format(self.file_name, node.line_num, value.name, node.right.type)) elif hasattr(node.right, 'name') and isinstance(self.search_scopes(node.right.name), (EnumSymbol, ClassSymbol)): var_name = node.left.value value = self.search_scopes(node.right.name) value.accessed = True elif isinstance(node.right, Collection) or isinstance(node.right, Range): var_name = node.left.value value, collection_type = self.visit(node.right) elif isinstance(node.left, DotAccess): field_assignment = True var_name = self.visit(node.left) value = self.visit(node.right) elif isinstance(node.right, DotAccess): var_name = node.left.value value = self.search_scopes(node.right.obj) value.accessed = True value = self.infer_type(value) elif isinstance(node.left, CollectionAccess): collection_assignment = True var_name = node.left.collection.value value = self.visit(node.right) else: var_name = node.left.value value = self.visit(node.right) if isinstance(value, VarSymbol): value = value.type lookup_var = self.search_scopes(var_name) if not lookup_var: if collection_type: col_sym = CollectionSymbol(var_name, value, collection_type) col_sym.val_assigned = True self.define(var_name, col_sym) elif field_assignment: if var_name is value: return else: error('file={} line={} Type Error: What are you trying to do?!?! (fix this message)'.format(self.file_name, node.line_num)) elif isinstance(value, FuncSymbol): value.name = var_name self.define(var_name, value) elif hasattr(value, 'name') and value.name == FUNC: var = self.visit(node.right) if isinstance(var, FuncSymbol): self.define(var_name, var) elif isinstance(var, BuiltinTypeSymbol): self.define(var_name, var.func) else: val_info = self.search_scopes(node.right.value) func_sym = FuncSymbol(var_name, val_info.type.return_type, val_info.parameters, val_info.body, val_info.parameter_defaults) self.define(var_name, func_sym) else: var_sym = VarSymbol(var_name, value, node.left.read_only) var_sym.val_assigned = True self.define(var_name, var_sym) else: if isinstance(node.left, VarDecl): error('file={} line={}: Cannot redefine the type of a declared variable: {}'.format(self.file_name, node.line_num, var_name)) if collection_assignment: col = self.search_scopes(node.left.collection.value) if col.type.name == TUPLE: error('file={} line={}: Cannot change the elements of a tuple: {}'.format(self.file_name, node.line_num, var_name)) elif lookup_var.item_types == value: return if lookup_var.read_only: error('file={} line={}: Cannot change the value of a variable declared constant: {}'.format(self.file_name, node.line_num, var_name)) lookup_var.val_assigned = True if lookup_var.type in (self.search_scopes(DOUBLE), self.search_scopes(FLOAT)): if value in (self.search_scopes(INT), self.search_scopes(DOUBLE), self.search_scopes(FLOAT)): return if lookup_var.type is value: return if hasattr(value, 'type') and lookup_var.type is value.type: return if isinstance(value, TypeSymbol): value.accessed = True if value.type is self.search_scopes(FUNC): if value.type.return_type == lookup_var.type: return if hasattr(value, 'value'): if value.value == lookup_var.type.name: return error('file={} line={} Type Error: Not good things happening (fix this message)'.format(self.file_name, node.line_num))
def visit_switch(self, node): switch_var = self.visit(node.value) for case in node.cases: case_type = self.visit(case) if case_type != DEFAULT and case_type is not switch_var.type: error('file={} line={}: Types in switch do not match case'.format(self.file_name, node.line_num))
def eat_value(self, *token_value): if self.current_token.value in token_value: self.next_token() else: error('file={} line={} Syntax Error: expected {}'.format(self.file_name, self.line_num, ", ".join(token_value)))
def visit_methodcall(self, node): # TODO: Finish this, make Symbols for Classes and Methods # TODO: hardcoded error for tuple methods, thing of a better way to do it if isinstance(self.search_scopes(node.obj), CollectionSymbol) and self.search_scopes(node.obj).type.name == TUPLE: if node.name in ('set', 'append'): error('file={} line={}: Immutable Error: cannot use `{}` method'.format(self.file_name, node.line_num, node.name))