Add support for variable environments and declaration; Add nullable and non-nullable types

This commit is contained in:
Garrett Dickinson 2022-07-31 18:16:44 -05:00
parent a5ee63fffc
commit 175309ac7d
10 changed files with 287 additions and 43 deletions

View File

@ -22,6 +22,7 @@ public class Cobalt {
} else if (args.length == 1) { } else if (args.length == 1) {
runFile(args[0]); runFile(args[0]);
} else { } else {
System.out.println("Cobalt v0.1.0");
runPrompt(); runPrompt();
} }
} }

View File

@ -0,0 +1,54 @@
package cobalt.lang;
import java.util.HashMap;
import java.util.Map;
class Environment {
final Environment enclosing;
private final Map<String, Object> values = new HashMap<>();
Environment() {
enclosing = null;
}
Environment(Environment enclosing) {
this.enclosing = enclosing;
}
Object get(Token name) {
if (values.containsKey(name.lexeme)) {
return values.get(name.lexeme);
}
if (enclosing != null) return enclosing.get(name);
throw new RuntimeError(name,
"Undefined reference to variable: '" + name.lexeme + "'.");
}
void define(Token name, Object value) {
if (!values.containsKey(name.lexeme)) {
values.put(name.lexeme, value);
} else {
throw new RuntimeError(name,
"Undefined reference to variable: '" + name.lexeme + "'.");
}
}
void assign(Token name, Object value) {
if (values.containsKey(name.lexeme)) {
values.put(name.lexeme, value);
return;
}
if (enclosing != null) {
enclosing.assign(name, value);
return;
}
throw new RuntimeError(name,
"Undefined variable '" + name.lexeme + "'.");
}
}

View File

@ -8,6 +8,8 @@ abstract class Expr {
R visitGroupingExpr(Grouping expr); R visitGroupingExpr(Grouping expr);
R visitLiteralExpr(Literal expr); R visitLiteralExpr(Literal expr);
R visitUnaryExpr(Unary expr); R visitUnaryExpr(Unary expr);
R visitVarExpr(Variable expr);
R visitAssignExpr(Assign expr);
} }
@ -68,5 +70,33 @@ abstract class Expr {
final Expr right; final Expr right;
} }
static class Variable extends Expr {
Variable(Token name, boolean nullable) {
this.name = name;
this.nullable = nullable;
}
<R> R accept(Visitor<R> visitor) {
return visitor.visitVarExpr(this);
}
final Token name;
final boolean nullable;
}
static class Assign extends Expr {
Assign(Token name, Expr value) {
this.name = name;
this.value = value;
}
<R> R accept(Visitor<R> visitor) {
return visitor.visitAssignExpr(this);
}
final Token name;
final Expr value;
}
abstract <R> R accept(Visitor<R> visitor); abstract <R> R accept(Visitor<R> visitor);
} }

View File

