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> {
|
private record Polish(boolean parentheses, boolean reverse) implements Expr.Visitor<String> {
|
||||||
@Override
|
@Override
|
||||||
public String visitBinaryExpr(Expr.Binary binary) {
|
public String visitAssignExpr(Expr.Assign expr) {
|
||||||
var left = binary.left().accept(this);
|
var target = expr.name().lexeme();
|
||||||
var op = binary.operator().lexeme();
|
var value = expr.value().accept(this);
|
||||||
var right = binary.right().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);
|
return reverse ? wrap(left, right, op) : wrap(op, left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String visitGroupingExpr(Expr.Grouping grouping) {
|
public String visitGroupingExpr(Expr.Grouping expr) {
|
||||||
var inner = grouping.expression().accept(this);
|
var inner = expr.expression().accept(this);
|
||||||
return wrap(inner);
|
return wrap(inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String visitLiteralExpr(Expr.Literal literal) {
|
public String visitLiteralExpr(Expr.Literal expr) {
|
||||||
if (literal.value() == null) return "nil";
|
if (expr.value() == null) return "nil";
|
||||||
return literal.value().toString();
|
return expr.value().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String visitUnaryExpr(Expr.Unary unary) {
|
public String visitUnaryExpr(Expr.Unary expr) {
|
||||||
var op = unary.operator().lexeme();
|
var op = expr.operator().lexeme();
|
||||||
var right = unary.right().accept(this);
|
var right = expr.right().accept(this);
|
||||||
|
|
||||||
return reverse ? wrap(right, op) : wrap(op, right);
|
return reverse ? wrap(right, op) : wrap(op, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitVariableExpr(Expr.Variable expr) {
|
||||||
|
return expr.name().lexeme();
|
||||||
|
}
|
||||||
|
|
||||||
public String wrap(String... inner) {
|
public String wrap(String... inner) {
|
||||||
var inners = String.join(" ", 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;
|
import java.util.List;
|
||||||
|
|
||||||
public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
||||||
|
private Environment environment = new Environment();
|
||||||
|
|
||||||
void interpret(List<Stmt> statements) {
|
void interpret(List<Stmt> statements) {
|
||||||
try {
|
try {
|
||||||
@@ -16,6 +17,17 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
|||||||
stmt.accept(this);
|
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) {
|
private Object evaluate(Expr expr) {
|
||||||
return expr.accept(this);
|
return expr.accept(this);
|
||||||
}
|
}
|
||||||
@@ -31,6 +43,12 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitBlockStmt(Stmt.Block stmt) {
|
||||||
|
executeBlock(stmt.statements(), new Environment(environment));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visitExpressionStmt(Stmt.Expression stmt) {
|
public Void visitExpressionStmt(Stmt.Expression stmt) {
|
||||||
evaluate(stmt.expression());
|
evaluate(stmt.expression());
|
||||||
@@ -45,16 +63,30 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object visitBinaryExpr(Expr.Binary binary) {
|
public Void visitVarStmt(Stmt.Var stmt) {
|
||||||
var left = evaluate(binary.left());
|
Object value = stmt.initializer() == null ? null : evaluate(stmt.initializer());
|
||||||
var right = evaluate(binary.right());
|
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 ->
|
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 MINUS -> (double) left - (double) right;
|
||||||
case PLUS -> {
|
case PLUS -> {
|
||||||
if (left instanceof Double leftD && right instanceof Double rightD) {
|
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;
|
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 SLASH -> (double) left / (double) right;
|
||||||
case STAR -> (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
|
@Override
|
||||||
public Object visitGroupingExpr(Expr.Grouping grouping) {
|
public Object visitGroupingExpr(Expr.Grouping expr) {
|
||||||
return evaluate(grouping.expression());
|
return evaluate(expr.expression());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object visitLiteralExpr(Expr.Literal literal) {
|
public Object visitLiteralExpr(Expr.Literal expr) {
|
||||||
return literal.value();
|
return expr.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object visitUnaryExpr(Expr.Unary unary) {
|
public Object visitUnaryExpr(Expr.Unary expr) {
|
||||||
var right = evaluate(unary.right());
|
var right = evaluate(expr.right());
|
||||||
|
|
||||||
return switch(unary.operator().type()) {
|
return switch(expr.operator().type()) {
|
||||||
case MINUS -> -asNumber(unary.operator(), right);
|
case MINUS -> -asNumber(expr.operator(), right);
|
||||||
case BANG -> !isTruthy(right);
|
case BANG -> !isTruthy(right);
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitVariableExpr(Expr.Variable expr) {
|
||||||
|
return environment.get(expr.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean isTruthy(Object object) {
|
private boolean isTruthy(Object object) {
|
||||||
if (object == null) return false;
|
if (object == null) return false;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class Lox {
|
|||||||
var reader = new BufferedReader(input);
|
var reader = new BufferedReader(input);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
System.out.println("> ");
|
System.out.print("> ");
|
||||||
String line = reader.readLine();
|
String line = reader.readLine();
|
||||||
if (line == null) break;
|
if (line == null) break;
|
||||||
run(line);
|
run(line);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package xyz.ctsk.lox;
|
package xyz.ctsk.lox;
|
||||||
|
|
||||||
|
import java.sql.Statement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -9,12 +10,20 @@ import static xyz.ctsk.lox.TokenType.*;
|
|||||||
*
|
*
|
||||||
* A recursive descent parser for the following grammar:
|
* A recursive descent parser for the following grammar:
|
||||||
* <p>
|
* <p>
|
||||||
* program → statement* EOF ;
|
* program → declaration* EOF ;
|
||||||
|
* declaration → varDecl
|
||||||
|
* | statement ;
|
||||||
|
* varDecl → "var" IDENTIFIER ( "=" expression )? ";" ;
|
||||||
* statement → exprStmt
|
* statement → exprStmt
|
||||||
* | printStmt ;
|
* | printStmt
|
||||||
|
* | block ;
|
||||||
|
* block → "{" declaration* "}" ;
|
||||||
* exprStmt → expression ";" ;
|
* exprStmt → expression ";" ;
|
||||||
* printStmt → "print" expression ";" ;
|
* printStmt → "print" expression ";" ;
|
||||||
* expression → equality ;
|
* expression → equality ;
|
||||||
|
* expression → assignment ;
|
||||||
|
* assignment → IDENTIFIER "=" assignment
|
||||||
|
* | equality ;
|
||||||
* equality → comparison ( ( "!=" | "==" ) comparison )* ;
|
* equality → comparison ( ( "!=" | "==" ) comparison )* ;
|
||||||
* comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
|
* comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
|
||||||
* term → factor ( ( "-" | "+" ) factor )* ;
|
* term → factor ( ( "-" | "+" ) factor )* ;
|
||||||
@@ -22,7 +31,8 @@ import static xyz.ctsk.lox.TokenType.*;
|
|||||||
* unary → ( "!" | "-" ) unary
|
* unary → ( "!" | "-" ) unary
|
||||||
* | primary ;
|
* | primary ;
|
||||||
* primary → NUMBER | STRING | "true" | "false" | "nil"
|
* primary → NUMBER | STRING | "true" | "false" | "nil"
|
||||||
* | "(" expression ")" ;
|
* | "(" expression ")"
|
||||||
|
* | IDENTIFIER;
|
||||||
*/
|
*/
|
||||||
public class Parser {
|
public class Parser {
|
||||||
private final List<Token> tokens;
|
private final List<Token> tokens;
|
||||||
@@ -37,16 +47,37 @@ public class Parser {
|
|||||||
List<Stmt> statements = new ArrayList<>();
|
List<Stmt> statements = new ArrayList<>();
|
||||||
|
|
||||||
while (!isAtEnd()) {
|
while (!isAtEnd()) {
|
||||||
statements.add(statement());
|
statements.add(declaration());
|
||||||
}
|
}
|
||||||
|
|
||||||
return statements;
|
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() {
|
private Stmt statement() {
|
||||||
if (match(PRINT)) return printStatement();
|
if (match(PRINT)) return printStatement();
|
||||||
|
if (match(LEFT_BRACE)) return new Stmt.Block(blockStatement());
|
||||||
return expressionStatement();
|
return expressionStatement();
|
||||||
}
|
}
|
||||||
private Stmt printStatement() {
|
private Stmt printStatement() {
|
||||||
@@ -55,6 +86,17 @@ public class Parser {
|
|||||||
return new Stmt.Print(value);
|
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() {
|
private Stmt expressionStatement() {
|
||||||
Expr expr = expression();
|
Expr expr = expression();
|
||||||
consume(SEMICOLON, "Expect ';' after expression.");
|
consume(SEMICOLON, "Expect ';' after expression.");
|
||||||
@@ -62,7 +104,26 @@ public class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Expr expression() {
|
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() {
|
private Expr equality() {
|
||||||
@@ -132,6 +193,10 @@ public class Parser {
|
|||||||
return new Expr.Literal(previous().literal());
|
return new Expr.Literal(previous().literal());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (match(IDENTIFIER)) {
|
||||||
|
return new Expr.Variable(previous());
|
||||||
|
}
|
||||||
|
|
||||||
if (match(LEFT_PAREN)) {
|
if (match(LEFT_PAREN)) {
|
||||||
Expr expr = expression();
|
Expr expr = expression();
|
||||||
consume(RIGHT_PAREN, "Expect ')' after expression.");
|
consume(RIGHT_PAREN, "Expect ')' after expression.");
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
@Grammar({
|
@Grammar({
|
||||||
@Root(name = "Expr",
|
@Root(name = "Expr",
|
||||||
rules = {
|
rules = {
|
||||||
|
@Rule(head = "Assign", body = {"Token name", "Expr value"}),
|
||||||
@Rule(head = "Binary", body = {"Expr left", "Token operator", "Expr right"}),
|
@Rule(head = "Binary", body = {"Expr left", "Token operator", "Expr right"}),
|
||||||
@Rule(head = "Grouping", body = {"Expr expression"}),
|
@Rule(head = "Grouping", body = {"Expr expression"}),
|
||||||
@Rule(head = "Literal", body = {"Object value"}),
|
@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",
|
@Root(name = "Stmt",
|
||||||
rules = {
|
rules = {
|
||||||
@Rule(head = "Expression", body = "Expr expression"),
|
@Rule(head = "Block", body = {"List<Stmt> statements"}),
|
||||||
@Rule(head = "Print", body = "Expr expression")
|
@Rule(head = "Expression", body = {"Expr expression"}),
|
||||||
|
@Rule(head = "Print", body = {"Expr expression"}),
|
||||||
|
@Rule(head = "Var", body = {"Token name", "Expr initializer"})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
package xyz.ctsk.lox;
|
package xyz.ctsk.lox;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class GrammarProcessor extends AbstractProcessor {
|
|||||||
|
|
||||||
for (var rule : rules) {
|
for (var rule : rules) {
|
||||||
var visitorName = getVisitorName(rule.head(), parentName);
|
var visitorName = getVisitorName(rule.head(), parentName);
|
||||||
var parameter = "%s %s".formatted(rule.head(), rule.head().toLowerCase());
|
var parameter = "%s %s".formatted(rule.head(), parentName.toLowerCase());
|
||||||
writer.write("""
|
writer.write("""
|
||||||
R %s(%s);
|
R %s(%s);
|
||||||
""".formatted(visitorName, parameter).indent(indent + INDENT));
|
""".formatted(visitorName, parameter).indent(indent + INDENT));
|
||||||
@@ -71,6 +71,8 @@ public class GrammarProcessor extends AbstractProcessor {
|
|||||||
writer.write("""
|
writer.write("""
|
||||||
package %s;
|
package %s;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
interface %s {
|
interface %s {
|
||||||
""".formatted(packageName, baseName)
|
""".formatted(packageName, baseName)
|
||||||
);
|
);
|
||||||
@@ -80,7 +82,7 @@ public class GrammarProcessor extends AbstractProcessor {
|
|||||||
writer.write("""
|
writer.write("""
|
||||||
<R> R accept(Visitor<R> visitor);
|
<R> R accept(Visitor<R> visitor);
|
||||||
|
|
||||||
"""
|
""".indent(INDENT)
|
||||||
);
|
);
|
||||||
|
|
||||||
for (var rule : root.rules()) {
|
for (var rule : root.rules()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user