native interface refactored into slots.

pkVar is removed and slots (like wren) are implemented for accessing or passing pocketlang values to C and vice versa. Yet all the slot related functions are callable at runtime (a fiber is associated with the VM) which needs to be refactored to allocate slots even if it's not runtime, to let users make use of the pocketlang values before or without running a function.
This commit is contained in:
Thakee Nathees 2022-04-23 08:00:49 +05:30
parent 13b70d6512
commit bb588e9470
16 changed files with 736 additions and 789 deletions

View File

@ -131,6 +131,8 @@ static PKVM* intializePocketVM() {
config.resolve_path_fn = resolvePath;
// FIXME:
// Refactor and make it portable. Maybe custom is_tty() function?.
// Windows isatty depricated -- use _isatty.
if (!!isatty(fileno(stderr))) {
#ifdef _WIN32
DWORD outmode = 0;

View File

@ -62,13 +62,13 @@ DEF(_fileOpen, "") {
if (!pkCheckArgcRange(vm, argc, 1, 2)) return;
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
const char* mode_str = "r";
FileAccessMode mode = FMODE_READ;
if (argc == 2) {
if (!pkGetArgString(vm, 2, &mode_str, NULL)) return;
if (!pkValidateSlotString(vm, 2, &mode_str, NULL)) return;
// Check if the mode string is valid, and update the mode value.
do {
@ -123,7 +123,7 @@ DEF(_fileRead, "") {
// TODO: this is temporary.
char buff[2048];
fread((void*)buff, sizeof(char), sizeof(buff), file->fp);
pkReturnString(vm, (const char*)buff);
pkSetSlotString(vm, 0, (const char*)buff);
}
DEF(_fileWrite, "") {
@ -133,7 +133,7 @@ DEF(_fileWrite, "") {
File* file = (File*)pkGetSelf(vm);
const char* text; uint32_t length;
if (!pkGetArgString(vm, 1, &text, &length)) return;
if (!pkValidateSlotString(vm, 1, &text, &length)) return;
if (file->closed) {
pkSetRuntimeError(vm, "Cannot write to a closed file.");

View File

@ -19,53 +19,53 @@ DEF(stdMathFloor,
"floor(value:num) -> num\n") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
pkReturnNumber(vm, floor(num));
if (!pkValidateSlotNumber(vm, 1, &num)) return;
pkSetSlotNumber(vm, 0, floor(num));
}
DEF(stdMathCeil,
"ceil(value:num) -> num\n") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
pkReturnNumber(vm, ceil(num));
if (!pkValidateSlotNumber(vm, 1, &num)) return;
pkSetSlotNumber(vm, 0, ceil(num));
}
DEF(stdMathPow,
"pow(value:num) -> num\n") {
double num, ex;
if (!pkGetArgNumber(vm, 1, &num)) return;
if (!pkGetArgNumber(vm, 2, &ex)) return;
pkReturnNumber(vm, pow(num, ex));
if (!pkValidateSlotNumber(vm, 1, &num)) return;
if (!pkValidateSlotNumber(vm, 2, &ex)) return;
pkSetSlotNumber(vm, 0, pow(num, ex));
}
DEF(stdMathSqrt,
"sqrt(value:num) -> num\n") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
pkReturnNumber(vm, sqrt(num));
if (!pkValidateSlotNumber(vm, 1, &num)) return;
pkSetSlotNumber(vm, 0, sqrt(num));
}
DEF(stdMathAbs,
"abs(value:num) -> num\n") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
if (!pkValidateSlotNumber(vm, 1, &num)) return;
if (num < 0) num = -num;
pkReturnNumber(vm, num);
pkSetSlotNumber(vm, 0, num);
}
DEF(stdMathSign,
"sign(value:num) -> num\n") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
if (!pkValidateSlotNumber(vm, 1, &num)) return;
if (num < 0) num = -1;
else if (num > 0) num = +1;
else num = 0;
pkReturnNumber(vm, num);
pkSetSlotNumber(vm, 0, num);
}
DEF(stdMathSine,
@ -74,8 +74,8 @@ DEF(stdMathSine,
"in radians.") {
double rad;
if (!pkGetArgNumber(vm, 1, &rad)) return;
pkReturnNumber(vm, sin(rad));
if (!pkValidateSlotNumber(vm, 1, &rad)) return;
pkSetSlotNumber(vm, 0, sin(rad));
}
DEF(stdMathCosine,
@ -84,8 +84,8 @@ DEF(stdMathCosine,
"in radians.") {
double rad;
if (!pkGetArgNumber(vm, 1, &rad)) return;
pkReturnNumber(vm, cos(rad));
if (!pkValidateSlotNumber(vm, 1, &rad)) return;
pkSetSlotNumber(vm, 0, cos(rad));
}
DEF(stdMathTangent,
@ -94,8 +94,8 @@ DEF(stdMathTangent,
"in radians.") {
double rad;
if (!pkGetArgNumber(vm, 1, &rad)) return;
pkReturnNumber(vm, tan(rad));
if (!pkValidateSlotNumber(vm, 1, &rad)) return;
pkSetSlotNumber(vm, 0, tan(rad));
}
DEF(stdMathSinh,
@ -103,8 +103,8 @@ DEF(stdMathSinh,
"Return the hyperbolic sine value of the argument [val].") {
double val;
if (!pkGetArgNumber(vm, 1, &val)) return;
pkReturnNumber(vm, sinh(val));
if (!pkValidateSlotNumber(vm, 1, &val)) return;
pkSetSlotNumber(vm, 0, sinh(val));
}
DEF(stdMathCosh,
@ -112,8 +112,8 @@ DEF(stdMathCosh,
"Return the hyperbolic cosine value of the argument [val].") {
double val;
if (!pkGetArgNumber(vm, 1, &val)) return;
pkReturnNumber(vm, cosh(val));
if (!pkValidateSlotNumber(vm, 1, &val)) return;
pkSetSlotNumber(vm, 0, cosh(val));
}
DEF(stdMathTanh,
@ -121,8 +121,8 @@ DEF(stdMathTanh,
"Return the hyperbolic tangent value of the argument [val].") {
double val;
if (!pkGetArgNumber(vm, 1, &val)) return;
pkReturnNumber(vm, tanh(val));
if (!pkValidateSlotNumber(vm, 1, &val)) return;
pkSetSlotNumber(vm, 0, tanh(val));
}
DEF(stdMathArcSine,
@ -131,13 +131,13 @@ DEF(stdMathArcSine,
"expressed in radians.") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
if (!pkValidateSlotNumber(vm, 1, &num)) return;
if (num < -1 || 1 < num) {
pkSetRuntimeError(vm, "Argument should be between -1 and +1");
}
pkReturnNumber(vm, asin(num));
pkSetSlotNumber(vm, 0, asin(num));
}
DEF(stdMathArcCosine,
@ -146,13 +146,13 @@ DEF(stdMathArcCosine,
"an angle expressed in radians.") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
if (!pkValidateSlotNumber(vm, 1, &num)) return;
if (num < -1 || 1 < num) {
pkSetRuntimeError(vm, "Argument should be between -1 and +1");
}
pkReturnNumber(vm, acos(num));
pkSetSlotNumber(vm, 0, acos(num));
}
DEF(stdMathArcTangent,
@ -161,8 +161,8 @@ DEF(stdMathArcTangent,
"an angle expressed in radians.") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
pkReturnNumber(vm, atan(num));
if (!pkValidateSlotNumber(vm, 1, &num)) return;
pkSetSlotNumber(vm, 0, atan(num));
}
DEF(stdMathLog10,
@ -170,8 +170,8 @@ DEF(stdMathLog10,
"Return the logarithm to base 10 of argument [value]") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
pkReturnNumber(vm, log10(num));
if (!pkValidateSlotNumber(vm, 1, &num)) return;
pkSetSlotNumber(vm, 0, log10(num));
}
DEF(stdMathRound,
@ -179,8 +179,8 @@ DEF(stdMathRound,
"Round to nearest integer, away from zero and return the number.") {
double num;
if (!pkGetArgNumber(vm, 1, &num)) return;
pkReturnNumber(vm, round(num));
if (!pkValidateSlotNumber(vm, 1, &num)) return;
pkSetSlotNumber(vm, 0, round(num));
}
void registerModuleMath(PKVM* vm) {

View File

@ -94,7 +94,7 @@ static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) {
DEF(_pathSetStyleUnix, "") {
bool value;
if (!pkGetArgBool(vm, 1, &value)) return;
if (!pkValidateSlotBool(vm, 1, &value)) return;
cwk_path_set_style((value) ? CWK_STYLE_UNIX : CWK_STYLE_WINDOWS);
}
@ -103,22 +103,22 @@ DEF(_pathGetCWD, "") {
if (get_cwd(cwd, sizeof(cwd)) == NULL) {
// TODO: Handle error.
}
pkReturnString(vm, cwd);
pkSetSlotString(vm, 0, cwd);
}
DEF(_pathAbspath, "") {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
char abspath[FILENAME_MAX];
size_t len = pathAbs(path, abspath, sizeof(abspath));
pkReturnStringLength(vm, abspath, len);
uint32_t len = (uint32_t) pathAbs(path, abspath, sizeof(abspath));
pkSetSlotStringLength(vm, 0, abspath, len);
}
DEF(_pathRelpath, "") {
const char* from, * path;
if (!pkGetArgString(vm, 1, &from, NULL)) return;
if (!pkGetArgString(vm, 2, &path, NULL)) return;
if (!pkValidateSlotString(vm, 1, &from, NULL)) return;
if (!pkValidateSlotString(vm, 2, &path, NULL)) return;
char abs_from[FILENAME_MAX];
pathAbs(from, abs_from, sizeof(abs_from));
@ -127,9 +127,9 @@ DEF(_pathRelpath, "") {
pathAbs(path, abs_path, sizeof(abs_path));
char result[FILENAME_MAX];
size_t len = cwk_path_get_relative(abs_from, abs_path,
result, sizeof(result));
pkReturnStringLength(vm, result, len);
uint32_t len = (uint32_t) cwk_path_get_relative(abs_from, abs_path,
result, sizeof(result));
pkSetSlotStringLength(vm, 0, result, len);
}
DEF(_pathJoin, "") {
@ -143,79 +143,80 @@ DEF(_pathJoin, "") {
}
for (int i = 0; i < argc; i++) {
pkGetArgString(vm, i + 1, &paths[i], NULL);
pkValidateSlotString(vm, i + 1, &paths[i], NULL);
}
paths[argc] = NULL;
char result[FILENAME_MAX];
size_t len = cwk_path_join_multiple(paths, result, sizeof(result));
pkReturnStringLength(vm, result, len);
uint32_t len = (uint32_t) cwk_path_join_multiple(paths, result,
sizeof(result));
pkSetSlotStringLength(vm, 0, result, len);
}
DEF(_pathNormalize, "") {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
char result[FILENAME_MAX];
size_t len = cwk_path_normalize(path, result, sizeof(result));
pkReturnStringLength(vm, result, len);
uint32_t len = (uint32_t) cwk_path_normalize(path, result, sizeof(result));
pkSetSlotStringLength(vm, 0, result, len);
}
DEF(_pathBaseName, "") {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
const char* base_name;
size_t length;
cwk_path_get_basename(path, &base_name, &length);
pkReturnString(vm, base_name);
pkSetSlotStringLength(vm, 0, base_name, (uint32_t)length);
}
DEF(_pathDirName, "") {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
size_t length;
cwk_path_get_dirname(path, &length);
pkReturnStringLength(vm, path, length);
pkSetSlotStringLength(vm, 0, path, (uint32_t)length);
}
DEF(_pathIsPathAbs, "") {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
pkReturnBool(vm, cwk_path_is_absolute(path));
pkSetSlotBool(vm, 0, cwk_path_is_absolute(path));
}
DEF(_pathGetExtension, "") {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
const char* ext;
size_t length;
if (cwk_path_get_extension(path, &ext, &length)) {
pkReturnStringLength(vm, ext, length);
pkSetSlotStringLength(vm, 0, ext, (uint32_t)length);
} else {
pkReturnStringLength(vm, NULL, 0);
pkSetSlotStringLength(vm, 0, NULL, 0);
}
}
DEF(_pathExists, "") {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
pkReturnBool(vm, pathIsExists(path));
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
pkSetSlotBool(vm, 0, pathIsExists(path));
}
DEF(_pathIsFile, "") {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
pkReturnBool(vm, pathIsFileExists(path));
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
pkSetSlotBool(vm, 0, pathIsFileExists(path));
}
DEF(_pathIsDir, "") {
const char* path;
if (!pkGetArgString(vm, 1, &path, NULL)) return;
pkReturnBool(vm, pathIsDirectoryExists(path));
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
pkSetSlotBool(vm, 0, pathIsDirectoryExists(path));
}
void registerModulePath(PKVM* vm) {

View File

@ -69,12 +69,6 @@ typedef struct PKVM PKVM;
// till it's released with pkReleaseHandle().
typedef struct PkHandle PkHandle;
// A temproary pointer to the pocketlang variable. This pointer is acquired
// from the pocketlang's current stack frame and the pointer will become
// dangling once the stack frame is popped. If you want to keep the value
// alive use `pkNewHandle()`.
typedef void* PkVar;
typedef enum PkVarType PkVarType;
typedef enum PkErrorType PkErrorType;
typedef enum PkResult PkResult;
@ -228,12 +222,12 @@ struct PkCompileOptions {
// Create a new PkConfiguration with the default values and return it.
// Override those default configuration to adopt to another hosting
// application.
PK_PUBLIC PkConfiguration pkNewConfiguration(void);
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(void);
PK_PUBLIC PkCompileOptions pkNewCompilerOptions();
// Allocate, initialize and returns a new VM.
PK_PUBLIC PKVM* pkNewVM(PkConfiguration* config);
@ -247,29 +241,17 @@ PK_PUBLIC void pkSetUserData(PKVM* vm, void* user_data);
// Returns the associated user data.
PK_PUBLIC void* pkGetUserData(const PKVM* vm);
// Create a new handle for the [value]. This is useful to keep the [value]
// alive once it acquired from the stack. **DO NOT** use the [value] once
// creating a new handle for it instead get the value from the handle by
// using pkGetHandleValue() function (otherwise the [value] would become a
// dangling pointer once it's stack frame is popped).
PK_PUBLIC PkHandle* pkNewHandle(PKVM* vm, PkVar value);
// Return the PkVar pointer in the handle, the returned pointer will be valid
// till the handle is released.
PK_PUBLIC PkVar pkGetHandleValue(const PkHandle* handle);
// Release the handle and allow its value to be garbage collected. Always call
// this for every handles before freeing the VM.
PK_PUBLIC void pkReleaseHandle(PKVM* vm, PkHandle* handle);
// Add a global [value] to the given module.
PK_PUBLIC void pkModuleAddGlobal(PKVM* vm, PkHandle* module,
const char* name, PkHandle* 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);
// Returns the global value with the [name] in the given [module], if the name
// not exists in the globals of the module, it'll return NULL.
PK_PUBLIC PkHandle* pkModuleGetGlobal(PKVM* vm, PkHandle* module,
const char* name);
// Register the module to the PKVM's modules map, once after it can be
// imported in other modules.
PK_PUBLIC void pkRegisterModule(PKVM* vm, PkHandle* module);
// Add a native function to the given module. If [arity] is -1 that means
// the function has variadic parameters and use pkGetArgc() to get the argc.
@ -283,14 +265,6 @@ PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module,
// it's statements are wrapped around an implicit main function.
PK_PUBLIC PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module);
// 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);
// Register the module to the PKVM's modules map, once after it can be
// imported in other modules.
PK_PUBLIC void pkRegisterModule(PKVM* vm, PkHandle* module);
// Create a new class on the [module] with the [name] and return it.
// If the [base_class] is NULL by default it'll set to "Object" class.
PK_PUBLIC PkHandle* pkNewClass(PKVM* vm, const char* name,
@ -304,9 +278,6 @@ PK_PUBLIC void pkClassAddMethod(PKVM* vm, PkHandle* cls,
const char* name,
pkNativeFn fptr, int arity);
// Create and return a new fiber around the function [fn].
PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn);
// 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,
@ -322,6 +293,13 @@ PK_PUBLIC PkResult pkInterpretSource(PKVM* vm,
PkStringPtr path,
const PkCompileOptions* options);
// FIXME:
// Remove pkNewFiber and pkRunFiber functions and add interface to run closure
// with pkRunClosure or pkRunFunction.
// Create and return a new fiber around the function [fn].
PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn);
// 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
// result (success or failure) if you need the yielded or returned value use
@ -330,13 +308,8 @@ PK_PUBLIC PkResult pkInterpretSource(PKVM* vm,
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 result (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);
/*****************************************************************************/
/* NATIVE FUNCTION API */
/* NATIVE / RUNTIME FUNCTION API */
/*****************************************************************************/
// Set a runtime error to VM.
@ -348,10 +321,6 @@ PK_PUBLIC void pkSetRuntimeError(PKVM* vm, const char* message);
// Returns native [self] of the current method as a void*.
PK_PUBLIC void* pkGetSelf(const PKVM* vm);
// Return the type of the [value] this will help to get the type of the
// variable that was extracted from pkGetArg() earlier.
PK_PUBLIC PkVarType pkGetValueType(const PkVar value);
// Return the current functions argument count. This is needed for functions
// registered with -1 argument count (which means variadic arguments).
PK_PUBLIC int pkGetArgc(const PKVM* vm);
@ -361,70 +330,72 @@ PK_PUBLIC int pkGetArgc(const PKVM* vm);
// that min <= max, and pocketlang won't validate this in release binary.
PK_PUBLIC bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max);
// 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 handles to
// keep the var alive even after that.
PK_PUBLIC PkVar pkGetArg(const PKVM* vm, int arg);
// SLOTS DOCS
// The functions below are used to extract the function arguments from the
// stack as a type. They will first validate the argument's type and set a
// runtime error if it's not and return false. Otherwise it'll set the [value]
// with the extracted value.
//
// NOTE: The arguments are 1 based (to get the first argument use 1 not 0).
// Only use arg index 0 to get the value of attribute setter call.
// Helper function to check if the argument at the [arg] slot is Boolean and
// if not set a runtime error.
PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int arg, bool* value);
PK_PUBLIC bool pkGetArgBool(PKVM* vm, int arg, bool* value);
PK_PUBLIC bool pkGetArgNumber(PKVM* vm, int arg, double* value);
PK_PUBLIC bool pkGetArgString(PKVM* vm, int arg,
const char** value, uint32_t* length);
PK_PUBLIC bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value);
// Helper function to check if the argument at the [arg] slot is Number and
// if not set a runtime error.
PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int arg, double* value);
// The functions follow are used to set the return value of the current native
// function's. Don't use it outside a registered native function.
// Helper function to check if the argument at the [arg] slot is String and
// if not set a runtime error.
PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int arg,
const char** value, uint32_t* length);
PK_PUBLIC void pkReturnNull(PKVM* vm);
PK_PUBLIC void pkReturnBool(PKVM* vm, bool value);
PK_PUBLIC void pkReturnNumber(PKVM* vm, double value);
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);
PK_PUBLIC void pkReturnHandle(PKVM* vm, PkHandle* handle);
// Make sure the fiber has [count] number of slots to work with (including the
// arguments).
PK_PUBLIC void pkReserveSlots(PKVM* vm, int count);
// 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 available number of slots to work with. It has at least the
// number argument the function is registered plus one for return value.
PK_PUBLIC int pkGetSlotsCount(PKVM* vm);
// 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 the type of the variable at the [index] slot.
PK_PUBLIC PkVarType pkGetSlotType(PKVM* vm, int index);
// Returns true if the fiber is finished it's execution and cannot be resumed
// anymore.
PK_PUBLIC bool pkFiberIsDone(const PkHandle* fiber);
// Returns boolean value at the [index] slot. If the value at the [index]
// is not a boolean it'll be casted (only for booleans).
PK_PUBLIC bool pkGetSlotBool(PKVM* vm, int index);
/*****************************************************************************/
/* POCKETLANG TYPE FUNCTIONS */
/*****************************************************************************/
// Returns number value at the [index] slot. If the value at the [index]
// is not a boolean, an assertion will fail.
PK_PUBLIC double pkGetSlotNumber(PKVM* vm, int index);
// The functions below will allocate a new object and return's it's value
// wrapped around a handler.
// Returns the string at the [index] slot. The returned pointer is only valid
// inside the native function that called this. Afterwards it may garbage
// collected and become demangled. If the [length] is not NULL the length of
// the string will be written.
PK_PUBLIC const char* pkGetSlotString(PKVM* vm, int index, uint32_t* length);
PK_PUBLIC PkHandle* pkNewString(PKVM* vm, const char* value);
PK_PUBLIC PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len);
PK_PUBLIC PkHandle* pkNewList(PKVM* vm);
PK_PUBLIC PkHandle* pkNewMap(PKVM* vm);
// Capture the variable at the [index] slot and return its handle. As long as
// the handle is not released with `pkReleaseHandle()` the variable won't be
// garbage collected.
PK_PUBLIC PkHandle* pkGetSlotHandle(PKVM* vm, int index);
// TODO: Create a primitive (non garbage collected) variable buffer (or a
// fixed size array) to store them and make the handle points to the variable
// in that buffer, this will prevent us from invoking an allocation call for
// each time we want to pass a primitive type.
// Set the [index] slot value as pocketlang null.
PK_PUBLIC void pkSetSlotNull(PKVM* vm, int index);
//PK_PUBLIC PkVar pkPushNull(PKVM* vm);
//PK_PUBLIC PkVar pkPushBool(PKVM* vm, bool value);
//PK_PUBLIC PkVar pkPushNumber(PKVM* vm, double value);
// Set the [index] slot boolean value as the given [value].
PK_PUBLIC void pkSetSlotBool(PKVM* vm, int index, bool value);
// Set the [index] slot numeric value as the given [value].
PK_PUBLIC void pkSetSlotNumber(PKVM* vm, int index, double value);
// Create a new String copying the [value] and set it to [index] slot.
PK_PUBLIC void pkSetSlotString(PKVM* vm, int index, const char* value);
// Create a new String copying the [value] and set it to [index] slot. Unlike
// the above function it'll copy only the spicified length.
PK_PUBLIC void pkSetSlotStringLength(PKVM* vm, int index,
const char* value, uint32_t length);
// Set the [index] slot's value as the given [handle]. The function won't
// reclaim the ownership of the handle and you can still use it till
// it's released by yourself.
PK_PUBLIC void PkSetSlotHandle(PKVM* vm, int index, PkHandle* handle);
#ifdef __cplusplus
} // extern "C"

View File

@ -3386,27 +3386,6 @@ PkResult compile(PKVM* vm, Module* module, const char* source,
return PK_RESULT_SUCCESS;
}
// FIXME:
// move this to pk_core or create new pk_public.c to implement all
// public functions.
PkResult pkCompileModule(PKVM* vm, PkHandle* module_handle, PkStringPtr source,
const PkCompileOptions* options) {
ASSERT(source.string != NULL, OOPS);
// FIXME:
// CHECK_NULL, CHECK_TYPE marcros can be use after this function is moved.
ASSERT(module_handle != NULL, "Argument module was NULL.");
ASSERT(IS_OBJ_TYPE(module_handle->value, OBJ_MODULE),
"Given handle is not a module.");
Module* module = (Module*)AS_OBJ(module_handle->value);
PkResult result = compile(vm, module, source.string, options);
if (source.on_done) source.on_done(vm, source);
return result;
}
void compilerMarkObjects(PKVM* vm, Compiler* compiler) {
// Mark the module which is currently being compiled.

View File

@ -7,7 +7,6 @@
#ifndef PK_COMPILER_H
#define PK_COMPILER_H
#include "pk_internal.h"
#include "pk_value.h"
typedef enum {

View File

@ -25,145 +25,6 @@
static const char* DOCSTRING(fn) = docstring; \
static void fn(PKVM* vm)
// Create a new module with the given [name] and returns as a Module* for
// internal. Which will be wrapped by pkNewModule to return a pkHandle*.
static Module* newModuleInternal(PKVM* vm, const char* name);
// Adds a function to the module with the give properties and add the function
// to the module's globals variables.
static void moduleAddFunctionInternal(PKVM* vm, Module* module,
const char* name, pkNativeFn fptr,
int arity, const char* docstring);
/*****************************************************************************/
/* CORE PUBLIC API */
/*****************************************************************************/
#define CHECK_NULL(name) \
ASSERT(name != NULL, "Argument " #name " was NULL.");
#define CHECK_TYPE(handle, type) \
do { \
CHECK_NULL(handle); \
ASSERT(IS_OBJ_TYPE(handle->value, type), \
"Given handle is not of type " #type "."); \
} while (false)
PkHandle* pkNewModule(PKVM* vm, const char* name) {
CHECK_NULL(name);
Module* module = newModuleInternal(vm, name);
return vmNewHandle(vm, VAR_OBJ(module));
}
void pkRegisterModule(PKVM* vm, PkHandle* module) {
CHECK_TYPE(module, OBJ_MODULE);
Module* module_ = (Module*)AS_OBJ(module->value);
vmRegisterModule(vm, module_, module_->name);
}
PkHandle* pkNewClass(PKVM* vm, const char* name,
PkHandle* base_class, PkHandle* module,
pkNewInstanceFn new_fn,
pkDeleteInstanceFn delete_fn) {
CHECK_NULL(module);
CHECK_NULL(name);
CHECK_TYPE(module, OBJ_MODULE);
Class* super = vm->builtin_classes[PK_OBJECT];
if (base_class != NULL) {
CHECK_TYPE(base_class, OBJ_CLASS);
super = (Class*)AS_OBJ(base_class->value);
}
Class* class_ = newClass(vm, name, (int)strlen(name),
super, (Module*)AS_OBJ(module->value),
NULL, NULL);
class_->new_fn = new_fn;
class_->delete_fn = delete_fn;
return vmNewHandle(vm, VAR_OBJ(class_));
}
void pkClassAddMethod(PKVM* vm, PkHandle* cls,
const char* name,
pkNativeFn fptr, int arity) {
CHECK_NULL(cls);
CHECK_NULL(fptr);
CHECK_TYPE(cls, OBJ_CLASS);
Class* class_ = (Class*)AS_OBJ(cls->value);
Function* fn = newFunction(vm, name, (int)strlen(name),
class_->owner, true, NULL, NULL);
// No need to push the function to temp references of the VM
// since it's written to the constant pool of the module and the module
// won't be garbage collected (class handle has reference to the module).
Closure* method = newClosure(vm, fn);
// FIXME: name "_init" is literal everywhere.
if (strcmp(name, "_init") == 0) {
class_->ctor = method;
} else {
vmPushTempRef(vm, &method->_super); // method.
pkClosureBufferWrite(&class_->methods, vm, method);
vmPopTempRef(vm); // method.
}
}
void* pkGetSelf(const PKVM* vm) {
ASSERT(IS_OBJ_TYPE(vm->fiber->self, OBJ_INST), OOPS);
Instance* inst = (Instance*)AS_OBJ(vm->fiber->self);
ASSERT(inst->native != NULL, OOPS);
return inst->native;
}
void pkModuleAddGlobal(PKVM* vm, PkHandle* module,
const char* name, PkHandle* value) {
CHECK_TYPE(module, OBJ_MODULE);
CHECK_NULL(value);
moduleAddGlobal(vm, (Module*)AS_OBJ(module->value),
name, (uint32_t)strlen(name), value->value);
}
PkHandle* pkModuleGetGlobal(PKVM* vm, PkHandle* module, const char* name) {
CHECK_TYPE(module, OBJ_MODULE);
CHECK_NULL(name);
Module* module_ = (Module*)AS_OBJ(module->value);
int index = moduleGetGlobalIndex(module_, name, (uint32_t)strlen(name));
if (index == -1) return NULL;
return vmNewHandle(vm, module_->globals.data[index]);
}
void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name,
pkNativeFn fptr, int arity) {
CHECK_TYPE(module, OBJ_MODULE);
CHECK_NULL(fptr);
moduleAddFunctionInternal(vm, (Module*)AS_OBJ(module->value),
name, fptr, arity,
NULL /*TODO: Public API for function docstring.*/);
}
PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module) {
CHECK_TYPE(module, OBJ_MODULE);
Module* _module = (Module*)AS_OBJ(module->value);
int main_index = moduleGetGlobalIndex(_module, IMPLICIT_MAIN_NAME,
(uint32_t)strlen(IMPLICIT_MAIN_NAME));
if (main_index == -1) return NULL;
ASSERT_INDEX(main_index, (int)_module->globals.count);
Var main_fn = _module->globals.data[main_index];
ASSERT(IS_OBJ_TYPE(main_fn, OBJ_CLOSURE), OOPS);
return vmNewHandle(vm, main_fn);
}
// A convenient macro to get the nth (1 based) argument of the current
// function.
#define ARG(n) (vm->fiber->ret[n])
@ -184,172 +45,6 @@ PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module) {
RET(VAR_NULL); \
} while(false)
// Check for errors in before calling the get arg public api function.
#define CHECK_GET_ARG_API_ERRORS() \
do { \
ASSERT(vm->fiber != NULL, \
"This function can only be called at runtime."); \
if (arg != 0) {/* If Native setter, the value would be at fiber->ret */ \
ASSERT(arg > 0 && arg <= ARGC, "Invalid argument index."); \
} \
ASSERT(value != NULL, "Argument [value] was NULL."); \
} while (false)
// Set error for incompatible type provided as an argument. (TODO: got type).
#define ERR_INVALID_ARG_TYPE(m_type) \
do { \
if (arg != 0) { /* If Native setter, arg index would be 0. */ \
char buff[STR_INT_BUFF_SIZE]; \
sprintf(buff, "%d", arg); \
VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$' at argument $.", \
m_type, buff)); \
} else { \
VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$'.", m_type)); \
} \
} while (false)
int pkGetArgc(const PKVM* vm) {
ASSERT(vm->fiber != NULL, "This function can only be called at runtime.");
return ARGC;
}
bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max) {
ASSERT(min <= max, "invalid argc range (min > max).");
if (argc < min) {
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", min);
VM_SET_ERROR(vm, stringFormat(vm, "Expected at least %s argument(s).",
buff));
return false;
} else if (argc > max) {
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", max);
VM_SET_ERROR(vm, stringFormat(vm, "Expected at most %s argument(s).",
buff));
return false;
}
return true;
}
PkVar pkGetArg(const PKVM* vm, int arg) {
ASSERT(vm->fiber != NULL, "This function can only be called at runtime.");
ASSERT(arg > 0 || arg <= ARGC, "Invalid argument index.");
return &(ARG(arg));
}
bool pkGetArgBool(PKVM* vm, int arg, bool* value) {
CHECK_GET_ARG_API_ERRORS();
Var val = ARG(arg);
*value = toBool(val);
return true;
}
bool pkGetArgNumber(PKVM* vm, int arg, double* value) {
CHECK_GET_ARG_API_ERRORS();
Var val = ARG(arg);
if (IS_NUM(val)) {
*value = AS_NUM(val);
} else if (IS_BOOL(val)) {
*value = AS_BOOL(val) ? 1 : 0;
} else {
ERR_INVALID_ARG_TYPE("number");
return false;
}
return true;
}
bool pkGetArgString(PKVM* vm, int arg, const char** value, uint32_t* length) {
CHECK_GET_ARG_API_ERRORS();
Var val = ARG(arg);
if (IS_OBJ_TYPE(val, OBJ_STRING)) {
String* str = (String*)AS_OBJ(val);
*value = str->data;
if (length) *length = str->length;
} else {
ERR_INVALID_ARG_TYPE("string");
return false;
}
return true;
}
bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) {
CHECK_GET_ARG_API_ERRORS();
Var val = ARG(arg);
if (pkGetValueType((PkVar)&val) != type) {
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", arg);
VM_SET_ERROR(vm, stringFormat(vm, "Expected a $ at argument $.",
getPkVarTypeName(type), buff));
return false;
}
*value = (PkVar)&val;
return true;
}
void pkReturnNull(PKVM* vm) {
RET(VAR_NULL);
}
void pkReturnBool(PKVM* vm, bool value) {
RET(VAR_BOOL(value));
}
void pkReturnNumber(PKVM* vm, double value) {
RET(VAR_NUM(value));
}
void pkReturnString(PKVM* vm, const char* value) {
RET(VAR_OBJ(newString(vm, value)));
}
void pkReturnStringLength(PKVM* vm, const char* value, size_t length) {
RET(VAR_OBJ(newStringLength(vm, value, (uint32_t)length)));
}
void pkReturnValue(PKVM* vm, PkVar value) {
RET(*(Var*)value);
}
void pkReturnHandle(PKVM* vm, PkHandle* handle) {
RET(handle->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;
}
#undef CHECK_NULL
#undef CHECK_TYPE
/*****************************************************************************/
/* VALIDATORS */
/*****************************************************************************/
@ -833,7 +528,7 @@ static void initializeBuiltinFunctions(PKVM* vm) {
/*****************************************************************************/
// Create a module and add it to the vm's core modules, returns the module.
static Module* newModuleInternal(PKVM* vm, const char* name) {
Module* newModuleInternal(PKVM* vm, const char* name) {
String* _name = newString(vm, name);
vmPushTempRef(vm, &_name->_super); // _name
@ -854,9 +549,9 @@ static Module* newModuleInternal(PKVM* vm, const char* name) {
}
// An internal function to add a function to the given [module].
static void moduleAddFunctionInternal(PKVM* vm, Module* module,
const char* name, pkNativeFn fptr,
int arity, const char* docstring) {
void moduleAddFunctionInternal(PKVM* vm, Module* module,
const char* name, pkNativeFn fptr,
int arity, const char* docstring) {
Function* fn = newFunction(vm, name, (int)strlen(name),
module, true, docstring, NULL);

View File

@ -13,6 +13,17 @@
// Initialize core language, builtin function and core libs.
void initializeCore(PKVM* vm);
// Create a new module with the given [name] and returns as a Module*.
// This is function is a wrapper around `newModule()` function to create
// native modules for pocket core and public native api.
Module* newModuleInternal(PKVM* vm, const char* name);
// Adds a function to the module with the give properties and add the function
// to the module's globals variables.
void moduleAddFunctionInternal(PKVM* vm, Module* module,
const char* name, pkNativeFn fptr,
int arity, const char* docstring);
/*****************************************************************************/
/* OPERATORS */
/*****************************************************************************/

View File

@ -7,8 +7,6 @@
#include "pk_debug.h"
#include <stdio.h>
#include "pk_core.h"
#include "pk_value.h"
#include "pk_vm.h"
// FIXME:

553
src/pk_public.c Normal file
View File

@ -0,0 +1,553 @@
/*
* Copyright (c) 2020-2022 Thakee Nathees
* Copyright (c) 2021-2022 Pocketlang Contributors
* Distributed Under The MIT License
*/
// This file contains all the pocketlang public function implementations.
#include "include/pocketlang.h"
#include "pk_core.h"
#include "pk_value.h"
#include "pk_vm.h"
#define CHECK_ARG_NULL(name) \
ASSERT((name) != NULL, "Argument " #name " was NULL.");
#define CHECK_HANDLE_TYPE(handle, type) \
do { \
CHECK_ARG_NULL(handle); \
ASSERT(IS_OBJ_TYPE(handle->value, type), \
"Given handle is not of type " #type "."); \
} while (false)
// The default allocator that will be used to initialize the PKVM's
// configuration if the host doesn't provided any allocators for us.
static void* defaultRealloc(void* memory, size_t new_size, void* _);
PkConfiguration pkNewConfiguration() {
PkConfiguration config;
config.realloc_fn = defaultRealloc;
config.stdout_write = NULL;
config.stderr_write = NULL;
config.stdin_read = NULL;
config.load_script_fn = NULL;
config.resolve_path_fn = NULL;
config.use_ansi_color = false;
config.user_data = NULL;
return config;
}
PkCompileOptions pkNewCompilerOptions() {
PkCompileOptions options;
options.debug = false;
options.repl_mode = false;
return options;
}
PKVM* pkNewVM(PkConfiguration* config) {
PkConfiguration default_config = pkNewConfiguration();
if (config == NULL) config = &default_config;
PKVM* vm = (PKVM*)config->realloc_fn(NULL, sizeof(PKVM), config->user_data);
memset(vm, 0, sizeof(PKVM));
vm->config = *config;
vm->working_set_count = 0;
vm->working_set_capacity = MIN_CAPACITY;
vm->working_set = (Object**)vm->config.realloc_fn(
NULL, sizeof(Object*) * vm->working_set_capacity, NULL);
vm->next_gc = INITIAL_GC_SIZE;
vm->min_heap_size = MIN_HEAP_SIZE;
vm->heap_fill_percent = HEAP_FILL_PERCENT;
vm->modules = newMap(vm);
vm->builtins_count = 0;
// This is necessary to prevent garbage collection skip the entry in this
// array while we're building it.
for (int i = 0; i < PK_INSTANCE; i++) {
vm->builtin_classes[i] = NULL;
}
initializeCore(vm);
return vm;
}
void pkFreeVM(PKVM* vm) {
Object* obj = vm->first;
while (obj != NULL) {
Object* next = obj->next;
freeObject(vm, obj);
obj = next;
}
vm->working_set = (Object**)vm->config.realloc_fn(
vm->working_set, 0, vm->config.user_data);
// Tell the host application that it forget to release all of it's handles
// before freeing the VM.
ASSERT(vm->handles == NULL, "Not all handles were released.");
DEALLOCATE(vm, vm);
}
void* pkGetUserData(const PKVM* vm) {
return vm->config.user_data;
}
void pkSetUserData(PKVM* vm, void* user_data) {
vm->config.user_data = user_data;
}
PkHandle* pkNewModule(PKVM* vm, const char* name) {
CHECK_ARG_NULL(name);
Module* module = newModuleInternal(vm, name);
return vmNewHandle(vm, VAR_OBJ(module));
}
void pkRegisterModule(PKVM* vm, PkHandle* module) {
CHECK_HANDLE_TYPE(module, OBJ_MODULE);
Module* module_ = (Module*)AS_OBJ(module->value);
vmRegisterModule(vm, module_, module_->name);
}
void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name,
pkNativeFn fptr, int arity) {
CHECK_HANDLE_TYPE(module, OBJ_MODULE);
CHECK_ARG_NULL(fptr);
moduleAddFunctionInternal(vm, (Module*)AS_OBJ(module->value),
name, fptr, arity,
NULL /*TODO: Public API for function docstring.*/);
}
PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module) {
CHECK_HANDLE_TYPE(module, OBJ_MODULE);
Module* _module = (Module*)AS_OBJ(module->value);
int main_index = moduleGetGlobalIndex(_module, IMPLICIT_MAIN_NAME,
(uint32_t)strlen(IMPLICIT_MAIN_NAME));
if (main_index == -1) return NULL;
ASSERT_INDEX(main_index, (int)_module->globals.count);
Var main_fn = _module->globals.data[main_index];
ASSERT(IS_OBJ_TYPE(main_fn, OBJ_CLOSURE), OOPS);
return vmNewHandle(vm, main_fn);
}
PkHandle* pkNewClass(PKVM* vm, const char* name,
PkHandle* base_class, PkHandle* module,
pkNewInstanceFn new_fn,
pkDeleteInstanceFn delete_fn) {
CHECK_ARG_NULL(module);
CHECK_ARG_NULL(name);
CHECK_HANDLE_TYPE(module, OBJ_MODULE);
Class* super = vm->builtin_classes[PK_OBJECT];
if (base_class != NULL) {
CHECK_HANDLE_TYPE(base_class, OBJ_CLASS);
super = (Class*)AS_OBJ(base_class->value);
}
Class* class_ = newClass(vm, name, (int)strlen(name),
super, (Module*)AS_OBJ(module->value),
NULL, NULL);
class_->new_fn = new_fn;
class_->delete_fn = delete_fn;
return vmNewHandle(vm, VAR_OBJ(class_));
}
void pkClassAddMethod(PKVM* vm, PkHandle* cls,
const char* name,
pkNativeFn fptr, int arity) {
CHECK_ARG_NULL(cls);
CHECK_ARG_NULL(fptr);
CHECK_HANDLE_TYPE(cls, OBJ_CLASS);
Class* class_ = (Class*)AS_OBJ(cls->value);
Function* fn = newFunction(vm, name, (int)strlen(name),
class_->owner, true, NULL, NULL);
// No need to push the function to temp references of the VM
// since it's written to the constant pool of the module and the module
// won't be garbage collected (class handle has reference to the module).
Closure* method = newClosure(vm, fn);
// FIXME: name "_init" is literal everywhere.
if (strcmp(name, "_init") == 0) {
class_->ctor = method;
} else {
vmPushTempRef(vm, &method->_super); // method.
pkClosureBufferWrite(&class_->methods, vm, method);
vmPopTempRef(vm); // method.
}
}
void pkReleaseHandle(PKVM* vm, PkHandle* handle) {
ASSERT(handle != NULL, "Given handle was NULL.");
// If the handle is the head of the vm's handle chain set it to the next one.
if (handle == vm->handles) {
vm->handles = handle->next;
}
// Remove the handle from the chain by connecting the both ends together.
if (handle->next) handle->next->prev = handle->prev;
if (handle->prev) handle->prev->next = handle->next;
// Free the handle.
DEALLOCATE(vm, handle);
}
PkResult pkCompileModule(PKVM* vm, PkHandle* module_handle, PkStringPtr source,
const PkCompileOptions* options) {
CHECK_ARG_NULL(source.string);
CHECK_HANDLE_TYPE(module_handle, OBJ_MODULE);
Module* module = (Module*)AS_OBJ(module_handle->value);
PkResult result = compile(vm, module, source.string, options);
if (source.on_done) source.on_done(vm, source);
return result;
}
// 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_ = newString(vm, path.string);
if (path.on_done) path.on_done(vm, path);
vmPushTempRef(vm, &path_->_super); // path_
// FIXME:
// Should I clean the module if it already exists before compiling it?
// Load a new module to the vm's modules cache.
Module* module = vmGetModule(vm, path_);
if (module == NULL) {
module = newModule(vm);
module->path = path_;
vmPushTempRef(vm, &module->_super); // module.
vmRegisterModule(vm, module, path_);
vmPopTempRef(vm); // module.
}
vmPopTempRef(vm); // path_
// Compile the source.
PkResult result = compile(vm, module, source.string, options);
if (source.on_done) source.on_done(vm, source);
if (result != PK_RESULT_SUCCESS) return result;
// Set module initialized to true before the execution ends to prevent cyclic
// inclusion cause a crash.
module->initialized = true;
return vmRunFiber(vm, newFiber(vm, module->body));
}
PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) {
CHECK_HANDLE_TYPE(fn, OBJ_CLOSURE);
Fiber* fiber = newFiber(vm, (Closure*)AS_OBJ(fn->value));
vmPushTempRef(vm, &fiber->_super); // fiber
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(fiber));
vmPopTempRef(vm); // fiber
return handle;
}
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 vmRunFiber(vm, _fiber);
}
// TODO: Get resume argument.
PkResult pkResumeFiber(PKVM* vm, 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);
if (!vmSwitchFiber(vm, _fiber, NULL /* TODO: argument */)) {
return PK_RESULT_RUNTIME_ERROR;
}
return vmRunFiber(vm, _fiber);
}
/*****************************************************************************/
/* RUNTIME */
/*****************************************************************************/
#define CHECK_RUNTIME() \
do { \
ASSERT(vm->fiber != NULL, \
"This function can only be called at runtime."); \
} while (false)
// A convenient macro to get the nth (1 based) argument of the current
// function.
#define ARG(n) (vm->fiber->ret[n])
// Nth slot is same as Nth argument, It'll also work if we allocate more
// slots but the caller should ensure the index.
#define SLOT(n) ARG(n)
// This will work.
#define SET_SLOT(n, val) SLOT(n) = (val);
// Evaluates to the current function's argument count.
#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1)
void pkSetRuntimeError(PKVM* vm, const char* message) {
CHECK_RUNTIME();
VM_SET_ERROR(vm, newString(vm, message));
}
void* pkGetSelf(const PKVM* vm) {
CHECK_RUNTIME();
ASSERT(IS_OBJ_TYPE(vm->fiber->self, OBJ_INST), OOPS);
Instance* inst = (Instance*)AS_OBJ(vm->fiber->self);
ASSERT(inst->native != NULL, OOPS);
return inst->native;
}
int pkGetArgc(const PKVM* vm) {
CHECK_RUNTIME();
return ARGC;
}
bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max) {
ASSERT(min <= max, "invalid argc range (min > max).");
if (argc < min) {
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", min);
VM_SET_ERROR(vm, stringFormat(vm, "Expected at least %s argument(s).",
buff));
return false;
} else if (argc > max) {
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", max);
VM_SET_ERROR(vm, stringFormat(vm, "Expected at most %s argument(s).",
buff));
return false;
}
return true;
}
#define VALIDATE_SLOT_INDEX(index) \
do { \
ASSERT(index >= 0, "Slot index was negative."); \
ASSERT(index < pkGetSlotsCount(vm), \
"Slot index is too large. Did you forget to call pkReserveSlots()?."); \
} while (false)
// ARGC won't be the real arity if any slots allocated before calling argument
// validation calling this first is the callers responsibility.
#define VALIDATE_ARGC(arg) \
ASSERT(arg > 0 && arg <= ARGC, "Invalid argument index.")
// Set error for incompatible type provided as an argument. (TODO: got type).
#define ERR_INVALID_ARG_TYPE(ty_name) \
do { \
char buff[STR_INT_BUFF_SIZE]; \
sprintf(buff, "%d", arg); \
VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$' at argument $.", \
ty_name, buff)); \
} while (false)
// FIXME: If the user needs just the boolean value of the object, they should
// use pkGetSlotBool().
PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int arg, bool* value) {
CHECK_RUNTIME();
VALIDATE_ARGC(arg);
Var val = ARG(arg);
if (!IS_BOOL(val)) {
ERR_INVALID_ARG_TYPE("Boolean");
return false;
}
if (value) *value = AS_BOOL(val);
return true;
}
PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int arg, double* value) {
CHECK_RUNTIME();
VALIDATE_ARGC(arg);
Var val = ARG(arg);
if (!IS_NUM(val)) {
ERR_INVALID_ARG_TYPE("Number");
return false;
}
if (value) *value = AS_NUM(val);
return true;
}
PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int arg, const char** value,
uint32_t* length) {
CHECK_RUNTIME();
VALIDATE_ARGC(arg);
Var val = ARG(arg);
if (!IS_OBJ_TYPE(val, OBJ_STRING)) {
ERR_INVALID_ARG_TYPE("String");
return false;
}
String* str = (String*)AS_OBJ(val);
if (value) *value = str->data;
if (length) *length = str->length;
return true;
}
void pkReserveSlots(PKVM* vm, int count) {
CHECK_RUNTIME();
int needed = (int)(vm->fiber->ret - vm->fiber->stack) + count;
vmEnsureStackSize(vm, needed);
}
int pkGetSlotsCount(PKVM* vm) {
CHECK_RUNTIME();
return (int)(vm->fiber->sp - vm->fiber->ret);
}
PkVarType pkGetSlotType(PKVM* vm, int index) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
return getVarType(SLOT(index));
}
bool pkGetSlotBool(PKVM* vm, int index) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
Var value = SLOT(index);
return toBool(value);
}
double pkGetSlotNumber(PKVM* vm, int index) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
Var value = SLOT(index);
ASSERT(IS_NUM(value), "Slot value wasn't a Number.");
return AS_NUM(value);
}
const char* pkGetSlotString(PKVM* vm, int index, uint32_t* length) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
Var value = SLOT(index);
ASSERT(IS_OBJ_TYPE(value, OBJ_STRING), "Slot value wasn't a String.");
if (length != NULL) *length = ((String*)AS_OBJ(value))->length;
return ((String*)AS_OBJ(value))->data;
}
PkHandle* pkGetSlotHandle(PKVM* vm, int index) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
return vmNewHandle(vm, SLOT(index));
}
void pkSetSlotNull(PKVM* vm, int index) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
SET_SLOT(index, VAR_NULL);
}
void pkSetSlotBool(PKVM* vm, int index, bool value) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
SET_SLOT(index, VAR_BOOL(value));
}
void pkSetSlotNumber(PKVM* vm, int index, double value) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
SET_SLOT(index, VAR_NUM(value));
}
void pkSetSlotString(PKVM* vm, int index, const char* value) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
SET_SLOT(index, VAR_OBJ(newString(vm, value)));
}
PK_PUBLIC void pkSetSlotStringLength(PKVM* vm, int index,
const char* value, uint32_t length) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
SET_SLOT(index, VAR_OBJ(newStringLength(vm, value, length)));
}
void PkSetSlotHandle(PKVM* vm, int index, PkHandle* handle) {
CHECK_RUNTIME();
VALIDATE_SLOT_INDEX(index);
SET_SLOT(index, handle->value);
}
#undef CHECK_RUNTIME
#undef VALIDATE_ARGC
#undef ERR_INVALID_ARG_TYPE
#undef ARG
#undef SLOT
#undef SET_SLOT
#undef ARGC
#undef CHECK_NULL
#undef CHECK_TYPE
/*****************************************************************************/
/* INTERNAL */
/*****************************************************************************/
// The default allocator that will be used to initialize the vm's configuration
// if the host doesn't provided any allocators for us.
static void* defaultRealloc(void* memory, size_t new_size, void* _) {
if (new_size == 0) {
free(memory);
return NULL;
}
return realloc(memory, new_size);
}

