def get(self, token): name = token.lexeme if name in self.values: return self.values[name] log_error(self.filename, token.line, ErrorStep.RUNTIME, "Undefined variable \"{}\"".format(name))
def visit_while_statement(self, statement): line = statement.condition.operator.line log_error(self.filename, line, ErrorStep.RUNTIME, "While loops aren't supported yet") condition = statement.condition body = statement.body while self.is_truthy(self.evaluate(condition)): self.execute(body)
def check_for_indirect_recursion(self, fun, calls, virtual_stack): if fun in virtual_stack: # Add so it's displayed on the error virtual_stack.append(fun) log_error(self.filename, -1, ErrorStep.RUNTIME, "Found indirect recursion. Recursion isn't allowed. Stack: {}".format(virtual_stack)) virtual_stack.append(fun) for fun_call in calls[fun]: self.check_for_indirect_recursion(fun_call, calls, virtual_stack)
def visit_unary_expression(self, expression): value = self.evaluate(expression.right) token_type = expression.operator.token_type if token_type == TokenType.SUBSTRACT: return -value if token_type == TokenType.NOT: return not self.is_truthy(value) # Shouldn't reach this point log_error(self.filename, expression.operator.line, ErrorStep.RUNTIME, "Error while interpreting the file") return None
def translate_spaces_to_tabs(self): indentations_np = np.array(self.indentations) indentations_np_no_zeros = np.where(indentations_np == 0, sys.maxsize, indentations_np) minimum = np.min(indentations_np_no_zeros) modulo = indentations_np % minimum for i in range(len(modulo)): if modulo[i] != 0: log_error(self.filename, i, ErrorStep.SCANNING, "Indentation error") tabs = indentations_np // minimum self.indentations = tabs.tolist()
def interpret(self, statements): self.check_unsupported_actions(statements) for statement in statements: self.execute(statement) # Ugly, but needed virtual_main_token = Token(TokenType.IDENTIFIER, -1, "main") main_func = self.environment.get(virtual_main_token) if main_func.arity() != 0: log_error(self.filename, main_func.declaration.name.line, ErrorStep.RUNTIME, "\"main\" must have no parameters") exit_code = main_func.call(self, []) print("END OF EXECUTION. Value returned from \"main\":", exit_code)
def assignment(self): expression = self.or_expr() if self.match([TokenType.ASSIGMENT]): assigment_token = self.previous() value = self.assignment() if isinstance(value, expr.Variable): token = value.token return expr.Assignment(token, value) log_error(self.filename, assigment_token.line, ErrorStep.PARSING, "Invalid assigment") return expression
def finish_call(self, callee): arguments = [] if not self.check(TokenType.RIGHT_PARENTHESIS): arguments.append(self.expression()) while self.match([TokenType.COMMA]): if len(arguments) >= Parser.MAX_ARGUMENTS: log_error( self.filename, -1, ErrorStep.PARSING, "Can't have more than {} arguments".format( Parser.MAX_ARGUMENTS)) arguments.append(self.expression()) line = self.consume(TokenType.RIGHT_PARENTHESIS).line return expr.Call(callee, arguments, line)
def scan_identifier(self): while self.is_alphanumeric(self.peek()): self.advance() identifier = self.source[self.start:self.current] if identifier in Scanner.unsupported_keywords: log_error(self.filename, self.line, ErrorStep.SCANNING, "Found unsupported keyword \"{}\"".format(identifier)) token_type = TokenType.IDENTIFIER if identifier not in Scanner.keywords else Scanner.keywords[ identifier] # Ignore "from" and "import" statements if token_type in [TokenType.FROM, TokenType.IMPORT]: while self.peek() != "\n" and not self.is_at_end(): self.advance() return self.add_token(token_type)
def function(self): name = self.consume(TokenType.IDENTIFIER) self.consume(TokenType.LEFT_PARENTHESIS) parameters = [] if not self.check(TokenType.RIGHT_PARENTHESIS): parameters.append(self.consume(TokenType.IDENTIFIER)) while self.match([TokenType.COMMA]): if len(parameters) >= Parser.MAX_ARGUMENTS: log_error( self.filename, name.line, ErrorStep.PARSING, "Can't have more than {} parameters".format( Parser.MAX_ARGUMENTS)) parameters.append(self.consume(TokenType.IDENTIFIER)) self.consume(TokenType.RIGHT_PARENTHESIS) self.consume(TokenType.LEFT_CURLY_BRACE) body = self.block() return stmt.Function(name, parameters, body)
def check_for_recursion(self, statements): calls = {} for statement in statements: if not isinstance(statement, stmt.Function): continue func_name = statement.name.lexeme functions_called_within_func = set() self.find_functions_called_in_statements( statement.body, functions_called_within_func) if func_name in functions_called_within_func: log_error(self.filename, statement.name.line, ErrorStep.RUNTIME, "Found direct recursion") calls[func_name] = functions_called_within_func # TODO: Check for indirect recursion for fun in calls.keys(): self.check_for_indirect_recursion(fun, calls, [])
def declaration(self): try: if self.match([TokenType.IDENTIFIER]): # Identifiers can be both assignment or function call # If it's assignment if self.check(TokenType.ASSIGMENT): return self.var_declaration() # If it's function call, undo advance produced by self.match. Continue to self.statement below self.rollback() elif self.match([TokenType.DEF]): return self.function() elif self.match([TokenType.RETURN]): return self.return_statement() return self.statement() except ParsingException: self.synchronize() log_error(self.filename, self.previous().line, ErrorStep.PARSING, "Syntax error")
def count_number_of_returns(self, statement): if isinstance(statement, stmt.Block): returns = 0 for stmt_in_block in statement.statements: returns += self.count_number_of_returns(stmt_in_block) return returns if isinstance(statement, stmt.Expression): return 0 if isinstance(statement, stmt.Function): line = statement.name.line log_error(self.filename, line, ErrorStep.RUNTIME, "Function \"{}\": Local functions aren't supported".format(statement.name.lexeme)) if isinstance(statement, stmt.If): then_returns = self.count_number_of_returns(statement.then_branch) else_returns = 0 if statement.else_branch is None else self.count_number_of_returns( statement.else_branch) return then_returns + else_returns if isinstance(statement, stmt.Return): return 1 if isinstance(statement, stmt.Variable): return 0 if isinstance(statement, stmt.While): log_error(self.filename, -1, ErrorStep.RUNTIME, "While statements aren't supported") log_error(self.filename, -1, ErrorStep.RUNTIME, "Shouldn't reach this point")
def visit_binary_expression(self, expression): left_value = self.evaluate(expression.left) right_value = self.evaluate(expression.right) token_type = expression.operator.token_type if token_type == TokenType.ADD: return left_value + right_value if token_type == TokenType.SUBSTRACT: return left_value - right_value if token_type == TokenType.PRODUCT: return left_value * right_value if token_type == TokenType.DIVISION: return left_value / right_value if token_type == TokenType.FLOOR_DIVISION: return left_value // right_value if token_type == TokenType.MODULUS: return left_value % right_value if token_type == TokenType.POWER: return left_value ** right_value if token_type == TokenType.EQUAL: return left_value == right_value if token_type == TokenType.NOT_EQUAL: return left_value != right_value if token_type == TokenType.GREATER_THAN: return left_value > right_value if token_type == TokenType.GREATER_OR_EQUAL: return left_value >= right_value if token_type == TokenType.LESS_THAN: return left_value < right_value if token_type == TokenType.LESS_OR_EQUAL: return left_value <= right_value # Shouldn't reach this point log_error(self.filename, expression.token.line, ErrorStep.RUNTIME, "Error while interpreting the file") return None
def find_functions_called_in_statement(self, statement, set_of_funcs): if isinstance(statement, stmt.Block): self.find_functions_called_in_statements( statement.statements, set_of_funcs) elif isinstance(statement, stmt.Expression): self.find_functions_called_in_expression( statement.expression, set_of_funcs) elif isinstance(statement, stmt.Function): log_error(self.filename, statement.name.line, ErrorStep.RUNTIME, "Shouldn't reach this point") elif isinstance(statement, stmt.If): self.find_functions_called_in_expression( statement.condition, set_of_funcs) self.find_functions_called_in_statements( statement.then_branch.statements, set_of_funcs) if statement.else_branch is not None: self.find_functions_called_in_statements( statement.else_branch.statements, set_of_funcs) elif isinstance(statement, stmt.Return): self.find_functions_called_in_expression( statement.value, set_of_funcs) elif isinstance(statement, stmt.Variable): self.find_functions_called_in_expression( statement.initializer, set_of_funcs)
def define(self, name, value): is_function = isinstance(value, Callable) if self.indentation_level == 0 and not is_function: log_error( self.filename, name.line, ErrorStep.RUNTIME, "Error with variable \"{}\". Global variables aren't supported" .format(name)) if self.indentation_level != 0 and is_function: log_error(self.filename, name.line, ErrorStep.RUNTIME, "Local functions aren't supported") # Functions cannot be overriden if is_function and name.lexeme in self.values: log_error( self.filename, name.line, ErrorStep.RUNTIME, "Function \"{}\" was already defined".format(name.lexeme)) self.values[name.lexeme] = value
def visit_call_expression(self, expression): line = expression.line function = self.evaluate(expression.callee) if not isinstance(function, Callable): log_error(self.filename, line, ErrorStep.RUNTIME, "Trying to call a not callable instance") if self.environment.indentation_level == 0: log_error(self.filename, line, ErrorStep.RUNTIME, "Trying to call function \"{}\" from global scope".format(function.declaration.name.lexeme)) arguments = [] for arg in expression.arguments: arguments.append(self.evaluate(arg)) if len(arguments) != function.arity(): log_error(self.filename, line, ErrorStep.RUNTIME, "Expected {} arguments but got {} instead".format(function.arity(), len(arguments))) return function.call(self, arguments)
def check_for_single_return(self, statements): for statement in statements: if not isinstance(statement, stmt.Function): continue body = statement.body number_of_returns = 0 for func_stmt in body: number_of_returns += self.count_number_of_returns(func_stmt) name = statement.name.lexeme line = statement.name.line if number_of_returns == 0: log_error(self.filename, line, ErrorStep.RUNTIME, "Function \"{}\" returns no values".format(name)) elif number_of_returns > 1: log_error(self.filename, line, ErrorStep.RUNTIME, "Function \"{}\" has more than one return".format(name)) last_statement = body[-1] if not isinstance(last_statement, stmt.Return): log_error(self.filename, line, ErrorStep.RUNTIME, "Function \"{}\": The last statement must be a return statement".format(name))
def scan_token(self): c = self.advance() if c in " \t\r": return if c == "\n": self.check_for_semicolon() self.line += 1 self.found_character = False return # If it's the first character found in the line, check for potential "}"s if not self.found_character: self.check_for_right_curly_braces() self.found_character = True if c == "(": self.add_token(TokenType.LEFT_PARENTHESIS) elif c == ")": self.add_token(TokenType.RIGHT_PARENTHESIS) elif c == ".": self.add_token(TokenType.DOT) elif c == ",": self.add_token(TokenType.COMMA) elif c == ":": self.add_left_curly_brace() elif c == "+": self.add_token(TokenType.ADD) elif c == "-": self.add_token(TokenType.SUBSTRACT) elif c == "%": self.add_token(TokenType.MODULUS) elif c == "*": next_is_asterisk = self.next_matches("*") token_type = TokenType.POWER if next_is_asterisk else TokenType.PRODUCT self.add_token(token_type) elif c == "/": next_is_slash = self.next_matches("/") token_type = TokenType.FLOOR_DIVISION if next_is_slash else TokenType.DIVISION self.add_token(token_type) elif c == "!": next_is_equal = self.next_matches("=") if not next_is_equal: log_error(self.filename, self.line, ErrorStep.SCANNING, "! character doesn't precede =") self.add_token(TokenType.NOT_EQUAL) elif c == "=": next_is_equal = self.next_matches("=") token_type = TokenType.EQUAL if next_is_equal else TokenType.ASSIGMENT self.add_token(token_type) elif c == "<": next_is_equal = self.next_matches("=") token_type = TokenType.LESS_OR_EQUAL if next_is_equal else TokenType.LESS_THAN self.add_token(token_type) elif c == ">": next_is_equal = self.next_matches("=") token_type = TokenType.GREATER_OR_EQUAL if next_is_equal else TokenType.GREATER_THAN self.add_token(token_type) elif c == "#": while self.peek() != "\n" and not self.is_at_end(): self.advance() elif c.isnumeric(): self.scan_number() elif self.is_alphabetic(c): self.scan_identifier() else: log_error(self.filename, self.line, ErrorStep.SCANNING, "Unexpected character \"{}\"".format(c))