forked from Mirror/wren
Local variables.
This commit is contained in:
@ -1,2 +1,3 @@
|
||||
123.ok
|
||||
123.yar
|
||||
var a = 123
|
||||
var b = 345
|
||||
a
|
||||
|
||||
115
src/compiler.c
115
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;
|
||||
|
||||
176
src/vm.c
176
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);
|
||||
}
|
||||
|
||||
51
src/vm.h
51
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user