Beispiel #1
0
 private void Declare(Token name)
 {
     if (scopes.Count > 0)
     {
         var scope = scopes[scopes.Count - 1];
         if (scope.ContainsKey(name.lexeme))
         {
             Lox.ReportError(name, "Variable with this name was already declared in the same scope.");
         }
         else
         {
             scope.Add(name.lexeme, false);
             int varIndex = GetNextIndex();
             Peek(varIndices).Add(name.lexeme, varIndex);
         }
     }
     else
     {
         if (globalVarIndices.ContainsKey(name.lexeme))
         {
             Lox.ReportError(name, "Variable with this name was already declared in global scope.");
         }
         else
         {
             globalVarIndices.Add(name.lexeme, globalIndex++);
         }
     }
 }
Beispiel #2
0
        Expr ParsePrimary()
        {
            if (match(TokenType.NUMBER, TokenType.STRING))
            {
                return(new Expr.Literal(previous().data));
            }

            if (match(TokenType.TRUE))
            {
                return(new Expr.Literal(true));
            }
            if (match(TokenType.FALSE))
            {
                return(new Expr.Literal(false));
            }
            if (match(TokenType.NIL))
            {
                return(new Expr.Literal(null));
            }
            if (match(TokenType.IDENTIFIER))
            {
                return(new Expr.Variable(previous()));
            }
            if (match(TokenType.FUN))
            {
                return(ParseLambda());
            }
            if (match(TokenType.THIS))
            {
                return(new Expr.This(previous()));
            }
            if (match(TokenType.SUPER))
            {
                Token keyword = previous();
                consume(TokenType.DOT, "Expect '.' after super keyword.");
                Token method = consume(TokenType.IDENTIFIER, "Expect superclass method name.");
                return(new Expr.Super(keyword, method));
            }

            if (match(TokenType.LEFT_PAREN))
            {
                Expr expr = ParseExpression();
                consume(TokenType.RIGHT_PAREN, "Expect ')' after expression.");
                return(new Expr.Grouping(expr));
            }

            // Challenge 3: Add error productions to handle each binary operator appearing without a left-hand operand
            if (match(TokenType.PLUS, TokenType.STAR, TokenType.SLASH))
            {
                Lox.ReportError(previous().line, previous().column, "Expect left-hand operand for binary operator.");
                synchronize();
            }

            throw Error(peek(), "Expect expression.");
        }
Beispiel #3
0
 // This is only called from outside the class. That way we know that when the for loop is done,
 // the entire program has been resolved and only then can we report any unused globals.
 public void Resolve(List <Stmt> stmts)
 {
     foreach (Stmt stmt in stmts)
     {
         Resolve(stmt);
     }
     foreach (var v in unusedGlobals)
     {
         Lox.ReportError(v.Value, "Variable '" + v.Key + "' not used.");
     }
 }
Beispiel #4
0
 public object visitSuperExpr(Expr.Super expr)
 {
     if (currentClass != ClassType.SUBCLASS)
     {
         Lox.ReportError(expr.keyword, "Can't use 'super' outside of a sub class.");
     }
     else
     {
         ResolveLocal(expr, expr.keyword.lexeme, AccessType.RHS);
     }
     return(null);
 }
Beispiel #5
0
        public object visitVariableExpr(Expr.Variable expr)
        {
            string name = expr.name.lexeme;

            if (scopes.Count > 0 && scopes[scopes.Count - 1].TryGetValue(name, out bool resolved) && !resolved)
            {
                Lox.ReportError(expr.name, "Can't read local variable in its own initializer.");
            }

            ResolveLocal(expr, name, AccessType.RHS);
            return(null);
        }
Beispiel #6
0
        private void EndScope()
        {
            Pop(scopes);
            Pop(varIndices);
            Pop(scopeIndices);

            var unusedVarsInScope = Pop(unusedVars);

            foreach (var v in unusedVarsInScope)
            {
                Lox.ReportError(v.Value, "Variable '" + v.Key + "' not used.");
            }
        }
