Пример #1
0
    def scan_token(self) -> None:
        """ Scan source and append tokens """

        char: str = self.get_next()

        if char in "!=<>" and self.match("="):
            char = char + "="
        elif char == "/" and self.match("/"):
            while self.peek() != '\n' and not self.is_end():
                self.get_next()
            return
        elif char == "\n":
            self.line += 1
        if char in Scanner.lex:
            tok = Scanner.lex.get(char, None)
            if tok is not None:
                self.add_token(tok, None, self.line)
        elif char == '"':
            self.get_string()
        elif char.isdigit():
            self.get_number()
        elif self.is_valid_first(char):
            self.get_identifier()

        elif not char.isspace():
            raise_error(LoxError, self.line, f'Unexpected character: {char}')
Пример #2
0
 def visit_class_stmt(self, stmt: loxStmtAST.Class) -> None:
     enclosing_class = self.current_class
     self.current_class = Resolver.ClassType.CLASS
     self.declare(stmt.name)
     self.define(stmt.name)
     if stmt.superclass is not None and stmt.name.lexeme == stmt.superclass.name.lexeme:
         raise_error(LoxError, stmt.superclass.name,
                     "A class cannot inherit from itself.")
     if stmt.superclass is not None:
         self.resolve_expr(stmt.superclass)
     if stmt.superclass is not None:
         self.begin_scope()
         self.scopes.peek()["super"] = True
     self.begin_scope()
     self.scopes.peek()["this"] = True
     for method in stmt.methods:
         if method.name.lexeme == "init":
             declaration: Resolver.FunctionType = Resolver.FunctionType.INITIALIZER
         else:
             declaration = Resolver.FunctionType.METHOD
         self.resolve_function(method, declaration)
     self.end_scope()
     if stmt.superclass is not None:
         self.end_scope()
     self.current_class = enclosing_class
     return None
Пример #3
0
 def consume(self, tok_type: loxtoken.TokenType,
             message: str) -> Optional[loxtoken.Token]:
     """ If token of correct type advance otherwise raise error with message """
     if self.check(tok_type):
         return self.advance()
     raise_error(LoxParseError, self.peek(), message)
     return None
Пример #4
0
 def visit_variable_expr(self, expr: loxExprAST.Variable) -> None:
     if not self.scopes.is_empty() and self.scopes.peek().get(
             expr.name.lexeme) is False:
         raise_error(LoxError, expr.name,
                     "Cannot read local variable in its own initializer.")
     self.resolve_local(expr, expr.name)
     return None
Пример #5
0
    def function(self, kind: str) -> loxStmtAST.Function:
        """ funDecl → "fun" function ; 
            function   → IDENTIFIER "(" parameters? ")" block ;
            parameters → IDENTIFIER ( "," IDENTIFIER )* ; """

        name: loxtoken.Token = self.consume(Parser.tokentypes.IDENTIFIER,
                                            f'Expect {kind} name.')
        self.consume(Parser.tokentypes.LEFT_PAREN,
                     f"Expect '(' after {kind} name.")
        parameters: List[loxtoken.Token] = []
        if not self.check(Parser.tokentypes.RIGHT_PAREN):
            while True:
                if len(parameters) >= 255:
                    raise_error(LoxParseError, self.peek(),
                                "Cannot have more than 255 parameters.")
                parameters.append(
                    self.consume(Parser.tokentypes.IDENTIFIER,
                                 "Expect parameter name."))
                if not self.match(Parser.tokentypes.COMMA):
                    break
        self.consume(Parser.tokentypes.RIGHT_PAREN,
                     "Expect ')' after parameters.")
        self.consume(
            Parser.tokentypes.LEFT_BRACE,
            f'Expect {{ before {kind} body.')  # {{ to escape { in string
        body: List[loxStmtAST.Stmt] = self.block_stmt()
        return loxStmtAST.Function(name, parameters, body)
