diff --git a/dub.sdl b/dub.sdl index d956472..c3201b0 100644 --- a/dub.sdl +++ b/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" } + diff --git a/src/clox/chunk.d b/src/clox/chunk.d index d473e93..0a2a417 100644 --- a/src/clox/chunk.d +++ b/src/clox/chunk.d @@ -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); diff --git a/src/clox/compiler.d b/src/clox/compiler.d index 8edc60b..7a4e3ee 100644 --- a/src/clox/compiler.d +++ b/src/clox/compiler.d @@ -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; } diff --git a/src/clox/dbg.d b/src/clox/dbg.d index 19d4d27..4d469dd 100644 --- a/src/clox/dbg.d +++ b/src/clox/dbg.d @@ -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; } } diff --git a/src/clox/obj.d b/src/clox/obj.d index 40f211a..a6f7c09 100644 --- a/src/clox/obj.d +++ b/src/clox/obj.d @@ -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); } } diff --git a/src/clox/parser.d b/src/clox/parser.d index cb943c6..48f1bb9 100644 --- a/src/clox/parser.d +++ b/src/clox/parser.d @@ -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); } diff --git a/src/clox/parserules.d b/src/clox/parserules.d index 9e725c4..55fcaa8 100644 --- a/src/clox/parserules.d +++ b/src/clox/parserules.d @@ -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), diff --git a/src/clox/value.d b/src/clox/value.d index ff985a5..d0a8e98 100644 --- a/src/clox/value.d +++ b/src/clox/value.d @@ -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); } } diff --git a/src/clox/vm.d b/src/clox/vm.d index 4d4cefa..d77a1fc 100644 --- a/src/clox/vm.d +++ b/src/clox/vm.d @@ -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; } }