Add assigments + scoping

This commit is contained in:
ctsk
2022-09-09 12:27:16 +02:00
parent 78cb1521df
commit 2d1bad5701
7 changed files with 202 additions and 38 deletions

View File

@@ -11,34 +11,47 @@ public class AstPrinter {
private record Polish(boolean parentheses, boolean reverse) implements Expr.Visitor<String> {
@Override
public String visitBinaryExpr(Expr.Binary binary) {
var left = binary.left().accept(this);
var op = binary.operator().lexeme();
var right = binary.right().accept(this);
public String visitAssignExpr(Expr.Assign expr) {
var target = expr.name().lexeme();
var value = expr.value().accept(this);
return reverse ? wrap(target, value, "=") : wrap("=", target, value);
}
@Override
public String visitBinaryExpr(Expr.Binary expr) {
var left = expr.left().accept(this);
var op = expr.operator().lexeme();
var right = expr.right().accept(this);
return reverse ? wrap(left, right, op) : wrap(op, left, right);
}
@Override
public String visitGroupingExpr(Expr.Grouping grouping) {
var inner = grouping.expression().accept(this);
public String visitGroupingExpr(Expr.Grouping expr) {
var inner = expr.expression().accept(this);
return wrap(inner);
}
@Override
public String visitLiteralExpr(Expr.Literal literal) {
if (literal.value() == null) return "nil";
return literal.value().toString();
public String visitLiteralExpr(Expr.Literal expr) {
if (expr.value() == null) return "nil";
return expr.value().toString();
}
@Override
public String visitUnaryExpr(Expr.Unary unary) {
var op = unary.operator().lexeme();
var right = unary.right().accept(this);
public String visitUnaryExpr(Expr.Unary expr) {
var op = expr.operator().lexeme();
var right = expr.right().accept(this);
return reverse ? wrap(right, op) : wrap(op, right);
}
@Override
public String visitVariableExpr(Expr.Variable expr) {
return expr.name().lexeme();
}
public String wrap(String... inner) {
var inners = String.join(" ", inner);

View File

@@ -0,0 +1,43 @@
package xyz.ctsk.lox;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class Environment {
private final Environment enclosing;
private final Map<String, Object> values = new HashMap<>();
Environment() {
enclosing = null;
}
Environment(Environment enclosing) {
this.enclosing = enclosing;
}
void define(String name, Object value) {
values.put(name, value);
}
void assign(Token name, Object value) {
if (values.containsKey(name.lexeme())) {
values.put(name.lexeme(), value);
} else if (enclosing != null) {
enclosing.assign(name, value);
} else {
throw new RuntimeError(name, "Undefined variable '%s'.".formatted(name.lexeme()));
}
}
Object get(Token name) {
if (values.containsKey(name.lexeme())) {
return values.get(name.lexeme());
} else if (enclosing != null) {
return enclosing.get(name);
} else {
var message = "Undefined variable '%s'.".formatted(name.lexeme());
throw new RuntimeError(name, message);
}
}
}

View File

@@ -3,6 +3,7 @@ package xyz.ctsk.lox;
import java.util.List;
public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
private Environment environment = new Environment();
void interpret(List<Stmt> statements) {
try {
@@ -16,6 +17,17 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
stmt.accept(this);
}
private void executeBlock(List<Stmt> statements, Environment environment) {
Environment previous = this.environment;
try {
this.environment = environment;
statements.forEach(this::execute);
} finally {
this.environment = previous;
}
}
private Object evaluate(Expr expr) {
return expr.accept(this);
}
@@ -31,6 +43,12 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
return text;
}
@Override
public Void visitBlockStmt(Stmt.Block stmt) {
executeBlock(stmt.statements(), new Environment(environment));
return null;
}
@Override
public Void visitExpressionStmt(Stmt.Expression stmt) {
evaluate(stmt.expression());
@@ -45,16 +63,30 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
}
@Override
public Object visitBinaryExpr(Expr.Binary binary) {
var left = evaluate(binary.left());
var right = evaluate(binary.right());
public Void visitVarStmt(Stmt.Var stmt) {
Object value = stmt.initializer() == null ? null : evaluate(stmt.initializer());
environment.define(stmt.name().lexeme(), value);
return null;
}
switch (binary.operator().type()) {
@Override
public Object visitAssignExpr(Expr.Assign expr) {
var value = evaluate(expr.value());
environment.assign(expr.name(), value);
return value;
}
@Override
public Object visitBinaryExpr(Expr.Binary expr) {
var left = evaluate(expr.left());
var right = evaluate(expr.right());
switch (expr.operator().type()) {
case MINUS, SLASH, STAR, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL ->
checkNumberOperands(binary.operator(), left, right);
checkNumberOperands(expr.operator(), left, right);
}
return switch (binary.operator().type()) {
return switch (expr.operator().type()) {
case MINUS -> (double) left - (double) right;
case PLUS -> {
if (left instanceof Double leftD && right instanceof Double rightD) {
@@ -65,7 +97,7 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
yield leftStr + rightStr;
}
throw new RuntimeError(binary.operator(), "Operands must be two numbers or two strings.");
throw new RuntimeError(expr.operator(), "Operands must be two numbers or two strings.");
}
case SLASH -> (double) left / (double) right;
case STAR -> (double) left * (double) right;
@@ -80,26 +112,31 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
}
@Override
public Object visitGroupingExpr(Expr.Grouping grouping) {
return evaluate(grouping.expression());
public Object visitGroupingExpr(Expr.Grouping expr) {
return evaluate(expr.expression());
}
@Override
public Object visitLiteralExpr(Expr.Literal literal) {
return literal.value();
public Object visitLiteralExpr(Expr.Literal expr) {
return expr.value();
}
@Override
public Object visitUnaryExpr(Expr.Unary unary) {
var right = evaluate(unary.right());
public Object visitUnaryExpr(Expr.Unary expr) {
var right = evaluate(expr.right());
return switch(unary.operator().type()) {
case MINUS -> -asNumber(unary.operator(), right);
return switch(expr.operator().type()) {
case MINUS -> -asNumber(expr.operator(), right);
case BANG -> !isTruthy(right);
default -> null;
};
}
@Override
public Object visitVariableExpr(Expr.Variable expr) {
return environment.get(expr.name());
}
private boolean isTruthy(Object object) {
if (object == null) return false;

View File

@@ -53,7 +53,7 @@ public class Lox {
var reader = new BufferedReader(input);
while (true) {
System.out.println("> ");
System.out.print("> ");
String line = reader.readLine();
if (line == null) break;
run(line);

View File

@@ -1,5 +1,6 @@
package xyz.ctsk.lox;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
@@ -9,12 +10,20 @@ import static xyz.ctsk.lox.TokenType.*;
*
* A recursive descent parser for the following grammar:
* <p>
* program → statement* EOF ;
* program → declaration* EOF ;
* declaration → varDecl
* | statement ;
* varDecl → "var" IDENTIFIER ( "=" expression )? ";" ;
* statement → exprStmt
* | printStmt ;
* | printStmt
* | block ;
* block → "{" declaration* "}" ;
* exprStmt → expression ";" ;
* printStmt → "print" expression ";" ;
* expression → equality ;
* expression → assignment ;
* assignment → IDENTIFIER "=" assignment
* | equality ;
* equality → comparison ( ( "!=" | "==" ) comparison )* ;
* comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
* term → factor ( ( "-" | "+" ) factor )* ;
@@ -22,7 +31,8 @@ import static xyz.ctsk.lox.TokenType.*;
* unary → ( "!" | "-" ) unary
* | primary ;
* primary → NUMBER | STRING | "true" | "false" | "nil"
* | "(" expression ")" ;
* | "(" expression ")"
* | IDENTIFIER;
*/
public class Parser {
private final List<Token> tokens;
@@ -37,16 +47,37 @@ public class Parser {
List<Stmt> statements = new ArrayList<>();
while (!isAtEnd()) {
statements.add(statement());
statements.add(declaration());
}
return statements;
}
private Stmt declaration() {
try {
return match(VAR) ? varDeclaration() : statement();
} catch (ParseError error) {
synchronize();
return null;
}
}
private Stmt varDeclaration() {
Token name = consume(IDENTIFIER, "Expect variable name.");
Expr initializer = null;
if (match(EQUAL)) {
initializer = expression();
}
consume(SEMICOLON, "Expect ';' after variable declaration.");
return new Stmt.Var(name, initializer);
}
private Stmt statement() {
if (match(PRINT)) return printStatement();
if (match(LEFT_BRACE)) return new Stmt.Block(blockStatement());
return expressionStatement();
}
private Stmt printStatement() {
@@ -55,6 +86,17 @@ public class Parser {
return new Stmt.Print(value);
}
private List<Stmt> blockStatement() {
var statements = new ArrayList<Stmt>();
while (!check(RIGHT_BRACE) && !isAtEnd()) {
statements.add(declaration());
}
consume(RIGHT_BRACE, "Expect '}' after block.");
return statements;
}
private Stmt expressionStatement() {
Expr expr = expression();
consume(SEMICOLON, "Expect ';' after expression.");
@@ -62,7 +104,26 @@ public class Parser {
}
private Expr expression() {
return equality();
return assignment();
}
private Expr assignment() {
Expr expr = equality();
if (match(EQUAL)) {
Token equals = previous();
Expr value = assignment();
if (expr instanceof Expr.Variable varExpr) {
return new Expr.Assign(varExpr.name(), value);
} else {
//We report an error but don't throw it because we do not need our parser to panic and sync.
//noinspection ThrowableNotThrown
error(equals, "Invalid assignment target.");
}
}
return expr;
}
private Expr equality() {
@@ -132,6 +193,10 @@ public class Parser {
return new Expr.Literal(previous().literal());
}
if (match(IDENTIFIER)) {
return new Expr.Variable(previous());
}
if (match(LEFT_PAREN)) {
Expr expr = expression();
consume(RIGHT_PAREN, "Expect ')' after expression.");

View File

@@ -1,15 +1,19 @@
@Grammar({
@Root(name = "Expr",
rules = {
@Rule(head = "Assign", body = {"Token name", "Expr value"}),
@Rule(head = "Binary", body = {"Expr left", "Token operator", "Expr right"}),
@Rule(head = "Grouping", body = {"Expr expression"}),
@Rule(head = "Literal", body = {"Object value"}),
@Rule(head = "Unary", body = {"Token operator", "Expr right"})
@Rule(head = "Unary", body = {"Token operator", "Expr right"}),
@Rule(head = "Variable", body = {"Token name"})
}),
@Root(name = "Stmt",
rules = {
@Rule(head = "Expression", body = "Expr expression"),
@Rule(head = "Print", body = "Expr expression")
@Rule(head = "Block", body = {"List<Stmt> statements"}),
@Rule(head = "Expression", body = {"Expr expression"}),
@Rule(head = "Print", body = {"Expr expression"}),
@Rule(head = "Var", body = {"Token name", "Expr initializer"})
})
})
package xyz.ctsk.lox;