Local variables.

This commit is contained in:
Bob Nystrom
2013-10-23 15:32:59 -07:00
parent 3da1cbe2fa
commit 40e6d2f077
4 changed files with 293 additions and 54 deletions

View File

@ -1,2 +1,3 @@
123.ok
123.yar
var a = 123
var b = 345
a

View File

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

176
src/vm.c
View File

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

View File

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