Пример #6
0
 def visit_set_expr(self, expr: loxExprAST.Set):
     set_object = self.evaluate(expr.set_object)
     if not isinstance(set_object, loxclass.LoxInstance):
         raise_error(LoxRuntimeError, expr.name,
                     "Only instances have fields.")
     value = self.evaluate(expr.value)
     set_object.set(expr.name, value)
     return value
Пример #7
0
 def get(self, name: loxtoken.Token):
     """ Get property """
     if name.lexeme in self.fields:
         return self.fields[name.lexeme]
     method = self.klass.find_method(name.lexeme)
     if method is not None:
         return method.bind(self)
     raise_error(LoxRuntimeError, name, "Undefined property '" + name.lexeme + "'.")
Пример #8
0
 def declare(self, name: Token):
     """ Declare variable. Set value to False """
     if self.scopes.is_empty():
         return None
     if name.lexeme in self.scopes.peek():
         raise_error(
             LoxError, name,
             "Variable with this name already declared in this scope.")
     self.scopes.peek()[name.lexeme] = False
Пример #9
0
 def get(self, name: Token) -> object:
     """ get value of token from nearest environment """
     if name.lexeme in self.values:
         return self.values[name.lexeme]
     if self.enclosing is not None:
         return self.enclosing.get(name)
     raise_error(LoxRuntimeError, name,
                 f'Undefined variable {name.lexeme}.')
     return None
Пример #10
0
 def visit_return_stmt(self, stmt: loxStmtAST.Return) -> None:
     if self.current_function == Resolver.FunctionType.NONE:
         raise_error(LoxError, stmt.keyword,
                     "Cannot return from top-level code.")
     if stmt.value is not None:
         if self.current_function == Resolver.FunctionType.INITIALIZER:
             raise_error(LoxError, stmt.keyword,
                         "Cannot return a value from an initializer.")
         self.resolve_expr(stmt.value)
     return None
Пример #11
0
 def visit_super_expr(self, expr: loxExprAST.Super):
     distance = self.locals[expr]
     superclass = self.environment.get_at(distance, "super")
     # "this" is always one level nearer than "super"'s environment.
     get_object = self.environment.get_at(distance - 1, "this")
     method = superclass.find_method(expr.method.lexeme)
     if method is None:
         raise_error(LoxRuntimeError, expr.method,
                     "Undefined property '" + expr.method.lexeme + "'.")
     return method.bind(get_object)
Пример #12
0
 def assign(self, name: Token, value: object) -> None:
     """ set value of name in nearest environment where it exits 
         otherwise return error """
     if name.lexeme in self.values:
         self.values[name.lexeme] = value
         return
     if self.enclosing is not None:
         self.enclosing.assign(name, value)
         return
     raise_error(LoxRuntimeError, name,
                 f'Undefined variable {name.lexeme}.')
Пример #13
0
 def check_number_operands(operator: loxtoken.Token,
                           op1: Any,
                           op2: Any = None) -> bool:
     """ Check if one or two operands are numeric (float) """
     if op2 is None:
         if isinstance(op1, float):
             return True
         raise_error(LoxRuntimeError, operator, "Operand must be a number.")
     else:
         if isinstance(op1, float) and isinstance(op2, float):
             return True
         raise_error(LoxRuntimeError, operator,
                     "Both operands must be a number.")
     return False
Пример #14
0
    def get_string(self) -> None:
        """ Process literal string and add token 
            Increase line no if required """

        while self.peek() != '"' and not self.is_end():
            char: str = self.get_next()
            if char == "\n":
                self.line += 1
        if self.is_end():
            raise_error(LoxError, self.line, "Unterminated string.")
            return
        value: str = self.source[self.start + 1:self.current]
        self.add_token(Scanner.token_types.STRING, value, self.line)
        self.get_next()