@ -1,14 +1,11 @@
//
// Continue at Section 7.2.3
// Page 100
//
package cobalt.lang; package cobalt.lang;
import java.util.List; import java.util.List;
class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> { 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 {
for (Stmt statement : statements) { for (Stmt statement : statements) {
@ -38,6 +35,19 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
return null; return null;
} }
@Override
public Object visitVarExpr(Expr.Variable expr) {
// TODO: Fix analogous variable issues
return environment.get(expr.name);
// Object var_value = environment.get(expr.name);
// if (var_value != null) {
// return environment.get(expr.name);
// } else {
// throw new RuntimeError(expr.name,
// "Reference to undefined variable: '" + expr.name + "'.");
// }
}
private boolean isTruthy(Object object) { private boolean isTruthy(Object object) {
if (object == null) if (object == null)
return false; return false;
@ -59,6 +69,26 @@ 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;
for (Stmt statement : statements) {
execute(statement);
}
} finally {
this.environment = previous;
}
}
@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);
@ -72,6 +102,28 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
return null; return null;
} }
@Override
public Void visitVarStmt(Stmt.Var stmt) {
Object value = null;
if (stmt.initializer != null) {
value = evaluate(stmt.initializer);
}
if (!stmt.nullable && value == null) {
throw new RuntimeError(stmt.name, "Assignment of 'nil' to non-nullable type");
} else {
environment.define(stmt.name, value);
return null;
}
}
@Override
public Object visitAssignExpr(Expr.Assign expr) {
Object value = evaluate(expr.value);
environment.assign(expr.name, value);
return value;
}
@Override @Override
public Object visitBinaryExpr(Expr.Binary expr) { public Object visitBinaryExpr(Expr.Binary expr) {
Object left = evaluate(expr.left); Object left = evaluate(expr.left);
@ -100,8 +152,6 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
checkNumberOperand(expr.operator, right); checkNumberOperand(expr.operator, right);
return (double) left - (double) right; return (double) left - (double) right;
case PLUS: case PLUS:
System.out.println("Left: " + left.getClass());
System.out.println("Right: " + right.getClass());
if (left instanceof Double && right instanceof Double) { if (left instanceof Double && right instanceof Double) {
return (double) left + (double) right; return (double) left + (double) right;
} else if (left instanceof String && right instanceof String) { } else if (left instanceof String && right instanceof String) {
@ -110,8 +160,12 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
return (String) left + (Double) right; return (String) left + (Double) right;
} else if (left instanceof Double && right instanceof String) { } else if (left instanceof Double && right instanceof String) {
return (Double) left + (String) right; return (Double) left + (String) right;
} else if ((left == null) && right instanceof String) {
return "nil" + (String) right;
} else if (left instanceof String && (right == null)) {
return (String) left + "nil";
} }
throw new RuntimeError(expr.operator, "Operands must be of type number or string"); throw new RuntimeError(expr.operator, "Invalid operands: '" + left + "' and '" + right + "'");
case SLASH: case SLASH:
checkNumberOperands(expr.operator, left, right); checkNumberOperands(expr.operator, left, right);
checkNumberOperand(expr.operator, right); checkNumberOperand(expr.operator, right);

View File

@ -1,6 +1,9 @@
package cobalt.lang; package cobalt.lang;
import java.util.List; import java.util.List;
import cobalt.lang.Expr.Variable;
import java.util.ArrayList; import java.util.ArrayList;
import static cobalt.lang.TokenType.*; import static cobalt.lang.TokenType.*;
@ -18,7 +21,7 @@ class Parser {
List<Stmt> parse() { List<Stmt> parse() {
List<Stmt> statements = new ArrayList<>(); List<Stmt> statements = new ArrayList<>();
while (!isAtEnd()) { while (!isAtEnd()) {
statements.add(statement()); statements.add(declaration());
} }
return statements; return statements;
@ -26,12 +29,24 @@ class Parser {
private Expr expression() { private Expr expression() {
return equality(); return assignment();
}
private Stmt declaration() {
try {
if (match(VAR)) return varDeclaration();
return statement();
} catch (ParseError error) {
synchronize();
return null;
}
} }
private Stmt statement() { private Stmt statement() {
if (match(PRINT)) return printStatement(); if (match(PRINT)) return printStatement();
if (match(LEFT_BRACE)) return new Stmt.Block(block());
return expressionStatement(); return expressionStatement();
} }
@ -39,17 +54,72 @@ class Parser {
private Stmt printStatement() { private Stmt printStatement() {
Expr value = expression(); Expr value = expression();
consume(SEMICOLON, "Expect ';' after value."); consume(SEMICOLON, "Expected ';' after value.");
return new Stmt.Print(value); return new Stmt.Print(value);
} }
private Stmt varDeclaration() {
Token name = consume(IDENTIFIER, "Expected variable name.");
Expr initializer = null;
boolean nullable = false;
if (match(NULLABLE)) {
nullable = true;
}
if (match(EQUAL)) {
initializer = expression();
}
consume(SEMICOLON, "Expected ';' after variable declaration.");
return new Stmt.Var(name, nullable, initializer);
}
private Stmt expressionStatement() { private Stmt expressionStatement() {
Expr expr = expression(); Expr expr = expression();
consume(SEMICOLON, "Expect ';' after value."); consume(SEMICOLON, "Expected ';' after value.");
return new Stmt.Expression(expr); return new Stmt.Expression(expr);
} }
private List<Stmt> block() {
List<Stmt> statements = new ArrayList<>();
while (!check(RIGHT_BRACE) && !isAtEnd()) {
statements.add(declaration());
}
consume(RIGHT_BRACE, "Expected '}' after block.");
return statements;
}
private Expr assignment() {
Expr expr = equality();
if (match(EQUAL)) {
Token equals = previous();
Expr value = assignment();
if (expr instanceof Expr.Variable) {
Variable var = (Expr.Variable) expr;
Token name = var.name;
if (!var.nullable && value == null) {
error(equals, "Invalid assignment of 'nil' on non-nullable type");
} else {
return new Expr.Assign(name, value);
}
}
error(equals, "Invalid assignment on non-variable type");
}
return expr;
}
// Parser definition for handling equalities // Parser definition for handling equalities
// //
// equality -> comparison ( ( "!=" | "==" ) comparison )* ; // equality -> comparison ( ( "!=" | "==" ) comparison )* ;
@ -139,6 +209,14 @@ class Parser {
return new Expr.Literal(previous().literal); return new Expr.Literal(previous().literal);
} }
if (match(IDENTIFIER)) {
if (match(NULLABLE)) {
return new Expr.Variable(previous(), true);
} else {
return new Expr.Variable(previous(), false);
}
}
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.");

View File

@ -89,14 +89,15 @@ class Scanner {
case '>': case '>':
addToken(match('=') ? GREATER_EQUAL : GREATER); addToken(match('=') ? GREATER_EQUAL : GREATER);
break; break;
case '/': case '/': addToken(SLASH); break;
if (match('/')) { case '?': addToken(NULLABLE); break;
// A comment goes until the end of the line
while (peek() != '\n' && !isAtEnd()) advance(); // Comments
} else { case '#':
addToken(SLASH); // A comment goes until the end of the line
} while (peek() != '\n' && !isAtEnd()) advance();
break; break;
// Whitespace and new lines // Whitespace and new lines
case ' ': case ' ':

View File

@ -4,22 +4,24 @@ import java.util.List;
abstract class Stmt { abstract class Stmt {
interface Visitor<R> { interface Visitor<R> {
//R visitBlockStmt(Block stmt); R visitBlockStmt(Block stmt);
R visitExpressionStmt(Expression stmt); R visitExpressionStmt(Expression stmt);
R visitPrintStmt(Print stmt); R visitPrintStmt(Print stmt);
//R visitVarStmt(Var stmt); R visitVarStmt(Var stmt);
} }
// static class Block extends Stmt {
// Block(List<Stmt> statements) {
// this.statements = statements;
// }
// <R> R accept(Visitor<R> visitor) { static class Block extends Stmt {
// return visitor.visitBlockStmt(this); Block(List<Stmt> statements) {
// } this.statements = statements;
}
<R> R accept(Visitor<R> visitor) {
return visitor.visitBlockStmt(this);
}
final List<Stmt> statements;
}
// final List<Stmt> statements;
// }
static class Expression extends Stmt { static class Expression extends Stmt {
Expression(Expr expression) { Expression(Expr expression) {
this.expression = expression; this.expression = expression;
@ -42,19 +44,22 @@ abstract class Stmt {
final Expr expression; final Expr expression;
} }
// static class Var extends Stmt {
// Var(Token name, Expr initializer) { static class Var extends Stmt {
// this.name = name; Var(Token name, boolean nullable, Expr initializer) {
// this.initializer = initializer; this.name = name;
// } this.nullable = nullable;
this.initializer = initializer;
}
// <R> R accept(Visitor<R> visitor) { <R> R accept(Visitor<R> visitor) {
// return visitor.visitVarStmt(this); return visitor.visitVarStmt(this);
// } }
// final Token name; final Token name;
// final Expr initializer; final boolean nullable;
// } final Expr initializer;
}
abstract <R> R accept(Visitor<R> visitor); abstract <R> R accept(Visitor<R> visitor);
} }

View File

@ -7,7 +7,7 @@ enum TokenType {
// One or two character tokens. // One or two character tokens.
BANG, BANG_EQUAL, EQUAL, EQUAL_EQUAL, BANG, BANG_EQUAL, EQUAL, EQUAL_EQUAL,
GREATER, GREATER_EQUAL, LESS, LESS_EQUAL, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL, NULLABLE,
// Literals // Literals
IDENTIFIER, STRING, NUMBER, IDENTIFIER, STRING, NUMBER,

View File

@ -18,6 +18,7 @@ public class GenerateAst {
"Binary : Expr left, Token operator, Expr right", "Binary : Expr left, Token operator, Expr right",
"Grouping : Expr expression", "Grouping : Expr expression",
"Literal : Object value", "Literal : Object value",
"Variable : Token Name",
"Unary : Token operator, Expr right", "Unary : Token operator, Expr right",
"Variable : Token name" "Variable : Token name"
)); ));

View File

@ -1,5 +1,25 @@
# Statement tests
print "These are statement tests for Cobalt!"; print "These are statement tests for Cobalt!";
print 10 + 12; print 10 + 12;
print 13 + (14 - 7) * 3; print 13 + (14 - 7) * 3;
print "Hello, " + "World!"; print "Hello, " + "World!";
print "String " + "Multiplication " * 3; print "String " + "Multiplication " * 3;
# Variable declaration
var a = 10;
print ("a is " + a);
# Nullable variable declaration
var b? = nil;
print ("b is " + b);
# Examples of structured block statements with scoping
{
print "we are now in a nested scope!";
# Don't really like this behaviour, as I think it should throw a runtime error
# Instead it pulls a's state of 10 in the upper environment block
# Still trying to resolve this...
var a = a + 5;
print ("a is " + a);
}