Merge pull request #44 from ThakeeNathees/repl-refactor-3

REPL refactor [3/3]
This commit is contained in:
Thakee Nathees 2021-06-08 20:22:39 +05:30 committed by GitHub
commit 63d3039eeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 391 additions and 245 deletions

View File

@ -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.

View File

@ -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) {

View File

@ -40,7 +40,7 @@
/* PUBLIC FUNCTIONS */
/*****************************************************************************/
void pathInit() {
void pathInit(void) {
cwk_path_set_style(CWK_STYLE_UNIX);
}

View File

@ -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

View File

@ -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");

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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;
}

View File

@ -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, ...) {

View File

@ -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
View File

@ -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;

View File

@ -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);