Global Variables 21

This commit is contained in:
nazrin 2025-06-07 08:05:31 +00:00
parent a7b7348f61
commit 4f2211eb72
9 changed files with 178 additions and 36 deletions

10
dub.sdl
View file

@ -7,17 +7,23 @@ dependency "commandr" version="~>1.1.0"
dependency "colored" version="~>0.0.33" dependency "colored" version="~>0.0.33"
targetType "executable" targetType "executable"
sourcePaths sourcePaths
configuration "clox" { configuration "clox" {
buildOptions "betterC"
/* debugVersions "memTrace" */
debugVersions "traceExec" debugVersions "traceExec"
/* debugVersions "printCode" */
dflags "-checkaction=C"
libs "libbacktrace" libs "libbacktrace"
debugVersions "printCode"
targetType "executable" targetType "executable"
sourcePaths "src/clox" "src/common" sourcePaths "src/clox"
buildRequirements "requireBoundsCheck" "requireContracts" buildRequirements "requireBoundsCheck" "requireContracts"
} }
configuration "jlox" { configuration "jlox" {
targetType "executable" targetType "executable"
sourcePaths "src/jlox" "src/common" sourcePaths "src/jlox" "src/common"
versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" "LoxPrintMultiple" versions "LoxConcatNonStrings" "LoxExtraNativeFuncs" "LoxPrintMultiple"
buildRequirements "requireBoundsCheck" "requireContracts" buildRequirements "requireBoundsCheck" "requireContracts"
} }

View file

