mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-05 20:26:53 +08:00
functions are replaced with clsoures
Every occurrence of function as first classes citizen is replaced with closures yet upvalues are still to do.
This commit is contained in:
parent
3b5da9cad3
commit
3feb9ac723
@ -328,9 +328,7 @@ struct PkCompileOptions {
|
||||
bool debug;
|
||||
|
||||
// Set to true if compiling in REPL mode, This will print repr version of
|
||||
// each evaluated non-null values. Note that if [repl_mode] is true the
|
||||
// [expression] should also be true otherwise it's incompatible, (will fail
|
||||
// an assertion).
|
||||
// each evaluated non-null values.
|
||||
bool repl_mode;
|
||||
|
||||
};
|
||||
|
@ -242,12 +242,6 @@ typedef enum {
|
||||
DEPTH_LOCAL, //< Local scope. Increase with inner scope.
|
||||
} Depth;
|
||||
|
||||
typedef enum {
|
||||
FN_NATIVE, //< Native C function.
|
||||
FN_SCRIPT, //< Script functions defined with 'def'.
|
||||
FN_LITERAL, //< Literal functions defined with 'function(){...}'
|
||||
} FuncType;
|
||||
|
||||
typedef struct {
|
||||
const char* name; //< Directly points into the source string.
|
||||
uint32_t length; //< Length of the name.
|
||||
@ -465,6 +459,13 @@ static OpInfo opcode_info[] = {
|
||||
/* INITALIZATION FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
// FIXME:
|
||||
// This forward declaration can be removed once the interpolated string's
|
||||
// "list_join" function replaced with BUILD_STRING opcode. (The declaration
|
||||
// needed at compiler initialization function to find the "list_join" function.
|
||||
static int findBuiltinFunction(const PKVM* vm,
|
||||
const char* name, uint32_t length);
|
||||
|
||||
// This should be called once the compiler initialized (to access it's fields).
|
||||
static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler,
|
||||
const char* source, const char* path) {
|
||||
@ -1220,6 +1221,22 @@ static bool matchAssignment(Compiler* compiler) {
|
||||
/* NAME SEARCH (AT COMPILATION PHASE) */
|
||||
/*****************************************************************************/
|
||||
|
||||
// Find the builtin function name and returns it's index in the builtins array
|
||||
// if not found returns -1.
|
||||
static int findBuiltinFunction(const PKVM* vm,
|
||||
const char* name, uint32_t length) {
|
||||
|
||||
for (int i = 0; i < vm->builtins_count; i++) {
|
||||
|
||||
uint32_t bfn_length = (uint32_t)strlen(vm->builtins[i]->fn->name);
|
||||
if (bfn_length != length) continue;
|
||||
if (strncmp(name, vm->builtins[i]->fn->name, length) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Result type for an identifier definition.
|
||||
typedef enum {
|
||||
NAME_NOT_DEFINED,
|
||||
@ -1314,7 +1331,7 @@ static void compilerChangeStack(Compiler* compiler, int num);
|
||||
|
||||
// Forward declaration of grammar functions.
|
||||
static void parsePrecedence(Compiler* compiler, Precedence precedence);
|
||||
static int compileFunction(Compiler* compiler, FuncType fn_type);
|
||||
static int compileFunction(Compiler* compiler, bool is_literal);
|
||||
static void compileExpression(Compiler* compiler);
|
||||
|
||||
static void exprLiteral(Compiler* compiler);
|
||||
@ -1520,8 +1537,8 @@ static void exprInterpolation(Compiler* compiler) {
|
||||
}
|
||||
|
||||
static void exprFunc(Compiler* compiler) {
|
||||
int fn_index = compileFunction(compiler, FN_LITERAL);
|
||||
emitOpcode(compiler, OP_PUSH_CONSTANT);
|
||||
int fn_index = compileFunction(compiler, true);
|
||||
emitOpcode(compiler, OP_PUSH_CLOSURE);
|
||||
emitShort(compiler, fn_index);
|
||||
}
|
||||
|
||||
@ -2116,7 +2133,7 @@ static int compileClass(Compiler* compiler) {
|
||||
int cls_index, ctor_index;
|
||||
Class* cls = newClass(compiler->parser.vm, compiler->module,
|
||||
name, (uint32_t)name_len, &cls_index, &ctor_index);
|
||||
cls->ctor->arity = 0;
|
||||
cls->ctor->fn->arity = 0;
|
||||
|
||||
// FIXME:
|
||||
// Temproary patch for moving functions and classes to constant buffer.
|
||||
@ -2134,9 +2151,9 @@ static int compileClass(Compiler* compiler) {
|
||||
}
|
||||
|
||||
// Compile the constructor function.
|
||||
ASSERT(compiler->func->ptr == compiler->module->body, OOPS);
|
||||
ASSERT(compiler->func->ptr == compiler->module->body->fn, OOPS);
|
||||
Func curr_fn;
|
||||
compilerPushFunc(compiler, &curr_fn, cls->ctor, ctor_index);
|
||||
compilerPushFunc(compiler, &curr_fn, cls->ctor->fn, ctor_index);
|
||||
compilerEnterBlock(compiler);
|
||||
|
||||
// Push an instance on the stack.
|
||||
@ -2194,12 +2211,12 @@ static int compileClass(Compiler* compiler) {
|
||||
}
|
||||
|
||||
// Compile a function and return it's index in the module's function buffer.
|
||||
static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
static int compileFunction(Compiler* compiler, bool is_literal) {
|
||||
|
||||
const char* name;
|
||||
int name_length;
|
||||
|
||||
if (fn_type != FN_LITERAL) {
|
||||
if (!is_literal) {
|
||||
consume(compiler, TK_NAME, "Expected a function name.");
|
||||
name = compiler->parser.previous.start;
|
||||
name_length = compiler->parser.previous.length;
|
||||
@ -2211,20 +2228,21 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
|
||||
int fn_index;
|
||||
Function* func = newFunction(compiler->parser.vm, name, name_length,
|
||||
compiler->module, fn_type == FN_NATIVE, NULL,
|
||||
&fn_index);
|
||||
compiler->module, false, NULL, &fn_index);
|
||||
if (fn_index >= MAX_CONSTANTS) {
|
||||
parseError(compiler, "A module should contain at most %d "
|
||||
"unique constants.", MAX_CONSTANTS);
|
||||
}
|
||||
|
||||
if (fn_type != FN_LITERAL) {
|
||||
// FIXME: remove native keyword for functions.
|
||||
ASSERT(fn_type != FN_NATIVE, OOPS);
|
||||
if (!is_literal) {
|
||||
ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS);
|
||||
int name_line = compiler->parser.previous.line;
|
||||
int g_index = compilerAddVariable(compiler, name, name_length, name_line);
|
||||
moduleSetGlobal(compiler->module, g_index, VAR_OBJ(func));
|
||||
|
||||
vmPushTempRef(compiler->parser.vm, &func->_super); // func.
|
||||
Closure* closure = newClosure(compiler->parser.vm, func);
|
||||
moduleSetGlobal(compiler->module, g_index, VAR_OBJ(closure));
|
||||
vmPopTempRef(compiler->parser.vm); // func.
|
||||
}
|
||||
|
||||
Func curr_fn;
|
||||
@ -2270,16 +2288,11 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
func->arity = argc;
|
||||
compilerChangeStack(compiler, argc);
|
||||
|
||||
if (fn_type != FN_NATIVE) {
|
||||
compileBlockBody(compiler, BLOCK_FUNC);
|
||||
compileBlockBody(compiler, BLOCK_FUNC);
|
||||
|
||||
consume(compiler, TK_END, "Expected 'end' after function definition end.");
|
||||
compilerExitBlock(compiler); // Parameter depth.
|
||||
emitFunctionEnd(compiler);
|
||||
|
||||
} else {
|
||||
compilerExitBlock(compiler); // Parameter depth.
|
||||
}
|
||||
consume(compiler, TK_END, "Expected 'end' after function definition end.");
|
||||
compilerExitBlock(compiler); // Parameter depth.
|
||||
emitFunctionEnd(compiler);
|
||||
|
||||
#if DUMP_BYTECODE
|
||||
// FIXME:
|
||||
@ -2879,7 +2892,7 @@ static void compileStatement(Compiler* compiler) {
|
||||
|
||||
// If running REPL mode, print the expression's evaluated value.
|
||||
if (compiler->options && compiler->options->repl_mode &&
|
||||
compiler->func->ptr == compiler->module->body &&
|
||||
compiler->func->ptr == compiler->module->body->fn &&
|
||||
is_expression /*&& compiler->scope_depth == DEPTH_GLOBAL*/) {
|
||||
emitOpcode(compiler, OP_REPL_PRINT);
|
||||
}
|
||||
@ -2899,11 +2912,8 @@ static void compileTopLevelStatement(Compiler* compiler) {
|
||||
if (match(compiler, TK_CLASS)) {
|
||||
compileClass(compiler);
|
||||
|
||||
} else if (match(compiler, TK_NATIVE)) {
|
||||
compileFunction(compiler, FN_NATIVE);
|
||||
|
||||
} else if (match(compiler, TK_DEF)) {
|
||||
compileFunction(compiler, FN_SCRIPT);
|
||||
compileFunction(compiler, false);
|
||||
|
||||
} else if (match(compiler, TK_FROM)) {
|
||||
compileFromImport(compiler);
|
||||
@ -2949,7 +2959,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source,
|
||||
// If we're compiling for a module that was already compiled (when running
|
||||
// REPL or evaluating an expression) we don't need the old main anymore.
|
||||
// just use the globals and functions of the module and use a new body func.
|
||||
pkByteBufferClear(&module->body->fn->opcodes, vm);
|
||||
pkByteBufferClear(&module->body->fn->fn->opcodes, vm);
|
||||
|
||||
// Remember the count of constants, names, and globals, If the compilation
|
||||
// failed discard all of them and roll back.
|
||||
@ -2959,7 +2969,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source,
|
||||
|
||||
Func curr_fn;
|
||||
curr_fn.depth = DEPTH_MODULE;
|
||||
curr_fn.ptr = module->body;
|
||||
curr_fn.ptr = module->body->fn;
|
||||
curr_fn.outer_func = NULL;
|
||||
compiler->func = &curr_fn;
|
||||
|
||||
|
106
src/pk_core.c
106
src/pk_core.c
@ -81,8 +81,10 @@ PkHandle* pkGetMainFunction(PKVM* vm, PkHandle* module) {
|
||||
int main_index = moduleGetGlobalIndex(_module, IMPLICIT_MAIN_NAME,
|
||||
(uint32_t)strlen(IMPLICIT_MAIN_NAME));
|
||||
if (main_index == -1) return NULL;
|
||||
ASSERT_INDEX(main_index, (int)_module->constants.count);
|
||||
return vmNewHandle(vm, _module->constants.data[main_index]);
|
||||
ASSERT_INDEX(main_index, _module->globals.count);
|
||||
Var main_fn = _module->globals.data[main_index];
|
||||
ASSERT(IS_OBJ_TYPE(main_fn, OBJ_CLOSURE), OOPS);
|
||||
return vmNewHandle(vm, main_fn);
|
||||
}
|
||||
|
||||
// A convenient macro to get the nth (1 based) argument of the current
|
||||
@ -389,9 +391,7 @@ static inline bool validateCond(PKVM* vm, bool condition, const char* err) {
|
||||
VALIDATE_ARG_OBJ(String, OBJ_STRING, "string")
|
||||
VALIDATE_ARG_OBJ(List, OBJ_LIST, "list")
|
||||
VALIDATE_ARG_OBJ(Map, OBJ_MAP, "map")
|
||||
VALIDATE_ARG_OBJ(Function, OBJ_FUNC, "function")
|
||||
VALIDATE_ARG_OBJ(Closure, OBJ_CLOSURE, "closure")
|
||||
VALIDATE_ARG_OBJ(Upvalue, OBJ_UPVALUE, "upvalue")
|
||||
VALIDATE_ARG_OBJ(Fiber, OBJ_FIBER, "fiber")
|
||||
VALIDATE_ARG_OBJ(Class, OBJ_CLASS, "class")
|
||||
|
||||
@ -399,26 +399,6 @@ static inline bool validateCond(PKVM* vm, bool condition, const char* err) {
|
||||
/* SHARED FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
int findBuiltinFunction(const PKVM* vm, const char* name, uint32_t length) {
|
||||
for (uint32_t i = 0; i < vm->builtins_count; i++) {
|
||||
if (length == vm->builtins[i].length &&
|
||||
strncmp(name, vm->builtins[i].name, length) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Function* getBuiltinFunction(const PKVM* vm, int index) {
|
||||
ASSERT_INDEX((uint32_t)index, vm->builtins_count);
|
||||
return vm->builtins[index].fn;
|
||||
}
|
||||
|
||||
const char* getBuiltinFunctionName(const PKVM* vm, int index) {
|
||||
ASSERT_INDEX((uint32_t)index, vm->builtins_count);
|
||||
return vm->builtins[index].name;
|
||||
}
|
||||
|
||||
Module* getCoreLib(const PKVM* vm, String* name) {
|
||||
Var lib = mapGet(vm->core_libs, VAR_OBJ(name));
|
||||
if (IS_UNDEF(lib)) return NULL;
|
||||
@ -438,7 +418,7 @@ DEF(coreTypeName,
|
||||
}
|
||||
|
||||
DEF(coreHelp,
|
||||
"help([fn]) -> null\n"
|
||||
"help([fn:Closure]) -> null\n"
|
||||
"This will write an error message to stdout and return null.") {
|
||||
|
||||
int argc = ARGC;
|
||||
@ -456,18 +436,18 @@ DEF(coreHelp,
|
||||
// TODO: Extend help() to work with modules and classes.
|
||||
// Add docstring (like python) to support it in pocketlang.
|
||||
|
||||
Function* fn;
|
||||
if (!validateArgFunction(vm, 1, &fn)) return;
|
||||
Closure* closure;
|
||||
if (!validateArgClosure(vm, 1, &closure)) return;
|
||||
|
||||
// If there ins't an io function callback, we're done.
|
||||
if (vm->config.write_fn == NULL) RET(VAR_NULL);
|
||||
|
||||
if (fn->docstring != NULL) {
|
||||
vm->config.write_fn(vm, fn->docstring);
|
||||
if (closure->fn->docstring != NULL) {
|
||||
vm->config.write_fn(vm, closure->fn->docstring);
|
||||
vm->config.write_fn(vm, "\n\n");
|
||||
} else {
|
||||
vm->config.write_fn(vm, "function '");
|
||||
vm->config.write_fn(vm, fn->name);
|
||||
vm->config.write_fn(vm, closure->fn->name);
|
||||
vm->config.write_fn(vm, "()' doesn't have a docstring.\n");
|
||||
}
|
||||
}
|
||||
@ -788,7 +768,11 @@ static void moduleAddFunctionInternal(PKVM* vm, Module* module,
|
||||
module, true, docstring, NULL);
|
||||
fn->native = fptr;
|
||||
fn->arity = arity;
|
||||
moduleAddGlobal(vm, module, name, (uint32_t)strlen(name), VAR_OBJ(fn));
|
||||
|
||||
vmPushTempRef(vm, &fn->_super); // fn.
|
||||
Closure* closure = newClosure(vm, fn);
|
||||
moduleAddGlobal(vm, module, name, (uint32_t)strlen(name), VAR_OBJ(closure));
|
||||
vmPopTempRef(vm); // fn.
|
||||
}
|
||||
|
||||
// 'lang' library methods.
|
||||
@ -812,18 +796,18 @@ DEF(stdLangGC,
|
||||
}
|
||||
|
||||
DEF(stdLangDisas,
|
||||
"disas(fn:Function) -> String\n"
|
||||
"disas(fn:Closure) -> String\n"
|
||||
"Returns the disassembled opcode of the function [fn].") {
|
||||
|
||||
// TODO: support dissasemble class constructors and module main body.
|
||||
|
||||
Function* fn;
|
||||
if (!validateArgFunction(vm, 1, &fn)) return;
|
||||
Closure* closure;
|
||||
if (!validateArgClosure(vm, 1, &closure)) return;
|
||||
|
||||
if (!validateCond(vm, !fn->is_native,
|
||||
if (!validateCond(vm, !closure->fn->is_native,
|
||||
"Cannot disassemble native functions.")) return;
|
||||
|
||||
dumpFunctionCode(vm, fn);
|
||||
dumpFunctionCode(vm, closure->fn);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -1123,12 +1107,12 @@ DEF(stdMathErfc,
|
||||
// -----------------------
|
||||
|
||||
DEF(stdFiberNew,
|
||||
"new(fn:Function) -> fiber\n"
|
||||
"new(fn:Closure) -> fiber\n"
|
||||
"Create and return a new fiber from the given function [fn].") {
|
||||
|
||||
Function* fn;
|
||||
if (!validateArgFunction(vm, 1, &fn)) return;
|
||||
RET(VAR_OBJ(newFiber(vm, fn)));
|
||||
Closure* closure;
|
||||
if (!validateArgClosure(vm, 1, &closure)) return;
|
||||
RET(VAR_OBJ(newFiber(vm, closure)));
|
||||
}
|
||||
|
||||
DEF(stdFiberRun,
|
||||
@ -1185,15 +1169,17 @@ DEF(stdFiberResume,
|
||||
/* CORE INITIALIZATION */
|
||||
/*****************************************************************************/
|
||||
|
||||
static void initializeBuiltinFN(PKVM* vm, BuiltinFn* bfn, const char* name,
|
||||
static void initializeBuiltinFN(PKVM* vm, Closure** bfn, const char* name,
|
||||
int length, int arity, pkNativeFn ptr,
|
||||
const char* docstring) {
|
||||
bfn->name = name;
|
||||
bfn->length = length;
|
||||
|
||||
bfn->fn = newFunction(vm, name, length, NULL, true, docstring, NULL);
|
||||
bfn->fn->arity = arity;
|
||||
bfn->fn->native = ptr;
|
||||
Function* fn = newFunction(vm, name, length, NULL, true, docstring, NULL);
|
||||
fn->arity = arity;
|
||||
fn->native = ptr;
|
||||
|
||||
vmPushTempRef(vm, &fn->_super); // fn.
|
||||
*bfn = newClosure(vm, fn);
|
||||
vmPopTempRef(vm); // fn.
|
||||
}
|
||||
|
||||
void initializeCore(PKVM* vm) {
|
||||
@ -1663,25 +1649,29 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
|
||||
case OBJ_FUNC:
|
||||
{
|
||||
Function* fn = (Function*)obj;
|
||||
// Functions aren't first class objects.
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
case OBJ_CLOSURE:
|
||||
{
|
||||
Closure* closure = (Closure*)obj;
|
||||
switch (attrib->hash) {
|
||||
|
||||
case CHECK_HASH("arity", 0x3e96bd7a):
|
||||
return VAR_NUM((double)(fn->arity));
|
||||
return VAR_NUM((double)(closure->fn->arity));
|
||||
|
||||
case CHECK_HASH("name", 0x8d39bde6):
|
||||
return VAR_OBJ(newString(vm, fn->name));
|
||||
return VAR_OBJ(newString(vm, closure->fn->name));
|
||||
|
||||
default:
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return VAR_NULL;
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
case OBJ_CLOSURE:
|
||||
case OBJ_UPVALUE:
|
||||
TODO;
|
||||
// Upvalues aren't first class objects.
|
||||
UNREACHABLE();
|
||||
|
||||
case OBJ_FIBER:
|
||||
@ -1693,7 +1683,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
return VAR_BOOL(fb->state == FIBER_DONE);
|
||||
|
||||
case CHECK_HASH("function", 0x9ed64249):
|
||||
return VAR_OBJ(fb->func);
|
||||
return VAR_OBJ(fb->closure);
|
||||
|
||||
default:
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
@ -1787,15 +1777,19 @@ do { \
|
||||
}
|
||||
|
||||
case OBJ_FUNC:
|
||||
// Functions aren't first class objects.
|
||||
UNREACHABLE();
|
||||
return;
|
||||
|
||||
case OBJ_CLOSURE:
|
||||
ATTRIB_IMMUTABLE("arity");
|
||||
ATTRIB_IMMUTABLE("name");
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
return;
|
||||
|
||||
case OBJ_CLOSURE:
|
||||
case OBJ_UPVALUE:
|
||||
TODO;
|
||||
ERR_NO_ATTRIB(vm, on, attrib);
|
||||
// Upvalues aren't first class objects.
|
||||
UNREACHABLE();
|
||||
return;
|
||||
|
||||
case OBJ_FIBER:
|
||||
|
@ -13,16 +13,6 @@
|
||||
// Initialize core language, builtin function and core libs.
|
||||
void initializeCore(PKVM* vm);
|
||||
|
||||
// Find the builtin function name and returns it's index in the builtins array
|
||||
// if not found returns -1.
|
||||
int findBuiltinFunction(const PKVM* vm, const char* name, uint32_t length);
|
||||
|
||||
// Returns the builtin function at index [index].
|
||||
Function* getBuiltinFunction(const PKVM* vm, int index);
|
||||
|
||||
// Returns the builtin function's name at index [index].
|
||||
const char* getBuiltinFunctionName(const PKVM* vm, int index);
|
||||
|
||||
// Return the core library with the [name] if exists in the core libs,
|
||||
// otherwise returns NULL.
|
||||
Module* getCoreLib(const PKVM* vm, String* name);
|
||||
|
@ -231,7 +231,8 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
case OP_PUSH_BUILTIN_FN:
|
||||
{
|
||||
int index = READ_BYTE();
|
||||
const char* name = getBuiltinFunctionName(vm, index);
|
||||
ASSERT_INDEX(index, vm->builtins_count);
|
||||
const char* name = vm->builtins[index]->fn->name;
|
||||
// Prints: %5d [Fn:%s]\n
|
||||
PRINT_INT(index);
|
||||
PRINT(" [Fn:");
|
||||
@ -240,6 +241,21 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_PUSH_CLOSURE:
|
||||
{
|
||||
int index = READ_SHORT();
|
||||
ASSERT_INDEX((uint32_t)index, func->owner->constants.count);
|
||||
Var value = func->owner->constants.data[index];
|
||||
ASSERT(IS_OBJ_TYPE(value, OBJ_FUNC), OOPS);
|
||||
|
||||
// Prints: %5d [val]\n
|
||||
PRINT_INT(index);
|
||||
PRINT(" ");
|
||||
dumpValue(vm, value);
|
||||
NEWLINE();
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_POP: NO_ARGS(); break;
|
||||
case OP_IMPORT:
|
||||
{
|
||||
@ -365,7 +381,7 @@ void dumpGlobalValues(PKVM* vm) {
|
||||
int frame_ind = fiber->frame_count - 1;
|
||||
ASSERT(frame_ind >= 0, OOPS);
|
||||
CallFrame* frame = &fiber->frames[frame_ind];
|
||||
Module* module = frame->fn->owner;
|
||||
Module* module = frame->closure->fn->owner;
|
||||
|
||||
for (uint32_t i = 0; i < module->global_names.count; i++) {
|
||||
String* name = module->names.data[module->global_names.data[i]];
|
||||
|
@ -97,6 +97,11 @@ OPCODE(STORE_GLOBAL, 1, 0)
|
||||
// params: 1 bytes index.
|
||||
OPCODE(PUSH_BUILTIN_FN, 1, 1)
|
||||
|
||||
// Push a closure for the function at the constant pool with index of the
|
||||
// first 2 bytes arguments.
|
||||
// params: 2 byte index.
|
||||
OPCODE(PUSH_CLOSURE, 2, 1)
|
||||
|
||||
// Pop the stack top.
|
||||
OPCODE(POP, 0, -1)
|
||||
|
||||
|
@ -75,9 +75,10 @@ PkHandle* pkNewMap(PKVM* vm) {
|
||||
}
|
||||
|
||||
PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) {
|
||||
__ASSERT(IS_OBJ_TYPE(fn->value, OBJ_FUNC), "Fn should be of type function.");
|
||||
__ASSERT(IS_OBJ_TYPE(fn->value, OBJ_CLOSURE),
|
||||
"Handle should be of type function.");
|
||||
|
||||
Fiber* fiber = newFiber(vm, (Function*)AS_OBJ(fn->value));
|
||||
Fiber* fiber = newFiber(vm, (Closure*)AS_OBJ(fn->value));
|
||||
vmPushTempRef(vm, &fiber->_super); // fiber
|
||||
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(fiber));
|
||||
vmPopTempRef(vm); // fiber
|
||||
@ -264,18 +265,17 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
|
||||
Fiber* fiber = (Fiber*)obj;
|
||||
vm->bytes_allocated += sizeof(Fiber);
|
||||
|
||||
markObject(vm, &fiber->func->_super);
|
||||
markObject(vm, &fiber->closure->_super);
|
||||
|
||||
// Blacken the stack.
|
||||
// Mark the stack.
|
||||
for (Var* local = fiber->stack; local < fiber->sp; local++) {
|
||||
markValue(vm, *local);
|
||||
}
|
||||
vm->bytes_allocated += sizeof(Var) * fiber->stack_size;
|
||||
|
||||
// Blacken call frames.
|
||||
// Mark call frames.
|
||||
for (int i = 0; i < fiber->frame_count; i++) {
|
||||
markObject(vm, (Object*)&fiber->frames[i].fn->_super);
|
||||
markObject(vm, &fiber->frames[i].fn->owner->_super);
|
||||
markObject(vm, (Object*)&fiber->frames[i].closure->_super);
|
||||
}
|
||||
vm->bytes_allocated += sizeof(CallFrame) * fiber->frame_capacity;
|
||||
|
||||
@ -379,7 +379,7 @@ Range* newRange(PKVM* vm, double from, double to) {
|
||||
return range;
|
||||
}
|
||||
|
||||
Module* newModule(PKVM* vm, String* name, bool is_core) {
|
||||
Module* newModule(PKVM* vm, String* name, bool is_native) {
|
||||
Module* module = ALLOCATE(vm, Module);
|
||||
varInitObject(&module->_super, vm, OBJ_MODULE);
|
||||
|
||||
@ -387,11 +387,11 @@ Module* newModule(PKVM* vm, String* name, bool is_core) {
|
||||
|
||||
module->path = name;
|
||||
module->name = NULL;
|
||||
module->initialized = is_core;
|
||||
module->initialized = is_native;
|
||||
module->body = NULL;
|
||||
|
||||
// Core modules has its name as the module name.
|
||||
if (is_core) module->name = name;
|
||||
if (is_native) module->name = name;
|
||||
|
||||
pkVarBufferInit(&module->globals);
|
||||
pkUintBufferInit(&module->global_names);
|
||||
@ -400,7 +400,7 @@ Module* newModule(PKVM* vm, String* name, bool is_core) {
|
||||
|
||||
// Add a implicit main function and the '__file__' global to the module, only
|
||||
// if it's not a core module.
|
||||
if (!is_core) {
|
||||
if (!is_native) {
|
||||
vmPushTempRef(vm, &module->_super); // module.
|
||||
|
||||
moduleAddMain(vm, module);
|
||||
@ -488,7 +488,7 @@ Upvalue* newUpvalue(PKVM* vm, Var* value) {
|
||||
return upvalue;
|
||||
}
|
||||
|
||||
Fiber* newFiber(PKVM* vm, Function* fn) {
|
||||
Fiber* newFiber(PKVM* vm, Closure* closure) {
|
||||
Fiber* fiber = ALLOCATE(vm, Fiber);
|
||||
|
||||
// Not sure why this memset is needed here. If it doesn't then remove it.
|
||||
@ -497,13 +497,13 @@ Fiber* newFiber(PKVM* vm, Function* fn) {
|
||||
varInitObject(&fiber->_super, vm, OBJ_FIBER);
|
||||
|
||||
fiber->state = FIBER_NEW;
|
||||
fiber->func = fn;
|
||||
fiber->closure = closure;
|
||||
|
||||
if (fn->is_native) {
|
||||
if (closure->fn->is_native) {
|
||||
// For native functions, we're only using stack for parameters,
|
||||
// there won't be any locals or temps (which are belongs to the
|
||||
// native "C" stack).
|
||||
int stack_size = utilPowerOf2Ceil(fn->arity + 1);
|
||||
int stack_size = utilPowerOf2Ceil(closure->fn->arity + 1);
|
||||
fiber->stack = ALLOCATE_ARRAY(vm, Var, stack_size);
|
||||
fiber->stack_size = stack_size;
|
||||
fiber->ret = fiber->stack;
|
||||
@ -511,7 +511,7 @@ Fiber* newFiber(PKVM* vm, Function* fn) {
|
||||
|
||||
} else {
|
||||
// Allocate stack.
|
||||
int stack_size = utilPowerOf2Ceil(fn->fn->stack_size + 1);
|
||||
int stack_size = utilPowerOf2Ceil(closure->fn->fn->stack_size + 1);
|
||||
if (stack_size < MIN_STACK_SIZE) stack_size = MIN_STACK_SIZE;
|
||||
fiber->stack = ALLOCATE_ARRAY(vm, Var, stack_size);
|
||||
fiber->stack_size = stack_size;
|
||||
@ -524,8 +524,8 @@ Fiber* newFiber(PKVM* vm, Function* fn) {
|
||||
fiber->frame_count = 1;
|
||||
|
||||
// Initialize the first frame.
|
||||
fiber->frames[0].fn = fn;
|
||||
fiber->frames[0].ip = fn->fn->opcodes.data;
|
||||
fiber->frames[0].closure = closure;
|
||||
fiber->frames[0].ip = closure->fn->fn->opcodes.data;
|
||||
fiber->frames[0].rbp = fiber->ret;
|
||||
}
|
||||
|
||||
@ -559,10 +559,15 @@ Class* newClass(PKVM* vm, Module* module, const char* name, uint32_t length,
|
||||
String* ctor_name = stringFormat(vm, "$(Ctor:@)", special, cls_name);
|
||||
|
||||
// Constructor.
|
||||
vmPushTempRef(vm, &ctor_name->_super); // ctor_name
|
||||
cls->ctor = newFunction(vm, ctor_name->data, ctor_name->length,
|
||||
module, false, NULL, ctor_index);
|
||||
vmPopTempRef(vm); // ctor_name
|
||||
vmPushTempRef(vm, &ctor_name->_super); // ctor_name.
|
||||
{
|
||||
Function* ctor_fn = newFunction(vm, ctor_name->data, ctor_name->length,
|
||||
module, false, NULL, ctor_index);
|
||||
vmPushTempRef(vm, &ctor_fn->_super); // ctor_fn.
|
||||
cls->ctor = newClosure(vm, ctor_fn);
|
||||
vmPopTempRef(vm); // ctor_fn.
|
||||
}
|
||||
vmPopTempRef(vm); // ctor_name.
|
||||
|
||||
vmPopTempRef(vm); // class.
|
||||
return cls;
|
||||
@ -1193,12 +1198,17 @@ void moduleSetGlobal(Module* module, int index, Var value) {
|
||||
void moduleAddMain(PKVM* vm, Module* module) {
|
||||
ASSERT(module->body == NULL, OOPS);
|
||||
|
||||
const char* fn_name = IMPLICIT_MAIN_NAME;
|
||||
module->body = newFunction(vm, fn_name, (int)strlen(fn_name),
|
||||
module, false, NULL/*TODO*/, NULL);
|
||||
module->body->arity = 0;
|
||||
module->initialized = false;
|
||||
|
||||
const char* fn_name = IMPLICIT_MAIN_NAME;
|
||||
Function* body_fn = newFunction(vm, fn_name, (int)strlen(fn_name),
|
||||
module, false, NULL/*TODO*/, NULL);
|
||||
body_fn->arity = 0;
|
||||
|
||||
vmPushTempRef(vm, &body_fn->_super); // body_fn.
|
||||
module->body = newClosure(vm, body_fn);
|
||||
vmPopTempRef(vm); // body_fn.
|
||||
|
||||
moduleAddGlobal(vm, module,
|
||||
IMPLICIT_MAIN_NAME, (uint32_t)strlen(IMPLICIT_MAIN_NAME),
|
||||
VAR_OBJ(module->body));
|
||||
@ -1653,8 +1663,8 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff,
|
||||
case OBJ_FIBER: {
|
||||
const Fiber* fb = (const Fiber*)obj;
|
||||
pkByteBufferAddString(buff, vm, "[Fiber:", 7);
|
||||
pkByteBufferAddString(buff, vm, fb->func->name,
|
||||
(uint32_t)strlen(fb->func->name));
|
||||
pkByteBufferAddString(buff, vm, fb->closure->fn->name,
|
||||
(uint32_t)strlen(fb->closure->fn->name));
|
||||
pkByteBufferWrite(buff, vm, ']');
|
||||
return;
|
||||
}
|
||||
|
@ -307,8 +307,8 @@ struct Module {
|
||||
pkUintBuffer global_names;
|
||||
|
||||
// Top level statements of a module are compiled to an implicit function
|
||||
// body whic will be executed if it's imported for the first time.
|
||||
Function* body;
|
||||
// body which will be executed if it's imported for the first time.
|
||||
Closure* body;
|
||||
|
||||
// If the [initialized] boolean is false, the body function of the module
|
||||
// will be executed when it's first imported and the 'initialized' boolean
|
||||
@ -437,9 +437,9 @@ struct Upvalue {
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const uint8_t* ip; //< Pointer to the next instruction byte code.
|
||||
const Function* fn; //< Function of the frame.
|
||||
Var* rbp; //< Stack base pointer. (%rbp)
|
||||
const uint8_t* ip; //< Pointer to the next instruction byte code.
|
||||
const Closure* closure; //< Closure of the frame.
|
||||
Var* rbp; //< Stack base pointer. (%rbp)
|
||||
} CallFrame;
|
||||
|
||||
typedef enum {
|
||||
@ -454,8 +454,8 @@ struct Fiber {
|
||||
|
||||
FiberState state;
|
||||
|
||||
// The root function of the fiber.
|
||||
Function* func;
|
||||
// The root closure of the fiber.
|
||||
Closure* closure;
|
||||
|
||||
// The stack of the execution holding locals and temps. A heap will be
|
||||
// allocated and grow as needed.
|
||||
@ -493,7 +493,7 @@ struct Class {
|
||||
// buffer.
|
||||
uint32_t name;
|
||||
|
||||
Function* ctor; //< The constructor function.
|
||||
Closure* ctor; //< The constructor function.
|
||||
pkUintBuffer field_names; //< Buffer of field names.
|
||||
// TODO: ordered names buffer for binary search.
|
||||
};
|
||||
@ -554,7 +554,7 @@ Range* newRange(PKVM* vm, double from, double to);
|
||||
|
||||
// FIXME:
|
||||
// We may need 2 different constructor for native and script modules.
|
||||
Module* newModule(PKVM* vm, String* name, bool is_core);
|
||||
Module* newModule(PKVM* vm, String* name, bool is_native);
|
||||
|
||||
// FIXME:
|
||||
// We may need 2 different constuctor for native and script functions.
|
||||
@ -568,8 +568,8 @@ Closure* newClosure(PKVM* vm, Function* fn);
|
||||
// Allocate a new upvalue object for the [value] and return it.
|
||||
Upvalue* newUpvalue(PKVM* vm, Var* value);
|
||||
|
||||
// Allocate new Fiber object around the function [fn] and return Fiber*.
|
||||
Fiber* newFiber(PKVM* vm, Function* fn);
|
||||
// Allocate new Fiber object for the [closure] and return Fiber*.
|
||||
Fiber* newFiber(PKVM* vm, Closure* closure);
|
||||
|
||||
// FIXME:
|
||||
// Same fix has to applied as newFunction() (see above).
|
||||
|
84
src/pk_vm.c
84
src/pk_vm.c
@ -256,8 +256,8 @@ void vmCollectGarbage(PKVM* vm) {
|
||||
|
||||
// Mark the core libs and builtin functions.
|
||||
markObject(vm, &vm->core_libs->_super);
|
||||
for (uint32_t i = 0; i < vm->builtins_count; i++) {
|
||||
markObject(vm, &vm->builtins[i].fn->_super);
|
||||
for (int i = 0; i < vm->builtins_count; i++) {
|
||||
markObject(vm, &vm->builtins[i]->_super);
|
||||
}
|
||||
|
||||
// Mark the modules cache.
|
||||
@ -323,10 +323,12 @@ void vmCollectGarbage(PKVM* vm) {
|
||||
} while (false)
|
||||
|
||||
bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv) {
|
||||
ASSERT(fiber->func->arity >= -1, OOPS " (Forget to initialize arity.)");
|
||||
ASSERT(fiber->closure->fn->arity >= -1,
|
||||
OOPS " (Forget to initialize arity.)");
|
||||
|
||||
if (argc != fiber->func->arity) {
|
||||
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", fiber->func->arity);
|
||||
if (argc != fiber->closure->fn->arity) {
|
||||
char buff[STR_INT_BUFF_SIZE];
|
||||
sprintf(buff, "%d", fiber->closure->fn->arity);
|
||||
_ERR_FAIL(stringFormat(vm, "Expected exactly $ argument(s).", buff));
|
||||
}
|
||||
|
||||
@ -513,8 +515,8 @@ static inline void growStack(PKVM* vm, int size) {
|
||||
}
|
||||
}
|
||||
|
||||
static inline void pushCallFrame(PKVM* vm, const Function* fn, Var* rbp) {
|
||||
ASSERT(!fn->is_native, "Native function shouldn't use call frames.");
|
||||
static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) {
|
||||
ASSERT(!closure->fn->is_native, OOPS);
|
||||
|
||||
// Grow the stack frame if needed.
|
||||
if (vm->fiber->frame_count + 1 > vm->fiber->frame_capacity) {
|
||||
@ -526,31 +528,32 @@ static inline void pushCallFrame(PKVM* vm, const Function* fn, Var* rbp) {
|
||||
}
|
||||
|
||||
// Grow the stack if needed.
|
||||
int needed = fn->fn->stack_size + (int)(vm->fiber->sp - vm->fiber->stack);
|
||||
int needed = (closure->fn->fn->stack_size +
|
||||
(int)(vm->fiber->sp - vm->fiber->stack));
|
||||
if (vm->fiber->stack_size <= needed) growStack(vm, needed);
|
||||
|
||||
CallFrame* frame = vm->fiber->frames + vm->fiber->frame_count++;
|
||||
frame->rbp = rbp;
|
||||
frame->fn = fn;
|
||||
frame->ip = fn->fn->opcodes.data;
|
||||
frame->closure = closure;
|
||||
frame->ip = closure->fn->fn->opcodes.data;
|
||||
}
|
||||
|
||||
static inline void reuseCallFrame(PKVM* vm, const Function* fn) {
|
||||
static inline void reuseCallFrame(PKVM* vm, const Closure* closure) {
|
||||
|
||||
ASSERT(!fn->is_native, "Native function shouldn't use call frames.");
|
||||
ASSERT(fn->arity >= 0, OOPS);
|
||||
ASSERT(!closure->fn->is_native, OOPS);
|
||||
ASSERT(closure->fn->arity >= 0, OOPS);
|
||||
ASSERT(vm->fiber->frame_count > 0, OOPS);
|
||||
|
||||
Fiber* fb = vm->fiber;
|
||||
|
||||
CallFrame* frame = fb->frames + fb->frame_count - 1;
|
||||
frame->fn = fn;
|
||||
frame->ip = fn->fn->opcodes.data;
|
||||
frame->closure = closure;
|
||||
frame->ip = closure->fn->fn->opcodes.data;
|
||||
|
||||
ASSERT(*frame->rbp == VAR_NULL, OOPS);
|
||||
|
||||
// Move all the argument(s) to the base of the current frame.
|
||||
Var* arg = fb->sp - fn->arity;
|
||||
Var* arg = fb->sp - closure->fn->arity;
|
||||
Var* target = frame->rbp + 1;
|
||||
for (; arg < fb->sp; arg++, target++) {
|
||||
*target = *arg;
|
||||
@ -560,7 +563,8 @@ static inline void reuseCallFrame(PKVM* vm, const Function* fn) {
|
||||
fb->sp = target;
|
||||
|
||||
// Grow the stack if needed (least probably).
|
||||
int needed = fn->fn->stack_size + (int)(vm->fiber->sp - vm->fiber->stack);
|
||||
int needed = (closure->fn->fn->stack_size +
|
||||
(int)(vm->fiber->sp - vm->fiber->stack));
|
||||
if (vm->fiber->stack_size <= needed) growStack(vm, needed);
|
||||
}
|
||||
|
||||
@ -574,7 +578,7 @@ static void reportError(PKVM* vm) {
|
||||
vm->config.error_fn(vm, PK_ERROR_RUNTIME, NULL, -1, fiber->error->data);
|
||||
for (int i = fiber->frame_count - 1; i >= 0; i--) {
|
||||
CallFrame* frame = &fiber->frames[i];
|
||||
const Function* fn = frame->fn;
|
||||
const Function* fn = frame->closure->fn;
|
||||
ASSERT(!fn->is_native, OOPS);
|
||||
int line = fn->fn->oplines.data[frame->ip - fn->fn->opcodes.data - 1];
|
||||
vm->config.error_fn(vm, PK_ERROR_STACKTRACE, fn->owner->path->data, line,
|
||||
@ -658,7 +662,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
|
||||
frame = &vm->fiber->frames[vm->fiber->frame_count-1]; \
|
||||
ip = frame->ip; \
|
||||
rbp = frame->rbp; \
|
||||
module = frame->fn->owner; \
|
||||
module = frame->closure->fn->owner; \
|
||||
} while (false)
|
||||
|
||||
// Update the frame's execution variables before pushing another call frame.
|
||||
@ -854,8 +858,18 @@ L_vm_main_loop:
|
||||
{
|
||||
uint8_t index = READ_BYTE();
|
||||
ASSERT_INDEX(index, vm->builtins_count);
|
||||
Function* fn = vm->builtins[index].fn;
|
||||
PUSH(VAR_OBJ(fn));
|
||||
Closure* closure = vm->builtins[index];
|
||||
PUSH(VAR_OBJ(closure));
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(PUSH_CLOSURE):
|
||||
{
|
||||
uint16_t index = READ_SHORT();
|
||||
ASSERT_INDEX(index, module->constants.count);
|
||||
ASSERT(IS_OBJ_TYPE(module->constants.data[index], OBJ_FUNC), OOPS);
|
||||
Function* fn = (Function*)AS_OBJ(module->constants.data[index]);
|
||||
PUSH(VAR_OBJ(newClosure(vm, fn)));
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
@ -907,13 +921,17 @@ L_vm_main_loop:
|
||||
Fiber* call_fiber = vm->fiber;
|
||||
Var* callable = call_fiber->sp - argc - 1;
|
||||
|
||||
const Function* fn = NULL;
|
||||
const Closure* closure = NULL;
|
||||
|
||||
if (IS_OBJ_TYPE(*callable, OBJ_FUNC)) {
|
||||
fn = (const Function*)AS_OBJ(*callable);
|
||||
// Raw functions cannot be on the stack, since they're not first class
|
||||
// citizens.
|
||||
ASSERT(!IS_OBJ_TYPE(*callable, OBJ_FUNC), OOPS);
|
||||
|
||||
if (IS_OBJ_TYPE(*callable, OBJ_CLOSURE)) {
|
||||
closure = (const Closure*)AS_OBJ(*callable);
|
||||
|
||||
} else if (IS_OBJ_TYPE(*callable, OBJ_CLASS)) {
|
||||
fn = (const Function*)((Class*)AS_OBJ(*callable))->ctor;
|
||||
closure = (const Closure*)((Class*)AS_OBJ(*callable))->ctor;
|
||||
|
||||
} else {
|
||||
RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a callable to "
|
||||
@ -925,8 +943,8 @@ L_vm_main_loop:
|
||||
// If we reached here it's a valid callable.
|
||||
|
||||
// -1 argument means multiple number of args.
|
||||
if (fn->arity != -1 && fn->arity != argc) {
|
||||
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", fn->arity);
|
||||
if (closure->fn->arity != -1 && closure->fn->arity != argc) {
|
||||
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", closure->fn->arity);
|
||||
String* msg = stringFormat(vm, "Expected exactly $ argument(s).",
|
||||
buff);
|
||||
RUNTIME_ERROR(msg);
|
||||
@ -936,17 +954,17 @@ L_vm_main_loop:
|
||||
call_fiber->ret = callable;
|
||||
*(call_fiber->ret) = VAR_NULL; //< Set the return value to null.
|
||||
|
||||
if (fn->is_native) {
|
||||
if (closure->fn->is_native) {
|
||||
|
||||
if (fn->native == NULL) {
|
||||
if (closure->fn->native == NULL) {
|
||||
RUNTIME_ERROR(stringFormat(vm,
|
||||
"Native function pointer of $ was NULL.", fn->name));
|
||||
"Native function pointer of $ was NULL.", closure->fn->name));
|
||||
}
|
||||
|
||||
// Update the current frame's ip.
|
||||
UPDATE_FRAME();
|
||||
|
||||
fn->native(vm); //< Call the native function.
|
||||
closure->fn->native(vm); //< Call the native function.
|
||||
|
||||
// Calling yield() will change vm->fiber to it's caller fiber, which
|
||||
// would be null if we're not running the function with a fiber.
|
||||
@ -965,13 +983,13 @@ L_vm_main_loop:
|
||||
|
||||
if (instruction == OP_CALL) {
|
||||
UPDATE_FRAME(); //< Update the current frame's ip.
|
||||
pushCallFrame(vm, fn, callable);
|
||||
pushCallFrame(vm, closure, callable);
|
||||
LOAD_FRAME(); //< Load the top frame to vm's execution variables.
|
||||
|
||||
} else {
|
||||
ASSERT(instruction == OP_TAIL_CALL, OOPS);
|
||||
|
||||
reuseCallFrame(vm, fn);
|
||||
reuseCallFrame(vm, closure);
|
||||
LOAD_FRAME(); //< Re-load the frame to vm's execution variables.
|
||||
}
|
||||
}
|
||||
|
12
src/pk_vm.h
12
src/pk_vm.h
@ -46,14 +46,6 @@
|
||||
(vm->fiber->error = err); \
|
||||
} while (false)
|
||||
|
||||
// Builtin functions are stored in an array in the VM unlike other functions
|
||||
// builtin function's doesn't belongs to any module.
|
||||
typedef struct {
|
||||
const char* name; //< Name of the function.
|
||||
uint32_t length; //< Length of the name.
|
||||
Function* fn; //< Native function pointer.
|
||||
} BuiltinFn;
|
||||
|
||||
// A doubly link list of vars that have reference in the host application.
|
||||
// Handles are wrapper around Var that lives on the host application.
|
||||
struct PkHandle {
|
||||
@ -122,8 +114,8 @@ struct PKVM {
|
||||
Map* core_libs;
|
||||
|
||||
// Array of all builtin functions.
|
||||
BuiltinFn builtins[BUILTIN_FN_CAPACITY];
|
||||
uint32_t builtins_count;
|
||||
Closure* builtins[BUILTIN_FN_CAPACITY];
|
||||
int builtins_count;
|
||||
|
||||
// Current fiber.
|
||||
Fiber* fiber;
|
||||
|
Loading…
Reference in New Issue
Block a user