refactor for repl support [2/3]

This commit is contained in:
Thakee Nathees 2021-06-07 11:24:06 +05:30
parent 3092118008
commit fdf685731d
15 changed files with 502 additions and 191 deletions

View File

@ -44,7 +44,7 @@ if not defined INCLUDE goto :MSVC_INIT
goto :START
:MSVC_INIT
call :ColorText 0f "Not running on MSVM prompt, searching for one..."
call :ColorText 0f "Not running on MSVC prompt, searching for one..."
echo.
:: Find vswhere
@ -170,6 +170,7 @@ goto :END
:FAIL
call :ColorText 0c "Build failed. See the error messages."
echo.
exit /b 1
goto :END
:END

View File

@ -1,5 +1,15 @@
// To implement.
- refactor build.bat batch script to powershell
refactor makefile and setup a github action.
- change or add => to_string() to value.as_string
and add as_repr, as_bool.
str_lower("UPPER") to "UPPER".lower
- support new line just after '=' (and other places)
- Implement argparse.
- -v --version
- emit opcodes

View File

@ -9,7 +9,7 @@
#include <pocketlang.h>
// FIXME: everything below here is temproary and for testing.
// FIXME: everything below here is temproary and for testing.
void registerModules(PKVM* vm);
@ -23,7 +23,14 @@ size_t pathJoin(const char* from, const char* path, char* buffer,
// ---------------------------------------
void errorPrint(PKVM* vm, PkErrorType type, const char* file, int line,
void onResultDone(PKVM* vm, PkStringPtr result) {
if ((bool)result.user_data) {
free((void*)result.string);
}
}
void errorFunction(PKVM* vm, PkErrorType type, const char* file, int line,
const char* message) {
if (type == PK_ERROR_COMPILE) {
fprintf(stderr, "Error: %s\n at \"%s\":%i\n", message, file, line);
@ -40,11 +47,24 @@ void writeFunction(PKVM* vm, const char* text) {
fprintf(stdout, "%s", text);
}
void onResultDone(PKVM* vm, PkStringPtr result) {
static const char* read_line() {
// FIXME: use fgetc char by char till reach a new line.
const int size = 1024;
char* mem = (char*) malloc(size);
fgets(mem, size, stdin);
size_t len = strlen(mem);
if ((bool)result.user_data) {
free((void*)result.string);
}
// FIXME: handle \r\n, this is temp.
mem[len - 1] = '\0';
return mem;
}
PkStringPtr readFunction(PKVM* vm) {
PkStringPtr result;
result.string = read_line();
result.on_done = onResultDone;
result.user_data = (void*)true;
return result;
}
PkStringPtr resolvePath(PKVM* vm, const char* from, const char* path) {
@ -135,22 +155,31 @@ int main(int argc, char** argv) {
pathInit();
PkConfiguration config = pkNewConfiguration();
config.error_fn = errorPrint;
config.error_fn = errorFunction;
config.write_fn = writeFunction;
config.read_fn = readFunction;
config.load_script_fn = loadScript;
config.resolve_path_fn = resolvePath;
PkCompileOptions options = pkNewCompilerOptions();
options.debug = true; // TODO: update this with cli args.
PKVM* vm = pkNewVM(&config);
registerModules(vm);
PkInterpretResult result;
PkResult result;
// FIXME: this is temp till arg parse implemented.
if (argc >= 3 && strcmp(argv[1], "-c") == 0) {
if (argc == 1) {
// TODO:
//PkHandle* module = pkNewModule(vm, "$(REPL)");
} if (argc >= 3 && strcmp(argv[1], "-c") == 0) {
PkStringPtr source = { argv[2], NULL, NULL };
PkStringPtr path = { "$(Source)", NULL, NULL };
result = pkInterpretSource(vm, source, path);
result = pkInterpretSource(vm, source, path, NULL);
pkFreeVM(vm);
return result;
}
@ -159,7 +188,7 @@ int main(int argc, char** argv) {
PkStringPtr source = loadScript(vm, resolved.string);
if (source.string != NULL) {
result = pkInterpretSource(vm, source, resolved);
result = pkInterpretSource(vm, source, resolved, &options);
} else {
result = PK_RESULT_COMPILE_ERROR;

View File

@ -164,9 +164,4 @@
// + 1 for null byte '\0'
#define STR_INT_BUFF_SIZE 12
/*****************************************************************************/
/* INTERNAL TYPE DEFINES */
/*****************************************************************************/
#endif //PK_COMMON_H

View File

@ -40,6 +40,9 @@
// available in C++98.
#define ERROR_MESSAGE_SIZE 256
// The name of a literal function.
#define LITERAL_FN_NAME "$(LiteralFn)"
/*****************************************************************************
* TOKENS *
*****************************************************************************/
@ -328,6 +331,8 @@ struct Compiler {
Token previous, current, next; //< Currently parsed tokens.
bool has_errors; //< True if any syntex error occured at.
const PkCompileOptions* options; //< To configure the compilation.
// Current depth the compiler in (-1 means top level) 0 means function
// level and > 0 is inner scope.
int scope_depth;
@ -479,7 +484,7 @@ static void eatString(Compiler* compiler, bool single_quote) {
// '\0' will be added by varNewSring();
Var string = VAR_OBJ(newStringLength(compiler->vm, (const char*)buff.data,
(uint32_t)buff.count));
(uint32_t)buff.count));
byteBufferClear(&buff, compiler->vm);
@ -879,7 +884,7 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
int index; // For storing the search result below.
// Search through globals.
index = scriptSearchGlobals(compiler->script, name, length);
index = scriptGetGlobals(compiler->script, name, length);
if (index != -1) {
result.type = NAME_GLOBAL_VAR;
result.index = index;
@ -887,7 +892,7 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
}
// Search through functions.
index = scriptSearchFunc(compiler->script, name, length);
index = scriptGetFunc(compiler->script, name, length);
if (index != -1) {
result.type = NAME_FUNCTION;
result.index = index;
@ -1453,7 +1458,7 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) {
*****************************************************************************/
static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
Script* script) {
Script* script, const PkCompileOptions* options) {
compiler->vm = vm;
compiler->next_compiler = NULL;
@ -1462,6 +1467,7 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
compiler->script = script;
compiler->token_start = source;
compiler->has_errors = false;
compiler->options = options;
compiler->current_char = source;
compiler->current_line = 1;
@ -1689,7 +1695,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
}
} else {
name = "$(LiteralFn)";
name = LITERAL_FN_NAME;
name_length = (int)strlen(name);
}
@ -1860,8 +1866,14 @@ static Script* importFile(Compiler* compiler, const char* path) {
emitOpcode(compiler, OP_IMPORT);
emitShort(compiler, index);
// Option for the compilation, even if we're running on repl mode the
// imported script cannot run on repl mode.
PkCompileOptions options = pkNewCompilerOptions();
if (compiler->options) options = *compiler->options;
options.repl_mode = false;
// Compile the source to the script and clean the source.
bool success = compile(vm, scr, source.string);
bool success = compile(vm, scr, source.string, &options);
if (source.on_done != NULL) source.on_done(vm, source);
if (!success) parseError(compiler, "Compilation of imported script "
@ -2247,6 +2259,11 @@ static void compileForStatement(Compiler* compiler) {
// variable declaration, which will be handled.
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.
bool is_temproary = false;
if (match(compiler, TK_BREAK)) {
if (compiler->loop == NULL) {
parseError(compiler, "Cannot use 'break' outside a loop.");
@ -2304,22 +2321,55 @@ static void compileStatement(Compiler* compiler) {
compiler->new_local = false;
compileExpression(compiler);
consumeEndStatement(compiler);
if (!compiler->new_local) {
// Pop the temp.
emitOpcode(compiler, OP_POP);
}
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 (compiler->options && compiler->options->repl_mode &&
is_temproary /*&& 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
// as import statement, function define, and if we're running REPL mode top
// level expression's evaluvated value will be printed.
static void compileTopLevelStatement(Compiler* compiler) {
if (match(compiler, TK_NATIVE)) {
compileFunction(compiler, FN_NATIVE);
} else if (match(compiler, TK_DEF)) {
compileFunction(compiler, FN_SCRIPT);
} else if (match(compiler, TK_FROM)) {
compileFromImport(compiler);
} else if (match(compiler, TK_IMPORT)) {
compileRegularImport(compiler);
} else if (match(compiler, TK_MODULE)) {
parseError(compiler, "Module name must be the first statement "
"of the script.");
} else {
compileStatement(compiler);
}
}
bool compile(PKVM* vm, Script* script, const char* source) {
bool compile(PKVM* vm, Script* script, const char* source,
const PkCompileOptions* options) {
// Skip utf8 BOM if there is any.
if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3;
Compiler _compiler;
Compiler* compiler = &_compiler; //< Compiler pointer for quick access.
compilerInit(compiler, vm, source, script);
compilerInit(compiler, vm, source, script, options);
// If compiling for an imported script the vm->compiler would be the compiler
// of the script that imported this script. Add the all the compilers into a
@ -2327,6 +2377,12 @@ 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;
Func curr_fn;
curr_fn.depth = DEPTH_SCRIPT;
curr_fn.ptr = script->body;
@ -2356,40 +2412,25 @@ bool compile(PKVM* vm, Script* script, const char* source) {
}
while (!match(compiler, TK_EOF)) {
if (match(compiler, TK_NATIVE)) {
compileFunction(compiler, FN_NATIVE);
} else if (match(compiler, TK_DEF)) {
compileFunction(compiler, FN_SCRIPT);
} else if (match(compiler, TK_FROM)) {
compileFromImport(compiler);
} else if (match(compiler, TK_IMPORT)) {
compileRegularImport(compiler);
} else if (match(compiler, TK_MODULE)) {
parseError(compiler, "Module name must be the first statement "
"of the script.");
} else {
compileStatement(compiler);
}
compileTopLevelStatement(compiler);
skipNewLines(compiler);
}
emitOpcode(compiler, OP_PUSH_NULL);
emitOpcode(compiler, OP_RETURN);
emitOpcode(compiler, OP_END);
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);
}
// Resolve forward names (function names that are used before defined).
for (int i = 0; i < compiler->forwards_count; i++) {
ForwardName* forward = &compiler->forwards[i];
const char* name = forward->name;
int length = forward->length;
int index = scriptSearchFunc(script, name, (uint32_t)length);
int index = scriptGetFunc(script, name, (uint32_t)length);
if (index != -1) {
patchForward(compiler, forward->func, forward->instruction, index);
} else {
@ -2404,8 +2445,29 @@ bool compile(PKVM* vm, Script* script, const char* source) {
dumpFunctionCode(vm, script->body);
#endif
// Return true if success.
return !(compiler->has_errors);
// 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;
}
PkResult pkCompileModule(PKVM* vm, PkHandle* module, PkStringPtr source,
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");
Script* script = (Script*)AS_OBJ(scr);
bool success = compile(vm, script, source.string, options);
if (source.on_done) source.on_done(vm, source);
if (!success) return PK_RESULT_COMPILE_ERROR;
return PK_RESULT_SUCCESS;
}
void compilerMarkObjects(PKVM* vm, Compiler* compiler) {

View File

@ -25,8 +25,9 @@ typedef enum {
typedef struct Compiler Compiler;
// This will take source code as a cstring, compiles it to pocketlang bytecodes
// and append them to the script's implicit main (the $SourceBody function).
bool compile(PKVM* vm, Script* script, const char* source);
// and append them to the script's implicit main function ("$(SourceBody)").
bool compile(PKVM* vm, Script* script, const char* source,
const PkCompileOptions* options);
// Mark the heap allocated ojbects of the compiler at the garbage collection
// called at the marking phase of vmCollectGarbage().

View File

@ -14,7 +14,7 @@
#include "vm.h"
/*****************************************************************************/
/* PUBLIC API */
/* CORE PUBLIC API */
/*****************************************************************************/
// Create a new module with the given [name] and returns as a Script* for
@ -36,15 +36,44 @@ PkHandle* pkNewModule(PKVM* vm, const char* name) {
void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name,
pkNativeFn fptr, int arity) {
__ASSERT(module != NULL, "Argument module was NULL.");
Var scr = module->value;
__ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module");
moduleAddFunctionInternal(vm, (Script*)AS_OBJ(scr), name, fptr, arity);
}
PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
const char* name) {
__ASSERT(module != NULL, "Argument module was NULL.");
Var scr = module->value;
__ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module");
Script* script = (Script*)AS_OBJ(scr);
// TODO: Currently it's O(n) and could be optimized to O(log(n)) but does it
// worth it?
//
// 'function_names' buffer is un-necessary since the function itself has the
// reference to the function name and it can be refactored into a index buffer
// in an "increasing-name" order which can be used to binary search. Similer
// for 'global_names' refactor them from VarBuffer to GlobalVarBuffer where
// GlobalVar is struct { const char* name, Var value };
//
// "nicreasing-name" order index buffer:
// A buffer of int where each is an index in the function buffer and each
// points to different functions in an "increasing-name" (could be hash
// value) order. If we have more than some threshold number of function
// use binary search. (remember to skip literal functions).
for (uint32_t i = 0; i < script->functions.count; i++) {
const char* fn_name = script->functions.data[i]->name;
if (strcmp(name, fn_name) == 0) {
return vmNewHandle(vm, VAR_OBJ(script->functions.data[i]));
}
}
return NULL;
}
// A convinent macro to get the nth (1 based) argument of the current function.
#define ARG(n) vm->fiber->ret[n]
#define ARG(n) (vm->fiber->ret[n])
// Convinent macros to get the 1st, 2nd, 3rd arguments.
#define ARG1 ARG(1)
@ -260,7 +289,7 @@ static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size,
VALIDATE_ARG_OBJ(Fiber, OBJ_FIBER, "fiber")
/*****************************************************************************/
/* BUILTIN FUNCTIONS API */
/* SHARED FUNCTIONS */
/*****************************************************************************/
// findBuiltinFunction implementation (see core.h for description).
@ -299,12 +328,12 @@ Script* getCoreLib(const PKVM* vm, String* name) {
/*****************************************************************************/
#define FN_IS_PRIMITE_TYPE(name, check) \
void coreIs##name(PKVM* vm) { \
static void coreIs##name(PKVM* vm) { \
RET(VAR_BOOL(check(ARG1))); \
}
#define FN_IS_OBJ_TYPE(name, _enum) \
void coreIs##name(PKVM* vm) { \
static void coreIs##name(PKVM* vm) { \
Var arg1 = ARG1; \
if (IS_OBJ_TYPE(arg1, _enum)) { \
RET(VAR_TRUE); \
@ -370,20 +399,7 @@ PK_DOC(coreYield,
RET_ERR(newString(vm, "Invalid argument count."));
}
Fiber* caller = vm->fiber->caller;
// Return the yield value to the caller fiber.
if (caller != NULL) {
if (argc == 0) *caller->ret = VAR_NULL;
else *caller->ret = ARG1;
}
// Can be resumed by another caller fiber.
vm->fiber->caller = NULL;
vm->fiber->state = FIBER_YIELDED;
vm->fiber = caller;
return;
vmYieldFiber(vm, (argc == 1) ? &ARG1 : NULL);
}
PK_DOC(coreToString,
@ -400,24 +416,36 @@ PK_DOC(corePrint,
// output.
if (vm->config.write_fn == NULL) return;
String* str; //< Will be cleaned by garbage collector;
for (int i = 1; i <= ARGC; i++) {
Var arg = ARG(i);
// If it's already a string don't allocate a new string instead use it.
if (IS_OBJ_TYPE(arg, OBJ_STRING)) {
str = (String*)AS_OBJ(arg);
} else {
str = toString(vm, arg);
}
if (i != 1) vm->config.write_fn(vm, " ");
vm->config.write_fn(vm, str->data);
vm->config.write_fn(vm, toString(vm, ARG(i))->data);
}
vm->config.write_fn(vm, "\n");
}
PK_DOC(coreInput,
"input([msg:var]) -> string\n"
"Read a line from stdin and returns it without the line ending. Accepting "
"an optional argument [msg] and prints it before reading.") {
int argc = ARGC;
if (argc != 1 && argc != 2) {
RET_ERR(newString(vm, "Invalid argument count."));
}
// If the host appliaction donesn't provide any write function, return.
if (vm->config.read_fn == NULL) return;
if (argc == 1) {
vm->config.write_fn(vm, toString(vm, ARG1)->data);
}
PkStringPtr result = vm->config.read_fn(vm);
String* line = newString(vm, result.string);
if (result.on_done) result.on_done(vm, result);
RET(VAR_OBJ(line));
}
// String functions.
// -----------------
PK_DOC(coreStrLower,
@ -558,6 +586,8 @@ PK_DOC(coreFiberRun,
Fiber* fb;
if (!validateArgFiber(vm, 1, &fb)) return;
ASSERT(fb->func->arity >= -1 , OOPS " (Forget to initialize arity.)");
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));
@ -691,13 +721,13 @@ static void moduleAddFunctionInternal(PKVM* vm, Script* script,
int arity) {
// Check if function with the same name already exists.
if (scriptSearchFunc(script, name, (uint32_t)strlen(name)) != -1) {
if (scriptGetFunc(script, name, (uint32_t)strlen(name)) != -1) {
__ASSERT(false, stringFormat(vm, "A function named '$' already esists "
"on module '@'", name, script->moudle)->data);
}
// Check if a global variable with the same name already exists.
if (scriptSearchGlobals(script, name, (uint32_t)strlen(name)) != -1) {
if (scriptGetGlobals(script, name, (uint32_t)strlen(name)) != -1) {
__ASSERT(false, stringFormat(vm, "A global variable named '$' already "
"esists on module '@'", name, script->moudle)->data);
}
@ -707,6 +737,8 @@ static void moduleAddFunctionInternal(PKVM* vm, Script* script,
fn->arity = arity;
}
// TODO: make the below module functions as PK_DOC(name, doc);
// 'lang' library methods.
// -----------------------
@ -835,6 +867,11 @@ void initializeCore(PKVM* vm) {
INITALIZE_BUILTIN_FN("type_name", coreTypeName, 1);
// TOOD: (maybe remove is_*() functions) suspend by type_name.
// and add is keyword with modules for builtin types
// ex: val is Num; val is null; val is List; val is Range
// List.append(l, e) # List is implicitly imported core module.
// String.lower(s)
INITALIZE_BUILTIN_FN("is_null", coreIsNull, 1);
INITALIZE_BUILTIN_FN("is_bool", coreIsBool, 1);
INITALIZE_BUILTIN_FN("is_num", coreIsNum, 1);
@ -851,6 +888,7 @@ void initializeCore(PKVM* vm) {
INITALIZE_BUILTIN_FN("yield", coreYield, -1);
INITALIZE_BUILTIN_FN("to_string", coreToString, 1);
INITALIZE_BUILTIN_FN("print", corePrint, -1);
INITALIZE_BUILTIN_FN("input", coreInput, -1);
// String functions.
INITALIZE_BUILTIN_FN("str_lower", coreStrLower, 1);
@ -1101,14 +1139,14 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
Script* scr = (Script*)obj;
// Search in functions.
uint32_t index = scriptSearchFunc(scr, attrib->data, attrib->length);
uint32_t index = scriptGetFunc(scr, attrib->data, attrib->length);
if (index != -1) {
ASSERT_INDEX(index, scr->functions.count);
return VAR_OBJ(scr->functions.data[index]);
}
// Search in globals.
index = scriptSearchGlobals(scr, attrib->data, attrib->length);
index = scriptGetGlobals(scr, attrib->data, attrib->length);
if (index != -1) {
ASSERT_INDEX(index, scr->globals.count);
return scr->globals.data[index];
@ -1173,7 +1211,7 @@ do { \
Script* scr = (Script*)obj;
// Check globals.
uint32_t index = scriptSearchGlobals(scr, attrib->data, attrib->length);
uint32_t index = scriptGetGlobals(scr, attrib->data, attrib->length);
if (index != -1) {
ASSERT_INDEX(index, scr->globals.count);
scr->globals.data[index] = value;
@ -1181,7 +1219,7 @@ do { \
}
// Check function (Functions are immutable).
index = scriptSearchFunc(scr, attrib->data, attrib->length);
index = scriptGetFunc(scr, attrib->data, attrib->length);
if (index != -1) {
ASSERT_INDEX(index, scr->functions.count);
ATTRIB_IMMUTABLE(scr->functions.data[index]->name);

View File

@ -33,11 +33,11 @@ Script* getCoreLib(const PKVM* vm, String* name);
Var varAdd(PKVM* vm, Var v1, Var v2); // Returns v1 + v2.
Var varSubtract(PKVM* vm, Var v1, Var v2); // Returns v1 - v2.
Var varMultiply(PKVM* vm, Var v1, Var v2); // Returns v1 * v2.
Var varDivide(PKVM* vm, Var v1, Var v2); // Returns v1 / v2.
Var varModulo(PKVM* vm, Var v1, Var v2); // Returns v1 % v2.
Var varDivide(PKVM* vm, Var v1, Var v2); // Returns v1 / v2.
Var varModulo(PKVM* vm, Var v1, Var v2); // Returns v1 % v2.
bool varGreater(Var v1, Var v2); // Returns v1 > v2.
bool varLesser(Var v1, Var v2); // Returns v1 < v2.
bool varLesser(Var v1, Var v2); // Returns v1 < v2.
// Returns the attribute named [attrib] on the variable [on].
Var varGetAttrib(PKVM* vm, Var on, String* attrib);

View File

@ -7,6 +7,7 @@
#include <stdio.h>
#include "core.h"
#include "var.h"
#include "vm.h"
// To limit maximum elements to be dumpin in a map or a list.
@ -313,6 +314,8 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
case OP_GTEQ:
case OP_RANGE:
case OP_IN:
case OP_REPL_PRINT:
case OP_YIELD:
case OP_END:
NO_ARGS();
break;

View File

@ -63,7 +63,11 @@ extern "C" {
// }
//
#define PK_DOC(func, doc) \
/* TODO: static char __pkdoc__##func[] = doc;*/ void func(PKVM* vm)
/* TODO: static char __pkdoc__##func[] = doc;*/ static void func(PKVM* vm)
// 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)"
/*****************************************************************************/
/* POCKETLANG TYPES */
@ -100,6 +104,7 @@ typedef enum {
typedef struct PkStringPtr PkStringPtr;
typedef struct PkConfiguration PkConfiguration;
typedef struct PkCompileOptions PkCompileOptions;
// Type of the error message that pocketlang will provide with the pkErrorFn
// callback.
@ -109,13 +114,13 @@ typedef enum {
PK_ERROR_STACKTRACE, // One entry of a runtime error stack.
} PkErrorType;
// Result that pocketlang will return after running a script or a function
// or evaluvating an expression.
// Result that pocketlang will return after a compilation or running a script
// or a function or evaluvating an expression.
typedef enum {
PK_RESULT_SUCCESS = 0, // Successfully finished the execution.
PK_RESULT_COMPILE_ERROR, // Compilation failed.
PK_RESULT_RUNTIME_ERROR, // An error occured at runtime.
} PkInterpretResult;
} PkResult;
/*****************************************************************************/
/* POCKETLANG FUNCTION POINTERS & CALLBACKS */
@ -136,15 +141,20 @@ typedef void (*pkNativeFn)(PKVM* vm);
// function will return NULL.
typedef void* (*pkReallocFn)(void* memory, size_t new_size, void* user_data);
// Error callback function pointer. for runtime error it'll call first with
// Error callback function pointer. For runtime error it'll call first with
// PK_ERROR_RUNTIME followed by multiple callbacks with PK_ERROR_STACKTRACE.
// The error messages should be written to stderr.
typedef void (*pkErrorFn) (PKVM* vm, PkErrorType type,
const char* file, int line,
const char* message);
// A function callback used by `print()` statement.
// A function callback to write [text] to stdout.
typedef void (*pkWriteFn) (PKVM* vm, const char* text);
// A function callback to read a line from stdin. The returned string shouldn't
// contain a line ending (\n or \r\n).
typedef PkStringPtr (*pkReadFn) (PKVM* vm);
// A function callback symbol for clean/free the pkStringResult.
typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result);
@ -169,6 +179,11 @@ typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path);
// application.
PK_PUBLIC PkConfiguration pkNewConfiguration();
// 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();
// Allocate initialize and returns a new VM
PK_PUBLIC PKVM* pkNewVM(PkConfiguration* config);
@ -195,21 +210,36 @@ PK_PUBLIC PkVar pkGetHandleValue(const PkHandle* handle);
// this for every handles before freeing the VM.
PK_PUBLIC void pkReleaseHandle(PKVM* vm, PkHandle* handle);
// Add a new module named [name] to the [vm]. Note that the module shouldn't
// already existed, otherwise an assertion will fail to indicate that.
PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name);
// Add a native function to the given script. If [arity] is -1 that means
// The function has variadic parameters and use pkGetArgc() to get the argc.
PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module,
const char* name,
pkNativeFn fptr, int arity);
// Returns the function from the [module] as a handle, if not found it'll
// return NULL.
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.
PK_PUBLIC PkResult pkCompileModule(PKVM* vm, PkHandle* module,
PkStringPtr source,
const PkCompileOptions* options);
// Interpret the source and return the result. Once It's done with the source
// and path 'on_done' will be called to clean the string if it's not NULL.
PK_PUBLIC PkInterpretResult pkInterpretSource(PKVM* vm,
PkStringPtr source,
PkStringPtr path);
// Set the compiler options with the the [options] argument or it can be set to
// NULL for default options.
PK_PUBLIC PkResult pkInterpretSource(PKVM* vm,
PkStringPtr source,
PkStringPtr path,
const PkCompileOptions* options);
//PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber,
// int argc, PkHandle** argv);
/*****************************************************************************/
/* POCKETLANG PUBLIC TYPE DEFINES */
@ -232,6 +262,7 @@ struct PkConfiguration {
pkErrorFn error_fn;
pkWriteFn write_fn;
pkReadFn read_fn;
pkResolvePathFn resolve_path_fn;
pkLoadScriptFn load_script_fn;
@ -240,6 +271,29 @@ struct PkConfiguration {
void* user_data;
};
// The options to configure the compilation provided by the command line
// arguments (or other ways the host application provides).
struct PkCompileOptions {
// Compile debug version of the source.
bool debug;
// TODO: don't use FILE* pointer or any of <stdio.h> functions here.
// instead add a stream option to vm.config.write_fn callback.
//
// Dump the compiled opcodes to the given [dump_stream] FILE* could be stdio,
// stderr, or a file pointer.
//bool dump_opcodes;
//FILE* dump_stream;
// Set to true if compiling in REPL mode, This will print repr version of
// each evaluvated non-null values. Note that if [repl_mode] is true the
// [expression] should also be true otherwise it's incompatible, (will fail
// an assertion).
bool repl_mode;
};
/*****************************************************************************/
/* NATIVE FUNCTION API */
/*****************************************************************************/
@ -257,7 +311,7 @@ PK_PUBLIC int pkGetArgc(const PKVM* vm);
// Return the [arg] th argument as a PkVar. This pointer will only be
// valid till the current function ends, because it points to the var at the
// stack and it'll popped when the current call frame ended. Use handlers to
// stack and it'll popped when the current call frame ended. Use handles to
// keep the var alive even after that.
PK_PUBLIC PkVar pkGetArg(const PKVM* vm, int arg);
@ -294,8 +348,14 @@ PK_PUBLIC PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len);
PK_PUBLIC PkHandle* pkNewList(PKVM* vm);
PK_PUBLIC PkHandle* pkNewMap(PKVM* vm);
PK_PUBLIC const char* pkStringGetData(const PkVar value);
// Add a new module named [name] to the [vm]. Note that the module shouldn't
// already existed, otherwise an assertion will fail to indicate that.
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

View File

@ -188,6 +188,13 @@ OPCODE(GTEQ, 0, -1)
OPCODE(RANGE, 0, -1) //< Pop 2 integer make range push.
OPCODE(IN, 0, -1)
// Print the repr string of the value at the stack top, used in REPL mode.
// 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)

View File

@ -10,7 +10,7 @@
#include "vm.h"
/*****************************************************************************/
/* PUBLIC API */
/* VAR PUBLIC API */
/*****************************************************************************/
PkVarType pkGetValueType(const PkVar value) {
@ -55,6 +55,13 @@ PkHandle* pkNewMap(PKVM* vm) {
return vmNewHandle(vm, VAR_OBJ(newMap(vm)));
}
PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) {
__ASSERT(IS_OBJ_TYPE(fn->value, OBJ_FUNC), "Fn should be of type function.");
Fiber* fiber = newFiber(vm, (Function*)AS_OBJ(fn->value));
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.");
@ -137,7 +144,7 @@ void grayVarBuffer(PKVM* vm, VarBuffer* self) {
GRAY_OBJ_BUFFER(String)
GRAY_OBJ_BUFFER(Function)
static void blackenObject(Object* obj, PKVM* vm) {
static void _blackenObject(Object* obj, PKVM* vm) {
// TODO: trace here.
switch (obj->type) {
@ -242,12 +249,11 @@ static void blackenObject(Object* obj, PKVM* vm) {
}
}
void blackenObjects(PKVM* vm) {
while (vm->gray_list_count > 0) {
// Pop the gray object from the list.
Object* gray = vm->gray_list[--vm->gray_list_count];
blackenObject(gray, vm);
_blackenObject(gray, vm);
}
}
@ -334,8 +340,9 @@ Script* newScript(PKVM* vm, String* path) {
stringBufferInit(&script->names);
vmPushTempRef(vm, &script->_super);
const char* fn_name = "$(SourceBody)";
const char* fn_name = PK_BODY_FN_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);
return script;
@ -847,8 +854,10 @@ struct OuterSequence {
};
typedef struct OuterSequence OuterSequence;
static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
OuterSequence* outer) {
static void _toStringInternal(PKVM* vm, const Var v, ByteBuffer* buff,
OuterSequence* outer, bool repr) {
ASSERT(outer == NULL || repr, OOPS);
if (IS_NULL(v)) {
byteBufferAddString(buff, vm, "null", 4);
return;
@ -887,9 +896,27 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
case OBJ_STRING:
{
const String* str = (const String*)obj;
if (outer == NULL) {
if (outer == NULL && !repr) {
byteBufferAddString(buff, vm, str->data, str->length);
return;
} else {
// If recursive return with quotes (ex: [42, "hello", 0..10]).
byteBufferWrite(buff, vm, '"');
for (const char* c = str->data; *c != '\0'; c++) {
switch (*c) {
case '"': byteBufferAddString(buff, vm, "\\\"", 2); break;
case '\\': byteBufferAddString(buff, vm, "\\\\", 2); break;
case '\n': byteBufferAddString(buff, vm, "\\n", 2); break;
case '\r': byteBufferAddString(buff, vm, "\\r", 2); break;
case '\t': byteBufferAddString(buff, vm, "\\t", 2); break;
default:
byteBufferWrite(buff, vm, *c);
break;
}
}
byteBufferWrite(buff, vm, '"');
return;
}
// If recursive return with quotes (ex: [42, "hello", 0..10]).
@ -924,7 +951,7 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
byteBufferWrite(buff, vm, '[');
for (uint32_t i = 0; i < list->elements.count; i++) {
if (i != 0) byteBufferAddString(buff, vm, ", ", 2);
toStringInternal(vm, list->elements.data[i], buff, &seq_list);
_toStringInternal(vm, list->elements.data[i], buff, &seq_list, true);
}
byteBufferWrite(buff, vm, ']');
return;
@ -971,9 +998,9 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
byteBufferAddString(buff, vm, ", ", 2);
_first = false;
}
toStringInternal(vm, map->entries[i].key, buff, &seq_map);
_toStringInternal(vm, map->entries[i].key, buff, &seq_map, true);
byteBufferWrite(buff, vm, ':');
toStringInternal(vm, map->entries[i].value, buff, &seq_map);
_toStringInternal(vm, map->entries[i].value, buff, &seq_map, true);
i++;
} while (i < map->capacity);
@ -1044,10 +1071,23 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff,
return;
}
String* toString(PKVM* vm, Var v) {
String* toString(PKVM* vm, const Var value) {
// If it's already a string don't allocate a new string.
if (IS_OBJ_TYPE(value, OBJ_STRING)) {
return (String*)AS_OBJ(value);
}
ByteBuffer buff;
byteBufferInit(&buff);
toStringInternal(vm, v, &buff, NULL);
_toStringInternal(vm, value, &buff, NULL, false);
return newStringLength(vm, (const char*)buff.data, buff.count);
}
String* toRepr(PKVM* vm, const Var value) {
ByteBuffer buff;
byteBufferInit(&buff);
_toStringInternal(vm, value, &buff, NULL, true);
return newStringLength(vm, (const char*)buff.data, buff.count);
}
@ -1168,7 +1208,7 @@ uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,
return self->names.count - 1;
}
int scriptSearchFunc(Script* script, const char* name, uint32_t length) {
int scriptGetFunc(Script* script, const char* name, uint32_t length) {
for (uint32_t i = 0; i < script->function_names.count; i++) {
uint32_t name_index = script->function_names.data[i];
String* fn_name = script->names.data[name_index];
@ -1180,7 +1220,7 @@ int scriptSearchFunc(Script* script, const char* name, uint32_t length) {
return -1;
}
int scriptSearchGlobals(Script* script, const char* name, uint32_t length) {
int scriptGetGlobals(Script* script, const char* name, uint32_t length) {
for (uint32_t i = 0; i < script->global_names.count; i++) {
uint32_t name_index = script->global_names.data[i];
String* g_name = script->names.data[name_index];

View File

@ -138,7 +138,7 @@
#define IS_INT(value) ((value & _MASK_INTEGER) == _MASK_INTEGER)
#define IS_NUM(value) ((value & _MASK_QNAN) != _MASK_QNAN)
#define IS_OBJ(value) ((value & _MASK_OBJECT) == _MASK_OBJECT)
#define IS_OBJ_TYPE(obj, obj_type) IS_OBJ(obj) && AS_OBJ(obj)->type == obj_type
#define IS_OBJ_TYPE(var, obj_type) IS_OBJ(var) && AS_OBJ(var)->type == obj_type
// Decode types.
#define AS_BOOL(value) ((value) == VAR_TRUE)
@ -408,7 +408,7 @@ Script* newScript(PKVM* vm, String* path);
// would be builtin function. For builtin function arity and the native
// function pointer would be initialized after calling this function.
Function* newFunction(PKVM* vm, const char* name, int length, Script* owner,
bool is_native);
bool is_native);
// Allocate new Fiber object around the function [fn] and return Fiber*.
Fiber* newFiber(PKVM* vm, Function* fn);
@ -500,8 +500,12 @@ uint32_t varHashValue(Var v);
// Return true if the object type is hashable.
bool isObjectHashable(ObjectType type);
// Returns the string version of the value.
String* toString(PKVM* vm, Var v);
// Returns the string version of the [value].
String* toString(PKVM* vm, const Var value);
// Returns the representation version of the [value], similer of python's
// __repr__() method.
String * toRepr(PKVM * vm, const Var value);
// Returns the truthy value of the var.
bool toBool(Var v);
@ -523,10 +527,10 @@ uint32_t scriptAddName(Script* self, PKVM* vm, const char* name,
// Search for the function name in the script and return it's index in it's
// [functions] buffer. If not found returns -1.
int scriptSearchFunc(Script* script, const char* name, uint32_t length);
int scriptGetFunc(Script* script, const char* name, uint32_t length);
// Search for the global variable name in the script and return it's index in
// it's [globals] buffer. If not found returns -1.
int scriptSearchGlobals(Script* script, const char* name, uint32_t length);
int scriptGetGlobals(Script* script, const char* name, uint32_t length);
#endif // VAR_H

163
src/vm.c
View File

@ -21,12 +21,17 @@
// if the host doesn't provided any allocators for us.
static void* defaultRealloc(void* memory, size_t new_size, void* user_data);
// Runs the [fiber] if it's at yielded state, this will resume the execution
// till the next yield or return statement, and return result.
static PkResult runFiber(PKVM* vm, Fiber* fiber);
PkConfiguration pkNewConfiguration() {
PkConfiguration config;
config.realloc_fn = defaultRealloc;
config.error_fn = NULL;
config.write_fn = NULL;
config.read_fn = NULL;
config.load_script_fn = NULL;
config.resolve_path_fn = NULL;
@ -35,6 +40,16 @@ PkConfiguration pkNewConfiguration() {
return config;
}
PkCompileOptions pkNewCompilerOptions() {
PkCompileOptions options;
options.debug = false;
// TODO:
//options.dump_opcodes = false;
//options.dump_stream = stdout;
options.repl_mode = false;
return options;
}
PKVM* pkNewVM(PkConfiguration* config) {
PkConfiguration default_config = pkNewConfiguration();
@ -112,6 +127,49 @@ void pkReleaseHandle(PKVM* vm, PkHandle* handle) {
DEALLOCATE(vm, handle);
}
// This function is responsible to call on_done function if it's done with the
// provided string pointers.
PkResult pkInterpretSource(PKVM* vm, PkStringPtr source, PkStringPtr path,
const PkCompileOptions* options) {
String* path_name = newString(vm, path.string);
if (path.on_done) path.on_done(vm, path);
vmPushTempRef(vm, &path_name->_super); // path_name.
// TODO: Should I clean the script if it already exists before compiling it?
// Load a new script to the vm's scripts cache.
Script* scr = vmGetScript(vm, path_name);
if (scr == NULL) {
scr = newScript(vm, path_name);
vmPushTempRef(vm, &scr->_super); // scr.
mapSet(vm, vm->scripts, VAR_OBJ(path_name), VAR_OBJ(scr));
vmPopTempRef(vm); // scr.
}
vmPopTempRef(vm); // path_name.
// Compile the source.
bool success = compile(vm, scr, source.string, options);
if (source.on_done) source.on_done(vm, source);
if (!success) return PK_RESULT_COMPILE_ERROR;
// Set script initialized to true before the execution ends to prevent cyclic
// inclusion cause a crash.
scr->initialized = true;
return runFiber(vm, newFiber(vm, scr->body));
}
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);
}
/*****************************************************************************/
/* SHARED FUNCTIONS */
/*****************************************************************************/
PkHandle* vmNewHandle(PKVM* vm, Var value) {
PkHandle* handle = (PkHandle*)ALLOCATE(vm, PkHandle);
handle->value = value;
@ -122,7 +180,6 @@ PkHandle* vmNewHandle(PKVM* vm, Var value) {
return handle;
}
void* vmRealloc(PKVM* vm, void* memory, size_t old_size, size_t new_size) {
// TODO: Debug trace allocations here.
@ -152,6 +209,13 @@ void vmPopTempRef(PKVM* vm) {
vm->temp_reference_count--;
}
Script* vmGetScript(PKVM* vm, String* path) {
Var scr = mapGet(vm->scripts, VAR_OBJ(path));
if (IS_UNDEF(scr)) return NULL;
ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS);
return (Script*)AS_OBJ(scr);
}
void vmCollectGarbage(PKVM* vm) {
// Reset VM's bytes_allocated value and count it again so that we don't
@ -217,6 +281,22 @@ void vmCollectGarbage(PKVM* vm) {
if (vm->next_gc < vm->min_heap_size) vm->next_gc = vm->min_heap_size;
}
void vmYieldFiber(PKVM* vm, Var* value) {
Fiber* caller = vm->fiber->caller;
// Return the yield value to the caller fiber.
if (caller != NULL) {
if (value == NULL) *caller->ret = VAR_NULL;
else *caller->ret = *value;
}
// Can be resumed by another caller fiber.
vm->fiber->caller = NULL;
vm->fiber->state = FIBER_YIELDED;
vm->fiber = caller;
}
/*****************************************************************************/
/* VM INTERNALS */
/*****************************************************************************/
@ -231,13 +311,6 @@ static void* defaultRealloc(void* memory, size_t new_size, void* user_data) {
return realloc(memory, new_size);
}
static inline Script* getScript(PKVM* vm, String* path) {
Var scr = mapGet(vm->scripts, VAR_OBJ(path));
if (IS_UNDEF(scr)) return NULL;
ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS);
return (Script*)AS_OBJ(scr);
}
// If failed to resolve it'll return false. Parameter [result] should be points
// to the string which is the path that has to be resolved and once it resolved
// the provided result's string's on_done() will be called and, it's string
@ -353,12 +426,7 @@ static inline void pushCallFrame(PKVM* vm, const Function* fn) {
frame->ip = fn->fn->opcodes.data;
}
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);
}
void vmReportError(PKVM* vm) {
static void reportError(PKVM* vm) {
ASSERT(HAS_ERROR(), "runtimeError() should be called after an error.");
// TODO: pass the error to the caller of the fiber.
@ -376,44 +444,11 @@ void vmReportError(PKVM* vm) {
}
}
// This function is responsible to call on_done function if it's done with the
// provided string pointers.
PkInterpretResult pkInterpretSource(PKVM* vm, PkStringPtr source,
PkStringPtr path) {
String* path_name = newString(vm, path.string);
if (path.on_done) path.on_done(vm, path);
vmPushTempRef(vm, &path_name->_super); // path_name.
// TODO: Should I clean the script if it already exists before compiling it?
// Load a new script to the vm's scripts cache.
Script* scr = getScript(vm, path_name);
if (scr == NULL) {
scr = newScript(vm, path_name);
vmPushTempRef(vm, &scr->_super); // scr.
mapSet(vm, vm->scripts, VAR_OBJ(path_name), VAR_OBJ(scr));
vmPopTempRef(vm); // scr.
}
vmPopTempRef(vm); // path_name.
// Compile the source.
bool success = compile(vm, scr, source.string);
if (source.on_done) source.on_done(vm, source);
if (!success) return PK_RESULT_COMPILE_ERROR;
// Set script initialized to true before the execution ends to prevent cyclic
// inclusion cause a crash.
scr->initialized = true;
return vmRunFiber(vm, newFiber(vm, scr->body));
}
/******************************************************************************
* RUNTIME *
*****************************************************************************/
PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
static PkResult runFiber(PKVM* vm, Fiber* fiber) {
// Set the fiber as the vm's current fiber (another root object) to prevent
// it from garbage collection and get the reference from native functions.
@ -423,9 +458,6 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
fiber->state = FIBER_RUNNING;
// The instruction pointer.
// Note: sing 'uint8_t** ip' as reference to the instruction pointer in the
// call frame seems a bit slower because of the dereferencing (~0.1 sec for
// 100 million calls).
register const uint8_t* ip;
register Var* rbp; //< Stack base pointer register.
@ -444,7 +476,7 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
do { \
if (HAS_ERROR()) { \
UPDATE_FRAME(); \
vmReportError(vm); \
reportError(vm); \
return PK_RESULT_RUNTIME_ERROR; \
} \
} while (false)
@ -454,7 +486,7 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
do { \
vm->fiber->error = err_msg; \
UPDATE_FRAME(); \
vmReportError(vm); \
reportError(vm); \
return PK_RESULT_RUNTIME_ERROR; \
} while (false)
@ -1150,6 +1182,31 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) {
// TODO: Implement bool varContaines(vm, on, value);
TODO;
OPCODE(REPL_PRINT):
{
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");
}
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;

View File

@ -176,8 +176,12 @@ void vmPushTempRef(PKVM* vm, Object* obj);
// Pop the top most object from temporary reference stack.
void vmPopTempRef(PKVM* vm);
// Runs the [fiber] if it's at yielded state, this will resume the execution
// till the next yield or return statement, and return result.
PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber);
// Returns the scrpt with the resolved [path] (also the key) in the vm's script
// cache. If not found itll return NULL.
Script* vmGetScript(PKVM* vm, String* path);
// 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);
#endif // VM_H