@ -1,5 +1,7 @@
module clox.chunk; module clox.chunk;
import std.algorithm.searching;
import clox.memory; import clox.memory;
import clox.value; import clox.value;
@ -13,6 +15,11 @@ enum OpCode : ubyte{
@(OpColour("255", "200", "100")) True, @(OpColour("255", "200", "100")) True,
@(OpColour("255", "200", "100")) False, @(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")) Equal,
@(OpColour("255", "100", "100")) Greater, @(OpColour("255", "100", "100")) Greater,
@(OpColour("255", "100", "100")) Less, @(OpColour("255", "100", "100")) Less,
@ -27,6 +34,7 @@ enum OpCode : ubyte{
@(OpColour("200", "100", "100")) Not, @(OpColour("200", "100", "100")) Not,
@(OpColour("100", "100", "200")) Negate, @(OpColour("100", "100", "200")) Negate,
@(OpColour("000", "200", "100")) Print,
@(OpColour("000", "200", "100")) Return, @(OpColour("000", "200", "100")) Return,
} }
@ -55,8 +63,12 @@ struct Chunk{
count++; count++;
} }
int addConstant(Value value){ int addConstant(Value value){
constants.write(value); int index = cast(int)constants.values[0 .. constants.count].countUntil(value);
return constants.count - 1; if(index >= 0)
return index;
assert(constants.count <= ubyte.max);
constants.write(value);
return constants.count - 1;
} }
void free(){ void free(){
FREE_ARRAY!ubyte(code, capacity); FREE_ARRAY!ubyte(code, capacity);

View file

@ -21,8 +21,9 @@ struct Compiler{
compilingChunk = chunk; compilingChunk = chunk;
parser.advance(); parser.advance();
parser.expression(); while(!parser.match(Token.Type.EOF)){
parser.consume(Token.Type.EOF, "Expect end of expression."); parser.declaration();
}
end(); end();
return !parser.hadError; return !parser.hadError;
} }

View file

@ -10,15 +10,16 @@ import clox.util;
private int simpleInstruction(alias OpCode op)(const char* name, int offset){ private int simpleInstruction(alias OpCode op)(const char* name, int offset){
enum c = getUDAs!(op, OpColour)[0]; 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; return offset + 1;
} }
private int constantInstruction(alias OpCode op)(const char* name, Chunk* chunk, int offset){ private int constantInstruction(alias OpCode op)(const char* name, Chunk* chunk, int offset){
enum c = getUDAs!(op, OpColour)[0]; enum c = getUDAs!(op, OpColour)[0];
ubyte constant = chunk.code[offset + 1]; ubyte constant = chunk.code[offset + 1];
printf(ctEval!(colour!("%-16s", c.r, c.g, c.b) ~ " %4d '").ptr, name, constant); printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Black)).ptr, name, constant);
chunk.constants.values[constant].print(); int pl = chunk.constants.values[constant].print();
printf("'\n"); foreach(i; 0 .. 16-pl)
printf(" ");
return offset + 2; return offset + 2;
} }
@ -26,11 +27,12 @@ void disassembleChunk(Chunk* chunk, const char* name = "chunk"){
printf(" == %s ==\n", name); printf(" == %s ==\n", name);
for(int offset = 0; offset < chunk.count;){ for(int offset = 0; offset < chunk.count;){
offset = disassembleInstruction(chunk, offset); offset = disassembleInstruction(chunk, offset);
printf("\n");
} }
} }
int disassembleInstruction(Chunk* chunk, int offset){ int disassembleInstruction(Chunk* chunk, int offset){
printf("%04d ", offset); printf("%5d ", offset);
if(offset > 0 && chunk.lines[offset] == chunk.lines[offset - 1]){ if(offset > 0 && chunk.lines[offset] == chunk.lines[offset - 1]){
printf(colour!(" | ", Colour.Black).ptr); printf(colour!(" | ", Colour.Black).ptr);
} else { } else {
@ -39,14 +41,14 @@ int disassembleInstruction(Chunk* chunk, int offset){
ubyte instruction = chunk.code[offset]; ubyte instruction = chunk.code[offset];
switch(instruction){ switch(instruction){
static foreach(e; EnumMembers!OpCode){ static foreach(e; EnumMembers!OpCode){
static if(e == OpCode.Constant){ static if(e == OpCode.Constant || e == OpCode.DefineGlobal || e == OpCode.GetGlobal){
case OpCode.Constant: return constantInstruction!(OpCode.Constant)("OP_CONSTANT", chunk, offset); case e: return constantInstruction!(e)(e.stringof, chunk, offset);
} else { } else {
case e: case e:
return simpleInstruction!e(e.stringof, offset); return simpleInstruction!e(e.stringof, offset);
} }
} }
default: printf("Unknown opcode %d\n", instruction); default: printf("Unknown opcode %d", instruction);
return offset + 1; return offset + 1;
} }
} }

View file

@ -22,9 +22,9 @@ struct Obj{
static if(type == Type.None) return &this; static if(type == Type.None) return &this;
} }
void print() const{ int print() const{
final switch(type){ 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); case Type.None: assert(0);
} }
} }

View file

