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) {
runFile(args[0]);
} else {
System.out.println("Cobalt v0.1.0");
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 visitLiteralExpr(Literal expr);
R visitUnaryExpr(Unary expr);
R visitVarExpr(Variable expr);
R visitAssignExpr(Assign expr);
}
@ -68,5 +70,33 @@ abstract class Expr {
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);
}

View File

@ -1,14 +1,11 @@
//
// Continue at Section 7.2.3
// Page 100
//
package cobalt.lang;
import java.util.List;
class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
private Environment environment = new Environment();
void interpret(List<Stmt> statements) {
try {
for (Stmt statement : statements) {
@ -38,6 +35,19 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
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) {
if (object == null)
return false;
@ -59,6 +69,26 @@ 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;
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
public Void visitExpressionStmt(Stmt.Expression stmt) {
evaluate(stmt.expression);
@ -72,6 +102,28 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
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
public Object visitBinaryExpr(Expr.Binary expr) {
Object left = evaluate(expr.left);
@ -100,8 +152,6 @@ class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
checkNumberOperand(expr.operator, right);
return (double) left - (double) right;
case PLUS:
System.out.println("Left: " + left.getClass());
System.out.println("Right: " + right.getClass());
if (left instanceof Double && right instanceof Double) {
return (double) left + (double) right;
} 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;
} else if (left instanceof Double && right instanceof String) {
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:
checkNumberOperands(expr.operator, left, right);
checkNumberOperand(expr.operator, right);

View File

@ -1,6 +1,9 @@
package cobalt.lang;
import java.util.List;
import cobalt.lang.Expr.Variable;
import java.util.ArrayList;
import static cobalt.lang.TokenType.*;
@ -18,7 +21,7 @@ class Parser {
List<Stmt> parse() {
List<Stmt> statements = new ArrayList<>();
while (!isAtEnd()) {
statements.add(statement());
statements.add(declaration());
}
return statements;
@ -26,12 +29,24 @@ class Parser {
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() {
if (match(PRINT)) return printStatement();
if (match(LEFT_BRACE)) return new Stmt.Block(block());
return expressionStatement();
}
@ -39,17 +54,72 @@ class Parser {
private Stmt printStatement() {
Expr value = expression();
consume(SEMICOLON, "Expect ';' after value.");
consume(SEMICOLON, "Expected ';' after 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() {
Expr expr = expression();
consume(SEMICOLON, "Expect ';' after value.");
consume(SEMICOLON, "Expected ';' after value.");
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
//
// equality -> comparison ( ( "!=" | "==" ) comparison )* ;
@ -139,6 +209,14 @@ class Parser {
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)) {
Expr expr = expression();
consume(RIGHT_PAREN, "Expect ')' after expression.");

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,25 @@
# Statement tests
print "These are statement tests for Cobalt!";
print 10 + 12;
print 13 + (14 - 7) * 3;
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);
}