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"
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"
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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),

View file

@ -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);
}
}

View file

@ -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;
}
}