Beispiel #7
0
 public object visitThisExpr(Expr.This expr)
 {
     if (currentClass == ClassType.NONE)
     {
         Lox.ReportError(expr.keyword, "Can't use 'this' outside of a class.");
     }
     else if (currentFunction == FunctionType.STATIC)
     {
         Lox.ReportError(expr.keyword, "Can't use 'this' in a static method.");
     }
     else
     {
         ResolveLocal(expr, expr.keyword.lexeme, AccessType.RHS);
     }
     return(null);
 }
Beispiel #8
0
 object Stmt.IVisitor <object> .visitReturnStmt(Stmt.Return stmt)
 {
     if (currentFunction == FunctionType.NONE)
     {
         Lox.ReportError(stmt.keyword, "Can only return from functions or methods.");
     }
     else if (stmt.value != null)
     {
         if (currentFunction == FunctionType.INITIALIZER)
         {
             Lox.ReportError(stmt.keyword, "Cannot return value from an initializer.");
         }
         else
         {
             Resolve(stmt.value);
         }
     }
     return(null);
 }
Beispiel #9
0
        private void LexString()
        {
            while (peek() != '"' && !isAtEnd())
            {
                advance();
            }

            if (isAtEnd())
            {
                Lox.ReportError(line, column, "String literal must end with double quotes.");
                return;
            }

            int    literalStart = start + 1;
            string literal      = this.source.Substring(literalStart, current - literalStart);

            AddToken(TokenType.STRING, literal);

            // closing quote
            advance();
        }
Beispiel #10
0
 private ParseError Error(Token token, string message)
 {
     Lox.ReportError(token, message);
     return(new ParseError());
 }
Beispiel #11
0
        private void ProcessTokenStartingWith(char currChar)
        {
            switch (currChar)
            {
            // Single-character tokens
            case '(':
                AddToken(TokenType.LEFT_PAREN);
                break;

            case ')':
                AddToken(TokenType.RIGHT_PAREN);
                break;

            case '{':
                AddToken(TokenType.LEFT_BRACE);
                break;

            case '}':
                AddToken(TokenType.RIGHT_BRACE);
                break;

            case ',':
                AddToken(TokenType.COMMA);
                break;

            case '.':
                AddToken(TokenType.DOT);
                break;

            case ';':
                AddToken(TokenType.SEMICOLON);
                break;

            case '?':
                AddToken(TokenType.QMARK);
                break;

            case ':':
                AddToken(TokenType.COLON);
                break;

            // 1-2 character tokens
            case '!':
                AddToken(match('=') ? TokenType.BANG_EQUAL : TokenType.BANG);
                break;

            case '=':
                AddToken(match('=') ? TokenType.EQUAL_EQUAL : TokenType.EQUAL);
                break;

            case '>':
                AddToken(match('=') ? TokenType.GREATER_EQUAL : TokenType.GREATER);
                break;

            case '<':
                AddToken(match('=') ? TokenType.LESS_EQUAL : TokenType.LESS);
                break;

            case '+':
                AddToken(match('=') ? TokenType.PLUS_EQUAL : TokenType.PLUS);
                break;

            case '-':
                AddToken(match('=') ? TokenType.MINUS_EQUAL : TokenType.MINUS);
                break;

            case '*':
                AddToken(match('=') ? TokenType.STAR_EQUAL : TokenType.STAR);
                break;

            case '/':
                if (match('='))
                {
                    AddToken(TokenType.SLASH_EQUAL);
                }
                else if (match('/'))
                {
                    while (!isAtEnd() && peek() != '\n')
                    {
                        advance();
                    }
                }
                else
                {
                    AddToken(TokenType.SLASH);
                }
                break;

            default:
                if (isDigit(currChar))
                {
                    LexNumber();
                }
                else if (currChar == '"')
                {
                    LexString();
                }
                else if (Char.IsWhiteSpace(currChar))
                {
                    if (currChar == '\n')
                    {
                        line++;
                        column = 1;
                    }
                }
                else if (isAlpha(currChar) || currChar == '_')
                {
                    LexKeywordOrIdentifier();
                }
                else
                {
                    Lox.ReportError(line, column, "There are no tokens that start with " + currChar + ".");
                }
                break;
            }
        }
