From 40e6d2f077e44f17cafc43cc6948b1d345bab57b Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Wed, 23 Oct 2013 15:32:59 -0700 Subject: [PATCH] Local variables. --- example/hello.wren | 5 +- src/compiler.c | 115 ++++++++++++++++++++--------- src/vm.c | 176 +++++++++++++++++++++++++++++++++++++++++---- src/vm.h | 51 +++++++++++-- 4 files changed, 293 insertions(+), 54 deletions(-) diff --git a/example/hello.wren b/example/hello.wren index c3054d45..93e6a73d 100644 --- a/example/hello.wren +++ b/example/hello.wren @@ -1,2 +1,3 @@ -123.ok -123.yar +var a = 123 +var b = 345 +a diff --git a/src/compiler.c b/src/compiler.c index 8618346d..41690444 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -76,6 +76,9 @@ typedef struct Block* block; int numCodes; + // Symbol table for declared local variables in the current block. + SymbolTable locals; + // Non-zero if a compile error has occurred. int hasError; } Compiler; @@ -105,6 +108,7 @@ static char peekChar(Compiler* compiler); static void makeToken(Compiler* compiler, TokenType type); // Utility: +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) @@ -118,7 +122,14 @@ Block* compile(VM* vm, const char* source, size_t sourceLength) compiler.tokenStart = 0; compiler.currentChar = 0; - // TODO(bob): Zero-init current token. + initSymbolTable(&compiler.locals); + + // Zero-init the current token. This will get copied to previous when + // advance() is called below. + compiler.current.type = TOKEN_EOF; + compiler.current.start = 0; + compiler.current.end = 0; + // Read the first token. advance(&compiler); @@ -132,37 +143,47 @@ Block* compile(VM* vm, const char* source, size_t sourceLength) compiler.numCodes = 0; - do + for (;;) { statement(&compiler); - // TODO(bob): Discard previous value. - } while (!match(&compiler, TOKEN_EOF)); - compiler.block->bytecode[compiler.numCodes++] = CODE_END; + if (match(&compiler, TOKEN_EOF)) break; + + // Discard the result of the previous expression. + emit(&compiler, CODE_POP); + } + + emit(&compiler, CODE_END); + + compiler.block->numLocals = compiler.locals.count; return compiler.hasError ? NULL : compiler.block; } void statement(Compiler* compiler) { - /* if (match(compiler, TOKEN_VAR)) { - Token* name = consume(compiler, TOKEN_NAME); - Node* initializer = NULL; - if (match(compiler, TOKEN_EQ)) - { - initializer = expression(parser); - } - if (peek(parser) != TOKEN_OUTDENT) consume(compiler, TOKEN_LINE); + consume(compiler, TOKEN_NAME); + int local = addSymbol(&compiler->locals, + compiler->source + compiler->previous.start, + compiler->previous.end - compiler->previous.start); - NodeVar* node = malloc(sizeof(NodeVar)); - node->node.type = NODE_VAR; - node->name = name; - node->initializer = initializer; - return (Node*)node; + if (local == -1) + { + error(compiler, "Local variable is already defined."); + } + + // TODO(bob): Allow uninitialized vars? + consume(compiler, TOKEN_EQ); + + // Compile the initializer. + expression(compiler); + + emit(compiler, CODE_STORE_LOCAL); + emit(compiler, local); + return; } - */ // Statement expression. expression(compiler); @@ -186,23 +207,38 @@ void call(Compiler* compiler) if (match(compiler, TOKEN_DOT)) { consume(compiler, TOKEN_NAME); - int symbol = getSymbol(compiler->vm, - compiler->source + compiler->previous.start, - compiler->previous.end - compiler->previous.start); - printf("symbol %d\n", symbol); - + int symbol = ensureSymbol(&compiler->vm->symbols, + compiler->source + compiler->previous.start, + compiler->previous.end - compiler->previous.start); // Compile the method call. - compiler->block->bytecode[compiler->numCodes++] = CODE_CALL; - compiler->block->bytecode[compiler->numCodes++] = symbol; + emit(compiler, CODE_CALL); + emit(compiler, symbol); } } void primary(Compiler* compiler) { + if (match(compiler, TOKEN_NAME)) + { + int local = findSymbol(&compiler->locals, + compiler->source + compiler->previous.start, + compiler->previous.end - compiler->previous.start); + if (local == -1) + { + // TODO(bob): Look for globals or names in outer scopes. + error(compiler, "Unknown variable."); + } + + emit(compiler, CODE_LOAD_LOCAL); + emit(compiler, local); + return; + } + if (match(compiler, TOKEN_NUMBER)) { number(compiler, &compiler->previous); + return; } } @@ -219,18 +255,14 @@ void number(Compiler* compiler, Token* token) // Define a constant for the literal. // TODO(bob): See if constant with same value already exists. - Value constant = malloc(sizeof(Obj)); - constant->type = OBJ_INT; - constant->flags = 0; - // TODO(bob): Handle truncation! - constant->value = (int)value; + Value constant = makeNum((int)value); compiler->block->constants[compiler->block->numConstants++] = constant; // Compile the code to load the constant. - compiler->block->bytecode[compiler->numCodes++] = CODE_CONSTANT; - compiler->block->bytecode[compiler->numCodes++] = compiler->block->numConstants - 1; + emit(compiler, CODE_CONSTANT); + emit(compiler, compiler->block->numConstants - 1); } TokenType peek(Compiler* compiler) @@ -286,7 +318,17 @@ void readNextToken(Compiler* compiler) case '/': makeToken(compiler, TOKEN_SLASH); return; case '%': makeToken(compiler, TOKEN_PERCENT); return; case '+': makeToken(compiler, TOKEN_PLUS); return; - case '-': makeToken(compiler, TOKEN_MINUS); return; + case '-': + if (isDigit(peekChar(compiler))) + { + readNumber(compiler); + } + else + { + makeToken(compiler, TOKEN_MINUS); + } + return; + case '|': makeToken(compiler, TOKEN_PIPE); return; case '&': makeToken(compiler, TOKEN_AMP); return; case '=': @@ -434,6 +476,11 @@ void makeToken(Compiler* compiler, TokenType type) compiler->current.end = compiler->currentChar; } +void emit(Compiler* compiler, Code code) +{ + compiler->block->bytecode[compiler->numCodes++] = code; +} + void error(Compiler* compiler, const char* format, ...) { compiler->hasError = 1; diff --git a/src/vm.c b/src/vm.c index b565115f..c71330fd 100644 --- a/src/vm.c +++ b/src/vm.c @@ -4,68 +4,194 @@ #include "vm.h" +typedef struct +{ + // Index of the current (really next-to-be-executed) instruction in the + // block's bytecode. + int ip; + + // The block being executed. + Block* block; + + // Index of the stack slot that contains the first local for this block. + int locals; +} CallFrame; + typedef struct { Value stack[STACK_SIZE]; int stackSize; + + CallFrame frames[MAX_CALL_FRAMES]; + int numFrames; } Fiber; +static void callBlock(Fiber* fiber, Block* block, int locals); +static void push(Fiber* fiber, Value value); static Value pop(Fiber* fiber); +static Value primitiveNumAbs(Value number); VM* newVM() { VM* vm = malloc(sizeof(VM)); - vm->numSymbols = 0; + 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; return vm; } void freeVM(VM* vm) { + clearSymbolTable(&vm->symbols); free(vm); } -int getSymbol(VM* vm, const char* name, size_t length) +Value makeNum(int number) +{ + Value value = malloc(sizeof(Obj)); + value->type = OBJ_INT; + value->flags = 0; + value->value = number; + + return value; +} + +void initSymbolTable(SymbolTable* symbols) +{ + symbols->count = 0; +} + +void clearSymbolTable(SymbolTable* symbols) +{ + for (int i = 0; i < symbols->count; i++) + { + free(symbols->names[i]); + } +} + +int addSymbolUnchecked(SymbolTable* symbols, const char* name, size_t length) +{ + symbols->names[symbols->count] = malloc(length + 1); + strncpy(symbols->names[symbols->count], name, length); + symbols->names[symbols->count][length] = '\0'; + + return symbols->count++; +} + +int addSymbol(SymbolTable* symbols, const char* name, size_t length) +{ + // If already present, return an error. + if (findSymbol(symbols, name, length) != -1) return -1; + + return addSymbolUnchecked(symbols, name, length); +} + +int ensureSymbol(SymbolTable* symbols, const char* name, size_t length) +{ + // See if the symbol is already defined. + int existing = findSymbol(symbols, name, length); + if (existing != -1) return existing; + + // New symbol, so add it. + return addSymbolUnchecked(symbols, name, length); +} + +int findSymbol(SymbolTable* symbols, const char* name, size_t length) { // See if the symbol is already defined. // TODO(bob): O(n). Do something better. - for (int i = 0; i < vm->numSymbols; i++) + for (int i = 0; i < symbols->count; i++) { - if (strncmp(vm->symbols[i], name, length) == 0) return i; + if (strlen(symbols->names[i]) == length && + strncmp(symbols->names[i], name, length) == 0) return i; } - // New symbol, so add it. - vm->symbols[vm->numSymbols] = malloc(length); - strncpy(vm->symbols[vm->numSymbols], name, length); - return vm->numSymbols++; + return -1; } Value interpret(VM* vm, Block* block) { Fiber fiber; fiber.stackSize = 0; + fiber.numFrames = 0; + + callBlock(&fiber, block, 0); - int ip = 0; for (;;) { - switch (block->bytecode[ip++]) + CallFrame* frame = &fiber.frames[fiber.numFrames - 1]; + + switch (frame->block->bytecode[frame->ip++]) { case CODE_CONSTANT: { - Value value = block->constants[block->bytecode[ip++]]; + int constant = frame->block->bytecode[frame->ip++]; + Value value = frame->block->constants[constant]; fiber.stack[fiber.stackSize++] = value; break; } + case CODE_LOAD_LOCAL: + { + int local = frame->block->bytecode[frame->ip++]; + push(&fiber, fiber.stack[frame->locals + local]); + break; + } + + case CODE_STORE_LOCAL: + { + int local = frame->block->bytecode[frame->ip++]; + fiber.stack[frame->locals + local] = pop(&fiber); + break; + } + + case CODE_POP: + pop(&fiber); + break; + case CODE_CALL: { - int symbol = block->bytecode[ip++]; - printf("call %d\n", symbol); + Value receiver = pop(&fiber); + // TODO(bob): Arguments. + + int symbol = frame->block->bytecode[frame->ip++]; + + // TODO(bob): Support classes for other object types. + Class* classObj = &vm->numClass; + + 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); + } break; } case CODE_END: - return pop(&fiber); + { + Value result = pop(&fiber); + fiber.numFrames--; + + // If we are returning from the top-level block, just return the value. + if (fiber.numFrames == 0) return result; + + // Store the result of the block in the first slot, which is where the + // caller expects it. + fiber.stack[frame->locals] = result; + } } } } @@ -80,7 +206,29 @@ void printValue(Value value) } } +void callBlock(Fiber* fiber, Block* block, int locals) +{ + fiber->frames[fiber->numFrames].block = block; + fiber->frames[fiber->numFrames].ip = 0; + fiber->frames[fiber->numFrames].locals = locals; + fiber->numFrames++; +} + +void push(Fiber* fiber, Value value) +{ + // TODO(bob): Check for stack overflow. + fiber->stack[fiber->stackSize++] = value; +} + Value pop(Fiber* fiber) { return fiber->stack[--fiber->stackSize]; } + +Value primitiveNumAbs(Value number) +{ + int value = number->value; + if (value < 0) value = -value; + + return makeNum(value); +} diff --git a/src/vm.h b/src/vm.h index 3a2d7581..9df48e0e 100644 --- a/src/vm.h +++ b/src/vm.h @@ -1,8 +1,10 @@ #ifndef wren_vm_h #define wren_vm_h -// TODO(bob): Make this externally controllable. +// TODO(bob): Make these externally controllable. #define STACK_SIZE 1024 +#define MAX_CALL_FRAMES 256 + typedef enum { OBJ_INT @@ -31,6 +33,15 @@ typedef enum CODE_CONSTANT, // Load the constant at index [arg]. + CODE_POP, + // Pop and discard the top of stack. + + CODE_LOAD_LOCAL, + // Pushes the value in local slot [arg]. + + CODE_STORE_LOCAL, + // Pops and stores the value in local slot [arg]. + CODE_CALL, // Invoke the method with symbol [arg]. @@ -44,22 +55,54 @@ 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. - char* symbols[MAX_SYMBOLS]; - int numSymbols; + char* names[MAX_SYMBOLS]; + 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; } VM; VM* newVM(); void freeVM(VM* vm); -int getSymbol(VM* vm, const char* name, size_t length); +Value makeNum(int number); + +// Initializes the symbol table. +void initSymbolTable(SymbolTable* symbols); + +// Frees all dynamically allocated memory used by the symbol table, but not the +// SymbolTable itself. +void clearSymbolTable(SymbolTable* symbols); + +// Adds name to the symbol table. Returns the index of it in the table. Returns +// -1 if the symbol is already present. +int addSymbol(SymbolTable* symbols, const char* name, size_t length); + +// Adds name to the symbol table. Returns the index of it in the table. Will +// use an existing symbol if already present. +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);