mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-05 20:26:53 +08:00
Merge pull request #44 from ThakeeNathees/repl-refactor-3
REPL refactor [3/3]
This commit is contained in:
commit
63d3039eeb
@ -28,6 +28,8 @@
|
||||
- Hex, binary literals and floats like ".5".
|
||||
- Docs build to a single directory, and replace url space (%20) with a hyphen.
|
||||
- Complete all the TODO; macros.
|
||||
- implement MAX_ARGC checks (would cause a buffer overflow if not)
|
||||
when compiling and calling a function (also in fibers).
|
||||
|
||||
// Low priority.
|
||||
- Ignore line with '\' character.
|
||||
|
64
cli/main.c
64
cli/main.c
@ -14,7 +14,7 @@
|
||||
void registerModules(PKVM* vm);
|
||||
|
||||
// Path public functions (TODO: should I add a header for that?)
|
||||
void pathInit();
|
||||
void pathInit(void);
|
||||
bool pathIsAbsolute(const char* path);
|
||||
void pathGetDirName(const char* path, size_t* length);
|
||||
size_t pathNormalize(const char* path, char* buff, size_t buff_size);
|
||||
@ -23,6 +23,11 @@ size_t pathJoin(const char* from, const char* path, char* buffer,
|
||||
|
||||
// ---------------------------------------
|
||||
|
||||
// FIXME:
|
||||
typedef struct {
|
||||
bool repl_mode;
|
||||
} VmUserData;
|
||||
|
||||
void onResultDone(PKVM* vm, PkStringPtr result) {
|
||||
|
||||
if ((bool)result.user_data) {
|
||||
@ -31,15 +36,22 @@ void onResultDone(PKVM* vm, PkStringPtr result) {
|
||||
}
|
||||
|
||||
void errorFunction(PKVM* vm, PkErrorType type, const char* file, int line,
|
||||
const char* message) {
|
||||
const char* message) {
|
||||
|
||||
VmUserData* ud = (VmUserData*)pkGetUserData(vm);
|
||||
bool repl = (ud) ? ud->repl_mode : false;
|
||||
|
||||
if (type == PK_ERROR_COMPILE) {
|
||||
fprintf(stderr, "Error: %s\n at \"%s\":%i\n", message, file, line);
|
||||
|
||||
if (repl) fprintf(stderr, "Error: %s\n", message);
|
||||
else fprintf(stderr, "Error: %s\n at \"%s\":%i\n", message, file, line);
|
||||
|
||||
} else if (type == PK_ERROR_RUNTIME) {
|
||||
fprintf(stderr, "Error: %s\n", message);
|
||||
|
||||
} else if (type == PK_ERROR_STACKTRACE) {
|
||||
fprintf(stderr, " %s() [\"%s\":%i]\n", message, file, line);
|
||||
if (repl) fprintf(stderr, " %s() [line:%i]\n", message, line);
|
||||
else fprintf(stderr, " %s() [\"%s\":%i]\n", message, file, line);
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +59,7 @@ void writeFunction(PKVM* vm, const char* text) {
|
||||
fprintf(stdout, "%s", text);
|
||||
}
|
||||
|
||||
static const char* read_line() {
|
||||
static const char* read_line(void) {
|
||||
// FIXME: use fgetc char by char till reach a new line.
|
||||
const int size = 1024;
|
||||
char* mem = (char*) malloc(size);
|
||||
@ -141,15 +153,15 @@ int main(int argc, char** argv) {
|
||||
"PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n"
|
||||
"Copyright(c) 2020 - 2021 ThakeeNathees.\n"
|
||||
"Free and open source software under the terms of the MIT license.\n";
|
||||
const char* help = "usage: pocket [-c cmd | file]\n";
|
||||
|
||||
const char* usage = "usage: pocket [-c cmd | file]\n";
|
||||
|
||||
// TODO: implement arg parse, REPL.
|
||||
|
||||
if (argc < 2) {
|
||||
printf("%s\n%s", notice, help);
|
||||
return 0;
|
||||
}
|
||||
//if (argc < 2) {
|
||||
// printf("%s\n%s", notice, help);
|
||||
// return 0;
|
||||
//}
|
||||
|
||||
// Initialize cli.
|
||||
pathInit();
|
||||
@ -166,13 +178,39 @@ int main(int argc, char** argv) {
|
||||
|
||||
PKVM* vm = pkNewVM(&config);
|
||||
registerModules(vm);
|
||||
PkResult result;
|
||||
|
||||
// FIXME: this is temp till arg parse implemented.
|
||||
PkResult result;
|
||||
|
||||
if (argc == 1) {
|
||||
// TODO:
|
||||
//PkHandle* module = pkNewModule(vm, "$(REPL)");
|
||||
PkHandle* module = pkNewModule(vm, "$(REPL)");
|
||||
options.repl_mode = true;
|
||||
|
||||
VmUserData user_data;
|
||||
user_data.repl_mode = true;
|
||||
pkSetUserData(vm, &user_data);
|
||||
|
||||
printf("%s\n", notice);
|
||||
bool done = false;
|
||||
do {
|
||||
printf(">>> ");
|
||||
PkStringPtr line = { read_line(), onResultDone, (void*)true };
|
||||
// TODO: if line is empty continue.
|
||||
|
||||
result = pkCompileModule(vm, module, line, &options);
|
||||
if (result != PK_RESULT_SUCCESS) continue;
|
||||
|
||||
PkHandle* main_fn = pkGetFunction(vm, module, PK_IMPLICIT_MAIN_NAME);
|
||||
PkHandle* fiber = pkNewFiber(vm, main_fn);
|
||||
result = pkRunFiber(vm, fiber, 0, NULL);
|
||||
|
||||
pkReleaseHandle(vm, main_fn);
|
||||
pkReleaseHandle(vm, fiber);
|
||||
|
||||
} while (!done);
|
||||
|
||||
pkReleaseHandle(vm, module);
|
||||
|
||||
|
||||
} if (argc >= 3 && strcmp(argv[1], "-c") == 0) {
|
||||
|
||||
|
@ -40,7 +40,7 @@
|
||||
/* PUBLIC FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
|
||||
void pathInit() {
|
||||
void pathInit(void) {
|
||||
cwk_path_set_style(CWK_STYLE_UNIX);
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,11 @@
|
||||
// header for more information on Nan-tagging.
|
||||
#define VAR_NAN_TAGGING 1
|
||||
|
||||
// The maximum number of argument a pocketlang function supported to call. This
|
||||
// value is arbitary and feel free to change it. (Just used this limit for an
|
||||
// internal buffer to store values before calling a new fiber).
|
||||
#define MAX_ARGC 32
|
||||
|
||||
// The factor by which a buffer will grow when it's capacity reached.
|
||||
#define GROW_FACTOR 2
|
||||
|
||||
|
102
src/compiler.c
102
src/compiler.c
@ -251,7 +251,7 @@ typedef enum {
|
||||
|
||||
typedef struct {
|
||||
const char* name; //< Directly points into the source string.
|
||||
int length; //< Length of the name.
|
||||
uint32_t length; //< Length of the name.
|
||||
int depth; //< The depth the local is defined in.
|
||||
int line; //< The line variable declared for debugging.
|
||||
} Local;
|
||||
@ -643,7 +643,7 @@ static void lexToken(Compiler* compiler) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r': {
|
||||
char c = peekChar(compiler);
|
||||
c = peekChar(compiler);
|
||||
while (c == ' ' || c == '\t' || c == '\r') {
|
||||
eatChar(compiler);
|
||||
c = peekChar(compiler);
|
||||
@ -735,11 +735,6 @@ static TokenType peek(Compiler* self) {
|
||||
return self->current.type;
|
||||
}
|
||||
|
||||
// Returns next token type without lexing a new token.
|
||||
static TokenType peekNext(Compiler* self) {
|
||||
return self->next.type;
|
||||
}
|
||||
|
||||
// Consume the current token if it's expected and lex for the next token
|
||||
// and return true otherwise reutrn false.
|
||||
static bool match(Compiler* self, TokenType expected) {
|
||||
@ -794,7 +789,7 @@ static bool matchEndStatement(Compiler* compiler) {
|
||||
// Consume semi collon, multiple new lines or peek 'end' keyword.
|
||||
static void consumeEndStatement(Compiler* compiler) {
|
||||
if (!matchEndStatement(compiler)) {
|
||||
parseError(compiler, "Expected statement end with newline or ';'.");
|
||||
parseError(compiler, "Expected statement end with '\\n' or ';'.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -924,9 +919,9 @@ static void patchForward(Compiler* compiler, Fn* fn, int index, int name);
|
||||
|
||||
static int compilerAddConstant(Compiler* compiler, Var value);
|
||||
static int compilerGetVariable(Compiler* compiler, const char* name,
|
||||
int length);
|
||||
uint32_t length);
|
||||
static int compilerAddVariable(Compiler* compiler, const char* name,
|
||||
int length, int line);
|
||||
uint32_t length, int line);
|
||||
static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn,
|
||||
const char* name, int length, int line);
|
||||
|
||||
@ -1491,7 +1486,7 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
|
||||
// Return the index of the variable if it's already defined in the current
|
||||
// scope otherwise returns -1.
|
||||
static int compilerGetVariable(Compiler* compiler, const char* name,
|
||||
int length) {
|
||||
uint32_t length) {
|
||||
for (int i = compiler->local_count - 1; i >= 0; i--) {
|
||||
Local* local = &compiler->locals[i];
|
||||
if (length == local->length && strncmp(name, local->name, length) == 0) {
|
||||
@ -1504,13 +1499,13 @@ static int compilerGetVariable(Compiler* compiler, const char* name,
|
||||
// Add a variable and return it's index to the context. Assumes that the
|
||||
// variable name is unique and not defined before in the current scope.
|
||||
static int compilerAddVariable(Compiler* compiler, const char* name,
|
||||
int length, int line) {
|
||||
uint32_t length, int line) {
|
||||
|
||||
// TODO: should I validate the name for pre-defined, etc?
|
||||
|
||||
// Check if maximum variable count is reached.
|
||||
bool max_vars_reached = false;
|
||||
const char* var_type; // For max variables reached error message.
|
||||
const char* var_type = ""; // For max variables reached error message.
|
||||
if (compiler->scope_depth == DEPTH_GLOBAL) {
|
||||
if (compiler->local_count == MAX_VARIABLES) {
|
||||
max_vars_reached = true;
|
||||
@ -1726,7 +1721,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
argc++;
|
||||
|
||||
const char* param_name = compiler->previous.start;
|
||||
int param_len = compiler->previous.length;
|
||||
uint32_t param_len = compiler->previous.length;
|
||||
|
||||
// TODO: move this to a functions.
|
||||
bool predefined = false;
|
||||
@ -1757,7 +1752,12 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
||||
compileBlockBody(compiler, BLOCK_FUNC);
|
||||
consume(compiler, TK_END, "Expected 'end' after function definition end.");
|
||||
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
// TODO: This is the function end return, if we pop all the parameters the
|
||||
// below push_null is redundent (because we always have a null at the rbp
|
||||
// of the call frame. (for i in argc : emit(pop)) emit(return); but this
|
||||
// might be faster (right?).
|
||||
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
emitOpcode(compiler, OP_RETURN);
|
||||
emitOpcode(compiler, OP_END);
|
||||
}
|
||||
@ -1931,6 +1931,8 @@ static inline Script* compilerImport(Compiler* compiler) {
|
||||
// before executing the below instructions.
|
||||
static void compilerImportAll(Compiler* compiler, Script* script) {
|
||||
|
||||
ASSERT(script != NULL, OOPS);
|
||||
|
||||
// Line number of the variables which will be bind to the imported sybmols.
|
||||
int line = compiler->previous.line;
|
||||
|
||||
@ -2048,7 +2050,7 @@ static void compileFromImport(Compiler* compiler) {
|
||||
emitStoreVariable(compiler, var_index, true);
|
||||
emitOpcode(compiler, OP_POP);
|
||||
|
||||
} while (match(compiler, TK_COMMA));
|
||||
} while (match(compiler, TK_COMMA) && (skipNewLines(compiler), true));
|
||||
}
|
||||
|
||||
// Done getting all the attributes, now pop the lib from the stack.
|
||||
@ -2115,12 +2117,11 @@ static void compileRegularImport(Compiler* compiler) {
|
||||
emitOpcode(compiler, OP_POP);
|
||||
}
|
||||
|
||||
} while (match(compiler, TK_COMMA));
|
||||
} while (match(compiler, TK_COMMA) && (skipNewLines(compiler), true));
|
||||
|
||||
consumeEndStatement(compiler);
|
||||
}
|
||||
|
||||
|
||||
// Compiles an expression. An expression will result a value on top of the
|
||||
// stack.
|
||||
static void compileExpression(Compiler* compiler) {
|
||||
@ -2260,10 +2261,13 @@ static void compileForStatement(Compiler* compiler) {
|
||||
static void compileStatement(Compiler* compiler) {
|
||||
|
||||
// is_temproary will be set to true if the statement is an temporary
|
||||
// expression, it'll used to be pop from the stack. If running REPL mode used
|
||||
// to print it's value.
|
||||
// expression, it'll used to be pop from the stack.
|
||||
bool is_temproary = false;
|
||||
|
||||
// This will be set to true if the statement is an expression. It'll used to
|
||||
// print it's value when running in REPL mode.
|
||||
bool is_expression = false;
|
||||
|
||||
if (match(compiler, TK_BREAK)) {
|
||||
if (compiler->loop == NULL) {
|
||||
parseError(compiler, "Cannot use 'break' outside a loop.");
|
||||
@ -2321,19 +2325,23 @@ static void compileStatement(Compiler* compiler) {
|
||||
compiler->new_local = false;
|
||||
compileExpression(compiler);
|
||||
consumeEndStatement(compiler);
|
||||
|
||||
is_expression = true;
|
||||
if (!compiler->new_local) is_temproary = true;
|
||||
|
||||
compiler->new_local = false;
|
||||
}
|
||||
|
||||
// If running REPL mode, print the expression's evaluvated value. Python
|
||||
// does print local depth expression too. (it's just a design decision).
|
||||
// If running REPL mode, print the expression's evaluvated value. Only if
|
||||
// we're at the top level. Python does print local depth expression too.
|
||||
// (it's just a design decision).
|
||||
if (compiler->options && compiler->options->repl_mode &&
|
||||
is_temproary /*&& compiler->scope_depth == DEPTH_GLOBAL*/) {
|
||||
compiler->func->ptr == compiler->script->body &&
|
||||
is_expression /*&& compiler->scope_depth == DEPTH_GLOBAL*/) {
|
||||
emitOpcode(compiler, OP_REPL_PRINT);
|
||||
}
|
||||
|
||||
if (is_temproary) emitOpcode(compiler, OP_POP);
|
||||
|
||||
}
|
||||
|
||||
// Compile statements that are only valid at the top level of the script. Such
|
||||
@ -2377,11 +2385,16 @@ bool compile(PKVM* vm, Script* script, const char* source,
|
||||
compiler->next_compiler = vm->compiler;
|
||||
vm->compiler = compiler;
|
||||
|
||||
// Remember the count of the (valid) code of the provided script body. For
|
||||
// new scripts it'll be null, but if we're compiling it multiple times we
|
||||
// already have some code before. And if the compilation failed we discard
|
||||
// all the compiled opcodes and jump back to the valid_code.
|
||||
uint32_t valid_code = script->body->fn->opcodes.count;
|
||||
// If we're compiling for a script that was already compiled (when running
|
||||
// REPL or evaluvating an expression) we don't need the old main anymore.
|
||||
// just use the globals and functions of the script and use a new body func.
|
||||
ASSERT(script->body != NULL, OOPS);
|
||||
byteBufferClear(&script->body->fn->opcodes, vm);
|
||||
|
||||
// Remember the count of the globals and functions, If the compilation failed
|
||||
// discard all the globals and functions added by the compilation.
|
||||
uint32_t globals_count = script->globals.count;
|
||||
uint32_t functions_count = script->functions.count;
|
||||
|
||||
Func curr_fn;
|
||||
curr_fn.depth = DEPTH_SCRIPT;
|
||||
@ -2416,14 +2429,9 @@ bool compile(PKVM* vm, Script* script, const char* source,
|
||||
skipNewLines(compiler);
|
||||
}
|
||||
|
||||
if (compiler->options == NULL || !compiler->options->repl_mode) {
|
||||
emitOpcode(compiler, OP_PUSH_NULL);
|
||||
emitOpcode(compiler, OP_RETURN);
|
||||
emitOpcode(compiler, OP_END);
|
||||
|
||||
} else {
|
||||
emitOpcode(compiler, OP_YIELD);
|
||||
}
|
||||
// Already a null at the stack top, added when the fiber for the function created.
|
||||
emitOpcode(compiler, OP_RETURN);
|
||||
emitOpcode(compiler, OP_END);
|
||||
|
||||
// Resolve forward names (function names that are used before defined).
|
||||
for (int i = 0; i < compiler->forwards_count; i++) {
|
||||
@ -2441,23 +2449,21 @@ bool compile(PKVM* vm, Script* script, const char* source,
|
||||
|
||||
vm->compiler = compiler->next_compiler;
|
||||
|
||||
// If compilation failed, discard all the invalid functions and globals.
|
||||
if (compiler->has_errors) {
|
||||
script->globals.count = script->global_names.count = globals_count;
|
||||
script->functions.count = script->function_names.count = functions_count;
|
||||
}
|
||||
|
||||
#if DEBUG_DUMP_COMPILED_CODE
|
||||
dumpFunctionCode(vm, script->body);
|
||||
#endif
|
||||
|
||||
// If the compilation failed discard all the compiled invalid code.
|
||||
// TODO: May be shrink the buffer.
|
||||
if (compiler->has_errors) {
|
||||
script->body->fn->opcodes.count = valid_code;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we reach here, it means the compilation is success and return true.
|
||||
return true;
|
||||
|
||||
return !compiler->has_errors;
|
||||
}
|
||||
|
||||
PkResult pkCompileModule(PKVM* vm, PkHandle* module, PkStringPtr source,
|
||||
const PkCompileOptions* options) {
|
||||
const PkCompileOptions* options) {
|
||||
__ASSERT(module != NULL, "Argument module was NULL.");
|
||||
Var scr = module->value;
|
||||
__ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module");
|
||||
|
126
src/core.c
126
src/core.c
@ -218,6 +218,28 @@ void pkReturnValue(PKVM* vm, PkVar value) {
|
||||
RET(*(Var*)value);
|
||||
}
|
||||
|
||||
const char* pkStringGetData(const PkVar value) {
|
||||
const Var str = (*(const Var*)value);
|
||||
__ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string.");
|
||||
return ((String*)AS_OBJ(str))->data;
|
||||
}
|
||||
|
||||
PkVar pkFiberGetReturnValue(const PkHandle* fiber) {
|
||||
__ASSERT(fiber != NULL, "Handle fiber was NULL.");
|
||||
Var fb = fiber->value;
|
||||
__ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber");
|
||||
Fiber* _fiber = (Fiber*)AS_OBJ(fb);
|
||||
return (PkVar)_fiber->ret;
|
||||
}
|
||||
|
||||
bool pkFiberIsDone(const PkHandle* fiber) {
|
||||
__ASSERT(fiber != NULL, "Handle fiber was NULL.");
|
||||
Var fb = fiber->value;
|
||||
__ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber");
|
||||
Fiber* _fiber = (Fiber*)AS_OBJ(fb);
|
||||
return _fiber->state == FIBER_DONE;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* VALIDATORS */
|
||||
/*****************************************************************************/
|
||||
@ -294,7 +316,7 @@ static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size,
|
||||
|
||||
// findBuiltinFunction implementation (see core.h for description).
|
||||
int findBuiltinFunction(const PKVM* vm, const char* name, uint32_t length) {
|
||||
for (int i = 0; i < vm->builtins_count; i++) {
|
||||
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;
|
||||
@ -456,7 +478,7 @@ PK_DOC(coreStrLower,
|
||||
|
||||
String* result = newStringLength(vm, str->data, str->length);
|
||||
char* data = result->data;
|
||||
for (; *data; ++data) *data = tolower(*data);
|
||||
for (; *data; ++data) *data = (char)tolower(*data);
|
||||
// Since the string is modified re-hash it.
|
||||
result->hash = utilHashString(result->data);
|
||||
|
||||
@ -471,7 +493,7 @@ PK_DOC(coreStrUpper,
|
||||
|
||||
String* result = newStringLength(vm, str->data, str->length);
|
||||
char* data = result->data;
|
||||
for (; *data; ++data) *data = toupper(*data);
|
||||
for (; *data; ++data) *data = (char)toupper(*data);
|
||||
// Since the string is modified re-hash it.
|
||||
result->hash = utilHashString(result->data);
|
||||
|
||||
@ -586,56 +608,19 @@ PK_DOC(coreFiberRun,
|
||||
Fiber* fb;
|
||||
if (!validateArgFiber(vm, 1, &fb)) return;
|
||||
|
||||
ASSERT(fb->func->arity >= -1 , OOPS " (Forget to initialize arity.)");
|
||||
// Buffer of argument to call vmPrepareFiber().
|
||||
Var* args[MAX_ARGC];
|
||||
|
||||
if (argc - 1 != fb->func->arity) {
|
||||
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", fb->func->arity);
|
||||
RET_ERR(stringFormat(vm, "Expected excatly $ argument(s).", buff));
|
||||
}
|
||||
|
||||
if (fb->state != FIBER_NEW) {
|
||||
switch (fb->state) {
|
||||
case FIBER_NEW: UNREACHABLE();
|
||||
case FIBER_RUNNING:
|
||||
RET_ERR(newString(vm, "The fiber has already been running."));
|
||||
case FIBER_YIELDED:
|
||||
RET_ERR(newString(vm, "Cannot run a fiber which is yielded, use "
|
||||
"fiber_resume() instead."));
|
||||
case FIBER_DONE:
|
||||
RET_ERR(newString(vm, "The fiber has done running."));
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
ASSERT(fb->stack != NULL && fb->sp == fb->stack, OOPS);
|
||||
ASSERT(fb->ret == fb->sp, OOPS);
|
||||
|
||||
fb->state = FIBER_RUNNING;
|
||||
fb->caller = vm->fiber;
|
||||
|
||||
// Pass the function arguments.
|
||||
|
||||
// Assert we have the first frame (to push the arguments). And assert we have
|
||||
// enought stack space for parameters.
|
||||
ASSERT(fb->frame_count == 1, OOPS);
|
||||
ASSERT(fb->frames[0].rbp == fb->ret, OOPS);
|
||||
ASSERT((fb->stack + fb->stack_size) - fb->sp >= argc, OOPS);
|
||||
|
||||
// ARG1 is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc).
|
||||
// And ret[0] is the return value, parameters starts at ret[1], ...
|
||||
// ARG(1) is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc).
|
||||
for (int i = 1; i < argc; i++) {
|
||||
fb->ret[i] = ARG(i + 1);
|
||||
args[i - 1] = &ARG(i + 1);
|
||||
}
|
||||
fb->sp += argc; // Parameters and return value.
|
||||
|
||||
// Set the new fiber as the vm's fiber.
|
||||
vm->fiber = fb;
|
||||
|
||||
// fb->ret is "un initialized" and will be initialized by the fiber_resume()
|
||||
// call. But we're setting the value to VAR_NULL below to make it initialized
|
||||
// for the debugger, it'll prevent from crashing when we're trying to read
|
||||
// the value to dump.
|
||||
RET(VAR_NULL);
|
||||
// Switch fiber and start execution.
|
||||
if (vmPrepareFiber(vm, fb, argc - 1, args)) {
|
||||
ASSERT(fb == vm->fiber, OOPS);
|
||||
fb->state = FIBER_RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
PK_DOC(coreFiberResume,
|
||||
@ -652,36 +637,13 @@ PK_DOC(coreFiberResume,
|
||||
Fiber* fb;
|
||||
if (!validateArgFiber(vm, 1, &fb)) return;
|
||||
|
||||
if (fb->state != FIBER_YIELDED) {
|
||||
switch (fb->state) {
|
||||
case FIBER_NEW:
|
||||
RET_ERR(newString(vm, "The fiber hasn't started. call fiber_run() to "
|
||||
"start."));
|
||||
case FIBER_RUNNING:
|
||||
RET_ERR(newString(vm, "The fiber has already been running."));
|
||||
case FIBER_YIELDED: UNREACHABLE();
|
||||
case FIBER_DONE:
|
||||
RET_ERR(newString(vm, "The fiber has done running."));
|
||||
}
|
||||
UNREACHABLE();
|
||||
Var value = (argc == 1) ? VAR_NULL : ARG(2);
|
||||
|
||||
// Switch fiber and resume execution.
|
||||
if (vmSwitchFiber(vm, fb, &value)) {
|
||||
ASSERT(fb == vm->fiber, OOPS);
|
||||
fb->state = FIBER_RUNNING;
|
||||
}
|
||||
|
||||
fb->state = FIBER_RUNNING;
|
||||
fb->caller = vm->fiber;
|
||||
|
||||
// Pass the resume argument if it has any.
|
||||
|
||||
// Assert if we have a call frame and the stack size enough for the return
|
||||
// value and the resumed value.
|
||||
ASSERT(fb->frame_count != 0, OOPS);
|
||||
ASSERT((fb->stack + fb->stack_size) - fb->sp >= 2, OOPS);
|
||||
|
||||
// fb->ret will points to the return value of the 'yield()' call.
|
||||
if (argc == 1) *fb->ret = VAR_NULL;
|
||||
else *fb->ret = ARG(2);
|
||||
|
||||
// Set the new fiber as the vm's fiber.
|
||||
vm->fiber = fb;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
@ -1126,7 +1088,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
varBufferWrite(&list->elements, vm, VAR_NUM(i));
|
||||
}
|
||||
} else {
|
||||
newList(vm, 0);
|
||||
list = newList(vm, 0);
|
||||
}
|
||||
return VAR_OBJ(list);
|
||||
}
|
||||
@ -1139,7 +1101,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
Script* scr = (Script*)obj;
|
||||
|
||||
// Search in functions.
|
||||
uint32_t index = scriptGetFunc(scr, attrib->data, attrib->length);
|
||||
int index = scriptGetFunc(scr, attrib->data, attrib->length);
|
||||
if (index != -1) {
|
||||
ASSERT_INDEX(index, scr->functions.count);
|
||||
return VAR_OBJ(scr->functions.data[index]);
|
||||
@ -1167,7 +1129,6 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
||||
CHECK_MISSING_OBJ_TYPE(7);
|
||||
|
||||
UNREACHABLE();
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value) {
|
||||
@ -1211,7 +1172,7 @@ do { \
|
||||
Script* scr = (Script*)obj;
|
||||
|
||||
// Check globals.
|
||||
uint32_t index = scriptGetGlobals(scr, attrib->data, attrib->length);
|
||||
int index = scriptGetGlobals(scr, attrib->data, attrib->length);
|
||||
if (index != -1) {
|
||||
ASSERT_INDEX(index, scr->globals.count);
|
||||
scr->globals.data[index] = value;
|
||||
@ -1315,7 +1276,6 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
|
||||
|
||||
CHECK_MISSING_OBJ_TYPE(7);
|
||||
UNREACHABLE();
|
||||
return VAR_NULL;
|
||||
}
|
||||
|
||||
void varsetSubscript(PKVM* vm, Var on, Var key, Var value) {
|
||||
|
@ -26,7 +26,7 @@ static void _dumpValue(PKVM* vm, Var value, bool recursive) {
|
||||
return;
|
||||
}
|
||||
if (IS_BOOL(value)) {
|
||||
printf((AS_BOOL(value)) ? "true" : "false");
|
||||
printf("%s", (AS_BOOL(value)) ? "true" : "false");
|
||||
return;
|
||||
}
|
||||
if (IS_NUM(value)) {
|
||||
@ -199,7 +199,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
break;
|
||||
|
||||
case OP_PUSH_LOCAL_N:
|
||||
READ_BYTE();
|
||||
BYTE_ARG();
|
||||
break;
|
||||
|
||||
case OP_STORE_LOCAL_0:
|
||||
@ -215,7 +215,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
break;
|
||||
|
||||
case OP_STORE_LOCAL_N:
|
||||
READ_BYTE();
|
||||
BYTE_ARG();
|
||||
break;
|
||||
|
||||
case OP_PUSH_GLOBAL:
|
||||
@ -315,7 +315,6 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
||||
case OP_RANGE:
|
||||
case OP_IN:
|
||||
case OP_REPL_PRINT:
|
||||
case OP_YIELD:
|
||||
case OP_END:
|
||||
NO_ARGS();
|
||||
break;
|
||||
|
@ -67,7 +67,7 @@ extern "C" {
|
||||
|
||||
// Name of the implicit function for a module. When a module is parsed all of
|
||||
// it's statements are wrapped around an implicit function with this name.
|
||||
#define PK_BODY_FN_NAME "$(SourceBody)"
|
||||
#define PK_IMPLICIT_MAIN_NAME "$(SourceBody)"
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG TYPES */
|
||||
@ -177,12 +177,12 @@ typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path);
|
||||
// Create a new pkConfiguraition with the default values and return it.
|
||||
// Override those default configuration to adopt to another hosting
|
||||
// application.
|
||||
PK_PUBLIC PkConfiguration pkNewConfiguration();
|
||||
PK_PUBLIC PkConfiguration pkNewConfiguration(void);
|
||||
|
||||
// Create a new pkCompilerOptions with the default values and return it.
|
||||
// Override those default configuration to adopt to another hosting
|
||||
// application.
|
||||
PK_PUBLIC PkCompileOptions pkNewCompilerOptions();
|
||||
PK_PUBLIC PkCompileOptions pkNewCompilerOptions(void);
|
||||
|
||||
// Allocate initialize and returns a new VM
|
||||
PK_PUBLIC PKVM* pkNewVM(PkConfiguration* config);
|
||||
@ -221,9 +221,8 @@ PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module,
|
||||
PK_PUBLIC PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
|
||||
const char* name);
|
||||
|
||||
// Compile the [module] with the provided [source] and return true if the
|
||||
// compilation is success. Set the compiler options with the the [options]
|
||||
// argument or it can be set to NULL for default options.
|
||||
// Compile the [module] with the provided [source]. Set the compiler options
|
||||
// with the the [options] argument or set to NULL for default options.
|
||||
PK_PUBLIC PkResult pkCompileModule(PKVM* vm, PkHandle* module,
|
||||
PkStringPtr source,
|
||||
const PkCompileOptions* options);
|
||||
@ -237,9 +236,18 @@ PK_PUBLIC PkResult pkInterpretSource(PKVM* vm,
|
||||
PkStringPtr path,
|
||||
const PkCompileOptions* options);
|
||||
|
||||
// Runs the fiber's function with the provided arguments (param [arc] is the
|
||||
// argument count and [argv] are the values). It'll returns it's run status
|
||||
// reslt (success or failure) if you need the yielded or returned value use the
|
||||
// pkFiberGetReturnValue() function, and use pkFiberIsDone() function to check
|
||||
// if the fiber can be resumed with pkFiberResume() function.
|
||||
PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber,
|
||||
int argc, PkHandle** argv);
|
||||
|
||||
//PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber,
|
||||
// int argc, PkHandle** argv);
|
||||
// Resume a yielded fiber with an optional [value]. (could be set to NULL)
|
||||
// It'll returns it's run status reslt (success or failure) if you need the
|
||||
// yielded or returned value use the pkFiberGetReturnValue() function.
|
||||
PK_PUBLIC PkResult pkResumeFiber(PKVM* vm, PkHandle* fiber, PkVar value);
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG PUBLIC TYPE DEFINES */
|
||||
@ -336,6 +344,19 @@ PK_PUBLIC void pkReturnString(PKVM* vm, const char* value);
|
||||
PK_PUBLIC void pkReturnStringLength(PKVM* vm, const char* value, size_t len);
|
||||
PK_PUBLIC void pkReturnValue(PKVM* vm, PkVar value);
|
||||
|
||||
// Returns the cstring pointer of the given string. Make sure if the [value] is
|
||||
// a string before calling this function, otherwise it'll fail an assertion.
|
||||
PK_PUBLIC const char* pkStringGetData(const PkVar value);
|
||||
|
||||
// Returns the return value or if it's yielded, the yielded value of the fiber
|
||||
// as PkVar, this value lives on stack and will die (popped) once the fiber
|
||||
// resumed use handle to keep it alive.
|
||||
PK_PUBLIC PkVar pkFiberGetReturnValue(const PkHandle* fiber);
|
||||
|
||||
// Returns true if the fiber is finished it's execution and cannot be resumed
|
||||
// anymore.
|
||||
PK_PUBLIC bool pkFiberIsDone(const PkHandle* fiber);
|
||||
|
||||
/*****************************************************************************/
|
||||
/* POCKETLANG TYPE FUNCTIONS */
|
||||
/*****************************************************************************/
|
||||
@ -355,8 +376,6 @@ PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name);
|
||||
// Create and return a new fiber around the function [fn].
|
||||
PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn);
|
||||
|
||||
PK_PUBLIC const char* pkStringGetData(const PkVar value);
|
||||
|
||||
// TODO:
|
||||
// The below functions will push the primitive values on the stack and return
|
||||
// it's pointer as a PkVar it's usefull to convert your primitive values as
|
||||
|
@ -192,9 +192,6 @@ OPCODE(IN, 0, -1)
|
||||
// This will not pop the value.
|
||||
OPCODE(REPL_PRINT, 0, 0)
|
||||
|
||||
// Yield the current fiber. Used in REPL mode.
|
||||
OPCODE(YIELD, 0, 0)
|
||||
|
||||
// A sudo instruction which will never be called. A function's last opcode
|
||||
// used for debugging.
|
||||
OPCODE(END, 0, 0)
|
||||
|
18
src/utils.c
18
src/utils.c
@ -128,17 +128,17 @@ int utf8_encodeValue(int value, uint8_t* bytes) {
|
||||
// 2 byte character 110xxxxx 10xxxxxx -> last 6 bits write to 2nd byte and
|
||||
// first 5 bit write to first byte
|
||||
if (value <= 0x7ff) {
|
||||
*(bytes++) = 0b11000000 | ((value & 0b11111000000) >> 6);
|
||||
*(bytes) = 0b10000000 | ((value & 111111));
|
||||
*(bytes++) = (uint8_t)(0b11000000 | ((value & 0b11111000000) >> 6));
|
||||
*(bytes) = (uint8_t)(0b10000000 | ((value & 111111)));
|
||||
return 2;
|
||||
}
|
||||
|
||||
// 3 byte character 1110xxxx 10xxxxxx 10xxxxxx -> from last, 6 bits write
|
||||
// to 3rd byte, next 6 bits write to 2nd byte, and 4 bits to first byte.
|
||||
if (value <= 0xffff) {
|
||||
*(bytes++) = 0b11100000 | ((value & 0b1111000000000000) >> 12);
|
||||
*(bytes++) = 0b10000000 | ((value & 0b111111000000) >> 6);
|
||||
*(bytes) = 0b10000000 | ((value & 0b111111));
|
||||
*(bytes++) = (uint8_t)(0b11100000 | ((value & 0b1111000000000000) >> 12));
|
||||
*(bytes++) = (uint8_t)(0b10000000 | ((value & 0b111111000000) >> 6));
|
||||
*(bytes) = (uint8_t)(0b10000000 | ((value & 0b111111)));
|
||||
return 3;
|
||||
}
|
||||
|
||||
@ -146,10 +146,10 @@ int utf8_encodeValue(int value, uint8_t* bytes) {
|
||||
// to 4th byte, next 6 bits to 3rd byte, next 6 bits to 2nd byte, 3 bits
|
||||
// first byte.
|
||||
if (value <= 0x10ffff) {
|
||||
*(bytes++) = 0b11110000 | ((value & 0b111000000000000000000) >> 18);
|
||||
*(bytes++) = 0b10000000 | ((value & 0b111111000000000000) >> 12);
|
||||
*(bytes++) = 0b10000000 | ((value & 0b111111000000) >> 6);
|
||||
*(bytes) = 0b10000000 | ((value & 0b111111));
|
||||
*(bytes++) = (uint8_t)(0b11110000 | ((value & 0b111000000000000000000) >> 18));
|
||||
*(bytes++) = (uint8_t)(0b10000000 | ((value & 0b111111000000000000) >> 12));
|
||||
*(bytes++) = (uint8_t)(0b10000000 | ((value & 0b111111000000) >> 6));
|
||||
*(bytes) = (uint8_t)(0b10000000 | ((value & 0b111111)));
|
||||
return 4;
|
||||
}
|
||||
|
||||
|
51
src/var.c
51
src/var.c
@ -36,7 +36,6 @@ PkVarType pkGetValueType(const PkVar value) {
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
return (PkVarType)0;
|
||||
}
|
||||
|
||||
PkHandle* pkNewString(PKVM* vm, const char* value) {
|
||||
@ -62,12 +61,6 @@ PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) {
|
||||
return vmNewHandle(vm, VAR_OBJ(fiber));
|
||||
}
|
||||
|
||||
const char* pkStringGetData(const PkVar value) {
|
||||
const Var str = (*(const Var*)value);
|
||||
__ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string.");
|
||||
return ((String*)AS_OBJ(str))->data;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* VAR INTERNALS */
|
||||
/*****************************************************************************/
|
||||
@ -340,7 +333,7 @@ Script* newScript(PKVM* vm, String* path) {
|
||||
stringBufferInit(&script->names);
|
||||
|
||||
vmPushTempRef(vm, &script->_super);
|
||||
const char* fn_name = PK_BODY_FN_NAME;
|
||||
const char* fn_name = PK_IMPLICIT_MAIN_NAME;
|
||||
script->body = newFunction(vm, fn_name, (int)strlen(fn_name), script, false);
|
||||
script->body->arity = 0; // TODO: should it be 1 (ARGV)?.
|
||||
vmPopTempRef(vm);
|
||||
@ -401,8 +394,8 @@ Fiber* newFiber(PKVM* vm, Function* fn) {
|
||||
int stack_size = utilPowerOf2Ceil(fn->arity + 1);
|
||||
fiber->stack = ALLOCATE_ARRAY(vm, Var, stack_size);
|
||||
fiber->stack_size = stack_size;
|
||||
fiber->sp = fiber->stack;
|
||||
fiber->ret = fiber->stack;
|
||||
fiber->sp = fiber->stack + 1;
|
||||
|
||||
} else {
|
||||
// Allocate stack.
|
||||
@ -410,8 +403,8 @@ Fiber* newFiber(PKVM* vm, Function* fn) {
|
||||
if (stack_size < MIN_STACK_SIZE) stack_size = MIN_STACK_SIZE;
|
||||
fiber->stack = ALLOCATE_ARRAY(vm, Var, stack_size);
|
||||
fiber->stack_size = stack_size;
|
||||
fiber->sp = fiber->stack;
|
||||
fiber->ret = fiber->stack;
|
||||
fiber->sp = fiber->stack + 1;
|
||||
|
||||
// Allocate call frames.
|
||||
fiber->frame_capacity = INITIAL_CALL_FRAMES;
|
||||
@ -424,6 +417,10 @@ Fiber* newFiber(PKVM* vm, Function* fn) {
|
||||
fiber->frames[0].rbp = fiber->ret;
|
||||
}
|
||||
|
||||
// Initialize the return value to null (doesn't really have to do that here
|
||||
// but if we're trying to debut it may crash when dumping the return vaue).
|
||||
*fiber->ret = VAR_NULL;
|
||||
|
||||
return fiber;
|
||||
}
|
||||
|
||||
@ -500,7 +497,6 @@ static uint32_t _hashObject(Object* obj) {
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t varHashValue(Var v) {
|
||||
@ -754,7 +750,6 @@ const char* getPkVarTypeName(PkVarType type) {
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* getObjectTypeName(ObjectType type) {
|
||||
@ -767,9 +762,8 @@ const char* getObjectTypeName(ObjectType type) {
|
||||
case OBJ_FUNC: return "Func";
|
||||
case OBJ_FIBER: return "Fiber";
|
||||
case OBJ_USER: return "UserObj";
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
const char* varTypeName(Var v) {
|
||||
@ -821,11 +815,11 @@ bool isValuesEqual(Var v1, Var v2) {
|
||||
*/
|
||||
List *l1 = (List*)o1, *l2 = (List*)o2;
|
||||
if (l1->elements.count != l2->elements.count) return false;
|
||||
Var* v1 = l1->elements.data;
|
||||
Var* v2 = l2->elements.data;
|
||||
Var* _v1 = l1->elements.data;
|
||||
Var* _v2 = l2->elements.data;
|
||||
for (uint32_t i = 0; i < l1->elements.count; i++) {
|
||||
if (!isValuesEqual(*v1, *v2)) return false;
|
||||
v1++, v2++;
|
||||
if (!isValuesEqual(*_v1, *_v2)) return false;
|
||||
_v1++, _v2++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -918,12 +912,7 @@ static void _toStringInternal(PKVM* vm, const Var v, ByteBuffer* buff,
|
||||
byteBufferWrite(buff, vm, '"');
|
||||
return;
|
||||
}
|
||||
|
||||
// If recursive return with quotes (ex: [42, "hello", 0..10]).
|
||||
byteBufferWrite(buff, vm, '"');
|
||||
byteBufferAddString(buff, vm, str->data, str->length);
|
||||
byteBufferWrite(buff, vm, '"');
|
||||
return;
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
case OBJ_LIST:
|
||||
@ -994,14 +983,13 @@ static void _toStringInternal(PKVM* vm, const Var v, ByteBuffer* buff,
|
||||
}
|
||||
if (_done) break;
|
||||
|
||||
if (!_first) {
|
||||
byteBufferAddString(buff, vm, ", ", 2);
|
||||
_first = false;
|
||||
}
|
||||
if (!_first) byteBufferAddString(buff, vm, ", ", 2);
|
||||
|
||||
_toStringInternal(vm, map->entries[i].key, buff, &seq_map, true);
|
||||
byteBufferWrite(buff, vm, ':');
|
||||
_toStringInternal(vm, map->entries[i].value, buff, &seq_map, true);
|
||||
i++;
|
||||
|
||||
i++; _first = false;
|
||||
} while (i < map->capacity);
|
||||
|
||||
byteBufferWrite(buff, vm, '}');
|
||||
@ -1106,13 +1094,12 @@ bool toBool(Var v) {
|
||||
case OBJ_RANGE: // [[FALLTHROUGH]]
|
||||
case OBJ_SCRIPT:
|
||||
case OBJ_FUNC:
|
||||
case OBJ_FIBER:
|
||||
case OBJ_USER:
|
||||
return true;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return true;
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
String* stringFormat(PKVM* vm, const char* fmt, ...) {
|
||||
|
@ -24,10 +24,9 @@
|
||||
*/
|
||||
|
||||
// To use dynamic variably-sized struct with a tail array add an array at the
|
||||
// end of the struct with size \ref DYNAMIC_TAIL_ARRAY. This method was a
|
||||
// legacy standard called "struct hack".
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
/** for std >= c99 it's just `arr[]` */
|
||||
// end of the struct with size DYNAMIC_TAIL_ARRAY. This method was a legacy
|
||||
// standard called "struct hack".
|
||||
#if defined(_MSC_VER) || __STDC_VERSION__ >= 199901L // std >= c99
|
||||
#define DYNAMIC_TAIL_ARRAY
|
||||
#else
|
||||
#define DYNAMIC_TAIL_ARRAY 0
|
||||
@ -218,7 +217,7 @@ typedef enum {
|
||||
// This will terminate compiler (because of 1/0 evaluvated) if ObjectType max
|
||||
// is not [count]. Use this to ensure every time switching ObjectType will
|
||||
// cover all object types.
|
||||
#if DEBUG
|
||||
#ifdef DEBUG
|
||||
#define CHECK_MISSING_OBJ_TYPE(count) (1/ ((int)(!(count ^ OBJ_USER))) )
|
||||
#else
|
||||
#define CHECK_MISSING_OBJ_TYPE(count) do {} while (false)
|
||||
|
191
src/vm.c
191
src/vm.c
@ -25,7 +25,7 @@ static void* defaultRealloc(void* memory, size_t new_size, void* user_data);
|
||||
// till the next yield or return statement, and return result.
|
||||
static PkResult runFiber(PKVM* vm, Fiber* fiber);
|
||||
|
||||
PkConfiguration pkNewConfiguration() {
|
||||
PkConfiguration pkNewConfiguration(void) {
|
||||
PkConfiguration config;
|
||||
config.realloc_fn = defaultRealloc;
|
||||
|
||||
@ -40,7 +40,7 @@ PkConfiguration pkNewConfiguration() {
|
||||
return config;
|
||||
}
|
||||
|
||||
PkCompileOptions pkNewCompilerOptions() {
|
||||
PkCompileOptions pkNewCompilerOptions(void) {
|
||||
PkCompileOptions options;
|
||||
options.debug = false;
|
||||
// TODO:
|
||||
@ -161,6 +161,39 @@ PkResult pkInterpretSource(PKVM* vm, PkStringPtr source, PkStringPtr path,
|
||||
return runFiber(vm, newFiber(vm, scr->body));
|
||||
}
|
||||
|
||||
PkResult pkRunFiber(PKVM* vm, PkHandle* fiber,
|
||||
int argc, PkHandle** argv) {
|
||||
__ASSERT(fiber != NULL, "Handle fiber was NULL.");
|
||||
Var fb = fiber->value;
|
||||
__ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber.");
|
||||
Fiber* _fiber = (Fiber*)AS_OBJ(fb);
|
||||
|
||||
Var* args[MAX_ARGC];
|
||||
for (int i = 0; i < argc; i++) {
|
||||
args[i] = &(argv[i]->value);
|
||||
}
|
||||
|
||||
if (!vmPrepareFiber(vm, _fiber, argc, args)) {
|
||||
return PK_RESULT_RUNTIME_ERROR;
|
||||
}
|
||||
|
||||
ASSERT(_fiber->frame_count == 1, OOPS);
|
||||
return runFiber(vm, _fiber);
|
||||
}
|
||||
|
||||
PkResult pkResumeFiber(PKVM* vm, PkHandle* fiber, PkVar value) {
|
||||
__ASSERT(fiber != NULL, "Handle fiber was NULL.");
|
||||
Var fb = fiber->value;
|
||||
__ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber.");
|
||||
Fiber* _fiber = (Fiber*)AS_OBJ(fb);
|
||||
|
||||
if (!vmSwitchFiber(vm, _fiber, (Var*)value)) {
|
||||
return PK_RESULT_RUNTIME_ERROR;
|
||||
}
|
||||
|
||||
return runFiber(vm, _fiber);
|
||||
}
|
||||
|
||||
void pkSetRuntimeError(PKVM* vm, const char* message) {
|
||||
__ASSERT(vm->fiber != NULL, "This function can only be called at runtime.");
|
||||
vm->fiber->error = newString(vm, message);
|
||||
@ -224,7 +257,7 @@ void vmCollectGarbage(PKVM* vm) {
|
||||
|
||||
// Mark the core libs and builtin functions.
|
||||
grayObject(vm, &vm->core_libs->_super);
|
||||
for (int i = 0; i < vm->builtins_count; i++) {
|
||||
for (uint32_t i = 0; i < vm->builtins_count; i++) {
|
||||
grayObject(vm, &vm->builtins[i].fn->_super);
|
||||
}
|
||||
|
||||
@ -281,6 +314,96 @@ void vmCollectGarbage(PKVM* vm) {
|
||||
if (vm->next_gc < vm->min_heap_size) vm->next_gc = vm->min_heap_size;
|
||||
}
|
||||
|
||||
#define _ERR_FAIL(msg) \
|
||||
do { \
|
||||
if (vm->fiber != NULL) vm->fiber->error = msg; \
|
||||
return false; \
|
||||
} while (false)
|
||||
|
||||
bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv) {
|
||||
ASSERT(fiber->func->arity >= -1, OOPS " (Forget to initialize arity.)");
|
||||
|
||||
if (argc != fiber->func->arity) {
|
||||
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", fiber->func->arity);
|
||||
_ERR_FAIL(stringFormat(vm, "Expected excatly $ argument(s).", buff));
|
||||
}
|
||||
|
||||
if (fiber->state != FIBER_NEW) {
|
||||
switch (fiber->state) {
|
||||
case FIBER_NEW: UNREACHABLE();
|
||||
case FIBER_RUNNING:
|
||||
_ERR_FAIL(newString(vm, "The fiber has already been running."));
|
||||
case FIBER_YIELDED:
|
||||
_ERR_FAIL(newString(vm, "Cannot run a fiber which is yielded, use "
|
||||
"fiber_resume() instead."));
|
||||
case FIBER_DONE:
|
||||
_ERR_FAIL(newString(vm, "The fiber has done running."));
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
ASSERT(fiber->stack != NULL && fiber->sp == fiber->stack + 1, OOPS);
|
||||
ASSERT(fiber->ret + 1 == fiber->sp, OOPS);
|
||||
|
||||
// Pass the function arguments.
|
||||
|
||||
// Assert we have the first frame (to push the arguments). And assert we have
|
||||
// enought stack space for parameters.
|
||||
ASSERT(fiber->frame_count == 1, OOPS);
|
||||
ASSERT(fiber->frames[0].rbp == fiber->ret, OOPS);
|
||||
ASSERT((fiber->stack + fiber->stack_size) - fiber->sp >= argc, OOPS);
|
||||
|
||||
// ARG1 is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc).
|
||||
// And ret[0] is the return value, parameters starts at ret[1], ...
|
||||
for (int i = 0; i < argc; i++) {
|
||||
fiber->ret[1 + i] = *argv[i]; // +1: ret[0] is return value.
|
||||
}
|
||||
fiber->sp += argc; // Parameters.
|
||||
|
||||
// Set the new fiber as the vm's fiber.
|
||||
fiber->caller = vm->fiber;
|
||||
vm->fiber = fiber;
|
||||
|
||||
// On success return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vmSwitchFiber(PKVM* vm, Fiber* fiber, Var* value) {
|
||||
if (fiber->state != FIBER_YIELDED) {
|
||||
switch (fiber->state) {
|
||||
case FIBER_NEW:
|
||||
_ERR_FAIL(newString(vm, "The fiber hasn't started. call fiber_run() "
|
||||
"to start."));
|
||||
case FIBER_RUNNING:
|
||||
_ERR_FAIL(newString(vm, "The fiber has already been running."));
|
||||
case FIBER_YIELDED: UNREACHABLE();
|
||||
case FIBER_DONE:
|
||||
_ERR_FAIL(newString(vm, "The fiber has done running."));
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
// Pass the resume argument if it has any.
|
||||
|
||||
// Assert if we have a call frame and the stack size enough for the return
|
||||
// value and the resumed value.
|
||||
ASSERT(fiber->frame_count != 0, OOPS);
|
||||
ASSERT((fiber->stack + fiber->stack_size) - fiber->sp >= 2, OOPS);
|
||||
|
||||
// fb->ret will points to the return value of the 'yield()' call.
|
||||
if (value == NULL) *fiber->ret = VAR_NULL;
|
||||
else *fiber->ret = *value;
|
||||
|
||||
// Switch fiber.
|
||||
fiber->caller = vm->fiber;
|
||||
vm->fiber = fiber;
|
||||
|
||||
// On success return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef _ERR_FAIL
|
||||
|
||||
void vmYieldFiber(PKVM* vm, Var* value) {
|
||||
|
||||
Fiber* caller = vm->fiber->caller;
|
||||
@ -454,7 +577,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
|
||||
// it from garbage collection and get the reference from native functions.
|
||||
vm->fiber = fiber;
|
||||
|
||||
ASSERT(fiber->state == FIBER_NEW, OOPS);
|
||||
ASSERT(fiber->state == FIBER_NEW || fiber->state == FIBER_YIELDED, OOPS);
|
||||
fiber->state = FIBER_RUNNING;
|
||||
|
||||
// The instruction pointer.
|
||||
@ -471,12 +594,24 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
|
||||
#define READ_BYTE() (*ip++)
|
||||
#define READ_SHORT() (ip+=2, (uint16_t)((ip[-2] << 8) | ip[-1]))
|
||||
|
||||
// Switch back to the caller of the current fiber, will be called when we're
|
||||
// done with the fiber or aborting it for runtime errors.
|
||||
#define FIBER_SWITCH_BACK() \
|
||||
do { \
|
||||
Fiber* caller = vm->fiber->caller; \
|
||||
ASSERT(caller == NULL || caller->state == FIBER_RUNNING, OOPS); \
|
||||
vm->fiber->state = FIBER_DONE; \
|
||||
vm->fiber->caller = NULL; \
|
||||
vm->fiber = caller; \
|
||||
} while (false)
|
||||
|
||||
// Check if any runtime error exists and if so returns RESULT_RUNTIME_ERROR.
|
||||
#define CHECK_ERROR() \
|
||||
do { \
|
||||
if (HAS_ERROR()) { \
|
||||
UPDATE_FRAME(); \
|
||||
reportError(vm); \
|
||||
FIBER_SWITCH_BACK(); \
|
||||
return PK_RESULT_RUNTIME_ERROR; \
|
||||
} \
|
||||
} while (false)
|
||||
@ -487,6 +622,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
|
||||
vm->fiber->error = err_msg; \
|
||||
UPDATE_FRAME(); \
|
||||
reportError(vm); \
|
||||
FIBER_SWITCH_BACK(); \
|
||||
return PK_RESULT_RUNTIME_ERROR; \
|
||||
} while (false)
|
||||
|
||||
@ -522,8 +658,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
|
||||
#define OPCODE(code) case OP_##code
|
||||
#define DISPATCH() goto L_vm_main_loop
|
||||
|
||||
// TODO: remove the below push null and add it from the compiler.
|
||||
PUSH(VAR_NULL); // Return value of the script body.
|
||||
// Load the fiber's top call frame to the vm's execution variables.
|
||||
LOAD_FRAME();
|
||||
|
||||
L_vm_main_loop:
|
||||
@ -689,14 +824,14 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
|
||||
OPCODE(IMPORT):
|
||||
{
|
||||
String* name = script->names.data[READ_SHORT()];
|
||||
Var script = importScript(vm, name);
|
||||
Var scr = importScript(vm, name);
|
||||
|
||||
// TODO: implement fiber bsed execution.
|
||||
//ASSERT(IS_OBJ_TYPE(script, OBJ_SCRIPT), OOPS);
|
||||
//Script* scr = (Script*)AS_OBJ(script);
|
||||
//if (!scr->initialized) vmRunScript(vm, scr);
|
||||
|
||||
PUSH(script);
|
||||
PUSH(scr);
|
||||
CHECK_ERROR();
|
||||
DISPATCH();
|
||||
}
|
||||
@ -914,21 +1049,20 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
|
||||
// Pop the last frame, and if no more call frames, we're done with the
|
||||
// current fiber.
|
||||
if (--vm->fiber->frame_count == 0) {
|
||||
vm->fiber->state = FIBER_DONE;
|
||||
// TODO: if we're evaluvating an expressoin we need to set it's
|
||||
// value on the stack.
|
||||
//vm->fiber->sp = vm->fiber->stack; ??
|
||||
|
||||
// TODO:
|
||||
//vm->fiber->sp = vm->fiber->stack;.
|
||||
// Assert all the stack locals were popped.
|
||||
ASSERT(vm->fiber->sp == vm->fiber->stack, OOPS);
|
||||
|
||||
if (vm->fiber->caller == NULL) {
|
||||
FIBER_SWITCH_BACK();
|
||||
|
||||
if (vm->fiber == NULL) {
|
||||
return PK_RESULT_SUCCESS;
|
||||
|
||||
} else {
|
||||
Fiber* caller = vm->fiber->caller;
|
||||
ASSERT(caller->state == FIBER_RUNNING, OOPS);
|
||||
|
||||
vm->fiber->caller = NULL;
|
||||
vm->fiber = caller;
|
||||
*caller->ret = ret_value;
|
||||
*vm->fiber->ret = ret_value;
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -1186,27 +1320,14 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) {
|
||||
{
|
||||
if (vm->config.write_fn != NULL) {
|
||||
Var tmp = PEEK(-1);
|
||||
vm->config.write_fn(vm, toRepr(vm, tmp)->data);
|
||||
vm->config.write_fn(vm, "\n");
|
||||
if (!IS_NULL(tmp)) {
|
||||
vm->config.write_fn(vm, toRepr(vm, tmp)->data);
|
||||
vm->config.write_fn(vm, "\n");
|
||||
}
|
||||
}
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(YIELD):
|
||||
{
|
||||
Fiber* call_fiber = vm->fiber;
|
||||
|
||||
UPDATE_FRAME();
|
||||
vmYieldFiber(vm, NULL);
|
||||
if (vm->fiber == NULL) return PK_RESULT_SUCCESS;
|
||||
LOAD_FRAME();
|
||||
|
||||
// Pop function arguments except for the return value of the call fiber.
|
||||
call_fiber->sp = call_fiber->ret + 1;
|
||||
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
OPCODE(END):
|
||||
TODO;
|
||||
break;
|
||||
|
17
src/vm.h
17
src/vm.h
@ -40,7 +40,7 @@
|
||||
// entry of the array.
|
||||
typedef struct {
|
||||
const char* name; //< Name of the function.
|
||||
int length; //< Length of the name.
|
||||
uint32_t length; //< Length of the name.
|
||||
Function* fn; //< Native function pointer.
|
||||
} BuiltinFn;
|
||||
|
||||
@ -108,7 +108,7 @@ struct PKVM {
|
||||
|
||||
// Array of all builtin functions.
|
||||
BuiltinFn builtins[BUILTIN_FN_CAPACITY];
|
||||
int builtins_count;
|
||||
uint32_t builtins_count;
|
||||
|
||||
// Current fiber.
|
||||
Fiber* fiber;
|
||||
@ -180,6 +180,19 @@ void vmPopTempRef(PKVM* vm);
|
||||
// cache. If not found itll return NULL.
|
||||
Script* vmGetScript(PKVM* vm, String* path);
|
||||
|
||||
// ((Context switching - start))
|
||||
// Prepare a new fiber for execution with the given arguments. That can be used
|
||||
// different fiber_run apis. Return true on success, otherwise it'll set the
|
||||
// error to the vm's current fiber (if it has any).
|
||||
bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv);
|
||||
|
||||
// ((Context switching - resume))
|
||||
// Switch the running fiber of the vm from the current fiber to the provided
|
||||
// [fiber]. with an optional [value] (could be set to NULL). used in different
|
||||
// fiber_resume apis. Return true on success, otherwise it'll set the error to
|
||||
// the vm's current fiber (if it has any).
|
||||
bool vmSwitchFiber(PKVM* vm, Fiber* fiber, Var* value);
|
||||
|
||||
// Yield from the current fiber. If the [value] isn't NULL it'll set it as the
|
||||
// yield value.
|
||||
void vmYieldFiber(PKVM* vm, Var* value);
|
||||
|
Loading…
Reference in New Issue
Block a user