@ -8,6 +8,8 @@ import clox.compiler;
import clox.chunk; import clox.chunk;
import clox.parserules; import clox.parserules;
import clox.util; import clox.util;
import clox.object;
import clox.value;
struct Parser{ struct Parser{
Compiler* compiler; Compiler* compiler;
@ -33,10 +35,72 @@ struct Parser{
} }
errorAtCurrent(message); 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(){ void expression(){
parsePrecedence(Precedence.Assignment); 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){ void parsePrecedence(Precedence precedence){
advance(); advance();
ParseFn prefixRule = ParseRule.get(previous.type).prefix; ParseFn prefixRule = ParseRule.get(previous.type).prefix;
@ -44,14 +108,37 @@ struct Parser{
error("Expect expression."); error("Expect expression.");
return; return;
} }
prefixRule(compiler); bool canAssign = precedence <= Precedence.Assignment;
prefixRule(compiler, canAssign);
while(precedence <= ParseRule.get(current.type).precedence){ while(precedence <= ParseRule.get(current.type).precedence){
advance(); advance();
ParseFn infixRule = ParseRule.get(previous.type).infix; ParseFn infixRule = ParseRule.get(previous.type).infix;
infixRule(compiler); 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){ void errorAtCurrent(in char* message){
errorAt(current, message); errorAt(current, message);
} }

View file

@ -8,13 +8,13 @@ import clox.emitter;
import clox.value; import clox.value;
import clox.object; 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.expression();
compiler.parser.consume(Token.Type.RightParen, "Expect ')' after 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; Token operator = compiler.parser.previous;
compiler.parser.parsePrecedence(Precedence.Unary); compiler.parser.parsePrecedence(Precedence.Unary);
compiler.emitter.setLine(operator.line); compiler.emitter.setLine(operator.line);
@ -24,7 +24,7 @@ private void unary(Compiler* compiler){
default: assert(0); default: assert(0);
} }
} }
private void binary(Compiler* compiler){ private void binary(Compiler* compiler, bool _){
Token operator = compiler.parser.previous; Token operator = compiler.parser.previous;
immutable(ParseRule)* rule = ParseRule.get(operator.type); immutable(ParseRule)* rule = ParseRule.get(operator.type);
compiler.parser.parsePrecedence(cast(Precedence)(rule.precedence + 1)); 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; import core.stdc.stdlib : strtod;
Token token = compiler.parser.previous; Token token = compiler.parser.previous;
double value = strtod(token.lexeme.ptr, null); double value = strtod(token.lexeme.ptr, null);
compiler.emitter.setLine(token.line); compiler.emitter.setLine(token.line);
compiler.emitter.emitConstant(Value.from(value)); compiler.emitter.emitConstant(Value.from(value));
} }
private void literal(Compiler* compiler){ private void literal(Compiler* compiler, bool _){
Token token = compiler.parser.previous; Token token = compiler.parser.previous;
compiler.emitter.setLine(token.line); compiler.emitter.setLine(token.line);
switch(token.type){ switch(token.type){
@ -65,13 +65,16 @@ private void literal(Compiler* compiler){
default: assert(0); default: assert(0);
} }
} }
private void strlit(Compiler* compiler){ private void strlit(Compiler* compiler, bool _){
Token token = compiler.parser.previous; Token token = compiler.parser.previous;
const char[] str = token.lexeme[1 .. $-1]; const char[] str = token.lexeme[1 .. $-1];
Obj.String* strObj = Obj.String.copy(str); Obj.String* strObj = Obj.String.copy(str);
compiler.emitter.setLine(token.line); compiler.emitter.setLine(token.line);
compiler.emitter.emitConstant(Value.from(strObj)); compiler.emitter.emitConstant(Value.from(strObj));
} }
private void variable(Compiler* compiler, bool canAssign){
compiler.parser.namedVariable(compiler.parser.previous, canAssign);
}
struct ParseRule{ struct ParseRule{
@ -117,7 +120,7 @@ immutable ParseRule[Token.Type.max+1] rules = [
Token.Type.GreaterEqual : ParseRule(null, &binary, Precedence.Comparison), Token.Type.GreaterEqual : ParseRule(null, &binary, Precedence.Comparison),
Token.Type.Less : ParseRule(null, &binary, Precedence.Comparison), Token.Type.Less : ParseRule(null, &binary, Precedence.Comparison),
Token.Type.LessEqual : 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.String : ParseRule(&strlit, null, Precedence.None),
Token.Type.Number : ParseRule(&number, null, Precedence.None), Token.Type.Number : ParseRule(&number, null, Precedence.None),
Token.Type.And : ParseRule(null, null, Precedence.None), Token.Type.And : ParseRule(null, null, Precedence.None),

View file

@ -60,12 +60,12 @@ struct Value{
case Type.None: assert(0); case Type.None: assert(0);
} }
} }
void print() const{ int print() const{
final switch(type){ final switch(type){
case Type.Number: printf("%g", asNumber); break; case Type.Number: return printf("%g", asNumber);
case Type.Bool: printf(asBoolean ? "true" : "false"); break; case Type.Bool: return printf(asBoolean ? "true" : "false");
case Type.Nil: printf("nil"); break; case Type.Nil: return printf("nil");
case Type.Obj: asObj.print(); break; case Type.Obj: return asObj.print();
case Type.None: assert(0); case Type.None: assert(0);
} }
} }

View file

@ -3,6 +3,7 @@ module clox.vm;
import core.stdc.stdio; import core.stdc.stdio;
import clox.chunk; import clox.chunk;
import clox.util;
import clox.dbg; import clox.dbg;
import clox.object; import clox.object;
import clox.value; import clox.value;
@ -18,16 +19,19 @@ struct VM{
Chunk* chunk; Chunk* chunk;
ubyte* ip; ubyte* ip;
Stack!(Value, 256) stack; Stack!(Value, 256) stack;
Table globals;
Table strings; Table strings;
Obj* objects; Obj* objects;
enum InterpretResult{ Ok, CompileError, RuntimeError } enum InterpretResult{ Ok, CompileError, RuntimeError }
void initialise(){ void initialise(){
stack.reset(); stack.reset();
strings.initialise(); strings.initialise();
globals.initialise();
} }
void free(){ void free(){
freeObjects(); freeObjects();
strings.free(); strings.free();
globals.free();
} }
InterpretResult interpret(const char* source){ InterpretResult interpret(const char* source){
Chunk cnk; Chunk cnk;
@ -53,7 +57,8 @@ struct VM{
InterpretResult run(){ InterpretResult run(){
ubyte readByte() => *vm.ip++; ubyte readByte() => *vm.ip++;
auto readConstant() => chunk.constants.values[readByte()]; 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 checkBinaryType(alias type)() => peek(0).isType(type) && peek(1).isType(type);
bool checkSameType()() => peek(0).type == peek(1).type; bool checkSameType()() => peek(0).type == peek(1).type;
int binaryOp(string op, alias check, string checkMsg, alias pre)(){ int binaryOp(string op, alias check, string checkMsg, alias pre)(){
@ -68,14 +73,14 @@ struct VM{
} }
while(true){ while(true){
debug(traceExec){ debug(traceExec){
printf(" "); disassembleInstruction(vm.chunk, cast(int)(vm.ip - vm.chunk.code));
printf(" ");
foreach(slot; stack.live){ foreach(slot; stack.live){
printf("[ "); printf("");
slot.print(); slot.print();
printf(" ]"); printf(colour!(", ", Colour.Black).ptr);
} }
printf("\n"); printf("\n");
disassembleInstruction(vm.chunk, cast(int)(vm.ip - vm.chunk.code));
} }
OpCode instruction; OpCode instruction;
opSwitch: final switch(instruction = cast(OpCode)readByte()){ opSwitch: final switch(instruction = cast(OpCode)readByte()){
@ -86,6 +91,30 @@ struct VM{
case OpCode.Nil: stack.push(Value.nil); break; case OpCode.Nil: stack.push(Value.nil); break;
case OpCode.True: stack.push(Value.from(true)); break; case OpCode.True: stack.push(Value.from(true)); break;
case OpCode.False: stack.push(Value.from(false)); 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: "!=" ]){ static foreach(k, op; [ OpCode.Equal: "==", OpCode.NotEqual: "!=" ]){
case k: case k:
@ -125,9 +154,11 @@ struct VM{
} }
stack.push(Value.from(-stack.pop().asNumber)); stack.push(Value.from(-stack.pop().asNumber));
break; break;
case OpCode.Return: case OpCode.Print:
stack.pop().print(); stack.pop().print();
printf("\n"); printf("\n");
break;
case OpCode.Return:
return InterpretResult.Ok; return InterpretResult.Ok;
} }
} }