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++); } } }
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."); }
// 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."); } }
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); }
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); }
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."); } }
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); }
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); }
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(); }
private ParseError Error(Token token, string message) { Lox.ReportError(token, message); return(new ParseError()); }
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; } }
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); }
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); } }