mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-05 20:26:53 +08:00
Merge pull request #208 from ThakeeNathees/slots
Native interface refactored into slots.
This commit is contained in:
commit
95c318b6f7
@ -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;
|
||||
|
@ -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.");
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -7,7 +7,6 @@
|
||||
#ifndef PK_COMPILER_H
|
||||
#define PK_COMPILER_H
|
||||
|
||||
#include "pk_internal.h"
|
||||
#include "pk_value.h"
|
||||
|
||||
typedef enum {
|
||||
|
313
src/pk_core.c
313
src/pk_core.c
@ -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);
|
||||
|
@ -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 */
|
||||
/*****************************************************************************/
|
||||
|
@ -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
553
src/pk_public.c
Normal 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);
|
||||
}
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
218
src/pk_vm.c
218
src/pk_vm.c
@ -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.
|
||||
|
11
src/pk_vm.h
11
src/pk_vm.h
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
Loading…
Reference in New Issue
Block a user