Merge pull request #208 from ThakeeNathees/slots

Native interface refactored into slots.
This commit is contained in:
Thakee Nathees 2022-04-24 10:05:53 +05:30 committed by GitHub
commit 95c318b6f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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);
}
/*****************************************************************************/