View File

@ -12,73 +12,6 @@
#include "pk_utils.h"
#include "pk_vm.h"
/*****************************************************************************/
/* VAR PUBLIC API */
/*****************************************************************************/
PkVarType pkGetValueType(const PkVar value) {
__ASSERT(value != NULL, "Given value was NULL.");
const Var value_ = *(const Var*)(value);
if (IS_NULL(value_)) return PK_NULL;
if (IS_BOOL(value_)) return PK_BOOL;
if (IS_NUM(value_)) return PK_NUMBER;
ASSERT(IS_OBJ(*(const Var*)(value)),
"Invalid var pointer (Might be a dangling pointer).");
const Object* obj = AS_OBJ(value_);
return getObjPkVarType(obj->type);
}
PkHandle* pkNewString(PKVM* vm, const char* value) {
String* str = newString(vm, value);
vmPushTempRef(vm, &str->_super); // str
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(str));
vmPopTempRef(vm); // str
return handle;
}
PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len) {
String* str = newStringLength(vm, value, (uint32_t)len);
vmPushTempRef(vm, &str->_super); // str
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(str));
vmPopTempRef(vm); // str
return handle;
}
PkHandle* pkNewList(PKVM* vm) {
List* list = newList(vm, MIN_CAPACITY);
vmPushTempRef(vm, &list->_super); // list
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(list));
vmPopTempRef(vm); // list
return handle;
}
PkHandle* pkNewMap(PKVM* vm) {
Map* map = newMap(vm);
vmPushTempRef(vm, &map->_super); // map
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(map));
vmPopTempRef(vm); // map
return handle;
}
PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) {
__ASSERT(IS_OBJ_TYPE(fn->value, OBJ_CLOSURE),
"Handle should be of type function.");
Fiber* fiber = newFiber(vm, (Closure*)AS_OBJ(fn->value));
vmPushTempRef(vm, &fiber->_super); // fiber
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(fiber));
vmPopTempRef(vm); // fiber
return handle;
}
/*****************************************************************************/
/* VAR INTERNALS */
/*****************************************************************************/
// The maximum percentage of the map entries that can be filled before the map
// is grown. A lower percentage reduce collision which makes looks up faster
// but take more memory.
@ -270,6 +203,8 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) {
markObject(vm, &fiber->caller->_super);
markObject(vm, &fiber->error->_super);
markValue(vm, fiber->self);
} break;
case OBJ_CLASS:

View File

@ -455,6 +455,11 @@ struct Fiber {
// The stack pointer (%rsp) pointing to the stack top.
Var* sp;
// Heap allocated array of call frames will grow as needed.
CallFrame* frames;
int frame_capacity; //< Capacity of the frames array.
int frame_count; //< Number of frame entry in frames.
// All the open upvalues will form a linked list in the fiber and the
// upvalues are sorted in the same order their locals in the stack. The
// bellow pointer is the head of those upvalues near the stack top.
@ -473,11 +478,6 @@ struct Fiber {
// and reset to VAR_UNDEFINED.
Var self;
// Heap allocated array of call frames will grow as needed.
CallFrame* frames;
int frame_capacity; //< Capacity of the frames array.
int frame_count; //< Number of frame entry in frames.
// Caller of this fiber if it has one, NULL otherwise.
Fiber* caller;

View File

@ -7,205 +7,9 @@
#include "pk_vm.h"
#include <math.h>
#include "pk_core.h"
#include "pk_utils.h"
#include "pk_debug.h"
/*****************************************************************************/
/* VM PUBLIC API */
/*****************************************************************************/
// The default allocator that will be used to initialize the vm's configuration
// 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(void) {
PkConfiguration config;
config.realloc_fn = defaultRealloc;
config.stdout_write = NULL;
config.stderr_write = NULL;
config.stdin_read = NULL;
config.load_script_fn = NULL;
config.resolve_path_fn = NULL;
config.use_ansi_color = false;
config.user_data = NULL;
return config;
}
PkCompileOptions pkNewCompilerOptions(void) {
PkCompileOptions options;
options.debug = false;
options.repl_mode = false;
return options;
}
PKVM* pkNewVM(PkConfiguration* config) {
PkConfiguration default_config = pkNewConfiguration();
if (config == NULL) config = &default_config;
PKVM* vm = (PKVM*)config->realloc_fn(NULL, sizeof(PKVM), config->user_data);
memset(vm, 0, sizeof(PKVM));
vm->config = *config;
vm->working_set_count = 0;
vm->working_set_capacity = MIN_CAPACITY;
vm->working_set = (Object**)vm->config.realloc_fn(
NULL, sizeof(Object*) * vm->working_set_capacity, NULL);
vm->next_gc = INITIAL_GC_SIZE;
vm->min_heap_size = MIN_HEAP_SIZE;
vm->heap_fill_percent = HEAP_FILL_PERCENT;
vm->modules = newMap(vm);
vm->builtins_count = 0;
// This is necessary to prevent garbage collection skip the entry in this
// array while we're building it.
for (int i = 0; i < PK_INSTANCE; i++) {
vm->builtin_classes[i] = NULL;
}
initializeCore(vm);
return vm;
}
void pkFreeVM(PKVM* vm) {
Object* obj = vm->first;
while (obj != NULL) {
Object* next = obj->next;
freeObject(vm, obj);
obj = next;
}
vm->working_set = (Object**)vm->config.realloc_fn(
vm->working_set, 0, vm->config.user_data);
// Tell the host application that it forget to release all of it's handles
// before freeing the VM.
__ASSERT(vm->handles == NULL, "Not all handles were released.");
DEALLOCATE(vm, vm);
}
void* pkGetUserData(const PKVM* vm) {
return vm->config.user_data;
}
void pkSetUserData(PKVM* vm, void* user_data) {
vm->config.user_data = user_data;
}
PkHandle* pkNewHandle(PKVM* vm, PkVar value) {
return vmNewHandle(vm, *((Var*)value));
}
PkVar pkGetHandleValue(const PkHandle* handle) {
return (PkVar)&handle->value;
}
void pkReleaseHandle(PKVM* vm, PkHandle* handle) {
__ASSERT(handle != NULL, "Given handle was NULL.");
// If the handle is the head of the vm's handle chain set it to the next one.
if (handle == vm->handles) {
vm->handles = handle->next;
}
// Remove the handle from the chain by connecting the both ends together.
if (handle->next) handle->next->prev = handle->prev;
if (handle->prev) handle->prev->next = handle->next;
// Free the 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_ = newString(vm, path.string);
if (path.on_done) path.on_done(vm, path);
vmPushTempRef(vm, &path_->_super); // path_
// FIXME:
// Should I clean the module if it already exists before compiling it?
// Load a new module to the vm's modules cache.
Module* module = vmGetModule(vm, path_);
if (module == NULL) {
module = newModule(vm);
module->path = path_;
vmPushTempRef(vm, &module->_super); // module.
vmRegisterModule(vm, module, path_);
vmPopTempRef(vm); // module.
}
vmPopTempRef(vm); // path_
// Compile the source.
PkResult result = compile(vm, module, source.string, options);
if (source.on_done) source.on_done(vm, source);
if (result != PK_RESULT_SUCCESS) return result;
// Set module initialized to true before the execution ends to prevent cyclic
// inclusion cause a crash.
module->initialized = true;
return runFiber(vm, newFiber(vm, module->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_SET_ERROR(vm, newString(vm, message));
}
/*****************************************************************************/
/* SHARED FUNCTIONS */
/*****************************************************************************/
PkHandle* vmNewHandle(PKVM* vm, Var value) {
PkHandle* handle = (PkHandle*)ALLOCATE(vm, PkHandle);
handle->value = value;
@ -449,16 +253,6 @@ void vmYieldFiber(PKVM* vm, Var* value) {
/* VM INTERNALS */
/*****************************************************************************/
// The default allocator that will be used to initialize the vm's configuration
// if the host doesn't provided any allocators for us.
static void* defaultRealloc(void* memory, size_t new_size, void* user_data) {
if (new_size == 0) {
free(memory);
return NULL;
}
return realloc(memory, new_size);
}
// FIXME:
// We're assuming that the module should be available at the VM's modules cache
// which is added by the compilation pahse, but we cannot rely on the
@ -486,9 +280,11 @@ static inline Var importModule(PKVM* vm, String* key) {
return VAR_NULL;
}
static inline void growStack(PKVM* vm, int size) {
void vmEnsureStackSize(PKVM* vm, int size) {
Fiber* fiber = vm->fiber;
ASSERT(fiber->stack_size <= size, OOPS);
if (fiber->stack_size > size) return;
int new_size = utilPowerOf2Ceil(size);
Var* old_rbp = fiber->stack; //< Old stack base pointer.
@ -546,7 +342,7 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) {
// Grow the stack if needed.
int needed = (closure->fn->fn->stack_size +
(int)(vm->fiber->sp - vm->fiber->stack));
if (vm->fiber->stack_size <= needed) growStack(vm, needed);
vmEnsureStackSize(vm, needed);
CallFrame* frame = vm->fiber->frames + vm->fiber->frame_count++;
frame->rbp = rbp;
@ -589,7 +385,7 @@ static inline void reuseCallFrame(PKVM* vm, const Closure* closure) {
// Grow the stack if needed (least probably).
int needed = (closure->fn->fn->stack_size +
(int)(vm->fiber->sp - vm->fiber->stack));
if (vm->fiber->stack_size <= needed) growStack(vm, needed);
vmEnsureStackSize(vm, needed);
}
// Capture the [local] into an upvalue and return it. If the upvalue already
@ -691,7 +487,7 @@ static void reportError(PKVM* vm) {
* RUNTIME *
*****************************************************************************/
static PkResult runFiber(PKVM* vm, Fiber* fiber_) {
PkResult vmRunFiber(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.

View File

@ -8,8 +8,7 @@
#define PK_VM_H
#include "pk_compiler.h"
#include "pk_internal.h"
#include "pk_value.h"
#include "pk_core.h"
// The maximum number of temporary object reference to protect them from being
// garbage collected.
@ -140,6 +139,10 @@ void* vmRealloc(PKVM* vm, void* memory, size_t old_size, size_t new_size);
// Create and return a new handle for the [value].
PkHandle* vmNewHandle(PKVM* vm, Var value);
// If the stack size is less than [size], the stack will grow to keep more
// values on it.
void vmEnsureStackSize(PKVM* vm, int size);
// Trigger garbage collection. This is an implementation of mark and sweep
// garbage collection (https://en.wikipedia.org/wiki/Tracing_garbage_collection).
//
@ -214,4 +217,8 @@ bool vmSwitchFiber(PKVM* vm, Fiber* fiber, Var* value);
// yield value.
void vmYieldFiber(PKVM* vm, Var* value);
// Runs the [fiber] if it's at yielded state, this will resume the execution
// till the next yield or return statement, and return result.
PkResult vmRunFiber(PKVM* vm, Fiber* fiber);
#endif // PK_VM_H

View File

@ -24,12 +24,12 @@ static void cFunction(PKVM* vm) {
// Get the parameter from pocket VM.
double a;
if (!pkGetArgNumber(vm, 1, &a)) return;
if (!pkValidateSlotNumber(vm, 1, &a)) return;
printf("[C] a = %f\n", a);
// Return value to the pocket VM.
pkReturnNumber(vm, 3.14);
pkSetSlotNumber(vm, 0, 3.14);
}
/*****************************************************************************/