Global Variables 21
This commit is contained in:
parent
a7b7348f61
commit
4f2211eb72
9 changed files with 178 additions and 36 deletions
10
dub.sdl
10
dub.sdl
|
|
@ -7,17 +7,23 @@ dependency "commandr" version="~>1.1.0"
|
|||
dependency "colored" version="~>0.0.33"
|
||||
targetType "executable"
|
||||
sourcePaths
|
||||
|
||||
configuration "clox" {
|
||||
buildOptions "betterC"
|
||||
/* debugVersions "memTrace" */
|
||||
debugVersions "traceExec"
|
||||
/* debugVersions "printCode" */
|
||||
dflags "-checkaction=C"
|
||||
libs "libbacktrace"
|
||||
debugVersions "printCode"
|
||||
targetType "executable"
|
||||
sourcePaths "src/clox" "src/common"
|
||||
sourcePaths "src/clox"
|
||||
buildRequirements "requireBoundsCheck" "requireContracts"
|
||||
}
|
||||
|
||||
configuration "jlox" {
|
||||
targetType "executable"
|
||||
sourcePaths "src/jlox" "src/common"
|
||||
versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" "LoxPrintMultiple"
|
||||
buildRequirements "requireBoundsCheck" "requireContracts"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
module clox.chunk;
|
||||
|
||||
import std.algorithm.searching;
|
||||
|
||||
import clox.memory;
|
||||
import clox.value;
|
||||
|
||||
|
|
@ -13,6 +15,11 @@ enum OpCode : ubyte{
|
|||
@(OpColour("255", "200", "100")) True,
|
||||
@(OpColour("255", "200", "100")) False,
|
||||
|
||||
@(OpColour("000", "200", "100")) Pop,
|
||||
@(OpColour("000", "200", "150")) GetGlobal,
|
||||
@(OpColour("000", "200", "150")) DefineGlobal,
|
||||
@(OpColour("000", "200", "150")) SetGlobal,
|
||||
|
||||
@(OpColour("255", "100", "100")) Equal,
|
||||
@(OpColour("255", "100", "100")) Greater,
|
||||
@(OpColour("255", "100", "100")) Less,
|
||||
|
|
@ -27,6 +34,7 @@ enum OpCode : ubyte{
|
|||
|
||||
@(OpColour("200", "100", "100")) Not,
|
||||
@(OpColour("100", "100", "200")) Negate,
|
||||
@(OpColour("000", "200", "100")) Print,
|
||||
@(OpColour("000", "200", "100")) Return,
|
||||
}
|
||||
|
||||
|
|
@ -55,8 +63,12 @@ struct Chunk{
|
|||
count++;
|
||||
}
|
||||
int addConstant(Value value){
|
||||
constants.write(value);
|
||||
return constants.count - 1;
|
||||
int index = cast(int)constants.values[0 .. constants.count].countUntil(value);
|
||||
if(index >= 0)
|
||||
return index;
|
||||
assert(constants.count <= ubyte.max);
|
||||
constants.write(value);
|
||||
return constants.count - 1;
|
||||
}
|
||||
void free(){
|
||||
FREE_ARRAY!ubyte(code, capacity);
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ struct Compiler{
|
|||
compilingChunk = chunk;
|
||||
|
||||
parser.advance();
|
||||
parser.expression();
|
||||
parser.consume(Token.Type.EOF, "Expect end of expression.");
|
||||
while(!parser.match(Token.Type.EOF)){
|
||||
parser.declaration();
|
||||
}
|
||||
end();
|
||||
return !parser.hadError;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,16 @@ import clox.util;
|
|||
|
||||
private int simpleInstruction(alias OpCode op)(const char* name, int offset){
|
||||
enum c = getUDAs!(op, OpColour)[0];
|
||||
printf(colour!("%s\n", c.r, c.g, c.b).ptr, name);
|
||||
printf(colour!("%-27s ", c.r, c.g, c.b).ptr, name);
|
||||
return offset + 1;
|
||||
}
|
||||
private int constantInstruction(alias OpCode op)(const char* name, Chunk* chunk, int offset){
|
||||
enum c = getUDAs!(op, OpColour)[0];
|
||||
ubyte constant = chunk.code[offset + 1];
|
||||
printf(ctEval!(colour!("%-16s", c.r, c.g, c.b) ~ " %4d '").ptr, name, constant);
|
||||
chunk.constants.values[constant].print();
|
||||
printf("'\n");
|
||||
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Black)).ptr, name, constant);
|
||||
int pl = chunk.constants.values[constant].print();
|
||||
foreach(i; 0 .. 16-pl)
|
||||
printf(" ");
|
||||
return offset + 2;
|
||||
}
|
||||
|
||||
|
|
@ -26,11 +27,12 @@ void disassembleChunk(Chunk* chunk, const char* name = "chunk"){
|
|||
printf(" == %s ==\n", name);
|
||||
for(int offset = 0; offset < chunk.count;){
|
||||
offset = disassembleInstruction(chunk, offset);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
int disassembleInstruction(Chunk* chunk, int offset){
|
||||
printf("%04d ", offset);
|
||||
printf("%5d ", offset);
|
||||
if(offset > 0 && chunk.lines[offset] == chunk.lines[offset - 1]){
|
||||
printf(colour!(" | ", Colour.Black).ptr);
|
||||
} else {
|
||||
|
|
@ -39,14 +41,14 @@ int disassembleInstruction(Chunk* chunk, int offset){
|
|||
ubyte instruction = chunk.code[offset];
|
||||
switch(instruction){
|
||||
static foreach(e; EnumMembers!OpCode){
|
||||
static if(e == OpCode.Constant){
|
||||
case OpCode.Constant: return constantInstruction!(OpCode.Constant)("OP_CONSTANT", chunk, offset);
|
||||
static if(e == OpCode.Constant || e == OpCode.DefineGlobal || e == OpCode.GetGlobal){
|
||||
case e: return constantInstruction!(e)(e.stringof, chunk, offset);
|
||||
} else {
|
||||
case e:
|
||||
return simpleInstruction!e(e.stringof, offset);
|
||||
}
|
||||
}
|
||||
default: printf("Unknown opcode %d\n", instruction);
|
||||
default: printf("Unknown opcode %d", instruction);
|
||||
return offset + 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ struct Obj{
|
|||
static if(type == Type.None) return &this;
|
||||
}
|
||||
|
||||
void print() const{
|
||||
int print() const{
|
||||
final switch(type){
|
||||
case Type.String: printf(`"%s"`, asString.chars.ptr); break;
|
||||
case Type.String: return printf(`"%s"`, asString.chars.ptr); break;
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import clox.compiler;
|
|||
import clox.chunk;
|
||||
import clox.parserules;
|
||||
import clox.util;
|
||||
import clox.object;
|
||||
import clox.value;
|
||||
|
||||
struct Parser{
|
||||
Compiler* compiler;
|
||||
|
|
@ -33,10 +35,72 @@ struct Parser{
|
|||
}
|
||||
errorAtCurrent(message);
|
||||
}
|
||||
bool check(Token.Type type) => current.type == type;
|
||||
bool match(Token.Type type){
|
||||
if(!check(type))
|
||||
return false;
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
|
||||
void expression(){
|
||||
parsePrecedence(Precedence.Assignment);
|
||||
}
|
||||
void expressionStatement(){
|
||||
expression();
|
||||
consume(Token.Type.Semicolon, "Expect ';' after expression.");
|
||||
compiler.emitter.emit(OpCode.Pop);
|
||||
}
|
||||
void varDeclaration(){
|
||||
ubyte global = parseVariable("Expect variable name.");
|
||||
if(match(Token.Type.Equal))
|
||||
expression();
|
||||
else
|
||||
compiler.emitter.emit(OpCode.Nil);
|
||||
consume(Token.Type.Semicolon, "Expect ';' after variable declaration.");
|
||||
defineVariable(global);
|
||||
}
|
||||
void declaration(){
|
||||
if(match(Token.Type.Var))
|
||||
varDeclaration();
|
||||
else
|
||||
statement();
|
||||
if(compiler.parser.panicMode)
|
||||
synchronize();
|
||||
}
|
||||
void statement(){
|
||||
if(match(Token.Type.Print))
|
||||
printStatement();
|
||||
else
|
||||
expressionStatement();
|
||||
}
|
||||
void printStatement(){
|
||||
expression();
|
||||
consume(Token.Type.Semicolon, "Expect ';' after value.");
|
||||
compiler.emitter.emit(OpCode.Print);
|
||||
}
|
||||
|
||||
void defineVariable(ubyte global){
|
||||
compiler.emitter.emit(OpCode.DefineGlobal, global);
|
||||
}
|
||||
ubyte parseVariable(const char* errorMessage){
|
||||
consume(Token.Type.Identifier, errorMessage);
|
||||
return identifierConstant(&previous);
|
||||
}
|
||||
ubyte identifierConstant(Token* name){
|
||||
Value nameVal = Value.from(Obj.String.copy(name.lexeme));
|
||||
return cast(ubyte)compiler.emitter.makeConstant(nameVal);
|
||||
}
|
||||
void namedVariable(Token name, bool canAssign){
|
||||
ubyte arg = compiler.parser.identifierConstant(&name);
|
||||
if(canAssign && match(Token.Type.Equal)){
|
||||
expression();
|
||||
compiler.emitter.emit(OpCode.SetGlobal, arg);
|
||||
} else {
|
||||
compiler.emitter.emit(OpCode.GetGlobal, arg);
|
||||
}
|
||||
}
|
||||
|
||||
void parsePrecedence(Precedence precedence){
|
||||
advance();
|
||||
ParseFn prefixRule = ParseRule.get(previous.type).prefix;
|
||||
|
|
@ -44,14 +108,37 @@ struct Parser{
|
|||
error("Expect expression.");
|
||||
return;
|
||||
}
|
||||
prefixRule(compiler);
|
||||
bool canAssign = precedence <= Precedence.Assignment;
|
||||
prefixRule(compiler, canAssign);
|
||||
while(precedence <= ParseRule.get(current.type).precedence){
|
||||
advance();
|
||||
ParseFn infixRule = ParseRule.get(previous.type).infix;
|
||||
infixRule(compiler);
|
||||
}
|
||||
if(canAssign && match(Token.Type.Equal))
|
||||
error("Invalid assignment target.");
|
||||
}
|
||||
|
||||
void synchronize(){
|
||||
panicMode = false;
|
||||
while(current.type != Token.Type.EOF){
|
||||
if(previous.type == Token.Type.Semicolon)
|
||||
return;
|
||||
switch(current.type){
|
||||
case Token.Type.Class:
|
||||
case Token.Type.Fun:
|
||||
case Token.Type.Var:
|
||||
case Token.Type.For:
|
||||
case Token.Type.If:
|
||||
case Token.Type.While:
|
||||
case Token.Type.Print:
|
||||
case Token.Type.Return:
|
||||
return;
|
||||
default: break;
|
||||
}
|
||||
advance();
|
||||
}
|
||||
}
|
||||
void errorAtCurrent(in char* message){
|
||||
errorAt(current, message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import clox.emitter;
|
|||
import clox.value;
|
||||
import clox.object;
|
||||
|
||||
alias ParseFn = void function(Compiler* compiler);
|
||||
alias ParseFn = void function(Compiler* compiler, bool canAssign = false);
|
||||
|
||||
private void grouping(Compiler* compiler){
|
||||
private void grouping(Compiler* compiler, bool _){
|
||||
compiler.parser.expression();
|
||||
compiler.parser.consume(Token.Type.RightParen, "Expect ')' after expression.");
|
||||
}
|
||||
private void unary(Compiler* compiler){
|
||||
private void unary(Compiler* compiler, bool _){
|
||||
Token operator = compiler.parser.previous;
|
||||
compiler.parser.parsePrecedence(Precedence.Unary);
|
||||
compiler.emitter.setLine(operator.line);
|
||||
|
|
@ -24,7 +24,7 @@ private void unary(Compiler* compiler){
|
|||
default: assert(0);
|
||||
}
|
||||
}
|
||||
private void binary(Compiler* compiler){
|
||||
private void binary(Compiler* compiler, bool _){
|
||||
Token operator = compiler.parser.previous;
|
||||
immutable(ParseRule)* rule = ParseRule.get(operator.type);
|
||||
compiler.parser.parsePrecedence(cast(Precedence)(rule.precedence + 1));
|
||||
|
|
@ -48,14 +48,14 @@ private void binary(Compiler* compiler){
|
|||
}
|
||||
}
|
||||
|
||||
private void number(Compiler* compiler){
|
||||
private void number(Compiler* compiler, bool _){
|
||||
import core.stdc.stdlib : strtod;
|
||||
Token token = compiler.parser.previous;
|
||||
double value = strtod(token.lexeme.ptr, null);
|
||||
compiler.emitter.setLine(token.line);
|
||||
compiler.emitter.emitConstant(Value.from(value));
|
||||
}
|
||||
private void literal(Compiler* compiler){
|
||||
private void literal(Compiler* compiler, bool _){
|
||||
Token token = compiler.parser.previous;
|
||||
compiler.emitter.setLine(token.line);
|
||||
switch(token.type){
|
||||
|
|
@ -65,13 +65,16 @@ private void literal(Compiler* compiler){
|
|||
default: assert(0);
|
||||
}
|
||||
}
|
||||
private void strlit(Compiler* compiler){
|
||||
private void strlit(Compiler* compiler, bool _){
|
||||
Token token = compiler.parser.previous;
|
||||
const char[] str = token.lexeme[1 .. $-1];
|
||||
Obj.String* strObj = Obj.String.copy(str);
|
||||
compiler.emitter.setLine(token.line);
|
||||
compiler.emitter.emitConstant(Value.from(strObj));
|
||||
}
|
||||
private void variable(Compiler* compiler, bool canAssign){
|
||||
compiler.parser.namedVariable(compiler.parser.previous, canAssign);
|
||||
}
|
||||
|
||||
|
||||
struct ParseRule{
|
||||
|
|
@ -117,7 +120,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
|
|||
Token.Type.GreaterEqual : ParseRule(null, &binary, Precedence.Comparison),
|
||||
Token.Type.Less : ParseRule(null, &binary, Precedence.Comparison),
|
||||
Token.Type.LessEqual : ParseRule(null, &binary, Precedence.Comparison),
|
||||
Token.Type.Identifier : ParseRule(null, null, Precedence.None),
|
||||
Token.Type.Identifier : ParseRule(&variable, null, Precedence.None),
|
||||
Token.Type.String : ParseRule(&strlit, null, Precedence.None),
|
||||
Token.Type.Number : ParseRule(&number, null, Precedence.None),
|
||||
Token.Type.And : ParseRule(null, null, Precedence.None),
|
||||
|
|
|
|||
|
|
@ -60,12 +60,12 @@ struct Value{
|
|||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
void print() const{
|
||||
int print() const{
|
||||
final switch(type){
|
||||
case Type.Number: printf("%g", asNumber); break;
|
||||
case Type.Bool: printf(asBoolean ? "true" : "false"); break;
|
||||
case Type.Nil: printf("nil"); break;
|
||||
case Type.Obj: asObj.print(); break;
|
||||
case Type.Number: return printf("%g", asNumber);
|
||||
case Type.Bool: return printf(asBoolean ? "true" : "false");
|
||||
case Type.Nil: return printf("nil");
|
||||
case Type.Obj: return asObj.print();
|
||||
case Type.None: assert(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ module clox.vm;
|
|||
import core.stdc.stdio;
|
||||
|
||||
import clox.chunk;
|
||||
import clox.util;
|
||||
import clox.dbg;
|
||||
import clox.object;
|
||||
import clox.value;
|
||||
|
|
@ -18,16 +19,19 @@ struct VM{
|
|||
Chunk* chunk;
|
||||
ubyte* ip;
|
||||
Stack!(Value, 256) stack;
|
||||
Table globals;
|
||||
Table strings;
|
||||
Obj* objects;
|
||||
enum InterpretResult{ Ok, CompileError, RuntimeError }
|
||||
void initialise(){
|
||||
stack.reset();
|
||||
strings.initialise();
|
||||
globals.initialise();
|
||||
}
|
||||
void free(){
|
||||
freeObjects();
|
||||
strings.free();
|
||||
globals.free();
|
||||
}
|
||||
InterpretResult interpret(const char* source){
|
||||
Chunk cnk;
|
||||
|
|
@ -53,7 +57,8 @@ struct VM{
|
|||
InterpretResult run(){
|
||||
ubyte readByte() => *vm.ip++;
|
||||
auto readConstant() => chunk.constants.values[readByte()];
|
||||
Value peek(int distance) => stack.top[-1 - distance];
|
||||
Obj.String* readString() => readConstant().asObj.asString;
|
||||
Value peek(int distance = 0) => stack.top[-1 - distance];
|
||||
bool checkBinaryType(alias type)() => peek(0).isType(type) && peek(1).isType(type);
|
||||
bool checkSameType()() => peek(0).type == peek(1).type;
|
||||
int binaryOp(string op, alias check, string checkMsg, alias pre)(){
|
||||
|
|
@ -68,14 +73,14 @@ struct VM{
|
|||
}
|
||||
while(true){
|
||||
debug(traceExec){
|
||||
printf(" ");
|
||||
disassembleInstruction(vm.chunk, cast(int)(vm.ip - vm.chunk.code));
|
||||
printf(" ");
|
||||
foreach(slot; stack.live){
|
||||
printf("[ ");
|
||||
printf("");
|
||||
slot.print();
|
||||
printf(" ]");
|
||||
printf(colour!(", ", Colour.Black).ptr);
|
||||
}
|
||||
printf("\n");
|
||||
disassembleInstruction(vm.chunk, cast(int)(vm.ip - vm.chunk.code));
|
||||
}
|
||||
OpCode instruction;
|
||||
opSwitch: final switch(instruction = cast(OpCode)readByte()){
|
||||
|
|
@ -86,6 +91,30 @@ struct VM{
|
|||
case OpCode.Nil: stack.push(Value.nil); break;
|
||||
case OpCode.True: stack.push(Value.from(true)); break;
|
||||
case OpCode.False: stack.push(Value.from(false)); break;
|
||||
case OpCode.Pop: stack.pop(); break;
|
||||
|
||||
case OpCode.GetGlobal:
|
||||
Obj.String* name = readString();
|
||||
Value value;
|
||||
if(!globals.get(name, value)){
|
||||
runtimeError("Undefined variable '%s'.", name.chars.ptr);
|
||||
return InterpretResult.RuntimeError;
|
||||
}
|
||||
stack.push(value);
|
||||
break;
|
||||
case OpCode.DefineGlobal:
|
||||
Obj.String* name = readString();
|
||||
globals.set(name, peek(0));
|
||||
stack.pop();
|
||||
break;
|
||||
case OpCode.SetGlobal:
|
||||
Obj.String* name = readString();
|
||||
if(globals.set(name, peek(0))){
|
||||
globals.del(name);
|
||||
runtimeError("Undefined variable '%s'.", name.chars.ptr);
|
||||
return InterpretResult.RuntimeError;
|
||||
}
|
||||
break;
|
||||
|
||||
static foreach(k, op; [ OpCode.Equal: "==", OpCode.NotEqual: "!=" ]){
|
||||
case k:
|
||||
|
|
@ -125,9 +154,11 @@ struct VM{
|
|||
}
|
||||
stack.push(Value.from(-stack.pop().asNumber));
|
||||
break;
|
||||
case OpCode.Return:
|
||||
case OpCode.Print:
|
||||
stack.pop().print();
|
||||
printf("\n");
|
||||
break;
|
||||
case OpCode.Return:
|
||||
return InterpretResult.Ok;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue