Local Variables 22

This commit is contained in:
nazrin 2025-06-07 23:52:16 +00:00
parent 4f2211eb72
commit 72a41e81e6
18 changed files with 1335 additions and 115 deletions

View file

@ -4,6 +4,7 @@ import std.algorithm.searching;
import clox.memory;
import clox.value;
import clox.container.dynarray;
struct OpColour{
string r, g, b;
@ -16,8 +17,10 @@ enum OpCode : ubyte{
@(OpColour("255", "200", "100")) False,
@(OpColour("000", "200", "100")) Pop,
@(OpColour("060", "200", "150")) GetLocal,
@(OpColour("000", "200", "150")) GetGlobal,
@(OpColour("000", "200", "150")) DefineGlobal,
@(OpColour("060", "200", "150")) SetLocal,
@(OpColour("000", "200", "150")) SetGlobal,
@(OpColour("255", "100", "100")) Equal,
@ -39,40 +42,32 @@ enum OpCode : ubyte{
}
struct Chunk{
uint count;
uint capacity;
ubyte* code;
uint* lines;
ValueArray constants;
DynArray!ubyte code;
DynArray!uint lines;
DynArray!Value constants;
void initialise(){
count = 0;
capacity = 0;
code = null;
lines = null;
code.initialise();
lines.initialise();
constants.initialise();
}
void write(ubyte b, uint line = 0){
if(capacity < count + 1){
uint oldCapacity = capacity;
capacity = GROW_CAPACITY(oldCapacity);
code = GROW_ARRAY!ubyte(code, oldCapacity, capacity);
lines = GROW_ARRAY!uint(lines, oldCapacity, capacity);
}
code[count] = b;
lines[count] = line;
count++;
void write(in ubyte[] bytes, uint line = 0){
foreach(b; bytes)
write(b, line);
}
int addConstant(Value value){
int index = cast(int)constants.values[0 .. constants.count].countUntil(value);
void write(ubyte b, uint line = 0){
code ~= b;
lines ~= line;
}
size_t addConstant(Value value){
long index = constants[].countUntil(value);
if(index >= 0)
return index;
assert(constants.count <= ubyte.max);
constants.write(value);
constants ~= value;
return constants.count - 1;
}
void free(){
FREE_ARRAY!ubyte(code, capacity);
FREE_ARRAY!uint(lines, capacity);
code.free();
lines.free();
constants.free();
initialise();
}

View file

@ -7,17 +7,26 @@ import clox.parser;
import clox.chunk;
import clox.emitter;
import clox.dbg;
import clox.container.dynarray;
struct Compiler{
Scanner scanner;
Parser parser;
Emitter emitter;
struct Local{
enum Uninitialised = -1;
Token name;
int depth;
}
DynArray!Local locals;
int scopeDepth;
private Chunk* compilingChunk;
Chunk* currentChunk() => compilingChunk;
bool compile(const(char)* source, Chunk* chunk){
scanner.initialise(source);
parser.initialise(&this);
emitter.initialise(&this);
locals.initialise();
compilingChunk = chunk;
parser.advance();
@ -33,6 +42,7 @@ struct Compiler{
if(!parser.hadError)
currentChunk.disassembleChunk();
}
locals.free();
}
}

View file

@ -0,0 +1,45 @@
module clox.container.dynarray;
import clox.memory;
struct DynArray(T){
size_t count;
size_t capacity;
T* ptr;
void initialise(){
count = 0;
capacity = 0;
ptr = null;
}
void opOpAssign(string op: "~")(in T value){
if(capacity < count + 1){
size_t oldCapacity = capacity;
capacity = GROW_CAPACITY(oldCapacity);
ptr = GROW_ARRAY!T(ptr, oldCapacity, capacity);
}
ptr[count] = value;
count++;
}
auto opSlice(){
assert(ptr || !count);
return ptr[0 .. count];
}
ref auto opIndex(size_t i){
assert(ptr && count);
return ptr[i];
}
size_t opDollar(size_t pos: 0)(){
return count;
}
void free(){
if(ptr)
FREE_ARRAY!T(ptr, capacity);
initialise();
}
auto pop(){
assert(count);
count--;
return this[count-1];
}
}

View file

@ -1,26 +1,30 @@
module clox.container.stack;
struct Stack(T, size_t N){
@nogc: nothrow:
import clox.container.dynarray;
struct Stack(T){
T* top;
T[N] data;
/* invariant{ assert(top <= data.ptr + N); assert(top >= data.ptr); } */
void reset(){
private DynArray!T data;
void initialise(){
data.initialise();
top = data.ptr;
}
void push(T value){
assert(top < data.ptr + N);
debug assert(*top is T.init);
*(top++) = value;
data ~= value;
top = data.ptr + data.count;
}
T pop(){
assert(top > data.ptr);
T t = *(--top);
debug *(top) = T.init;
assert(data.count-- >= 0);
return t;
}
const(T)[] live() const @safe{
return data[0 .. (top - data.ptr)];
ref T opIndex(size_t i) => data[i];
const(T)[] live(){
return data[];
}
void free(){
data.free();
}
}

View file

@ -10,7 +10,7 @@ import clox.memory;
enum TABLE_MAX_LOAD = 0.75;
struct Table{
uint count, capacity;
size_t count, capacity;
Entry* entries;
struct Entry{
Obj.String* key;
@ -25,7 +25,7 @@ struct Table{
FREE_ARRAY!Entry(entries, capacity);
initialise();
}
private void adjustCapacity(uint cap){
private void adjustCapacity(size_t cap){
Entry* ent = ALLOCATE!Entry(cap);
for(int i = 0; i < cap; i++){
ent[i].key = null;
@ -46,7 +46,7 @@ struct Table{
entries = ent;
capacity = cap;
}
private Entry* findEntry(Entry* entries, int capacity, in Obj.String* key){
private Entry* findEntry(Entry* entries, size_t capacity, in Obj.String* key){
uint index = key.hash % capacity;
Entry* tombstone = null;
while(true){
@ -66,7 +66,7 @@ struct Table{
}
bool set(Obj.String* key, Value value){
if(count + 1 > capacity * TABLE_MAX_LOAD){
uint cap = GROW_CAPACITY(capacity);
size_t cap = GROW_CAPACITY(capacity);
adjustCapacity(cap);
}
Entry* entry = findEntry(entries, capacity, key);
@ -121,12 +121,13 @@ struct Table{
}
unittest{
import clox.vm : vm;
Table tbl;
tbl.initialise();
scope(exit)
scope(exit){
tbl.free();
scope(exit)
freeObjects();
vm.free();
}
assert(tbl.count == 0);
@ -146,7 +147,7 @@ unittest{
assert(tbl.count == 1);
foreach(i; 0..25){
Obj.String* str2 = Obj.String.copy("hello");
Obj.String* str2 = Obj.String.copy("hi 2");
tbl.set(str2, Value.from(i));
assert(tbl.get(str2, val));
assert(val.asNumber == i);

View file

@ -0,0 +1,73 @@
module clox.container.varint;
struct VarUint{
import std.bitmanip;
nothrow: @nogc:
uint i;
ubyte len;
private ubyte[4] data;
this(size_t l) @safe {
if(l < 0b1000_0000){
len = 1;
data[0] = (cast(ubyte)l);
return;
}
if(l < 0b0100_0000__0000_0000){
len = 2;
data[0 .. 2] = nativeToBigEndian(cast(ushort)l);
data[0] |= 0b1000_0000;
return;
}
if(l < 0b0010_0000__0000_0000__0000_0000__0000_0000){
len = 4;
data[0 .. 4] = nativeToBigEndian(cast(uint)l);
data[0] |= 0b1100_0000;
return;
}
assert(0);
}
static VarUint read(in ubyte* data){
VarUint v;
ubyte a = data[0];
if((data[0] & 0b1000_0000) == 0){
v.i = a;
v.len = 1;
return v;
}
if((a & 0b0100_0000) == 0){
ubyte[2] d = data[0 .. 2];
d[0] &= 0b0111_1111;
v.i = bigEndianToNative!ushort(d);
v.len = 2;
return v;
}
if((a & 0b0010_0000) == 0){
ubyte[4] d = data[0 .. 4];
d[0] &= 0b0011_1111;
v.i = bigEndianToNative!uint(d);
v.len = 4;
return v;
}
assert(0);
}
ubyte[] bytes(){
return data[0 .. len];
}
}
unittest{
import std.range;
assert(VarUint(5).bytes.length == 1);
assert(VarUint(127).bytes.length == 1);
assert(VarUint(128).bytes.length == 2);
assert(VarUint(536_870_911).bytes.length == 4);
foreach(i; [
0, 1, 2, 5,
150, 127, 128,
536_870_911,
ushort.max * 100
]){
auto vi = VarUint(i);
assert(i == VarUint.read(vi.bytes.ptr).i);
}
}

View file

@ -7,32 +7,35 @@ import std.traits : EnumMembers, hasUDA, getUDAs;
import clox.chunk;
import clox.value;
import clox.util;
import clox.container.varint;
private int simpleInstruction(alias OpCode op)(const char* name, int offset){
import std.stdio;
private long simpleInstruction(alias OpCode op)(const char* name, long offset){
enum c = getUDAs!(op, OpColour)[0];
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){
private long constantInstruction(alias OpCode op)(const char* name, Chunk* chunk, long offset){
VarUint constant = VarUint.read(&chunk.code[offset + 1]);
enum c = getUDAs!(op, OpColour)[0];
ubyte constant = chunk.code[offset + 1];
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Black)).ptr, name, constant);
int pl = chunk.constants.values[constant].print();
printf(ctEval!(colour!("%-14s", c.r, c.g, c.b) ~ colour!(" %4d ", Colour.Black)).ptr, name, constant.i);
long pl = chunk.constants[constant.i].print();
foreach(i; 0 .. 16-pl)
printf(" ");
return offset + 2;
return offset + 1 + constant.len;
}
void disassembleChunk(Chunk* chunk, const char* name = "chunk"){
printf(" == %s ==\n", name);
for(int offset = 0; offset < chunk.count;){
for(long offset = 0; offset < chunk.code.count;){
offset = disassembleInstruction(chunk, offset);
printf("\n");
}
}
int disassembleInstruction(Chunk* chunk, int offset){
printf("%5d ", offset);
long disassembleInstruction(Chunk* chunk, long offset){
printf("%5ld ", offset);
if(offset > 0 && chunk.lines[offset] == chunk.lines[offset - 1]){
printf(colour!(" | ", Colour.Black).ptr);
} else {
@ -48,7 +51,7 @@ int disassembleInstruction(Chunk* chunk, int offset){
return simpleInstruction!e(e.stringof, offset);
}
}
default: printf("Unknown opcode %d", instruction);
default: printf("(Unknown opcode %d)", instruction);
return offset + 1;
}
}

View file

@ -5,6 +5,7 @@ import core.stdc.stdio;
import clox.chunk;
import clox.compiler;
import clox.value;
import clox.container.varint;
struct Emitter{
Compiler* compiler;
@ -13,18 +14,19 @@ struct Emitter{
void initialise(Compiler* compiler){
this.compiler = compiler;
}
void emit(ubyte[] bytes...){
foreach(b; bytes)
void emit(Args...)(Args a){
static foreach(b; a)
compiler.currentChunk.write(b, compiler.parser.previous.line);
}
void emitReturn(){
emit(OpCode.Return);
}
void emitConstant(Value value){
emit(OpCode.Constant, cast(ubyte)makeConstant(value));
size_t c = makeConstant(value);
emit(OpCode.Constant, VarUint(c).bytes);
}
uint makeConstant(Value value){
uint constant = compiler.currentChunk.addConstant(value);
size_t makeConstant(Value value){
size_t constant = compiler.currentChunk.addConstant(value);
return constant;
}
}

View file

@ -18,6 +18,7 @@ int interpretResult(VM.InterpretResult result){
}
int runPrompt(){
vm.isREPL = true;
char[1024 * 4] line;
while(true){
printf(colour!("lox> ", Colour.Green).ptr);

View file

@ -2,7 +2,7 @@ module clox.memory;
import core.stdc.stdlib;
auto GROW_CAPACITY(uint capacity) => capacity < 8 ? 8 : capacity * 2;
auto GROW_CAPACITY(size_t capacity) => capacity < 8 ? 8 : capacity * 2;
auto GROW_ARRAY(T)(T* ptr, size_t oldCount, size_t newCount) => cast(T*)reallocate(ptr, T.sizeof * oldCount, T.sizeof * newCount);
auto FREE_ARRAY(T)(T* ptr, size_t oldCount) => reallocate!T(ptr, T.sizeof * oldCount, 0);
auto FREE(T)(T* ptr) => reallocate(ptr, T.sizeof, 0);

View file

@ -10,6 +10,8 @@ import clox.parserules;
import clox.util;
import clox.object;
import clox.value;
import clox.vm;
import clox.container.varint;
struct Parser{
Compiler* compiler;
@ -48,11 +50,16 @@ struct Parser{
}
void expressionStatement(){
expression();
consume(Token.Type.Semicolon, "Expect ';' after expression.");
compiler.emitter.emit(OpCode.Pop);
if(vm.isREPL){
match(Token.Type.Semicolon);
compiler.emitter.emit(OpCode.Print);
} else {
consume(Token.Type.Semicolon, "Expect ';' after expression.");
compiler.emitter.emit(OpCode.Pop);
}
}
void varDeclaration(){
ubyte global = parseVariable("Expect variable name.");
long global = parseVariable("Expect variable name.");
if(match(Token.Type.Equal))
expression();
else
@ -66,12 +73,16 @@ struct Parser{
else
statement();
if(compiler.parser.panicMode)
synchronize();
synchronise();
}
void statement(){
if(match(Token.Type.Print))
if(match(Token.Type.Print)){
printStatement();
else
} else if(match(Token.Type.LeftBrace)){
beginScope();
block();
endScope();
} else
expressionStatement();
}
void printStatement(){
@ -79,25 +90,87 @@ struct Parser{
consume(Token.Type.Semicolon, "Expect ';' after value.");
compiler.emitter.emit(OpCode.Print);
}
void defineVariable(ubyte global){
compiler.emitter.emit(OpCode.DefineGlobal, global);
void block(){
while(!check(Token.Type.RightBrace) && !check(Token.Type.EOF))
declaration();
consume(Token.Type.RightBrace, "Expect '}' after block.");
}
ubyte parseVariable(const char* errorMessage){
void defineVariable(long global){
if(compiler.scopeDepth > 0){
markInitialised();
return;
}
compiler.emitter.emit(OpCode.DefineGlobal, VarUint(global).bytes);
}
void markInitialised(){
compiler.locals[compiler.locals.count - 1].depth = compiler.scopeDepth;
}
void declareVariable(){
if(compiler.scopeDepth == 0)
return;
Token* name = &compiler.parser.previous;
for(long i = compiler.locals.count - 1; i >= 0; i--){
Compiler.Local* local = &compiler.locals[i];
if(local.depth != Compiler.Local.Uninitialised && local.depth < compiler.scopeDepth)
break;
if(name.lexeme == local.name.lexeme)
error("Already a variable with this name in this scope.");
}
addLocal(*name);
}
long parseVariable(const char* errorMessage){
consume(Token.Type.Identifier, errorMessage);
declareVariable();
if(compiler.scopeDepth > 0)
return 0;
return identifierConstant(&previous);
}
ubyte identifierConstant(Token* name){
long identifierConstant(Token* name){
Value nameVal = Value.from(Obj.String.copy(name.lexeme));
return cast(ubyte)compiler.emitter.makeConstant(nameVal);
return compiler.emitter.makeConstant(nameVal);
}
void namedVariable(Token name, bool canAssign){
ubyte arg = compiler.parser.identifierConstant(&name);
OpCode getOp, setOp;
long arg = resolveLocal(&name);
if(arg != -1){
getOp = OpCode.GetLocal;
setOp = OpCode.SetLocal;
} else {
arg = identifierConstant(&name);
getOp = OpCode.GetGlobal;
setOp = OpCode.SetGlobal;
}
if(canAssign && match(Token.Type.Equal)){
expression();
compiler.emitter.emit(OpCode.SetGlobal, arg);
compiler.emitter.emit(setOp, VarUint(arg).bytes);
} else {
compiler.emitter.emit(OpCode.GetGlobal, arg);
compiler.emitter.emit(getOp, VarUint(arg).bytes);
}
}
long resolveLocal(Token* name){
for(long i = compiler.locals.count - 1; i >= 0; i--){
Compiler.Local* local = &compiler.locals[i];
if(name.lexeme == local.name.lexeme){
if(local.depth == Compiler.Local.Uninitialised)
error("Can't read local variable in its own initialiser.");
return i;
}
}
return -1;
}
void addLocal(Token name){
compiler.locals ~= Compiler.Local(name, Compiler.Local.Uninitialised);
}
void beginScope(){
compiler.scopeDepth++;
}
void endScope(){
assert(--compiler.scopeDepth >= 0);
while(compiler.locals.count > 0 && compiler.locals[compiler.locals.count - 1].depth > compiler.scopeDepth){
compiler.emitter.emit(OpCode.Pop);
compiler.locals.count--;
}
}
@ -119,7 +192,7 @@ struct Parser{
error("Invalid assignment target.");
}
void synchronize(){
void synchronise(){
panicMode = false;
while(current.type != Token.Type.EOF){
if(previous.type == Token.Type.Semicolon)

View file

@ -99,7 +99,7 @@ struct Scanner{
while(true){
char c = peek();
switch(c){
case ' ', '\r':
case ' ', '\r', '\t':
advance();
break;
case '\n':

View file

@ -71,29 +71,3 @@ struct Value{
}
}
struct ValueArray{
uint count;
uint capacity;
Value* values;
void initialise(){
count = 0;
capacity = 0;
values = null;
}
void write(Value value){
if(capacity < count + 1){
int oldCapacity = capacity;
capacity = GROW_CAPACITY(oldCapacity);
values = GROW_ARRAY!Value(values, oldCapacity, capacity);
}
values[count] = value;
count++;
}
void free(){
if(values)
FREE_ARRAY!Value(values, capacity);
initialise();
}
}

View file

@ -12,19 +12,21 @@ import clox.compiler;
import clox.memory;
import clox.container.stack;
import clox.container.table;
import clox.container.varint;
VM vm;
struct VM{
Chunk* chunk;
ubyte* ip;
Stack!(Value, 256) stack;
Stack!Value stack;
Table globals;
Table strings;
Obj* objects;
bool isREPL;
enum InterpretResult{ Ok, CompileError, RuntimeError }
void initialise(){
stack.reset();
stack.initialise();
strings.initialise();
globals.initialise();
}
@ -32,6 +34,7 @@ struct VM{
freeObjects();
strings.free();
globals.free();
stack.free();
}
InterpretResult interpret(const char* source){
Chunk cnk;
@ -42,21 +45,27 @@ struct VM{
if(!compiler.compile(source, &cnk))
return InterpretResult.CompileError;
this.chunk = &cnk;
this.ip = cnk.code;
this.ip = cnk.code.ptr;
return run();
}
void runtimeError(Args...)(const char* format, Args args){
fprintf(stderr, format, args);
fputs("\n", stderr);
size_t instruction = vm.ip - vm.chunk.code - 1;
int line = vm.chunk.lines[instruction];
size_t instruction = vm.ip - vm.chunk.code.ptr - 1;
uint line = vm.chunk.lines[instruction];
fprintf(stderr, "[line %d] in script\n", line);
stack.reset();
}
InterpretResult run(){
ubyte readByte() => *vm.ip++;
auto readConstant() => chunk.constants.values[readByte()];
ubyte readByte() => *ip++;
long readVarUint(){
VarUint vi = VarUint.read(ip);
ip += vi.len;
return vi.i;
}
Value readConstant(){
return chunk.constants[readVarUint()];
}
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);
@ -73,7 +82,7 @@ struct VM{
}
while(true){
debug(traceExec){
disassembleInstruction(vm.chunk, cast(int)(vm.ip - vm.chunk.code));
disassembleInstruction(vm.chunk, vm.ip - vm.chunk.code.ptr);
printf(" ");
foreach(slot; stack.live){
printf("");
@ -93,6 +102,14 @@ struct VM{
case OpCode.False: stack.push(Value.from(false)); break;
case OpCode.Pop: stack.pop(); break;
case OpCode.GetLocal:
long slot = readVarUint();
stack.push(stack[slot]);
break;
case OpCode.SetLocal:
long slot = readVarUint();
stack[slot] = peek(0);
break;
case OpCode.GetGlobal:
Obj.String* name = readString();
Value value;