Beispiel #12
0
        public object visitClassStmt(Stmt.Class stmt)
        {
            ClassType enclosingClass = currentClass;

            currentClass = ClassType.CLASS;

            Declare(stmt.name);
            Define(stmt.name);
            MarkUnused(stmt.name);

            if (stmt.superclass != null)
            {
                if (stmt.superclass.name.lexeme == stmt.name.lexeme)
                {
                    Lox.ReportError(stmt.superclass.name, "Class cannot inherit from itself.");
                }
                else
                {
                    currentClass = ClassType.SUBCLASS;
                    Resolve(stmt.superclass);
                    BeginScope();
                    Peek(scopes)["super"]     = true;
                    Peek(varIndices)["super"] = GetNextIndex();
                }
            }

            BeginScope();
            // "this" is the only variable we declare at class scope. That means that when ResolveLocal()
            // is called on "this" in the methods, the resolver will compute depth = 1, index = 0.
            // In LoxFunction.Bind(), we create a new function with a closure whose only variable is "this" (at index 0)
            // and since it's a closure, it has a depth of 1 from the function body.
            Peek(scopes)["this"]     = true;
            Peek(varIndices)["this"] = GetNextIndex(); // TODO maybe change to 0

            foreach (Stmt.Function method in stmt.staticMethods)
            {
                Declare(method.name);
                Define(method.name);
                ResolveFunction(method.parameters, method.body, FunctionType.STATIC);
            }

            foreach (Stmt.Function method in stmt.methods)
            {
                Declare(method.name);
                Define(method.name);
                FunctionType declaration = FunctionType.METHOD;
                if (method.name.lexeme == "init")
                {
                    declaration = FunctionType.INITIALIZER;
                }
                ResolveFunction(method.parameters, method.body, declaration);
            }

            EndScope();

            if (stmt.superclass != null)
            {
                EndScope();
            }

            currentClass = enclosingClass;
            return(null);
        }
Beispiel #13
0
        public object visitBinaryExpr(Expr.Binary expr)
        {
            object left  = Evaluate(expr.left);
            object right = Evaluate(expr.right);

            if (expr.op.type == TokenType.AND)
            {
                return(GetTruthValue(left) && GetTruthValue(right));
            }
            if (expr.op.type == TokenType.OR)
            {
                return(GetTruthValue(left) || GetTruthValue(right));
            }

            // same for strings, numbers and bools
            switch (expr.op.type)
            {
            case TokenType.EQUAL_EQUAL:
                return(IsEqual(left, right));

            case TokenType.BANG_EQUAL:
                return(!IsEqual(left, right));
            }

            // string specific
            if (left is string || right is string)
            {
                if (expr.op.type != TokenType.PLUS)
                {
                    throw new RuntimeError(expr.op, expr.op.type.ToString() + " not a valid operator on strings.");
                }
                return(Stringify(left) + Stringify(right));
            }

            // number specific
            CheckNumberOperands(expr.op, left, right);

            switch (expr.op.type)
            {
            case TokenType.PLUS:
                return((double)left + (double)right);

            case TokenType.MINUS:
                return((double)left - (double)right);

            case TokenType.STAR:
                return((double)left * (double)right);

            case TokenType.SLASH:
                double r = (double)right;
                if (r == 0)
                {
                    throw new RuntimeError(expr.op, "Divide by zero error.");
                }
                return((double)left / r);

            case TokenType.GREATER:
                return((double)left > (double)right);

            case TokenType.GREATER_EQUAL:
                return((double)left >= (double)right);

            case TokenType.LESS:
                return((double)left < (double)right);

            case TokenType.LESS_EQUAL:
                return((double)left <= (double)right);

            default:
                Lox.ReportError(expr.op, expr.op.type.ToString() + " not a valid operator on doubles.");
                return(right);
            }
        }