mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 22:28:45 +01:00
Start working on class definitions.
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
class Foo {}
|
||||
var a = 123
|
||||
var b = 345
|
||||
a
|
||||
|
||||
@ -32,6 +32,8 @@ typedef enum
|
||||
TOKEN_EQEQ,
|
||||
TOKEN_BANGEQ,
|
||||
|
||||
TOKEN_CLASS,
|
||||
TOKEN_META,
|
||||
TOKEN_VAR,
|
||||
|
||||
TOKEN_NAME,
|
||||
@ -73,7 +75,7 @@ typedef struct
|
||||
Token previous;
|
||||
|
||||
// The block being compiled.
|
||||
Block* block;
|
||||
ObjBlock* block;
|
||||
int numCodes;
|
||||
|
||||
// Symbol table for declared local variables in the current block.
|
||||
@ -111,7 +113,7 @@ static void makeToken(Compiler* compiler, TokenType type);
|
||||
static void emit(Compiler* compiler, Code code);
|
||||
static void error(Compiler* compiler, const char* format, ...);
|
||||
|
||||
Block* compile(VM* vm, const char* source, size_t sourceLength)
|
||||
ObjBlock* compile(VM* vm, const char* source, size_t sourceLength)
|
||||
{
|
||||
Compiler compiler;
|
||||
compiler.vm = vm;
|
||||
@ -133,7 +135,7 @@ Block* compile(VM* vm, const char* source, size_t sourceLength)
|
||||
// Read the first token.
|
||||
advance(&compiler);
|
||||
|
||||
compiler.block = malloc(sizeof(Block));
|
||||
compiler.block = makeBlock();
|
||||
// TODO(bob): Hack! make variable sized.
|
||||
compiler.block->bytecode = malloc(sizeof(Code) * 1024);
|
||||
|
||||
@ -147,6 +149,8 @@ Block* compile(VM* vm, const char* source, size_t sourceLength)
|
||||
{
|
||||
statement(&compiler);
|
||||
|
||||
consume(&compiler, TOKEN_LINE);
|
||||
|
||||
if (match(&compiler, TOKEN_EOF)) break;
|
||||
|
||||
// Discard the result of the previous expression.
|
||||
@ -162,6 +166,36 @@ Block* compile(VM* vm, const char* source, size_t sourceLength)
|
||||
|
||||
void statement(Compiler* compiler)
|
||||
{
|
||||
if (match(compiler, TOKEN_CLASS))
|
||||
{
|
||||
consume(compiler, TOKEN_NAME);
|
||||
|
||||
// TODO(bob): Copied from below. Unify.
|
||||
int local = addSymbol(&compiler->locals,
|
||||
compiler->source + compiler->previous.start,
|
||||
compiler->previous.end - compiler->previous.start);
|
||||
|
||||
if (local == -1)
|
||||
{
|
||||
error(compiler, "Local variable is already defined.");
|
||||
}
|
||||
|
||||
// Create the empty class.
|
||||
emit(compiler, CODE_CLASS);
|
||||
|
||||
// Store it in its name.
|
||||
emit(compiler, CODE_STORE_LOCAL);
|
||||
emit(compiler, local);
|
||||
|
||||
// Compile the method definitions.
|
||||
consume(compiler, TOKEN_LEFT_BRACE);
|
||||
|
||||
// TODO(bob): Definitions!
|
||||
|
||||
consume(compiler, TOKEN_RIGHT_BRACE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (match(compiler, TOKEN_VAR))
|
||||
{
|
||||
consume(compiler, TOKEN_NAME);
|
||||
@ -187,7 +221,6 @@ void statement(Compiler* compiler)
|
||||
|
||||
// Statement expression.
|
||||
expression(compiler);
|
||||
consume(compiler, TOKEN_LINE);
|
||||
}
|
||||
|
||||
void expression(Compiler* compiler)
|
||||
@ -245,6 +278,7 @@ void primary(Compiler* compiler)
|
||||
void number(Compiler* compiler, Token* token)
|
||||
{
|
||||
char* end;
|
||||
// TODO(bob): Parse actual double!
|
||||
long value = strtol(compiler->source + token->start, &end, 10);
|
||||
// TODO(bob): Check errno == ERANGE here.
|
||||
if (end == compiler->source + token->start)
|
||||
@ -255,8 +289,7 @@ void number(Compiler* compiler, Token* token)
|
||||
|
||||
// Define a constant for the literal.
|
||||
// TODO(bob): See if constant with same value already exists.
|
||||
// TODO(bob): Handle truncation!
|
||||
Value constant = makeNum((int)value);
|
||||
Value constant = (Value)makeNum((double)value);
|
||||
|
||||
compiler->block->constants[compiler->block->numConstants++] = constant;
|
||||
|
||||
@ -413,6 +446,8 @@ void readName(Compiler* compiler)
|
||||
|
||||
TokenType type = TOKEN_NAME;
|
||||
|
||||
if (isKeyword(compiler, "class")) type = TOKEN_CLASS;
|
||||
if (isKeyword(compiler, "meta")) type = TOKEN_META;
|
||||
if (isKeyword(compiler, "var")) type = TOKEN_VAR;
|
||||
|
||||
makeToken(compiler, type);
|
||||
|
||||
@ -3,6 +3,6 @@
|
||||
|
||||
#include "vm.h"
|
||||
|
||||
Block* compile(VM* vm, const char* source, size_t sourceLength);
|
||||
ObjBlock* compile(VM* vm, const char* source, size_t sourceLength);
|
||||
|
||||
#endif
|
||||
|
||||
@ -26,7 +26,7 @@ int main(int argc, const char * argv[])
|
||||
size_t length;
|
||||
char* source = readFile(argv[1], &length);
|
||||
VM* vm = newVM();
|
||||
Block* block = compile(vm, source, length);
|
||||
ObjBlock* block = compile(vm, source, length);
|
||||
Value value = interpret(vm, block);
|
||||
printValue(value);
|
||||
printf("\n");
|
||||
|
||||
111
src/vm.c
111
src/vm.c
@ -11,7 +11,7 @@ typedef struct
|
||||
int ip;
|
||||
|
||||
// The block being executed.
|
||||
Block* block;
|
||||
ObjBlock* block;
|
||||
|
||||
// Index of the stack slot that contains the first local for this block.
|
||||
int locals;
|
||||
@ -26,7 +26,7 @@ typedef struct
|
||||
int numFrames;
|
||||
} Fiber;
|
||||
|
||||
static void callBlock(Fiber* fiber, Block* block, int locals);
|
||||
static void callBlock(Fiber* fiber, ObjBlock* block, int locals);
|
||||
static void push(Fiber* fiber, Value value);
|
||||
static Value pop(Fiber* fiber);
|
||||
static Value primitiveNumAbs(Value number);
|
||||
@ -36,12 +36,10 @@ VM* newVM()
|
||||
VM* vm = malloc(sizeof(VM));
|
||||
initSymbolTable(&vm->symbols);
|
||||
|
||||
for (int i = 0; i < MAX_SYMBOLS; i++)
|
||||
{
|
||||
vm->numClass.methods[i] = NULL;
|
||||
}
|
||||
|
||||
vm->numClass.methods[ensureSymbol(&vm->symbols, "abs", 3)] = primitiveNumAbs;
|
||||
vm->numClass = makeClass();
|
||||
int symbol = ensureSymbol(&vm->symbols, "abs", 3);
|
||||
vm->numClass->methods[symbol].type = METHOD_PRIMITIVE;
|
||||
vm->numClass->methods[symbol].primitive = primitiveNumAbs;
|
||||
|
||||
return vm;
|
||||
}
|
||||
@ -52,14 +50,35 @@ void freeVM(VM* vm)
|
||||
free(vm);
|
||||
}
|
||||
|
||||
Value makeNum(int number)
|
||||
ObjClass* makeClass()
|
||||
{
|
||||
Value value = malloc(sizeof(Obj));
|
||||
value->type = OBJ_INT;
|
||||
value->flags = 0;
|
||||
value->value = number;
|
||||
ObjClass* obj = malloc(sizeof(ObjClass));
|
||||
obj->obj.type = OBJ_CLASS;
|
||||
obj->obj.flags = 0;
|
||||
|
||||
return value;
|
||||
for (int i = 0; i < MAX_SYMBOLS; i++)
|
||||
{
|
||||
obj->methods[i].type = METHOD_NONE;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
ObjBlock* makeBlock()
|
||||
{
|
||||
ObjBlock* block = malloc(sizeof(ObjBlock));
|
||||
block->obj.type = OBJ_NUM;
|
||||
block->obj.flags = 0;
|
||||
return block;
|
||||
}
|
||||
|
||||
ObjNum* makeNum(double number)
|
||||
{
|
||||
ObjNum* num = malloc(sizeof(ObjNum));
|
||||
num->obj.type = OBJ_NUM;
|
||||
num->obj.flags = 0;
|
||||
num->value = number;
|
||||
return num;
|
||||
}
|
||||
|
||||
void initSymbolTable(SymbolTable* symbols)
|
||||
@ -115,7 +134,7 @@ int findSymbol(SymbolTable* symbols, const char* name, size_t length)
|
||||
return -1;
|
||||
}
|
||||
|
||||
Value interpret(VM* vm, Block* block)
|
||||
Value interpret(VM* vm, ObjBlock* block)
|
||||
{
|
||||
Fiber fiber;
|
||||
fiber.stackSize = 0;
|
||||
@ -137,21 +156,34 @@ Value interpret(VM* vm, Block* block)
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_CLASS:
|
||||
push(&fiber, (Value)makeClass());
|
||||
printf("push class at %d\n", fiber.stackSize - 1);
|
||||
break;
|
||||
|
||||
case CODE_LOAD_LOCAL:
|
||||
{
|
||||
int local = frame->block->bytecode[frame->ip++];
|
||||
push(&fiber, fiber.stack[frame->locals + local]);
|
||||
printf("load local %d to %d\n", local, fiber.stackSize - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_STORE_LOCAL:
|
||||
{
|
||||
int local = frame->block->bytecode[frame->ip++];
|
||||
fiber.stack[frame->locals + local] = pop(&fiber);
|
||||
printf("store local %d from %d\n", local, fiber.stackSize - 1);
|
||||
fiber.stack[frame->locals + local] = fiber.stack[fiber.stackSize - 1];
|
||||
break;
|
||||
}
|
||||
|
||||
case CODE_DUP:
|
||||
push(&fiber, fiber.stack[fiber.stackSize - 1]);
|
||||
printf("dup %d\n", fiber.stackSize - 1);
|
||||
break;
|
||||
|
||||
case CODE_POP:
|
||||
printf("pop %d\n", fiber.stackSize - 1);
|
||||
pop(&fiber);
|
||||
break;
|
||||
|
||||
@ -163,19 +195,24 @@ Value interpret(VM* vm, Block* block)
|
||||
int symbol = frame->block->bytecode[frame->ip++];
|
||||
|
||||
// TODO(bob): Support classes for other object types.
|
||||
Class* classObj = &vm->numClass;
|
||||
ObjClass* classObj = vm->numClass;
|
||||
Method* method = &classObj->methods[symbol];
|
||||
switch (method->type)
|
||||
{
|
||||
case METHOD_NONE:
|
||||
// TODO(bob): Should return nil or suspend fiber or something.
|
||||
printf("No method.\n");
|
||||
exit(1);
|
||||
break;
|
||||
|
||||
Primitive primitive = classObj->methods[symbol];
|
||||
if (primitive)
|
||||
{
|
||||
Value result = primitive(receiver);
|
||||
push(&fiber, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO(bob): Should return nil or suspend fiber or something.
|
||||
printf("No method.\n");
|
||||
exit(1);
|
||||
case METHOD_PRIMITIVE:
|
||||
push(&fiber, method->primitive(receiver));
|
||||
break;
|
||||
|
||||
case METHOD_BLOCK:
|
||||
// TODO(bob): Should pass in correct index for locals.
|
||||
callBlock(&fiber, method->block, fiber.stackSize);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -200,13 +237,21 @@ void printValue(Value value)
|
||||
{
|
||||
switch (value->type)
|
||||
{
|
||||
case OBJ_INT:
|
||||
printf("%d", value->value);
|
||||
case OBJ_NUM:
|
||||
printf("%f", ((ObjNum*)value)->value);
|
||||
break;
|
||||
|
||||
case OBJ_BLOCK:
|
||||
printf("[block]");
|
||||
break;
|
||||
|
||||
case OBJ_CLASS:
|
||||
printf("[class]");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void callBlock(Fiber* fiber, Block* block, int locals)
|
||||
void callBlock(Fiber* fiber, ObjBlock* block, int locals)
|
||||
{
|
||||
fiber->frames[fiber->numFrames].block = block;
|
||||
fiber->frames[fiber->numFrames].ip = 0;
|
||||
@ -227,8 +272,8 @@ Value pop(Fiber* fiber)
|
||||
|
||||
Value primitiveNumAbs(Value number)
|
||||
{
|
||||
int value = number->value;
|
||||
double value = ((ObjNum*)number)->value;
|
||||
if (value < 0) value = -value;
|
||||
|
||||
return makeNum(value);
|
||||
return (Value)makeNum(value);
|
||||
}
|
||||
|
||||
89
src/vm.h
89
src/vm.h
@ -4,10 +4,12 @@
|
||||
// TODO(bob): Make these externally controllable.
|
||||
#define STACK_SIZE 1024
|
||||
#define MAX_CALL_FRAMES 256
|
||||
|
||||
#define MAX_SYMBOLS 256
|
||||
|
||||
typedef enum {
|
||||
OBJ_INT
|
||||
OBJ_NUM,
|
||||
OBJ_BLOCK,
|
||||
OBJ_CLASS
|
||||
} ObjType;
|
||||
|
||||
typedef enum
|
||||
@ -16,23 +18,66 @@ typedef enum
|
||||
FLAG_MARKED = 0x01,
|
||||
} ObjFlags;
|
||||
|
||||
typedef struct sObj {
|
||||
typedef struct
|
||||
{
|
||||
ObjType type;
|
||||
ObjFlags flags;
|
||||
|
||||
union {
|
||||
/* OBJ_INT */
|
||||
int value;
|
||||
};
|
||||
} Obj;
|
||||
|
||||
typedef Obj* Value;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
double value;
|
||||
} ObjNum;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
unsigned char* bytecode;
|
||||
Value* constants;
|
||||
int numConstants;
|
||||
int numLocals;
|
||||
} ObjBlock;
|
||||
|
||||
typedef Value (*Primitive)(Value receiver);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
METHOD_NONE,
|
||||
METHOD_PRIMITIVE,
|
||||
METHOD_BLOCK
|
||||
} MethodType;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
MethodType type;
|
||||
union
|
||||
{
|
||||
Primitive primitive;
|
||||
ObjBlock* block;
|
||||
};
|
||||
} Method;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Obj obj;
|
||||
// TODO(bob): Hack. Probably don't want to use this much space.
|
||||
Method methods[MAX_SYMBOLS];
|
||||
} ObjClass;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CODE_CONSTANT,
|
||||
// Load the constant at index [arg].
|
||||
|
||||
CODE_CLASS,
|
||||
// Define a new empty class and push it.
|
||||
|
||||
CODE_DUP,
|
||||
// Push a copy of the top of stack.
|
||||
|
||||
CODE_POP,
|
||||
// Pop and discard the top of stack.
|
||||
|
||||
@ -40,7 +85,7 @@ typedef enum
|
||||
// Pushes the value in local slot [arg].
|
||||
|
||||
CODE_STORE_LOCAL,
|
||||
// Pops and stores the value in local slot [arg].
|
||||
// Stores the top of stack in local slot [arg]. Does not pop it.
|
||||
|
||||
CODE_CALL,
|
||||
// Invoke the method with symbol [arg].
|
||||
@ -50,18 +95,6 @@ typedef enum
|
||||
|
||||
} Code;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char* bytecode;
|
||||
Value* constants;
|
||||
int numConstants;
|
||||
int numLocals;
|
||||
} Block;
|
||||
|
||||
#define MAX_SYMBOLS 256
|
||||
|
||||
typedef Value (*Primitive)(Value receiver);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// TODO(bob): Make this dynamically sized.
|
||||
@ -69,22 +102,18 @@ typedef struct
|
||||
int count;
|
||||
} SymbolTable;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// TODO(bob): Hack. Probably don't want to use this much space.
|
||||
Primitive methods[MAX_SYMBOLS];
|
||||
} Class;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SymbolTable symbols;
|
||||
Class numClass;
|
||||
ObjClass* numClass;
|
||||
} VM;
|
||||
|
||||
VM* newVM();
|
||||
void freeVM(VM* vm);
|
||||
|
||||
Value makeNum(int number);
|
||||
ObjClass* makeClass();
|
||||
ObjBlock* makeBlock();
|
||||
ObjNum* makeNum(double number);
|
||||
|
||||
// Initializes the symbol table.
|
||||
void initSymbolTable(SymbolTable* symbols);
|
||||
@ -104,7 +133,7 @@ int ensureSymbol(SymbolTable* symbols, const char* name, size_t length);
|
||||
// Looks up name in the symbol table. Returns its index if found or -1 if not.
|
||||
int findSymbol(SymbolTable* symbols, const char* name, size_t length);
|
||||
|
||||
Value interpret(VM* vm, Block* block);
|
||||
Value interpret(VM* vm, ObjBlock* block);
|
||||
|
||||
void printValue(Value value);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user