Beispiel #1
0
    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))
Beispiel #2
0
    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)
Beispiel #3
0
    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)
Beispiel #4
0
    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
Beispiel #5
0
    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()
Beispiel #6
0
    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)
Beispiel #7
0
    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
Beispiel #8
0
    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)
Beispiel #9
0
    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)
Beispiel #10
0
    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)
Beispiel #11
0
    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, [])
Beispiel #12
0
    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")
Beispiel #13
0
    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")
Beispiel #14
0
    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
Beispiel #15
0
 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)
Beispiel #16
0
    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
Beispiel #17
0
    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)
Beispiel #18
0
    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))
Beispiel #19
0
    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))