Statements and State 8
This commit is contained in:
parent
f4338ba51f
commit
e749367886
7 changed files with 250 additions and 69 deletions
31
src/jlox/environment.d
Normal file
31
src/jlox/environment.d
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
module jlox.environment;
|
||||
|
||||
import jlox.token;
|
||||
import jlox.interpreter;
|
||||
import jlox.util;
|
||||
|
||||
class Environment{
|
||||
Environment enclosing;
|
||||
private TValue[string] values;
|
||||
|
||||
mixin defaultCtor;
|
||||
|
||||
void define(string name, TValue value){
|
||||
values[name] = value;
|
||||
}
|
||||
TValue get(Token name){
|
||||
if(TValue* value = name.lexeme in values)
|
||||
return *value;
|
||||
if(enclosing)
|
||||
return enclosing.get(name);
|
||||
throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'.");
|
||||
}
|
||||
void assign(Token name, TValue value){
|
||||
if(name.lexeme in values)
|
||||
return values[name.lexeme] = value;
|
||||
if(enclosing)
|
||||
return enclosing.assign(name, value);
|
||||
throw new RuntimeError(name, "Undefined variable '" ~ name.lexeme ~ "'.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -12,10 +12,12 @@ import jlox.util;
|
|||
|
||||
abstract class Expr{
|
||||
interface Visitor(R){
|
||||
R visit(Assign expr);
|
||||
R visit(Binary expr);
|
||||
R visit(Grouping expr);
|
||||
R visit(Literal expr);
|
||||
R visit(Unary expr);
|
||||
R visit(Variable expr);
|
||||
}
|
||||
private alias rTypes = AliasSeq!(string, TValue);
|
||||
static foreach(T; rTypes)
|
||||
|
|
@ -26,63 +28,61 @@ abstract class Expr{
|
|||
override T accept(Visitor!T visitor) => visitor.visit(this);
|
||||
}
|
||||
|
||||
static class Binary : Expr{
|
||||
static class Assign : typeof(this){
|
||||
Token name;
|
||||
Expr value;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
static class Binary : typeof(this){
|
||||
Expr left;
|
||||
Token operator;
|
||||
Expr right;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
static class Grouping : Expr{
|
||||
static class Grouping : typeof(this){
|
||||
Expr expression;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
static class Literal : Expr{
|
||||
static class Literal : typeof(this){
|
||||
TValue value;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
static class Unary : Expr{
|
||||
static class Unary : typeof(this){
|
||||
Token operator;
|
||||
Expr right;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
}
|
||||
|
||||
class AstPrinter : Expr.Visitor!string{
|
||||
string print(Expr expr){
|
||||
return expr.accept(this);
|
||||
}
|
||||
string parenthesize(Args...)(string name, Args args){
|
||||
string s = "(" ~ name;
|
||||
static foreach(expr; args){
|
||||
s ~= " ";
|
||||
s ~= expr.accept(this);
|
||||
}
|
||||
return s ~ ")";
|
||||
}
|
||||
string visit(Expr.Binary expr){
|
||||
return parenthesize(expr.operator.lexeme, expr.left, expr.right);
|
||||
}
|
||||
string visit(Expr.Grouping expr){
|
||||
return parenthesize("group", expr.expression);
|
||||
}
|
||||
string visit(Expr.Literal expr){
|
||||
if(expr.value.isNil)
|
||||
return "nil";
|
||||
return expr.value.to!string;
|
||||
}
|
||||
string visit(Expr.Unary expr){
|
||||
return parenthesize(expr.operator.lexeme, expr.right);
|
||||
static class Variable : typeof(this){
|
||||
Token name;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
}
|
||||
|
||||
unittest{
|
||||
auto expression = new Expr.Binary(
|
||||
new Expr.Unary(
|
||||
new Token(TokenType.MINUS, "-", TValue.nil(0), 1),
|
||||
new Expr.Literal(123)),
|
||||
new Token(TokenType.STAR, "*", TValue.nil(0), 1),
|
||||
new Expr.Grouping(
|
||||
new Expr.Literal(45.67)));
|
||||
assert(new AstPrinter().print(expression));
|
||||
}
|
||||
/* class AstPrinter : Expr.Visitor!string{ */
|
||||
/* string print(Expr expr){ */
|
||||
/* return expr.accept(this); */
|
||||
/* } */
|
||||
/* string parenthesize(Args...)(string name, Args args){ */
|
||||
/* string s = "(" ~ name; */
|
||||
/* static foreach(expr; args){ */
|
||||
/* s ~= " "; */
|
||||
/* s ~= expr.accept(this); */
|
||||
/* } */
|
||||
/* return s ~ ")"; */
|
||||
/* } */
|
||||
/* string visit(Expr.Binary expr){ */
|
||||
/* return parenthesize(expr.operator.lexeme, expr.left, expr.right); */
|
||||
/* } */
|
||||
/* string visit(Expr.Grouping expr){ */
|
||||
/* return parenthesize("group", expr.expression); */
|
||||
/* } */
|
||||
/* string visit(Expr.Literal expr){ */
|
||||
/* if(expr.value.isNil) */
|
||||
/* return "nil"; */
|
||||
/* return expr.value.to!string; */
|
||||
/* } */
|
||||
/* string visit(Expr.Unary expr){ */
|
||||
/* return parenthesize(expr.operator.lexeme, expr.right); */
|
||||
/* } */
|
||||
/* } */
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import std.functional : ctEval;
|
|||
import taggedalgebraic;
|
||||
|
||||
import jlox.expr;
|
||||
import jlox.stmt;
|
||||
import jlox.token;
|
||||
import jlox.tokentype;
|
||||
import jlox.token : TValue;
|
||||
import jlox.main;
|
||||
import jlox.environment;
|
||||
|
||||
class RuntimeError : Exception{
|
||||
const Token token;
|
||||
|
|
@ -21,15 +23,29 @@ class RuntimeError : Exception{
|
|||
}
|
||||
}
|
||||
|
||||
class Interpreter : Expr.Visitor!TValue{
|
||||
void interpret(Expr expression) {
|
||||
class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue {
|
||||
private Environment environment = new Environment();
|
||||
void interpret(Stmt[] statements){
|
||||
try {
|
||||
TValue value = evaluate(expression);
|
||||
writeln(value);
|
||||
foreach(statement; statements)
|
||||
execute(statement);
|
||||
} catch(RuntimeError error){
|
||||
Lox.runtimeError(error);
|
||||
}
|
||||
}
|
||||
private void execute(Stmt stmt){
|
||||
stmt.accept(this);
|
||||
}
|
||||
private void executeBlock(Stmt[] statements, Environment environment){
|
||||
Environment previous = this.environment;
|
||||
try {
|
||||
this.environment = environment;
|
||||
foreach(Stmt statement; statements)
|
||||
execute(statement);
|
||||
} finally {
|
||||
this.environment = previous;
|
||||
}
|
||||
}
|
||||
private TValue evaluate(Expr expr){
|
||||
return expr.accept(this);
|
||||
}
|
||||
|
|
@ -51,13 +67,29 @@ class Interpreter : Expr.Visitor!TValue{
|
|||
return;
|
||||
throw new RuntimeError(operator, "Operand must be a number.");
|
||||
}
|
||||
|
||||
|
||||
void visit(Stmt.Block stmt){
|
||||
executeBlock(stmt.statements, new Environment(environment));
|
||||
}
|
||||
void visit(Stmt.Expression stmt){
|
||||
evaluate(stmt.expression);
|
||||
}
|
||||
void visit(Stmt.Print stmt){
|
||||
TValue value = evaluate(stmt.expression);
|
||||
writeln(tvalueToString(value));
|
||||
}
|
||||
void visit(Stmt.Var stmt){
|
||||
environment.define(stmt.name.lexeme, stmt.initialiser is null ? TValue.nil(0) : evaluate(stmt.initialiser));
|
||||
}
|
||||
|
||||
TValue visit(Expr.Literal expr){
|
||||
return expr.value;
|
||||
}
|
||||
TValue visit(Expr.Grouping expr) {
|
||||
TValue visit(Expr.Grouping expr){
|
||||
return evaluate(expr.expression);
|
||||
}
|
||||
TValue visit(Expr.Unary expr) {
|
||||
TValue visit(Expr.Unary expr){
|
||||
TValue right = evaluate(expr.right);
|
||||
switch(expr.operator.type){
|
||||
case TokenType.MINUS:
|
||||
|
|
@ -104,5 +136,13 @@ class Interpreter : Expr.Visitor!TValue{
|
|||
assert(0);
|
||||
}
|
||||
}
|
||||
TValue visit(Expr.Variable expr){
|
||||
return environment.get(expr.name);
|
||||
}
|
||||
TValue visit(Expr.Assign expr){
|
||||
TValue value = evaluate(expr.value);
|
||||
environment.assign(expr.name, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import jlox.token;
|
|||
import jlox.tokentype;
|
||||
import jlox.scanner;
|
||||
import jlox.parser;
|
||||
import jlox.expr;
|
||||
import jlox.interpreter;
|
||||
|
||||
import jlox.expr;
|
||||
import jlox.stmt;
|
||||
|
||||
static class Lox{
|
||||
private static Interpreter interpreter;
|
||||
|
|
@ -45,13 +45,13 @@ static class Lox{
|
|||
Token[] tokens = scanner.scanTokens();
|
||||
|
||||
Parser parser = new Parser(tokens);
|
||||
Expr expression = parser.parse();
|
||||
Stmt[] statements = parser.parse();
|
||||
|
||||
if(hadError)
|
||||
return;
|
||||
|
||||
/* writeln((new AstPrinter).print(expression)); */
|
||||
interpreter.interpret(expression);
|
||||
interpreter.interpret(statements);
|
||||
}
|
||||
static void runFile(string path){
|
||||
string bytes = readText(path);
|
||||
|
|
|
|||
|
|
@ -7,21 +7,21 @@ import jlox.tokentype;
|
|||
import jlox.util;
|
||||
import jlox.expr;
|
||||
import jlox.main;
|
||||
import jlox.stmt;
|
||||
|
||||
class Parser{
|
||||
private Token[] tokens;
|
||||
private int current = 0;
|
||||
|
||||
mixin defaultCtor;
|
||||
Expr parse(){
|
||||
try {
|
||||
return expression();
|
||||
} catch (ParseError error) {
|
||||
return null;
|
||||
}
|
||||
Stmt[] parse(){
|
||||
Stmt[] statements;
|
||||
while(!isAtEnd)
|
||||
statements ~= declaration();
|
||||
return statements;
|
||||
}
|
||||
|
||||
private bool isAtEnd() => peek().type == EOF;
|
||||
private bool isAtEnd() => peek().type == TokenType.EOF;
|
||||
private Token peek() => tokens[current];
|
||||
private Token previous() => tokens[current-1];
|
||||
private bool check(TokenType type){
|
||||
|
|
@ -30,7 +30,7 @@ class Parser{
|
|||
return peek().type == type;
|
||||
}
|
||||
private Token advance(){
|
||||
if(!isAtEnd())
|
||||
if(!isAtEnd)
|
||||
current++;
|
||||
return previous();
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ class Parser{
|
|||
}
|
||||
private void synchronize(){
|
||||
advance();
|
||||
with(TokenType) while(!isAtEnd()){
|
||||
with(TokenType) while(!isAtEnd){
|
||||
if(previous().type == SEMICOLON)
|
||||
return;
|
||||
switch(peek().type){
|
||||
|
|
@ -73,15 +73,49 @@ class Parser{
|
|||
case RETURN:
|
||||
return;
|
||||
default:
|
||||
assert(0);
|
||||
/* assert(0); */
|
||||
}
|
||||
advance();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Stmt printStatement(){
|
||||
Expr value = expression();
|
||||
consume(TokenType.SEMICOLON, "Expect ';' after value.");
|
||||
return new Stmt.Print(value);
|
||||
}
|
||||
private Stmt expressionStatement(){
|
||||
Expr expr = expression();
|
||||
consume(TokenType.SEMICOLON, "Expect ';' after expression.");
|
||||
return new Stmt.Expression(expr);
|
||||
}
|
||||
private Stmt statement(){
|
||||
if(match(TokenType.LEFT_BRACE))
|
||||
return new Stmt.Block(block());
|
||||
if(match(TokenType.PRINT))
|
||||
return printStatement();
|
||||
return expressionStatement();
|
||||
}
|
||||
private Stmt[] block(){
|
||||
Stmt[] statements;
|
||||
while(!check(TokenType.RIGHT_BRACE) && !isAtEnd)
|
||||
statements ~= declaration();
|
||||
consume(TokenType.RIGHT_BRACE, "Expect '}' after block.");
|
||||
return statements;
|
||||
}
|
||||
private Expr expression(){
|
||||
return equality();
|
||||
return assignment();
|
||||
}
|
||||
private Expr assignment(){
|
||||
Expr expr = equality();
|
||||
if(match(TokenType.EQUAL)){
|
||||
Token equals = previous();
|
||||
Expr value = assignment();
|
||||
if(auto v = cast(Expr.Variable)expr)
|
||||
return new Expr.Assign(v.name, value);
|
||||
error(equals, "Invalid assignment target.");
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
private Expr equality(){
|
||||
Expr expr = comparison();
|
||||
|
|
@ -128,12 +162,14 @@ class Parser{
|
|||
return primary();
|
||||
}
|
||||
private Expr primary(){
|
||||
if(match(TokenType.IDENTIFIER))
|
||||
return new Expr.Variable(previous());
|
||||
if(match(TokenType.FALSE))
|
||||
return new Expr.Literal(false);
|
||||
return new Expr.Literal(TValue.bln(false));
|
||||
if(match(TokenType.TRUE))
|
||||
return new Expr.Literal(true);
|
||||
return new Expr.Literal(TValue.bln(true));
|
||||
if(match(TokenType.NIL))
|
||||
return new Expr.Literal(null);
|
||||
return new Expr.Literal(TValue.nil(0));
|
||||
if(match(TokenType.NUMBER, TokenType.STRING))
|
||||
return new Expr.Literal(previous().literal);
|
||||
if(match(TokenType.LEFT_PAREN)){
|
||||
|
|
@ -142,7 +178,23 @@ class Parser{
|
|||
return new Expr.Grouping(expr);
|
||||
}
|
||||
throw error(peek(), "Expect expression.");
|
||||
assert(0);
|
||||
}
|
||||
|
||||
private Stmt varDeclaration(){
|
||||
Token name = consume(TokenType.IDENTIFIER, "Expect variable name.");
|
||||
Expr initializer = null;
|
||||
if(match(TokenType.EQUAL))
|
||||
initializer = expression();
|
||||
consume(TokenType.SEMICOLON, "Expect ';' after variable declaration.");
|
||||
return new Stmt.Var(name, initializer);
|
||||
}
|
||||
private Stmt declaration(){
|
||||
try {
|
||||
if(match(TokenType.VAR))
|
||||
return varDeclaration();
|
||||
return statement();
|
||||
} catch(ParseError error){
|
||||
synchronize();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
46
src/jlox/stmt.d
Normal file
46
src/jlox/stmt.d
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
module jlox.stmt;
|
||||
|
||||
import std.conv;
|
||||
import std.stdio;
|
||||
import std.meta : AliasSeq;
|
||||
|
||||
import jlox.token;
|
||||
import jlox.tokentype;
|
||||
import jlox.util;
|
||||
import jlox.expr;
|
||||
|
||||
abstract class Stmt{
|
||||
interface Visitor(R){
|
||||
R visit(Block expr);
|
||||
R visit(Expression expr);
|
||||
R visit(Print expr);
|
||||
R visit(Var expr);
|
||||
}
|
||||
private alias rTypes = AliasSeq!(void);
|
||||
static foreach(T; rTypes)
|
||||
abstract T accept(Visitor!T visitor);
|
||||
private template defCtorAndAccept(){
|
||||
mixin defaultCtor;
|
||||
static foreach(T; rTypes)
|
||||
override T accept(Visitor!T visitor) => visitor.visit(this);
|
||||
}
|
||||
|
||||
static class Block : typeof(this){
|
||||
Stmt[] statements;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
static class Expression : typeof(this){
|
||||
Expr expression;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
static class Print : typeof(this){
|
||||
Expr expression;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
static class Var : typeof(this){
|
||||
Token name;
|
||||
Expr initialiser;
|
||||
mixin defCtorAndAccept;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -13,8 +13,20 @@ private struct Value{
|
|||
bool nil = false;
|
||||
}
|
||||
alias TValue = TaggedUnion!Value;
|
||||
string tvalueToString(TValue val){
|
||||
final switch(val.kind){
|
||||
case TValue.Kind.str:
|
||||
return val.strValue;
|
||||
case TValue.Kind.dbl:
|
||||
return val.dblValue.to!string;
|
||||
case TValue.Kind.bln:
|
||||
return val.blnValue ? "true" : "false";
|
||||
case TValue.Kind.nil:
|
||||
return "nil";
|
||||
}
|
||||
}
|
||||
|
||||
class Token {
|
||||
class Token{
|
||||
TokenType type;
|
||||
string lexeme;
|
||||
TValue literal;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue