Add assigments + scoping
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
43
jlox/lox/src/main/java/xyz/ctsk/lox/Environment.java
Normal file
43
jlox/lox/src/main/java/xyz/ctsk/lox/Environment.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user