Пример #15
0
    def finish_call(self, callee) -> loxExprAST.Expr:
        """ arguments → expression ( "," expression )* """

        arguments: List[loxExprAST.Expr] = []
        if not self.check(Parser.tokentypes.RIGHT_PAREN):
            while True:
                if len(arguments) >= 255:
                    raise_error(LoxParseError, self.peek(),
                                "Cannot have more than 255 arguments.")
                arguments.append(self.expression())
                if not self.match(Parser.tokentypes.COMMA):
                    break
        paren: loxtoken.Token = self.consume(Parser.tokentypes.RIGHT_PAREN,
                                             "Expect ')' after arguments.")
        return loxExprAST.Call(callee, paren, arguments)
Пример #16
0
    def assignment(self) -> loxExprAST.Expr:
        """ assignment → ( call "." )? IDENTIFIER "=" assignment | logic_or; """

        expr: loxExprAST.Expr = self.op_or()
        if self.match(Parser.tokentypes.EQUAL):
            equals: loxtoken.Token = self.previous()
            value: loxExprAST.Expr = self.assignment()
            if isinstance(expr, loxExprAST.Variable):
                name: loxtoken.Token = expr.name
                return loxExprAST.Assign(name, value)
            elif isinstance(expr, loxExprAST.Get):
                # get = expr
                return loxExprAST.Set(expr.get_object, expr.name, value)
            else:
                raise_error(LoxParseError, equals,
                            "Invalid assignment target.")
        return expr
Пример #17
0
    def visit_call_expr(self, expr: loxExprAST.Call) -> object:
        callee: loxcallable.LoxFunction = self.evaluate(expr.callee)
        arguments: List[object] = []
        for arg in expr.arguments:
            arguments.append(self.evaluate(arg))
        if not isinstance(
                callee, loxcallable.LoxCallable
        ) and loxcallable.LoxCallable not in callee.__bases__:
            raise_error(LoxRuntimeError, expr.paren,
                        "Can only call functions and classes.")

        # funct: loxcallable.LoxFunction = callee
        if len(arguments) != callee.arity():
            raise_error(
                LoxRuntimeError, expr.paren,
                "Expected " + str(callee.arity()) + " arguments but got " +
                str(len(arguments)) + ".")
        return callee.call(self, arguments)
Пример #18
0
    def visit_binary_expr(self, expr) -> Optional[Union[float, str, bool]]:
        left: Union[float, str, bool] = self.evaluate(expr.left)
        right: Union[float, str, bool] = self.evaluate(expr.right)

        if expr.operator.tok_type == Interpreter.tokentypes.MINUS:
            if self.check_number_operands(expr.operator, left, right):
                return float(left) - float(right)
        elif expr.operator.tok_type == Interpreter.tokentypes.SLASH:
            if self.check_number_operands(expr.operator, left, right):
                return float(left) / float(right)
        elif expr.operator.tok_type == Interpreter.tokentypes.STAR:
            if self.check_number_operands(expr.operator, left, right):
                return float(left) * float(right)
        elif expr.operator.tok_type == Interpreter.tokentypes.PLUS:
            if isinstance(left, float) and isinstance(right, float):
                return left + right
            elif isinstance(left, str) and isinstance(right, str):
                return left + right
            else:
                raise_error(LoxRuntimeError, expr.operator,
                            "Operands must both be a number or a string.")

        elif expr.operator.tok_type == Interpreter.tokentypes.GREATER:
            if self.check_number_operands(expr.operator, left, right):
                return float(left) > float(right)
        elif expr.operator.tok_type == Interpreter.tokentypes.GREATER_EQUAL:
            if self.check_number_operands(expr.operator, left, right):
                return float(left) >= float(right)
        elif expr.operator.tok_type == Interpreter.tokentypes.LESS:
            if self.check_number_operands(expr.operator, left, right):
                return float(left) < float(right)
        elif expr.operator.tok_type == Interpreter.tokentypes.LESS_EQUAL:
            if self.check_number_operands(expr.operator, left, right):
                return float(left) <= float(right)
        elif expr.operator.tok_type == Interpreter.tokentypes.EQUAL_EQUAL:
            return self.is_equal(left, right)
        elif expr.operator.tok_type == Interpreter.tokentypes.BANG_EQUAL:
            return not self.is_equal(left, right)

        return None
