"block" -> "function".

Moving away from using Smalltalk-style block objects for all scopes.
This commit is contained in:
Bob Nystrom
2013-11-06 06:52:28 -08:00
parent 4ff444b1ab
commit c0c6020292
6 changed files with 95 additions and 94 deletions

View File

@ -103,8 +103,8 @@ typedef struct sCompiler
// top level.
struct sCompiler* parent;
// The block being compiled.
ObjBlock* block;
// The function being compiled.
ObjFn* fn;
int numCodes;
// Symbol table for declared local variables in this block.
@ -117,7 +117,6 @@ enum
{
PREC_NONE,
PREC_LOWEST,
PREC_EQUALITY, // == !=
PREC_COMPARISON, // < > <= >=
PREC_BITWISE, // | &
@ -136,7 +135,7 @@ typedef struct
static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent);
static ObjBlock* compileBlock(Parser* parser, Compiler* parent,
static ObjFn* compileFunction(Parser* parser, Compiler* parent,
TokenType endToken);
static int addConstant(Compiler* compiler, Value constant);
@ -272,7 +271,7 @@ ParseRule rules[] =
/* TOKEN_EOF */ UNUSED
};
ObjBlock* compile(VM* vm, const char* source)
ObjFn* compile(VM* vm, const char* source)
{
Parser parser;
parser.vm = vm;
@ -296,7 +295,7 @@ ObjBlock* compile(VM* vm, const char* source)
// Read the first token.
nextToken(&parser);
return compileBlock(&parser, NULL, TOKEN_EOF);
return compileFunction(&parser, NULL, TOKEN_EOF);
}
void initCompiler(Compiler* compiler, Parser* parser,
@ -307,16 +306,16 @@ void initCompiler(Compiler* compiler, Parser* parser,
compiler->numCodes = 0;
initSymbolTable(&compiler->locals);
compiler->block = makeBlock();
compiler->fn = makeFunction();
// TODO(bob): Hack! make variable sized.
compiler->block->bytecode = malloc(sizeof(Code) * 1024);
compiler->fn->bytecode = malloc(sizeof(Code) * 1024);
// TODO(bob): Hack! make variable sized.
compiler->block->constants = malloc(sizeof(Value) * 256);
compiler->block->numConstants = 0;
compiler->fn->constants = malloc(sizeof(Value) * 256);
compiler->fn->numConstants = 0;
}
ObjBlock* compileBlock(Parser* parser, Compiler* parent, TokenType endToken)
ObjFn* compileFunction(Parser* parser, Compiler* parent, TokenType endToken)
{
Compiler compiler;
initCompiler(&compiler, parser, parent);
@ -340,15 +339,15 @@ ObjBlock* compileBlock(Parser* parser, Compiler* parent, TokenType endToken)
emit(&compiler, CODE_END);
compiler.block->numLocals = compiler.locals.count;
compiler.fn->numLocals = compiler.locals.count;
return parser->hasError ? NULL : compiler.block;
return parser->hasError ? NULL : compiler.fn;
}
int addConstant(Compiler* compiler, Value constant)
{
compiler->block->constants[compiler->block->numConstants++] = constant;
return compiler->block->numConstants - 1;
compiler->fn->constants[compiler->fn->numConstants++] = constant;
return compiler->fn->numConstants - 1;
}
int defineName(Compiler* compiler)
@ -394,7 +393,7 @@ int internSymbol(Compiler* compiler)
int emit(Compiler* compiler, Code code)
{
compiler->block->bytecode[compiler->numCodes++] = code;
compiler->fn->bytecode[compiler->numCodes++] = code;
return compiler->numCodes - 1;
}
@ -441,7 +440,7 @@ void statement(Compiler* compiler)
int symbol = internSymbol(compiler);
consume(compiler, TOKEN_LEFT_BRACE);
ObjBlock* method = compileBlock(compiler->parser, compiler,
ObjFn* method = compileFunction(compiler->parser, compiler,
TOKEN_RIGHT_BRACE);
consume(compiler, TOKEN_LINE);
@ -500,7 +499,7 @@ void expression(Compiler* compiler)
int elseJump = emit(compiler, 255);
// Patch the jump.
compiler->block->bytecode[ifJump] = compiler->numCodes - ifJump - 1;
compiler->fn->bytecode[ifJump] = compiler->numCodes - ifJump - 1;
// Compile the else branch if there is one.
if (match(compiler, TOKEN_ELSE))
@ -515,7 +514,7 @@ void expression(Compiler* compiler)
}
// Patch the jump over the else.
compiler->block->bytecode[elseJump] = compiler->numCodes - elseJump - 1;
compiler->fn->bytecode[elseJump] = compiler->numCodes - elseJump - 1;
return;
}
@ -552,15 +551,16 @@ void grouping(Compiler* compiler)
void block(Compiler* compiler)
{
ObjBlock* block = compileBlock(
// TODO(bob): Make this just a local scope, not a block object.
ObjFn* fn = compileFunction(
compiler->parser, compiler, TOKEN_RIGHT_BRACE);
// Add the block to the constant table.
compiler->block->constants[compiler->block->numConstants++] = (Value)block;
// Add the function to the constant table.
compiler->fn->constants[compiler->fn->numConstants++] = (Value)fn;
// Compile the code to load it.
emit(compiler, CODE_CONSTANT);
emit(compiler, compiler->block->numConstants - 1);
emit(compiler, compiler->fn->numConstants - 1);
}
void boolean(Compiler* compiler)

View File

@ -3,6 +3,6 @@
#include "vm.h"
ObjBlock* compile(VM* vm, const char* source);
ObjFn* compile(VM* vm, const char* source);
#endif

View File

@ -50,12 +50,12 @@ int runFile(const char* path)
{
char* source = readFile(path);
VM* vm = newVM();
ObjBlock* block = compile(vm, source);
ObjFn* fn = compile(vm, source);
int exitCode = 0;
if (block)
if (fn != NULL)
{
interpret(vm, block);
interpret(vm, fn);
}
else
{
@ -78,11 +78,11 @@ int runRepl()
char line[MAX_LINE];
fgets(line, MAX_LINE, stdin);
// TODO(bob): Handle failure.
ObjBlock* block = compile(vm, line);
ObjFn* fn = compile(vm, line);
if (block != NULL)
if (fn != NULL)
{
Value result = interpret(vm, block);
Value result = interpret(vm, fn);
printf("= ");
printValue(result);
printf("\n");

View File

@ -22,16 +22,6 @@
vm->globals[symbol] = (Value)obj; \
}
DEF_PRIMITIVE(block_call)
{
// The call instruction leading to this primitive has one argument. So when
// we push the block onto the callstack, we again use one argument. That
// ensures that the result of evaluating the block goes into the slot that
// the caller of *this* primitive is expecting.
callBlock(fiber, AS_BLOCK(args[0]), 1);
return NULL;
}
DEF_PRIMITIVE(bool_eqeq)
{
if (args[1]->type != OBJ_FALSE && args[1]->type != OBJ_TRUE)
@ -69,6 +59,16 @@ DEF_PRIMITIVE(bool_toString)
}
}
DEF_PRIMITIVE(fn_call)
{
// The call instruction leading to this primitive has one argument. So when
// we push the block onto the callstack, we again use one argument. That
// ensures that the result of evaluating the block goes into the slot that
// the caller of *this* primitive is expecting.
callFunction(fiber, AS_FN(args[0]), 1);
return NULL;
}
DEF_PRIMITIVE(num_abs)
{
double value = AS_NUM(args[0]);
@ -217,9 +217,6 @@ DEF_PRIMITIVE(io_write)
void registerPrimitives(VM* vm)
{
vm->blockClass = makeClass();
PRIMITIVE(vm->blockClass, "call", block_call);
vm->boolClass = makeClass();
PRIMITIVE(vm->boolClass, "toString", bool_toString);
PRIMITIVE(vm->boolClass, "== ", bool_eqeq);
@ -227,6 +224,9 @@ void registerPrimitives(VM* vm)
vm->classClass = makeClass();
vm->fnClass = makeClass();
PRIMITIVE(vm->fnClass, "call", fn_call);
vm->nullClass = makeClass();
vm->numClass = makeClass();

View File

@ -38,14 +38,6 @@ Value makeBool(int value)
return obj;
}
ObjBlock* makeBlock()
{
ObjBlock* block = malloc(sizeof(ObjBlock));
block->obj.type = OBJ_BLOCK;
block->obj.flags = 0;
return block;
}
ObjClass* makeSingleClass()
{
ObjClass* obj = malloc(sizeof(ObjClass));
@ -71,6 +63,14 @@ ObjClass* makeClass()
return classObj;
}
ObjFn* makeFunction()
{
ObjFn* fn = malloc(sizeof(ObjFn));
fn->obj.type = OBJ_FN;
fn->obj.flags = 0;
return fn;
}
ObjInstance* makeInstance(ObjClass* classObj)
{
ObjInstance* instance = malloc(sizeof(ObjInstance));
@ -306,24 +306,24 @@ void dumpCode(ObjBlock* block)
}
*/
Value interpret(VM* vm, ObjBlock* block)
Value interpret(VM* vm, ObjFn* fn)
{
Fiber fiber;
fiber.stackSize = 0;
fiber.numFrames = 0;
callBlock(&fiber, block, 0);
callFunction(&fiber, fn, 0);
for (;;)
{
CallFrame* frame = &fiber.frames[fiber.numFrames - 1];
switch (frame->block->bytecode[frame->ip++])
switch (frame->fn->bytecode[frame->ip++])
{
case CODE_CONSTANT:
{
int constant = frame->block->bytecode[frame->ip++];
Value value = frame->block->constants[constant];
int constant = frame->fn->bytecode[frame->ip++];
Value value = frame->fn->constants[constant];
push(&fiber, value);
break;
}
@ -357,40 +357,40 @@ Value interpret(VM* vm, ObjBlock* block)
case CODE_METHOD:
{
int symbol = frame->block->bytecode[frame->ip++];
int constant = frame->block->bytecode[frame->ip++];
int symbol = frame->fn->bytecode[frame->ip++];
int constant = frame->fn->bytecode[frame->ip++];
ObjClass* classObj = (ObjClass*)fiber.stack[fiber.stackSize - 1];
ObjBlock* body = (ObjBlock*)frame->block->constants[constant];
ObjFn* body = AS_FN(frame->fn->constants[constant]);
classObj->methods[symbol].type = METHOD_BLOCK;
classObj->methods[symbol].block = body;
classObj->methods[symbol].fn = body;
break;
}
case CODE_LOAD_LOCAL:
{
int local = frame->block->bytecode[frame->ip++];
int local = frame->fn->bytecode[frame->ip++];
push(&fiber, fiber.stack[frame->locals + local]);
break;
}
case CODE_STORE_LOCAL:
{
int local = frame->block->bytecode[frame->ip++];
int local = frame->fn->bytecode[frame->ip++];
fiber.stack[frame->locals + local] = fiber.stack[fiber.stackSize - 1];
break;
}
case CODE_LOAD_GLOBAL:
{
int global = frame->block->bytecode[frame->ip++];
int global = frame->fn->bytecode[frame->ip++];
push(&fiber, vm->globals[global]);
break;
}
case CODE_STORE_GLOBAL:
{
int global = frame->block->bytecode[frame->ip++];
int global = frame->fn->bytecode[frame->ip++];
vm->globals[global] = fiber.stack[fiber.stackSize - 1];
break;
}
@ -416,18 +416,14 @@ Value interpret(VM* vm, ObjBlock* block)
case CODE_CALL_10:
{
// Add one for the implicit receiver argument.
int numArgs = frame->block->bytecode[frame->ip - 1] - CODE_CALL_0 + 1;
int symbol = frame->block->bytecode[frame->ip++];
int numArgs = frame->fn->bytecode[frame->ip - 1] - CODE_CALL_0 + 1;
int symbol = frame->fn->bytecode[frame->ip++];
Value receiver = fiber.stack[fiber.stackSize - numArgs];
ObjClass* classObj;
switch (receiver->type)
{
case OBJ_BLOCK:
classObj = vm->blockClass;
break;
case OBJ_CLASS:
classObj = ((ObjClass*)receiver)->metaclass;
break;
@ -437,6 +433,10 @@ Value interpret(VM* vm, ObjBlock* block)
classObj = vm->boolClass;
break;
case OBJ_FN:
classObj = vm->fnClass;
break;
case OBJ_NULL:
classObj = vm->nullClass;
break;
@ -484,7 +484,7 @@ Value interpret(VM* vm, ObjBlock* block)
}
case METHOD_BLOCK:
callBlock(&fiber, method->block, numArgs);
callFunction(&fiber, method->fn, numArgs);
break;
}
break;
@ -492,14 +492,14 @@ Value interpret(VM* vm, ObjBlock* block)
case CODE_JUMP:
{
int offset = frame->block->bytecode[frame->ip++];
int offset = frame->fn->bytecode[frame->ip++];
frame->ip += offset;
break;
}
case CODE_JUMP_IF:
{
int offset = frame->block->bytecode[frame->ip++];
int offset = frame->fn->bytecode[frame->ip++];
Value condition = pop(&fiber);
// False is the only falsey value.
@ -530,20 +530,21 @@ Value interpret(VM* vm, ObjBlock* block)
}
}
void callBlock(Fiber* fiber, ObjBlock* block, int numArgs)
void callFunction(Fiber* fiber, ObjFn* fn, int numArgs)
{
fiber->frames[fiber->numFrames].block = block;
fiber->frames[fiber->numFrames].fn = fn;
fiber->frames[fiber->numFrames].ip = 0;
fiber->frames[fiber->numFrames].locals = fiber->stackSize - numArgs;
// Make empty slots for locals.
// TODO(bob): Should we do this eagerly, or have the bytecode have pushnil
// instructions before each time a local is stored for the first time?
for (int i = 0; i < block->numLocals - numArgs; i++)
for (int i = 0; i < fn->numLocals - numArgs; i++)
{
push(fiber, NULL);
}
// TODO(bob): Check for stack overflow.
fiber->numFrames++;
}
@ -552,10 +553,6 @@ void printValue(Value value)
// TODO(bob): Do more useful stuff here.
switch (value->type)
{
case OBJ_BLOCK:
printf("[block]");
break;
case OBJ_CLASS:
printf("[class]");
break;
@ -564,6 +561,10 @@ void printValue(Value value)
printf("false");
break;
case OBJ_FN:
printf("[fn]");
break;
case OBJ_INSTANCE:
printf("[instance]");
break;

View File

@ -6,12 +6,12 @@
#define MAX_CALL_FRAMES 256
#define MAX_SYMBOLS 256
// Get the block value of [obj] (0 or 1), which must be a block.
#define AS_BLOCK(obj) ((ObjBlock*)obj)
// Get the bool value of [obj] (0 or 1), which must be a boolean.
#define AS_BOOL(obj) (((Obj*)obj)->type == OBJ_TRUE)
// Get the function value of [obj] (0 or 1), which must be a function.
#define AS_FN(obj) ((ObjFn*)obj)
// Get the double value of [obj], which must be a number.
#define AS_NUM(obj) (((ObjNum*)obj)->value)
@ -19,9 +19,9 @@
#define AS_STRING(obj) (((ObjString*)obj)->value)
typedef enum {
OBJ_BLOCK,
OBJ_CLASS,
OBJ_FALSE,
OBJ_FN,
OBJ_INSTANCE,
OBJ_NULL,
OBJ_NUM,
@ -55,7 +55,7 @@ typedef struct
Value* constants;
int numConstants;
int numLocals;
} ObjBlock;
} ObjFn;
typedef enum
{
@ -70,7 +70,7 @@ typedef struct
union
{
Primitive primitive;
ObjBlock* block;
ObjFn* fn;
};
} Method;
@ -175,9 +175,9 @@ struct sVM
{
SymbolTable symbols;
ObjClass* blockClass;
ObjClass* boolClass;
ObjClass* classClass;
ObjClass* fnClass;
ObjClass* nullClass;
ObjClass* numClass;
ObjClass* stringClass;
@ -196,8 +196,8 @@ typedef struct
// block's bytecode.
int ip;
// The block being executed.
ObjBlock* block;
// The function being executed.
ObjFn* fn;
// Index of the stack slot that contains the first local for this block.
int locals;
@ -219,9 +219,9 @@ void freeVM(VM* vm);
// TODO(bob): Inline or macro?
Value makeBool(int value);
// Creates a new block object. Assumes the compiler will fill it in with
// Creates a new function object. Assumes the compiler will fill it in with
// bytecode, constants, etc.
ObjBlock* makeBlock();
ObjFn* makeFunction();
// Creates a new class object.
ObjClass* makeClass();
@ -259,11 +259,11 @@ int findSymbol(SymbolTable* symbols, const char* name, size_t length);
// Given an index in the symbol table, returns its name.
const char* getSymbolName(SymbolTable* symbols, int symbol);
Value interpret(VM* vm, ObjBlock* block);
Value interpret(VM* vm, ObjFn* fn);
// Push [block] onto [fiber]'s callstack and invoke it. Expects [numArgs]
// Push [fn] onto [fiber]'s callstack and invoke it. Expects [numArgs]
// arguments (including the receiver) to be on the top of the stack already.
void callBlock(Fiber* fiber, ObjBlock* block, int numArgs);
void callFunction(Fiber* fiber, ObjFn* fn, int numArgs);
void printValue(Value value);