Пример #19
0
 def visit_class_stmt(self, stmt: loxStmtAST.Class) -> None:
     superclass: Optional[loxExprAST.Variable] = None
     if stmt.superclass is not None:
         superclass = self.evaluate(stmt.superclass)
         if not isinstance(superclass, loxclass.LoxClass):
             raise_error(LoxRuntimeError, stmt.superclass.name,
                         "Superclass must be a class.")
     self.environment.define(stmt.name.lexeme, None)
     if stmt.superclass is not None:
         self.environment = loxenvironment.Environment(self.environment)
         self.environment.define("super", superclass)
     methods: Dict[str, loxcallable.LoxFunction] = dict()
     for method in stmt.methods:
         function = loxcallable.LoxFunction(method, self.environment,
                                            method.name.lexeme == "init")
         methods[method.name.lexeme] = function
     klass: loxclass.LoxClass = loxclass.LoxClass(stmt.name.lexeme,
                                                  superclass, methods)
     if stmt.superclass is not None:
         self.environment = self.environment.enclosing
     self.environment.assign(stmt.name, klass)
     return None
Пример #20
0
    def primary(self) -> Optional[loxExprAST.Expr]:
        """ primary    → "true" | "false" | "nil" | "this"
                         | NUMBER | STRING | IDENTIFIER | "(" expression ")"
                         | "super" "." IDENTIFIER ;

            NUMBER     → DIGIT+ ( "." DIGIT+ )? ;
            STRING     → '"' <any char except '"'>* '"' ;
            IDENTIFIER → ALPHA ( ALPHA | DIGIT )* ;
            ALPHA      → 'a' ... 'z' | 'A' ... 'Z' | '_' ;
            DIGIT      → '0' ... '9' ;"""

        if self.match(Parser.tokentypes.SUPER):
            keyword: loxtoken.Token = self.previous()
            self.consume(Parser.tokentypes.DOT, "Expect '.' after 'super'.")
            method: loxtoken.Token = self.consume(
                Parser.tokentypes.IDENTIFIER, "Expect superclass method name.")
            return loxExprAST.Super(keyword, method)
        if self.match(Parser.tokentypes.THIS):
            return loxExprAST.This(self.previous())
        if self.match(Parser.tokentypes.IDENTIFIER):
            return loxExprAST.Variable(self.previous())
        if self.match(Parser.tokentypes.FALSE):
            return loxExprAST.Literal(False)
        if self.match(Parser.tokentypes.TRUE):
            return loxExprAST.Literal(True)
        if self.match(Parser.tokentypes.NIL):
            return loxExprAST.Literal(None)

        if self.match(Parser.tokentypes.NUMBER, Parser.tokentypes.STRING):
            return loxExprAST.Literal(self.previous().literal)
        if self.match(Parser.tokentypes.LEFT_PAREN):
            expr: loxExprAST.Expr = self.expression()
            self.consume(Parser.tokentypes.RIGHT_PAREN,
                         "Expect ')' after expression")
            return loxExprAST.Grouping(expr)

        raise_error(LoxParseError, self.peek(), "Expect expression.")
        return None
Пример #21
0
 def visit_get_expr(self, expr: loxExprAST.Get):
     get_object = self.evaluate(expr.get_object)
     if isinstance(get_object, loxclass.LoxInstance):
         return get_object.get(expr.name)
     raise_error(LoxRuntimeError, expr.name,
                 "Only instances have properties.")
Пример #22
0
 def visit_this_expr(self, expr: loxExprAST.This) -> None:
     if self.current_class == Resolver.ClassType.NONE:
         raise_error(LoxError, expr.keyword,
                     "Cannot use 'this' outside of a class.")
     self.resolve_local(expr, expr.keyword)
     return None