diff --git a/cli/all.c b/cli/all.c index 22e0d50..294fd44 100644 --- a/cli/all.c +++ b/cli/all.c @@ -13,10 +13,9 @@ /* STD MODULE SOURCES */ /*****************************************************************************/ -#include "modules/modules.c" - -#include "modules/std_file.c" +#include "modules/std_io.c" #include "modules/std_path.c" +#include "modules/std_math.c" /*****************************************************************************/ /* THIRDPARTY SOURCES */ diff --git a/cli/main.c b/cli/main.c index 1c69f74..09ff9e4 100644 --- a/cli/main.c +++ b/cli/main.c @@ -129,11 +129,6 @@ static PKVM* intializePocketVM() { config.write_fn = writeFunction; config.read_fn = readFunction; - config.inst_free_fn = freeObj; - config.inst_name_fn = getObjName; - config.inst_get_attrib_fn = objGetAttrib; - config.inst_set_attrib_fn = objSetAttrib; - config.load_script_fn = loadScript; config.resolve_path_fn = resolvePath; @@ -193,7 +188,7 @@ int main(int argc, const char** argv) { user_data.repl_mode = false; pkSetUserData(vm, &user_data); - registerModules(vm); + REGISTER_ALL_MODULES(vm); PkCompileOptions options = pkNewCompilerOptions(); options.debug = debug; diff --git a/cli/modules/modules.c b/cli/modules/modules.c deleted file mode 100644 index 9ff605b..0000000 --- a/cli/modules/modules.c +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2020-2022 Thakee Nathees - * Copyright (c) 2021-2022 Pocketlang Contributors - * Distributed Under The MIT License - */ - -#include "modules.h" - -// Note: Everything here is for testing the native API, and will have to -// refactor everything. - -// Allocate a new module object of type [Ty]. -#define NEW_OBJ(Ty) (Ty*)malloc(sizeof(Ty)) - -// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj -// callback. -#define FREE_OBJ(ptr) free(ptr) - -/*****************************************************************************/ -/* MODULE FUNCTIONS DECLARATION */ -/*****************************************************************************/ - -void fileGetAttrib(PKVM* vm, File* file, const char* attrib); -bool fileSetAttrib(PKVM* vm, File* file, const char* attrib); -void fileClean(PKVM* vm, File* file); - -void registerModuleFile(PKVM* vm); -void registerModulePath(PKVM* vm); - -/*****************************************************************************/ -/* MODULE PUBLIC FUNCTIONS */ -/*****************************************************************************/ - -void initObj(Obj* obj, ObjType type) { - obj->type = type; -} - -void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { - Obj* obj = (Obj*)instance; - ASSERT(obj->type == (ObjType)id, OOPS); - - switch (obj->type) { - case OBJ_FILE: - fileGetAttrib(vm, (File*)obj, attrib.string); - return; - } - STATIC_ASSERT(_OBJ_MAX_ == 2); -} - -bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { - Obj* obj = (Obj*)instance; - ASSERT(obj->type == (ObjType)id, OOPS); - - switch (obj->type) { - case OBJ_FILE: - return fileSetAttrib(vm, (File*)obj, attrib.string); - } - STATIC_ASSERT(_OBJ_MAX_ == 2); - - return false; -} - -void freeObj(PKVM* vm, void* instance, uint32_t id) { - Obj* obj = (Obj*)instance; - ASSERT(obj->type == (ObjType)id, OOPS); - - switch (obj->type) { - case OBJ_FILE: - fileClean(vm, (File*)obj); - } - STATIC_ASSERT(_OBJ_MAX_ == 2); - - FREE_OBJ(obj); -} - -const char* getObjName(uint32_t id) { - switch ((ObjType)id) { - case OBJ_FILE: return "File"; - } - STATIC_ASSERT(_OBJ_MAX_ == 2); - return NULL; -} - -/*****************************************************************************/ -/* REGISTER MODULES */ -/*****************************************************************************/ - -void registerModules(PKVM* vm) { - registerModuleFile(vm); - registerModulePath(vm); -} diff --git a/cli/modules/modules.h b/cli/modules/modules.h index 842feb2..0088f4a 100644 --- a/cli/modules/modules.h +++ b/cli/modules/modules.h @@ -14,73 +14,38 @@ #include #include -/*****************************************************************************/ -/* MODULE OBJECTS */ -/*****************************************************************************/ - -// Str | If already exists | If does not exist | -// -----+-------------------+-------------------| -// 'r' | read from start | failure to open | -// 'w' | destroy contents | create new | -// 'a' | write to end | create new | -// 'r+' | read from start | error | -// 'w+' | destroy contents | create new | -// 'a+' | write to end | create new | -typedef enum { - FMODE_READ = (1 << 0), - FMODE_WRITE = (1 << 1), - FMODE_APPEND = (1 << 2), - _FMODE_EXT = (1 << 3), - FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ), - FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE), - FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND), -} FileAccessMode; - -typedef enum { - OBJ_FILE = 1, - - _OBJ_MAX_ -} ObjType; - -typedef struct { - ObjType type; -} Obj; - -typedef struct { - Obj _super; - FILE* fp; // C file poinnter. - FileAccessMode mode; // Access mode of the file. - bool closed; // True if the file isn't closed yet. -} File; - -/*****************************************************************************/ -/* MODULE PUBLIC FUNCTIONS */ -/*****************************************************************************/ - -// Initialize the native module object with it's default values. -void initObj(Obj* obj, ObjType type); - -// A function callback called by pocket VM to get attribute of a native -// instance. The value of the attributes will be returned with pkReturn...() -// functions and if the attribute doesn't exists on the instance we're -// shouldn't return anything, PKVM will know it and set error (or use some -// common attributes like "as_string", "as_repr", etc). -void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib); - -// A function callback called by pocket VM to set attribute of a native -// instance. -bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib); - -// The free callback of the object, that'll called by pocketlang when a -// pocketlang native instance garbage collected. -void freeObj(PKVM* vm, void* instance, uint32_t id); - -// The native instance get_name callback used to get the name of a native -// instance from pocketlang. Here the id we're using is the ObjType enum. -const char* getObjName(uint32_t id); +void registerModuleIO(PKVM* vm); +void registerModulePath(PKVM* vm); +void registerModuleMath(PKVM* vm); // Registers all the cli modules. -void registerModules(PKVM* vm); +#define REGISTER_ALL_MODULES(vm) \ + do { \ + registerModuleIO(vm); \ + registerModulePath(vm); \ + registerModuleMath(vm); \ + } while (false) + +/*****************************************************************************/ +/* MODULES INTERNAL */ +/*****************************************************************************/ + +// Allocate a new module object of type [Ty]. +#define NEW_OBJ(Ty) (Ty*)malloc(sizeof(Ty)) + +// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj +// callback. +#define FREE_OBJ(ptr) free(ptr) + +// Returns the docstring of the function, which is a static const char* defined +// just above the function by the DEF() macro below. +#define DOCSTRING(fn) __doc_##fn + +// A macro to declare a function, with docstring, which is defined as +// ___doc_ = docstring; That'll used to generate function help text. +#define DEF(fn, docstring) \ + static const char* DOCSTRING(fn) = docstring; \ + static void fn(PKVM* vm) /*****************************************************************************/ /* SHARED FUNCTIONS */ diff --git a/cli/modules/std_file.c b/cli/modules/std_io.c similarity index 52% rename from cli/modules/std_file.c rename to cli/modules/std_io.c index 7401177..84030c5 100644 --- a/cli/modules/std_file.c +++ b/cli/modules/std_io.c @@ -7,32 +7,56 @@ #include "modules.h" /*****************************************************************************/ -/* FILE OBJECT OPERATORS */ +/* FILE CLASS */ /*****************************************************************************/ -void fileGetAttrib(PKVM* vm, File* file, const char* attrib) { - if (strcmp(attrib, "closed") == 0) { - pkReturnBool(vm, file->closed); - return; - } + // Str | If already exists | If does not exist | + // -----+-------------------+-------------------| + // 'r' | read from start | failure to open | + // 'w' | destroy contents | create new | + // 'a' | write to end | create new | + // 'r+' | read from start | error | + // 'w+' | destroy contents | create new | + // 'a+' | write to end | create new | +typedef enum { + FMODE_NONE = 0, + FMODE_READ = (1 << 0), + FMODE_WRITE = (1 << 1), + FMODE_APPEND = (1 << 2), + _FMODE_EXT = (1 << 3), + FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ), + FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE), + FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND), +} FileAccessMode; + +typedef struct { + FILE* fp; // C file poinnter. + FileAccessMode mode; // Access mode of the file. + bool closed; // True if the file isn't closed yet. +} File; + +void* _newFile() { + File* file = NEW_OBJ(File); + file->closed = true; + file->mode = FMODE_NONE; + file->fp = NULL; + return file; } -bool fileSetAttrib(PKVM* vm, File* file, const char* attrib) { - return false; -} - -void fileClean(PKVM* vm, File* file) { +void _deleteFile(void* ptr) { + File* file = (File*)ptr; if (!file->closed) { if (fclose(file->fp) != 0) { /* TODO: error! */ } file->closed = true; } + FREE_OBJ(file); } /*****************************************************************************/ /* FILE MODULE FUNCTIONS */ /*****************************************************************************/ -static void _fileOpen(PKVM* vm) { +DEF(_fileOpen, "") { int argc = pkGetArgc(vm); if (!pkCheckArgcRange(vm, argc, 1, 2)) return; @@ -62,25 +86,29 @@ static void _fileOpen(PKVM* vm) { } while (false); } + // This TODO is just a blockade from running the bellow code, complete the + // native interface and test before removing it. + TODO; + FILE* fp = fopen(path, mode_str); if (fp != NULL) { - File* file = NEW_OBJ(File); - initObj(&file->_super, OBJ_FILE); - file->fp = fp; - file->mode = mode; - file->closed = false; - - pkReturnInstNative(vm, (void*)file, OBJ_FILE); + File* self = (File*)pkGetSelf(vm); + self->fp = fp; + self->mode = mode; + self->closed = false; } else { - pkReturnNull(vm); + pkSetRuntimeError(vm, "Error opening the file."); } } -static void _fileRead(PKVM* vm) { - File* file; - if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; +DEF(_fileRead, "") { + // This TODO is just a blockade from running the bellow code, complete the + // native interface and test before removing it. + TODO; + + File* file = (File*)pkGetSelf(vm); if (file->closed) { pkSetRuntimeError(vm, "Cannot read from a closed file."); @@ -98,11 +126,14 @@ static void _fileRead(PKVM* vm) { pkReturnString(vm, (const char*)buff); } -static void _fileWrite(PKVM* vm) { - File* file; +DEF(_fileWrite, "") { + // This TODO is just a blockade from running the bellow code, complete the + // native interface and test before removing it. + TODO; + + File* file = (File*)pkGetSelf(vm); const char* text; uint32_t length; - if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; - if (!pkGetArgString(vm, 2, &text, &length)) return; + if (!pkGetArgString(vm, 1, &text, &length)) return; if (file->closed) { pkSetRuntimeError(vm, "Cannot write to a closed file."); @@ -117,9 +148,12 @@ static void _fileWrite(PKVM* vm) { fwrite(text, sizeof(char), (size_t)length, file->fp); } -static void _fileClose(PKVM* vm) { - File* file; - if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; +DEF(_fileClose, "") { + // This TODO is just a blockade from running the bellow code, complete the + // native interface and test before removing it. + TODO; + + File* file = (File*)pkGetSelf(vm); if (file->closed) { pkSetRuntimeError(vm, "File already closed."); @@ -133,14 +167,16 @@ static void _fileClose(PKVM* vm) { file->closed = true; } -void registerModuleFile(PKVM* vm) { - PkHandle* file = pkNewModule(vm, "File"); +void registerModuleIO(PKVM* vm) { + PkHandle* io = pkNewModule(vm, "io"); - pkModuleAddFunction(vm, file, "open", _fileOpen, -1); - pkModuleAddFunction(vm, file, "read", _fileRead, 1); - pkModuleAddFunction(vm, file, "write", _fileWrite, 2); - pkModuleAddFunction(vm, file, "close", _fileClose, 1); + PkHandle* cls_file = pkNewClass(vm, "File", NULL, io, _newFile, _deleteFile); + pkClassAddMethod(vm, cls_file, "open", _fileOpen, -1); + pkClassAddMethod(vm, cls_file, "read", _fileRead, 0); + pkClassAddMethod(vm, cls_file, "write", _fileWrite, 1); + pkClassAddMethod(vm, cls_file, "close", _fileClose, 0); + pkReleaseHandle(vm, cls_file); - pkRegisterModule(vm, file); - pkReleaseHandle(vm, file); + pkRegisterModule(vm, io); + pkReleaseHandle(vm, io); } diff --git a/cli/modules/std_math.c b/cli/modules/std_math.c new file mode 100644 index 0000000..d27ae20 --- /dev/null +++ b/cli/modules/std_math.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2020-2022 Thakee Nathees + * Copyright (c) 2021-2022 Pocketlang Contributors + * Distributed Under The MIT License + */ + +#include "modules.h" + +#include + +// M_PI is non standard. The macro _USE_MATH_DEFINES defining before importing +// will define the constants for MSVC. But for a portable solution, +// we're defining it ourselves if it isn't already. +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +DEF(stdMathFloor, + "floor(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, floor(num)); +} + +DEF(stdMathCeil, + "ceil(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, 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)); +} + +DEF(stdMathSqrt, + "sqrt(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, sqrt(num)); +} + +DEF(stdMathAbs, + "abs(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + if (num < 0) num = -num; + pkReturnNumber(vm, num); +} + +DEF(stdMathSign, + "sign(value:num) -> num\n") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + if (num < 0) num = -1; + else if (num > 0) num = +1; + else num = 0; + pkReturnNumber(vm, num); +} + +DEF(stdMathSine, + "sin(rad:num) -> num\n" + "Return the sine value of the argument [rad] which is an angle expressed " + "in radians.") { + + double rad; + if (!pkGetArgNumber(vm, 1, &rad)) return; + pkReturnNumber(vm, sin(rad)); +} + +DEF(stdMathCosine, + "cos(rad:num) -> num\n" + "Return the cosine value of the argument [rad] which is an angle expressed " + "in radians.") { + + double rad; + if (!pkGetArgNumber(vm, 1, &rad)) return; + pkReturnNumber(vm, cos(rad)); +} + +DEF(stdMathTangent, + "tan(rad:num) -> num\n" + "Return the tangent value of the argument [rad] which is an angle expressed " + "in radians.") { + + double rad; + if (!pkGetArgNumber(vm, 1, &rad)) return; + pkReturnNumber(vm, tan(rad)); +} + +DEF(stdMathSinh, + "sinh(val) -> val\n" + "Return the hyperbolic sine value of the argument [val].") { + + double val; + if (!pkGetArgNumber(vm, 1, &val)) return; + pkReturnNumber(vm, sinh(val)); +} + +DEF(stdMathCosh, + "cosh(val) -> val\n" + "Return the hyperbolic cosine value of the argument [val].") { + + double val; + if (!pkGetArgNumber(vm, 1, &val)) return; + pkReturnNumber(vm, cosh(val)); +} + +DEF(stdMathTanh, + "tanh(val) -> val\n" + "Return the hyperbolic tangent value of the argument [val].") { + + double val; + if (!pkGetArgNumber(vm, 1, &val)) return; + pkReturnNumber(vm, tanh(val)); +} + +DEF(stdMathArcSine, + "asin(num) -> num\n" + "Return the arcsine value of the argument [num] which is an angle " + "expressed in radians.") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + + if (num < -1 || 1 < num) { + pkSetRuntimeError(vm, "Argument should be between -1 and +1"); + } + + pkReturnNumber(vm, asin(num)); +} + +DEF(stdMathArcCosine, + "acos(num) -> num\n" + "Return the arc cosine value of the argument [num] which is " + "an angle expressed in radians.") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + + if (num < -1 || 1 < num) { + pkSetRuntimeError(vm, "Argument should be between -1 and +1"); + } + + pkReturnNumber(vm, acos(num)); +} + +DEF(stdMathArcTangent, + "atan(num) -> num\n" + "Return the arc tangent value of the argument [num] which is " + "an angle expressed in radians.") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, atan(num)); +} + +DEF(stdMathLog10, + "log10(value:num) -> num\n" + "Return the logarithm to base 10 of argument [value]") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, log10(num)); +} + +DEF(stdMathRound, + "round(value:num) -> num\n" + "Round to nearest integer, away from zero and return the number.") { + + double num; + if (!pkGetArgNumber(vm, 1, &num)) return; + pkReturnNumber(vm, round(num)); +} + +void registerModuleMath(PKVM* vm) { + + PkHandle* math = pkNewModule(vm, "math"); + + pkModuleAddFunction(vm, math, "floor", stdMathFloor, 1); + pkModuleAddFunction(vm, math, "ceil", stdMathCeil, 1); + pkModuleAddFunction(vm, math, "pow", stdMathPow, 2); + pkModuleAddFunction(vm, math, "sqrt", stdMathSqrt, 1); + pkModuleAddFunction(vm, math, "abs", stdMathAbs, 1); + pkModuleAddFunction(vm, math, "sign", stdMathSign, 1); + pkModuleAddFunction(vm, math, "sin", stdMathSine, 1); + pkModuleAddFunction(vm, math, "cos", stdMathCosine, 1); + pkModuleAddFunction(vm, math, "tan", stdMathTangent, 1); + pkModuleAddFunction(vm, math, "sinh", stdMathSinh, 1); + pkModuleAddFunction(vm, math, "cosh", stdMathCosh, 1); + pkModuleAddFunction(vm, math, "tanh", stdMathTanh, 1); + pkModuleAddFunction(vm, math, "asin", stdMathArcSine, 1); + pkModuleAddFunction(vm, math, "acos", stdMathArcCosine, 1); + pkModuleAddFunction(vm, math, "atan", stdMathArcTangent, 1); + pkModuleAddFunction(vm, math, "log10", stdMathLog10, 1); + pkModuleAddFunction(vm, math, "round", stdMathRound, 1); + + // FIXME: + // Refactor native type interface and add PI as a global to the module. + // + // Note that currently it's mutable (since it's a global variable, not + // constant and pocketlang doesn't support constant) so the user shouldn't + // modify the PI, like in python. + //pkModuleAddGlobal(vm, math, "PI", Handle-Of-PI); + + pkRegisterModule(vm, math); + pkReleaseHandle(vm, math); +} diff --git a/cli/modules/std_path.c b/cli/modules/std_path.c index 52e16ba..4f62a4c 100644 --- a/cli/modules/std_path.c +++ b/cli/modules/std_path.c @@ -4,6 +4,8 @@ * Distributed Under The MIT License */ +#include "modules.h" + #include "thirdparty/cwalk/cwalk.h" #if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) #include "thirdparty/dirent/dirent.h" @@ -90,13 +92,13 @@ static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) { /* PATH MODULE FUNCTIONS */ /*****************************************************************************/ -static void _pathSetStyleUnix(PKVM* vm) { +DEF(_pathSetStyleUnix, "") { bool value; if (!pkGetArgBool(vm, 1, &value)) return; cwk_path_set_style((value) ? CWK_STYLE_UNIX : CWK_STYLE_WINDOWS); } -static void _pathGetCWD(PKVM* vm) { +DEF(_pathGetCWD, "") { char cwd[FILENAME_MAX]; if (get_cwd(cwd, sizeof(cwd)) == NULL) { // TODO: Handle error. @@ -104,7 +106,7 @@ static void _pathGetCWD(PKVM* vm) { pkReturnString(vm, cwd); } -static void _pathAbspath(PKVM* vm) { +DEF(_pathAbspath, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -113,7 +115,7 @@ static void _pathAbspath(PKVM* vm) { pkReturnStringLength(vm, abspath, len); } -static void _pathRelpath(PKVM* vm) { +DEF(_pathRelpath, "") { const char* from, * path; if (!pkGetArgString(vm, 1, &from, NULL)) return; if (!pkGetArgString(vm, 2, &path, NULL)) return; @@ -130,7 +132,7 @@ static void _pathRelpath(PKVM* vm) { pkReturnStringLength(vm, result, len); } -static void _pathJoin(PKVM* vm) { +DEF(_pathJoin, "") { const char* paths[MAX_JOIN_PATHS + 1]; // +1 for NULL. int argc = pkGetArgc(vm); @@ -150,7 +152,7 @@ static void _pathJoin(PKVM* vm) { pkReturnStringLength(vm, result, len); } -static void _pathNormalize(PKVM* vm) { +DEF(_pathNormalize, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -159,7 +161,7 @@ static void _pathNormalize(PKVM* vm) { pkReturnStringLength(vm, result, len); } -static void _pathBaseName(PKVM* vm) { +DEF(_pathBaseName, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -169,7 +171,7 @@ static void _pathBaseName(PKVM* vm) { pkReturnString(vm, base_name); } -static void _pathDirName(PKVM* vm) { +DEF(_pathDirName, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -178,14 +180,14 @@ static void _pathDirName(PKVM* vm) { pkReturnStringLength(vm, path, length); } -static void _pathIsPathAbs(PKVM* vm) { +DEF(_pathIsPathAbs, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; pkReturnBool(vm, cwk_path_is_absolute(path)); } -static void _pathGetExtension(PKVM* vm) { +DEF(_pathGetExtension, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; @@ -198,19 +200,19 @@ static void _pathGetExtension(PKVM* vm) { } } -static void _pathExists(PKVM* vm) { +DEF(_pathExists, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; pkReturnBool(vm, pathIsExists(path)); } -static void _pathIsFile(PKVM* vm) { +DEF(_pathIsFile, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; pkReturnBool(vm, pathIsFileExists(path)); } -static void _pathIsDir(PKVM* vm) { +DEF(_pathIsDir, "") { const char* path; if (!pkGetArgString(vm, 1, &path, NULL)) return; pkReturnBool(vm, pathIsDirectoryExists(path)); diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index da92a03..10690df 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -55,8 +55,9 @@ extern "C" { #define PK_PUBLIC #endif + /*****************************************************************************/ -/* POCKETLANG TYPES */ +/* POCKETLANG TYPEDEFS & CALLBACKS */ /*****************************************************************************/ // PocketLang Virtual Machine. It'll contain the state of the execution, stack, @@ -74,57 +75,13 @@ typedef struct PkHandle PkHandle; // alive use `pkNewHandle()`. typedef void* PkVar; -// Type enum of the pocketlang's first class types. Note that Object isn't -// instanciable (as of now) but they're considered first calss. -typedef enum { - PK_OBJECT = 0, - - PK_NULL, - PK_BOOL, - PK_NUMBER, - PK_STRING, - PK_LIST, - PK_MAP, - PK_RANGE, - PK_MODULE, - PK_CLOSURE, - PK_FIBER, - PK_CLASS, - PK_INSTANCE, -} PkVarType; - +typedef enum PkVarType PkVarType; +typedef enum PkErrorType PkErrorType; +typedef enum PkResult PkResult; typedef struct PkStringPtr PkStringPtr; typedef struct PkConfiguration PkConfiguration; typedef struct PkCompileOptions PkCompileOptions; -// Type of the error message that pocketlang will provide with the pkErrorFn -// callback. -typedef enum { - PK_ERROR_COMPILE = 0, // Compile time errors. - PK_ERROR_RUNTIME, // Runtime error message. - PK_ERROR_STACKTRACE, // One entry of a runtime error stack. -} PkErrorType; - -// Result that pocketlang will return after a compilation or running a script -// or a function or evaluating an expression. -typedef enum { - PK_RESULT_SUCCESS = 0, // Successfully finished the execution. - - // Unexpected EOF while compiling the source. This is another compile time - // error that will ONLY be returned if we're compiling with the REPL mode set - // in the compile options. We need this specific error to indicate the host - // application to add another line to the last input. If REPL is not enabled, - // this will be PK_RESULT_COMPILE_ERROR. - PK_RESULT_UNEXPECTED_EOF, - - PK_RESULT_COMPILE_ERROR, // Compilation failed. - PK_RESULT_RUNTIME_ERROR, // An error occurred at runtime. -} PkResult; - -/*****************************************************************************/ -/* POCKETLANG FUNCTION POINTERS & CALLBACKS */ -/*****************************************************************************/ - // C function pointer which is callable from pocketLang by native module // functions. typedef void (*pkNativeFn)(PKVM* vm); @@ -155,35 +112,6 @@ typedef void (*pkWriteFn) (PKVM* vm, const char* text); // contain a line ending (\n or \r\n). typedef PkStringPtr (*pkReadFn) (PKVM* vm); -// A function callback, that'll be called when a native instance (wrapper) is -// freed by by the garbage collector, to indicate that pocketlang is done with -// the native instance. -typedef void (*pkInstFreeFn) (PKVM* vm, void* instance, uint32_t id); - -// A function callback to get the type name of the native instance from -// pocketlang, using it's [id]. The returned string won't be copied by -// pocketlang so it's expected to be alived since the instance is alive and -// recomended to return a C literal string. -typedef const char* (*pkInstNameFn) (uint32_t id); - -// A get arribute callback, called by pocket VM when trying to get an attribute -// from a native type. to return the value of the attribute use 'pkReturn...()' -// functions. DON'T set an error to the VM if the attribute not exists. Example -// if the '.as_string' attribute doesn't exists, pocket VM will use a default -// to string value. -typedef void (*pkInstGetAttribFn) (PKVM* vm, void* instance, uint32_t id, - PkStringPtr attrib); - -// Use pkGetArg...(vm, 0, ptr) function to get the value of the attribute -// and use 0 as the argument index, using any other arg index value cause UB. -// -// If the attribute dones't exists DON'T set an error, instead return false. -// Pocket VM will handle it, On success update the native instance and return -// true. And DON'T ever use 'pkReturn...()' in the attribute setter It's is a -// void return function. -typedef bool (*pkInstSetAttribFn) (PKVM* vm, void* instance, uint32_t id, - PkStringPtr attrib); - // A function callback symbol for clean/free the pkStringResult. typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result); @@ -199,6 +127,105 @@ typedef PkStringPtr (*pkResolvePathFn) (PKVM* vm, const char* from, // to indicate if it's failed to load the script. typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path); +// A function callback to allocate and return a new instance of the registered +// class. Which will be called when the instance is constructed. The returned/ +// data is expected to be alive till the delete callback occurs. +typedef void* (*pkNewInstanceFn) (); + +// A function callback to de-allocate the aloocated native instance of the +// registered class. +typedef void (*pkDeleteInstanceFn) (void*); + +/*****************************************************************************/ +/* POCKETLANG TYPES */ +/*****************************************************************************/ + +// Type enum of the pocketlang's first class types. Note that Object isn't +// instanciable (as of now) but they're considered first calss. +enum PkVarType { + PK_OBJECT = 0, + + PK_NULL, + PK_BOOL, + PK_NUMBER, + PK_STRING, + PK_LIST, + PK_MAP, + PK_RANGE, + PK_MODULE, + PK_CLOSURE, + PK_FIBER, + PK_CLASS, + PK_INSTANCE, +}; + +// Type of the error message that pocketlang will provide with the pkErrorFn +// callback. +enum PkErrorType { + PK_ERROR_COMPILE = 0, // Compile time errors. + PK_ERROR_RUNTIME, // Runtime error message. + PK_ERROR_STACKTRACE, // One entry of a runtime error stack. +}; + +// Result that pocketlang will return after a compilation or running a script +// or a function or evaluating an expression. +enum PkResult { + PK_RESULT_SUCCESS = 0, // Successfully finished the execution. + + // Unexpected EOF while compiling the source. This is another compile time + // error that will ONLY be returned if we're compiling with the REPL mode set + // in the compile options. We need this specific error to indicate the host + // application to add another line to the last input. If REPL is not enabled, + // this will be PK_RESULT_COMPILE_ERROR. + PK_RESULT_UNEXPECTED_EOF, + + PK_RESULT_COMPILE_ERROR, // Compilation failed. + PK_RESULT_RUNTIME_ERROR, // An error occurred at runtime. +}; + +// A string pointer wrapper to pass c string between host application and +// pocket VM. With a on_done() callback to clean it when the pocket VM is done +// with the string. +struct PkStringPtr { + const char* string; //< The string result. + pkResultDoneFn on_done; //< Called once vm done with the string. + void* user_data; //< User related data. + + // These values are provided by the pocket VM to the host application, you're + // not expected to set this when provideing string to the pocket VM. + uint32_t length; //< Length of the string. + uint32_t hash; //< Its 32 bit FNV-1a hash. +}; + +struct PkConfiguration { + + // The callback used to allocate, reallocate, and free. If the function + // pointer is NULL it defaults to the VM's realloc(), free() wrappers. + pkReallocFn realloc_fn; + + pkErrorFn error_fn; + pkWriteFn write_fn; + pkReadFn read_fn; + + pkResolvePathFn resolve_path_fn; + pkLoadScriptFn load_script_fn; + + // User defined data associated with VM. + void* user_data; +}; + +// The options to configure the compilation provided by the command line +// arguments (or other ways the host application provides). +struct PkCompileOptions { + + // Compile debug version of the source. + bool debug; + + // Set to true if compiling in REPL mode, This will print repr version of + // each evaluated non-null values. + bool repl_mode; +}; + /*****************************************************************************/ /* POCKETLANG PUBLIC API */ /*****************************************************************************/ @@ -250,7 +277,7 @@ PK_PUBLIC PkHandle* pkModuleGetGlobal(PKVM* vm, PkHandle* module, const char* name); // 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. +// the function has variadic parameters and use pkGetArgc() to get the argc. // Note that the function will be added as a global variable of the module, // to retrieve the function use pkModuleGetGlobal(). PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module, @@ -261,6 +288,30 @@ 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, + PkHandle* base_class, PkHandle* module, + pkNewInstanceFn new_fn, + pkDeleteInstanceFn delete_fn); + +// Add a native method to the given class. If the [arity] is -1 that means +// the method has variadic parameters and use pkGetArgc() to get the argc. +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, @@ -289,59 +340,6 @@ PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber, // yielded or returned value use the pkFiberGetReturnValue() function. PK_PUBLIC PkResult pkResumeFiber(PKVM* vm, PkHandle* fiber, PkVar value); -/*****************************************************************************/ -/* POCKETLANG PUBLIC TYPE DEFINES */ -/*****************************************************************************/ - -// A string pointer wrapper to pass c string between host application and -// pocket VM. With a on_done() callback to clean it when the pocket VM is done -// with the string. -struct PkStringPtr { - const char* string; //< The string result. - pkResultDoneFn on_done; //< Called once vm done with the string. - void* user_data; //< User related data. - - // These values are provided by the pocket VM to the host application, you're - // not expected to set this when provideing string to the pocket VM. - uint32_t length; //< Length of the string. - uint32_t hash; //< Its 32 bit FNV-1a hash. -}; - -struct PkConfiguration { - - // The callback used to allocate, reallocate, and free. If the function - // pointer is NULL it defaults to the VM's realloc(), free() wrappers. - pkReallocFn realloc_fn; - - pkErrorFn error_fn; - pkWriteFn write_fn; - pkReadFn read_fn; - - pkInstFreeFn inst_free_fn; - pkInstNameFn inst_name_fn; - pkInstGetAttribFn inst_get_attrib_fn; - pkInstSetAttribFn inst_set_attrib_fn; - - pkResolvePathFn resolve_path_fn; - pkLoadScriptFn load_script_fn; - - // User defined data associated with VM. - void* user_data; -}; - -// The options to configure the compilation provided by the command line -// arguments (or other ways the host application provides). -struct PkCompileOptions { - - // Compile debug version of the source. - bool debug; - - // Set to true if compiling in REPL mode, This will print repr version of - // each evaluated non-null values. - bool repl_mode; - -}; - /*****************************************************************************/ /* NATIVE FUNCTION API */ /*****************************************************************************/ @@ -352,6 +350,9 @@ PK_PUBLIC void pkSetRuntimeError(PKVM* vm, const char* message); // TODO: Set a runtime error to VM, with the formated string. //PK_PUBLIC void pkSetRuntimeErrorFmt(PKVM* vm, const char* fmt, ...); +// 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); @@ -383,7 +384,6 @@ 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 pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value); PK_PUBLIC bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value); // The functions follow are used to set the return value of the current native @@ -397,8 +397,6 @@ 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); -PK_PUBLIC void pkReturnInstNative(PKVM* vm, void* data, uint32_t id); - // 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); @@ -424,23 +422,6 @@ PK_PUBLIC PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len); PK_PUBLIC PkHandle* pkNewList(PKVM* vm); PK_PUBLIC PkHandle* pkNewMap(PKVM* vm); -// 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 and return a new fiber around the function [fn]. -PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn); - -// Create and return a native instance around the [data]. The [id] is the -// unique id of the instance, this would be used to check if two instances are -// equal and used to get the name of the instance using NativeTypeNameFn -// callback. -PK_PUBLIC PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id); - // 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 diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 064ad5c..4b38e0f 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -125,6 +125,8 @@ typedef enum { TK_NOT, // not / ! TK_TRUE, // true TK_FALSE, // false + TK_SELF, // self + // TODO: TK_SUPER TK_DO, // do TK_THEN, // then @@ -186,6 +188,7 @@ static _Keyword _keywords[] = { { "not", 3, TK_NOT }, { "true", 4, TK_TRUE }, { "false", 5, TK_FALSE }, + { "self", 4, TK_SELF }, { "do", 2, TK_DO }, { "then", 4, TK_THEN }, { "while", 5, TK_WHILE }, @@ -241,6 +244,14 @@ typedef enum { DEPTH_LOCAL, //< Local scope. Increase with inner scope. } Depth; +typedef enum { + FUNC_MAIN, // The body function of the script. + FUNC_TOPLEVEL, + FUNC_LITERAL, + FUNC_METHOD, + FUNC_CONSTRUCTOR, +} FuncType; + typedef struct { const char* name; //< Directly points into the source string. uint32_t length; //< Length of the name. @@ -314,6 +325,9 @@ typedef struct sUpvalueInfo { typedef struct sFunc { + // Type of the current function. + FuncType type; + // Scope of the function. -2 for module body function, -1 for top level // function and literal functions will have the scope where it declared. int depth; @@ -396,8 +410,9 @@ typedef struct sParser { ForwardName forwards[MAX_FORWARD_NAMES]; int forwards_count; - bool repl_mode; //< True if compiling for REPL. - bool has_errors; //< True if any syntex error occurred at. + bool repl_mode; + bool parsing_class; + bool has_errors; bool need_more_lines; //< True if we need more lines in REPL mode. } Parser; @@ -507,6 +522,7 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler, parser->forwards_count = 0; parser->repl_mode = !!(compiler->options && compiler->options->repl_mode); + parser->parsing_class = false; parser->has_errors = false; parser->need_more_lines = false; } @@ -1257,6 +1273,19 @@ static int findBuiltinFunction(const PKVM* vm, return -1; } +// Find the builtin classes name and returns it's index in the VM's builtin +// classes array, if not found returns -1. +static int findBuiltinClass(const PKVM* vm, + const char* name, uint32_t length) { + for (int i = 0; i < PK_INSTANCE; i++) { + uint32_t bfn_length = vm->builtin_classes[i]->name->length; + if (IS_CSTR_EQ(vm->builtin_classes[i]->name, name, length)) { + return i; + } + } + return -1; +} + // Find the local with the [name] in the given function [func] and return // it's index, if not found returns -1. static int findLocal(Func* func, const char* name, uint32_t length) { @@ -1337,7 +1366,8 @@ typedef enum { NAME_LOCAL_VAR, //< Including parameter. NAME_UPVALUE, //< Local to an enclosing function. NAME_GLOBAL_VAR, - NAME_BUILTIN_FN, //< Native builtin function. + NAME_BUILTIN_FN, //< Native builtin function. + NAME_BUILTIN_TY, //< Builtin primitive type classes. } NameDefnType; // Identifier search result. @@ -1394,6 +1424,13 @@ static NameSearchResult compilerSearchName(Compiler* compiler, return result; } + index = findBuiltinClass(compiler->parser.vm, name, length); + if (index != -1) { + result.type = NAME_BUILTIN_TY; + result.index = index; + return result; + } + return result; } @@ -1423,7 +1460,7 @@ static void compilerChangeStack(Compiler* compiler, int num); // Forward declaration of grammar functions. static void parsePrecedence(Compiler* compiler, Precedence precedence); -static void compileFunction(Compiler* compiler, bool is_literal); +static int compileFunction(Compiler* compiler, FuncType fn_type); static void compileExpression(Compiler* compiler); static void exprLiteral(Compiler* compiler); @@ -1448,6 +1485,8 @@ static void exprSubscript(Compiler* compiler); // true, false, null, self. static void exprValue(Compiler* compiler); +static void exprSelf(Compiler* compiler); + #define NO_RULE { NULL, NULL, PREC_NONE } #define NO_INFIX PREC_NONE @@ -1513,6 +1552,7 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence /* TK_NOT */ { exprUnaryOp, NULL, PREC_UNARY }, /* TK_TRUE */ { exprValue, NULL, NO_INFIX }, /* TK_FALSE */ { exprValue, NULL, NO_INFIX }, + /* TK_FALSE */ { exprSelf, NULL, NO_INFIX }, /* TK_DO */ NO_RULE, /* TK_THEN */ NO_RULE, /* TK_WHILE */ NO_RULE, @@ -1573,6 +1613,11 @@ static void emitPushName(Compiler* compiler, NameDefnType type, int index) { emitOpcode(compiler, OP_PUSH_BUILTIN_FN); emitByte(compiler, index); return; + + case NAME_BUILTIN_TY: + emitOpcode(compiler, OP_PUSH_BUILTIN_TY); + emitByte(compiler, index); + return; } } @@ -1584,6 +1629,7 @@ static void emitStoreName(Compiler* compiler, NameDefnType type, int index) { switch (type) { case NAME_NOT_DEFINED: case NAME_BUILTIN_FN: + case NAME_BUILTIN_TY: UNREACHABLE(); case NAME_LOCAL_VAR: @@ -1671,7 +1717,7 @@ static void exprInterpolation(Compiler* compiler) { } static void exprFunction(Compiler* compiler) { - compileFunction(compiler, true); + compileFunction(compiler, FUNC_LITERAL); } static void exprName(Compiler* compiler) { @@ -1701,7 +1747,9 @@ static void exprName(Compiler* compiler) { // like python does) and it's recommented to define all the globals // before entering a local scope. - if (result.type == NAME_NOT_DEFINED || result.type == NAME_BUILTIN_FN) { + if (result.type == NAME_NOT_DEFINED || + result.type == NAME_BUILTIN_FN || + result.type == NAME_BUILTIN_TY ) { name_type = (compiler->scope_depth == DEPTH_GLOBAL) ? NAME_GLOBAL_VAR : NAME_LOCAL_VAR; @@ -1897,7 +1945,11 @@ static void exprMap(Compiler* compiler) { consume(compiler, TK_RBRACE, "Expected '}' after map elements."); } -static void exprCall(Compiler* compiler) { +// This function is reused between calls and method calls. if the [call_type] +// is OP_METHOD_CALL the [method] should refer a string in the module's +// constant pool, otherwise it's ignored. +static void _compileCall(Compiler* compiler, Opcode call_type, int method) { + ASSERT((call_type == OP_CALL) || (call_type == OP_METHOD_CALL), OOPS); // Compile parameters. int argc = 0; @@ -1911,14 +1963,24 @@ static void exprCall(Compiler* compiler) { consume(compiler, TK_RPARAN, "Expected ')' after parameter list."); } - emitOpcode(compiler, OP_CALL); + emitOpcode(compiler, call_type); + emitByte(compiler, argc); + if (call_type == OP_METHOD_CALL) { + ASSERT_INDEX(method, (int)compiler->module->constants.count); + emitShort(compiler, method); + } + // After the call the arguments will be popped and the callable // will be replaced with the return value. compilerChangeStack(compiler, -argc); } +static void exprCall(Compiler* compiler) { + _compileCall(compiler, OP_CALL, -1); +} + static void exprAttrib(Compiler* compiler) { consume(compiler, TK_NAME, "Expected an attribute name after '.'."); const char* name = compiler->parser.previous.start; @@ -1929,6 +1991,12 @@ static void exprAttrib(Compiler* compiler) { moduleAddString(compiler->module, compiler->parser.vm, name, length, &index); + // Check if it's a method call. + if (match(compiler, TK_LPARAN)) { + _compileCall(compiler, OP_METHOD_CALL, index); + return; + } + if (compiler->l_value && matchAssignment(compiler)) { TokenType assignment = compiler->parser.previous.type; skipNewLines(compiler); @@ -1986,6 +2054,25 @@ static void exprValue(Compiler* compiler) { } } +static void exprSelf(Compiler* compiler) { + + if (compiler->func->type == FUNC_CONSTRUCTOR || + compiler->func->type == FUNC_METHOD) { + emitOpcode(compiler, OP_PUSH_SELF); + return; + } + + // If we reach here 'self' is used in either non method or a closure + // inside a method. + + if (!compiler->parser.parsing_class) { + parseError(compiler, "Invalid use of 'self'."); + } else { + // FIXME: + parseError(compiler, "TODO: Closures cannot capture 'self' for now."); + } +} + static void parsePrecedence(Compiler* compiler, Precedence precedence) { lexToken(&(compiler->parser)); GrammarFn prefix = getRule(compiler->parser.previous.type)->prefix; @@ -2155,7 +2242,8 @@ static void compilerExitBlock(Compiler* compiler) { } static void compilerPushFunc(Compiler* compiler, Func* fn, - Function* func) { + Function* func, FuncType type) { + fn->type = type; fn->outer_func = compiler->func; fn->local_count = 0; fn->stack_size = 0; @@ -2269,99 +2357,74 @@ static void compileStatement(Compiler* compiler); static void compileBlockBody(Compiler* compiler, BlockType type); // Compile a class and return it's index in the module's types buffer. -static void compileClass(Compiler* compiler) { +static int compileClass(Compiler* compiler) { + + ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS); // Consume the name of the type. - consume(compiler, TK_NAME, "Expected a type name."); + consume(compiler, TK_NAME, "Expected a class name."); const char* name = compiler->parser.previous.start; int name_len = compiler->parser.previous.length; + int name_line = compiler->parser.previous.line; // Create a new class. - int cls_index, ctor_index; - Class* cls = newClass(compiler->parser.vm, compiler->module, - name, (uint32_t)name_len, &cls_index, &ctor_index); - cls->ctor->fn->arity = 0; - - // FIXME: - // Temproary patch for moving functions and classes to constant buffer. - ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS); - int index = compilerAddVariable(compiler, - compiler->parser.previous.start, - compiler->parser.previous.length, - compiler->parser.previous.line); - moduleSetGlobal(compiler->module, index, VAR_OBJ(cls)); + int cls_index; + PKVM* _vm = compiler->parser.vm; + Class* cls = newClass(_vm, name, name_len, + _vm->builtin_classes[PK_OBJECT], compiler->module, + NULL, &cls_index); + vmPushTempRef(_vm, &cls->_super); // cls. + compiler->parser.parsing_class = true; // Check count exceeded. checkMaxConstantsReached(compiler, cls_index); - checkMaxConstantsReached(compiler, ctor_index); - - // Compile the constructor function. - ASSERT(compiler->func->ptr == compiler->module->body->fn, OOPS); - Func curr_fn; - compilerPushFunc(compiler, &curr_fn, cls->ctor->fn); - compilerEnterBlock(compiler); - - // Push an instance on the stack. - emitOpcode(compiler, OP_PUSH_INSTANCE); - emitShort(compiler, cls_index); skipNewLines(compiler); - TokenType next = peek(compiler); - while (next != TK_END && next != TK_EOF) { + while (!match(compiler, TK_END)) { + // At the top level the stack size should be 0, before and after compiling + // a top level statement, since there aren't any locals at the top level. + ASSERT(compiler->parser.has_errors || + compiler->func->stack_size == 0, OOPS); - // Compile field name. - consume(compiler, TK_NAME, "Expected a type name."); - const char* f_name = compiler->parser.previous.start; - int f_len = compiler->parser.previous.length; + consume(compiler, TK_DEF, "Expected method definition."); + int fn_index = compileFunction(compiler, FUNC_METHOD); + Var fn_var = compiler->module->constants.data[fn_index]; + ASSERT(IS_OBJ_TYPE(fn_var, OBJ_FUNC), OOPS); - int f_index = 0; - String* new_name = moduleAddString(compiler->module, compiler->parser.vm, - f_name, f_len, &f_index); + // TODO: check if the constructor or method already exists and report + // error. Make sure the error report line match the name token's line. - for (uint32_t i = 0; i < cls->field_names.count; i++) { - String* prev = moduleGetStringAt(compiler->module, - cls->field_names.data[i]); - ASSERT(prev != NULL, OOPS); - if (IS_STR_EQ(new_name, prev)) { - parseError(compiler, "Class field with name '%s' already exists.", - new_name->data); - } + Closure* method = newClosure(_vm, (Function*)AS_OBJ(fn_var)); + if (strcmp(method->fn->name, "_init") == 0) { + cls->ctor = method; + + } else { + vmPushTempRef(_vm, &method->_super); // method. + pkClosureBufferWrite(&cls->methods, _vm, method); + vmPopTempRef(_vm); // method. } - pkUintBufferWrite(&cls->field_names, compiler->parser.vm, f_index); - - // Consume the assignment expression. - consume(compiler, TK_EQ, "Expected an assignment after field name."); - compileExpression(compiler); // Assigned value. - consumeEndStatement(compiler); - - // At this point the stack top would be the expression. - emitOpcode(compiler, OP_INST_APPEND); + // At the top level the stack size should be 0, before and after compiling + // a top level statement, since there aren't any locals at the top level. + ASSERT(compiler->parser.has_errors || + compiler->func->stack_size == 0, OOPS); skipNewLines(compiler); - next = peek(compiler); } - consume(compiler, TK_END, "Expected 'end' after a class declaration end."); - // The instance pushed by the OP_PUSH_INSTANCE instruction is at the top - // of the stack, return it (Constructor will return the instance). Note that - // the emitFunctionEnd function will also add a return instruction but that's - // for functions which doesn't return anything explicitly. This return won't - // change compiler's stack size because it won't pop the return value. - emitOpcode(compiler, OP_RETURN); + compiler->parser.parsing_class = false; + vmPopTempRef(_vm); // cls. - compilerExitBlock(compiler); - emitFunctionEnd(compiler); - compilerPopFunc(compiler); + return cls_index; } // Compile a function and return it's index in the module's function buffer. -static void compileFunction(Compiler* compiler, bool is_literal) { +static int compileFunction(Compiler* compiler, FuncType fn_type) { const char* name; int name_length; - if (!is_literal) { + if (fn_type != FUNC_LITERAL) { consume(compiler, TK_NAME, "Expected a function name."); name = compiler->parser.previous.start; name_length = compiler->parser.previous.length; @@ -2376,7 +2439,7 @@ static void compileFunction(Compiler* compiler, bool is_literal) { compiler->module, false, NULL, &fn_index); checkMaxConstantsReached(compiler, fn_index); - if (!is_literal) { + if (fn_type != FUNC_LITERAL) { ASSERT(compiler->scope_depth == DEPTH_GLOBAL, OOPS); int name_line = compiler->parser.previous.line; int g_index = compilerAddVariable(compiler, name, name_length, name_line); @@ -2387,8 +2450,12 @@ static void compileFunction(Compiler* compiler, bool is_literal) { vmPopTempRef(compiler->parser.vm); // func. } + if (fn_type == FUNC_METHOD && strncmp(name, "_init", name_length) == 0) { + fn_type = FUNC_CONSTRUCTOR; + } + Func curr_fn; - compilerPushFunc(compiler, &curr_fn, func); + compilerPushFunc(compiler, &curr_fn, func, fn_type); int argc = 0; compilerEnterBlock(compiler); // Parameter depth. @@ -2431,6 +2498,11 @@ static void compileFunction(Compiler* compiler, bool is_literal) { compileBlockBody(compiler, BLOCK_FUNC); + if (fn_type == FUNC_CONSTRUCTOR) { + emitOpcode(compiler, OP_PUSH_SELF); + emitOpcode(compiler, OP_RETURN); + } + consume(compiler, TK_END, "Expected 'end' after function definition end."); compilerExitBlock(compiler); // Parameter depth. emitFunctionEnd(compiler); @@ -2448,7 +2520,7 @@ static void compileFunction(Compiler* compiler, bool is_literal) { // function of this function, and the bellow emit calls will write to the // outer function. If it's a literal function, we need to push a closure // of it on the stack. - if (is_literal) { + if (fn_type == FUNC_LITERAL) { emitOpcode(compiler, OP_PUSH_CLOSURE); emitShort(compiler, fn_index); @@ -2458,6 +2530,8 @@ static void compileFunction(Compiler* compiler, bool is_literal) { emitByte(compiler, curr_fn.upvalues[i].index); } } + + return fn_index; } // Finish a block body. @@ -3022,10 +3096,22 @@ static void compileStatement(Compiler* compiler) { } if (matchEndStatement(compiler)) { - emitOpcode(compiler, OP_PUSH_NULL); + + // Constructors will return self. + if (compiler->func->type == FUNC_CONSTRUCTOR) { + emitOpcode(compiler, OP_PUSH_SELF); + } else { + emitOpcode(compiler, OP_PUSH_NULL); + } + emitOpcode(compiler, OP_RETURN); } else { + + if (compiler->func->type == FUNC_CONSTRUCTOR) { + parseError(compiler, "Cannor 'return' a value from constructor."); + } + compileExpression(compiler); //< Return value is at stack top. // If the last expression parsed with compileExpression() is a call @@ -3085,7 +3171,7 @@ static void compileTopLevelStatement(Compiler* compiler) { compileClass(compiler); } else if (match(compiler, TK_DEF)) { - compileFunction(compiler, false); + compileFunction(compiler, FUNC_TOPLEVEL); } else if (match(compiler, TK_FROM)) { compileFromImport(compiler); @@ -3139,7 +3225,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source, uint32_t globals_count = module->globals.count; Func curr_fn; - compilerPushFunc(compiler, &curr_fn, module->body->fn); + compilerPushFunc(compiler, &curr_fn, module->body->fn, FUNC_MAIN); // Lex initial tokens. current <-- next. lexToken(&(compiler->parser)); @@ -3197,7 +3283,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source, } #if DUMP_BYTECODE - dumpFunctionCode(compiler->parser.vm, module->body); + dumpFunctionCode(compiler->parser.vm, module->body->fn); #endif // Return the compilation result. diff --git a/src/pk_core.c b/src/pk_core.c index b10b7e6..cf94153 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -15,13 +15,6 @@ #include "pk_utils.h" #include "pk_vm.h" -// M_PI is non standard. The macro _USE_MATH_DEFINES defining before importing -// will define the constants for MSVC. But for a portable solution, -// we're defining it ourselves if it isn't already. -#ifndef M_PI - #define M_PI 3.14159265358979323846 -#endif - // Returns the docstring of the function, which is a static const char* defined // just above the function by the DEF() macro below. #define DOCSTRING(fn) _pk_doc_##fn @@ -69,8 +62,67 @@ void pkRegisterModule(PKVM* vm, PkHandle* module) { 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) { + const char* name, PkHandle* value) { CHECK_TYPE(module, OBJ_MODULE); CHECK_NULL(value); @@ -230,33 +282,6 @@ bool pkGetArgString(PKVM* vm, int arg, const char** value, uint32_t* length) { return true; } -bool pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value) { - CHECK_GET_ARG_API_ERRORS(); - - Var val = ARG(arg); - bool is_native_instance = false; - - if (IS_OBJ_TYPE(val, OBJ_INST)) { - Instance* inst = ((Instance*)AS_OBJ(val)); - if (inst->is_native && inst->native_id == id) { - *value = inst->native; - is_native_instance = true; - } - } - - if (!is_native_instance) { - const char* ty_name = "$(?)"; - if (vm->config.inst_name_fn != NULL) { - ty_name = vm->config.inst_name_fn(id); - } - - ERR_INVALID_ARG_TYPE(ty_name); - return false; - } - - return true; -} - bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) { CHECK_GET_ARG_API_ERRORS(); @@ -300,10 +325,6 @@ void pkReturnHandle(PKVM* vm, PkHandle* handle) { RET(handle->value); } -void pkReturnInstNative(PKVM* vm, void* data, uint32_t id) { - RET(VAR_OBJ(newInstanceNative(vm, data, id))); -} - 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."); @@ -514,7 +535,7 @@ DEF(coreAssert, DEF(coreBin, "bin(value:num) -> string\n" - "Returns as a binary value string with '0x' prefix.") { + "Returns as a binary value string with '0b' prefix.") { int64_t value; if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return; @@ -592,6 +613,35 @@ DEF(coreToString, RET(VAR_OBJ(toString(vm, ARG(1)))); } +DEF(coreChr, + "chr(value:num) -> string\n" + "Returns the ASCII string value of the integer argument.") { + + int64_t num; + if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return; + + if (!IS_NUM_BYTE(num)) { + RET_ERR(newString(vm, "The number is not in a byte range.")); + } + + char c = (char)num; + RET(VAR_OBJ(newStringLength(vm, &c, 1))); +} + +DEF(coreOrd, + "ord(value:string) -> num\n" + "Returns integer value of the given ASCII character.") { + + String* c; + if (!validateArgString(vm, 1, &c)) return; + if (c->length != 1) { + RET_ERR(newString(vm, "Expected a string of length 1.")); + + } else { + RET(VAR_NUM((double)c->data[0])); + } +} + DEF(corePrint, "print(...) -> void\n" "Write each argument as space seperated, to the stdout and ends with a " @@ -679,35 +729,6 @@ DEF(coreStrSub, RET(VAR_OBJ(newStringLength(vm, str->data + pos, (uint32_t)len))); } -DEF(coreStrChr, - "str_chr(value:num) -> string\n" - "Returns the ASCII string value of the integer argument.") { - - int64_t num; - if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return; - - if (!IS_NUM_BYTE(num)) { - RET_ERR(newString(vm, "The number is not in a byte range.")); - } - - char c = (char)num; - RET(VAR_OBJ(newStringLength(vm, &c, 1))); -} - -DEF(coreStrOrd, - "str_ord(value:string) -> num\n" - "Returns integer value of the given ASCII character.") { - - String* c; - if (!validateArgString(vm, 1, &c)) return; - if (c->length != 1) { - RET_ERR(newString(vm, "Expected a string of length 1.")); - - } else { - RET(VAR_NUM((double)c->data[0])); - } -} - // List functions. // --------------- @@ -785,14 +806,17 @@ static void initializeBuiltinFunctions(PKVM* vm) { INITIALIZE_BUILTIN_FN("hex", coreHex, 1); INITIALIZE_BUILTIN_FN("yield", coreYield, -1); INITIALIZE_BUILTIN_FN("to_string", coreToString, 1); + INITIALIZE_BUILTIN_FN("chr", coreChr, 1); + INITIALIZE_BUILTIN_FN("ord", coreOrd, 1); INITIALIZE_BUILTIN_FN("print", corePrint, -1); INITIALIZE_BUILTIN_FN("input", coreInput, -1); INITIALIZE_BUILTIN_FN("exit", coreExit, -1); + // FIXME: + // move this functions as methods. and make "append()" a builtin. + // String functions. INITIALIZE_BUILTIN_FN("str_sub", coreStrSub, 3); - INITIALIZE_BUILTIN_FN("str_chr", coreStrChr, 1); - INITIALIZE_BUILTIN_FN("str_ord", coreStrOrd, 1); // List functions. INITIALIZE_BUILTIN_FN("list_append", coreListAppend, 2); @@ -913,254 +937,6 @@ DEF(stdLangWrite, } } -// TODO: Move math to cli as it's not part of the pocketlang core. -// -// 'math' library methods. -// ----------------------- - -DEF(stdMathFloor, - "floor(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(floor(num))); -} - -DEF(stdMathCeil, - "ceil(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(ceil(num))); -} - -DEF(stdMathPow, - "pow(value:num) -> num\n") { - - double num, ex; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - if (!validateNumeric(vm, ARG(2), &ex, "Argument 2")) return; - RET(VAR_NUM(pow(num, ex))); -} - -DEF(stdMathSqrt, - "sqrt(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(sqrt(num))); -} - -DEF(stdMathAbs, - "abs(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - if (num < 0) num = -num; - RET(VAR_NUM(num)); -} - -DEF(stdMathSign, - "sign(value:num) -> num\n") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - if (num < 0) num = -1; - else if (num > 0) num = +1; - else num = 0; - RET(VAR_NUM(num)); -} - -DEF(stdMathHash, - "hash(value:var) -> num\n" - "Return the hash value of the variable, if it's not hashable it'll " - "return null.") { - - if (IS_OBJ(ARG(1))) { - if (!isObjectHashable(AS_OBJ(ARG(1))->type)) { - RET(VAR_NULL); - } - } - RET(VAR_NUM((double)varHashValue(ARG(1)))); -} - -DEF(stdMathSine, - "sin(rad:num) -> num\n" - "Return the sine value of the argument [rad] which is an angle expressed " - "in radians.") { - - double rad; - if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return; - RET(VAR_NUM(sin(rad))); -} - -DEF(stdMathCosine, - "cos(rad:num) -> num\n" - "Return the cosine value of the argument [rad] which is an angle expressed " - "in radians.") { - - double rad; - if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return; - RET(VAR_NUM(cos(rad))); -} - -DEF(stdMathTangent, - "tan(rad:num) -> num\n" - "Return the tangent value of the argument [rad] which is an angle expressed " - "in radians.") { - - double rad; - if (!validateNumeric(vm, ARG(1), &rad, "Argument 1")) return; - RET(VAR_NUM(tan(rad))); -} - -DEF(stdMathSinh, - "sinh(val) -> val\n" - "Return the hyperbolic sine value of the argument [val].") { - - double val; - if (!validateNumeric(vm, ARG(1), &val, "Argument 1")) return; - RET(VAR_NUM(sinh(val))); -} - -DEF(stdMathCosh, - "cosh(val) -> val\n" - "Return the hyperbolic cosine value of the argument [val].") { - - double val; - if (!validateNumeric(vm, ARG(1), &val, "Argument 1")) return; - RET(VAR_NUM(cosh(val))); -} - -DEF(stdMathTanh, - "tanh(val) -> val\n" - "Return the hyperbolic tangent value of the argument [val].") { - - double val; - if (!validateNumeric(vm, ARG(1), &val, "Argument 1")) return; - RET(VAR_NUM(tanh(val))); -} - -DEF(stdMathArcSine, - "asin(num) -> num\n" - "Return the arcsine value of the argument [num] which is an angle " - "expressed in radians.") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - - if (num < -1 || 1 < num) { - RET_ERR(newString(vm, "Argument should be between -1 and +1")); - } - - RET(VAR_NUM(asin(num))); -} - -DEF(stdMathArcCosine, - "acos(num) -> num\n" - "Return the arc cosine value of the argument [num] which is " - "an angle expressed in radians.") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - - if (num < -1 || 1 < num) { - RET_ERR(newString(vm, "Argument should be between -1 and +1")); - } - - RET(VAR_NUM(acos(num))); -} - -DEF(stdMathArcTangent, - "atan(num) -> num\n" - "Return the arc tangent value of the argument [num] which is " - "an angle expressed in radians.") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(atan(num))); -} - -DEF(stdMathLog10, - "log10(value:num) -> num\n" - "Return the logarithm to base 10 of argument [value]") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(log10(num))); -} - -DEF(stdMathRound, - "round(value:num) -> num\n" - "Round to nearest integer, away from zero and return the number.") { - - double num; - if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return; - RET(VAR_NUM(round(num))); -} - -// 'Fiber' module methods. -// ----------------------- - -DEF(stdFiberNew, - "new(fn:Closure) -> fiber\n" - "Create and return a new fiber from the given function [fn].") { - - Closure* closure; - if (!validateArgClosure(vm, 1, &closure)) return; - RET(VAR_OBJ(newFiber(vm, closure))); -} - -DEF(stdFiberRun, - "run(fb:Fiber, ...) -> var\n" - "Runs the fiber's function with the provided arguments and returns it's " - "return value or the yielded value if it's yielded.") { - - int argc = ARGC; - if (argc == 0) // Missing the fiber argument. - RET_ERR(newString(vm, "Missing argument - fiber.")); - - Fiber* fb; - if (!validateArgFiber(vm, 1, &fb)) return; - - // Buffer of argument to call vmPrepareFiber(). - Var* args[MAX_ARGC]; - - // ARG(1) is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc). - for (int i = 1; i < argc; i++) { - args[i - 1] = &ARG(i + 1); - } - - // Switch fiber and start execution. - if (vmPrepareFiber(vm, fb, argc - 1, args)) { - ASSERT(fb == vm->fiber, OOPS); - fb->state = FIBER_RUNNING; - } -} - -DEF(stdFiberResume, - "resume(fb:Fiber) -> var\n" - "Resumes a yielded function from a previous call of fiber_run() function. " - "Return it's return value or the yielded value if it's yielded.") { - - int argc = ARGC; - if (argc == 0) // Missing the fiber argument. - RET_ERR(newString(vm, "Expected at least 1 argument(s).")); - if (argc > 2) // Can only accept 1 argument for resume. - RET_ERR(newString(vm, "Expected at most 2 argument(s).")); - - Fiber* fb; - if (!validateArgFiber(vm, 1, &fb)) return; - - Var value = (argc == 1) ? VAR_NULL : ARG(2); - - // Switch fiber and resume execution. - if (vmSwitchFiber(vm, fb, &value)) { - ASSERT(fb == vm->fiber, OOPS); - fb->state = FIBER_RUNNING; - } -} - static void initializeCoreModules(PKVM* vm) { #define MODULE_ADD_FN(module, name, fn, argc) \ moduleAddFunctionInternal(vm, module, name, fn, argc, DOCSTRING(fn)) @@ -1180,56 +956,260 @@ static void initializeCoreModules(PKVM* vm) { MODULE_ADD_FN(lang, "debug_break", stdLangDebugBreak, 0); #endif - NEW_MODULE(math, "math"); - MODULE_ADD_FN(math, "floor", stdMathFloor, 1); - MODULE_ADD_FN(math, "ceil", stdMathCeil, 1); - MODULE_ADD_FN(math, "pow", stdMathPow, 2); - MODULE_ADD_FN(math, "sqrt", stdMathSqrt, 1); - MODULE_ADD_FN(math, "abs", stdMathAbs, 1); - MODULE_ADD_FN(math, "sign", stdMathSign, 1); - MODULE_ADD_FN(math, "hash", stdMathHash, 1); - MODULE_ADD_FN(math, "sin", stdMathSine, 1); - MODULE_ADD_FN(math, "cos", stdMathCosine, 1); - MODULE_ADD_FN(math, "tan", stdMathTangent, 1); - MODULE_ADD_FN(math, "sinh", stdMathSinh, 1); - MODULE_ADD_FN(math, "cosh", stdMathCosh, 1); - MODULE_ADD_FN(math, "tanh", stdMathTanh, 1); - MODULE_ADD_FN(math, "asin", stdMathArcSine, 1); - MODULE_ADD_FN(math, "acos", stdMathArcCosine, 1); - MODULE_ADD_FN(math, "atan", stdMathArcTangent, 1); - MODULE_ADD_FN(math, "log10", stdMathLog10, 1); - MODULE_ADD_FN(math, "round", stdMathRound, 1); - - // Note that currently it's mutable (since it's a global variable, not - // constant and pocketlang doesn't support constant) so the user shouldn't - // modify the PI, like in python. - moduleAddGlobal(vm, math, "PI", 2, VAR_NUM(M_PI)); - - NEW_MODULE(fiber, "Fiber"); - MODULE_ADD_FN(fiber, "new", stdFiberNew, 1); - MODULE_ADD_FN(fiber, "run", stdFiberRun, -1); - MODULE_ADD_FN(fiber, "resume", stdFiberResume, -1); - #undef MODULE_ADD_FN #undef NEW_MODULE } +/*****************************************************************************/ +/* BUILTIN CLASS CONSTRUCTORS */ +/*****************************************************************************/ + +static void _ctorNull(PKVM* vm) { + RET(VAR_NULL); +} + +static void _ctorBool(PKVM* vm) { + RET(toBool(ARG(1))); +} + +static void _ctorNumber(PKVM* vm) { + double value; + if (!validateNumeric(vm, ARG(1), &value, "Argument 1")) return; + RET(VAR_NUM(value)); +} + +static void _ctorString(PKVM* vm) { + if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return; + if (ARGC == 0) { + RET(VAR_OBJ(newStringLength(vm, NULL, 0))); + return; + } + RET(VAR_OBJ(toString(vm, ARG(1)))); +} + +static void _ctorList(PKVM* vm) { + List* list = newList(vm, ARGC); + vmPushTempRef(vm, &list->_super); // list. + for (int i = 0; i < ARGC; i++) { + listAppend(vm, list, ARG(i + 1)); + } + vmPopTempRef(vm); // list. + RET(VAR_OBJ(list)); +} + +static void _ctorMap(PKVM* vm) { + RET(VAR_OBJ(newMap(vm))); +} + +static void _ctorRange(PKVM* vm) { + double from, to; + if (!validateNumeric(vm, ARG(1), &from, "Argument 1")) return; + if (!validateNumeric(vm, ARG(2), &to, "Argument 2")) return; + + RET(VAR_OBJ(newRange(vm, from, to))); +} + +static void _ctorFiber(PKVM* vm) { + Closure* closure; + if (!validateArgClosure(vm, 1, &closure)) return; + RET(VAR_OBJ(newFiber(vm, closure))); +} + +/*****************************************************************************/ +/* BUILTIN CLASS METHODS */ +/*****************************************************************************/ + +#define SELF (vm->fiber->self) + +DEF(_listAppend, + "List.append(value:var) -> List\n" + "Append the [value] to the list and return the list.") { + + ASSERT(IS_OBJ_TYPE(SELF, OBJ_LIST), OOPS); + + listAppend(vm, ((List*)AS_OBJ(SELF)), ARG(1)); + RET(SELF); +} + +DEF(_fiberRun, + "Fiber.run(...) -> var\n" + "Runs the fiber's function with the provided arguments and returns it's " + "return value or the yielded value if it's yielded.") { + + ASSERT(IS_OBJ_TYPE(SELF, OBJ_FIBER), OOPS); + Fiber* self = (Fiber*)AS_OBJ(SELF); + + // Buffer of argument to call vmPrepareFiber(). + Var* args[MAX_ARGC]; + + for (int i = 0; i < ARGC; i++) { + args[i] = &ARG(i + 1); + } + + // Switch fiber and start execution. + if (vmPrepareFiber(vm, self, ARGC, args)) { + self->state = FIBER_RUNNING; + } +} + +DEF(_fiberResume, + "Fiber.resume() -> var\n" + "Resumes a yielded function from a previous call of fiber_run() function. " + "Return it's return value or the yielded value if it's yielded.") { + + ASSERT(IS_OBJ_TYPE(SELF, OBJ_FIBER), OOPS); + Fiber* self = (Fiber*)AS_OBJ(SELF); + + if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return; + + Var value = (ARGC == 1) ? ARG(1) : VAR_NULL; + + // Switch fiber and resume execution. + if (vmSwitchFiber(vm, self, &value)) { + self->state = FIBER_RUNNING; + } +} + +#undef SELF + +/*****************************************************************************/ +/* BUILTIN CLASS INITIALIZATION */ +/*****************************************************************************/ + +static void initializePrimitiveClasses(PKVM* vm) { + for (int i = 0; i < PK_INSTANCE; i++) { + Class* super = NULL; + if (i != 0) super = vm->builtin_classes[PK_OBJECT]; + const char* name = getPkVarTypeName((PkVarType)i); + Class* cls = newClass(vm, name, (int)strlen(name), + super, NULL, NULL, NULL); + vm->builtin_classes[i] = cls; + cls->class_of = (PkVarType)i; + } + +#define ADD_CTOR(type, name, ptr, arity_) \ + do { \ + Function* fn = newFunction(vm, name, (int)strlen(name), \ + NULL, true, NULL, NULL); \ + fn->native = ptr; \ + fn->arity = arity_; \ + vmPushTempRef(vm, &fn->_super); /* fn. */ \ + vm->builtin_classes[type]->ctor = newClosure(vm, fn); \ + vmPopTempRef(vm); /* fn. */ \ + } while (false) + + ADD_CTOR(PK_NULL, "@ctorNull", _ctorNull, 0); + ADD_CTOR(PK_BOOL, "@ctorBool", _ctorBool, 1); + ADD_CTOR(PK_NUMBER, "@ctorNumber", _ctorNumber, 1); + ADD_CTOR(PK_STRING, "@ctorString", _ctorString, -1); + ADD_CTOR(PK_LIST, "@ctorList", _ctorList, -1); + ADD_CTOR(PK_MAP, "@ctorMap", _ctorMap, 0); + ADD_CTOR(PK_FIBER, "@ctorFiber", _ctorFiber, 1); + +#undef ADD_CTOR + +#define ADD_METHOD(type, name, ptr, arity_) \ + do { \ + Function* fn = newFunction(vm, name, (int)strlen(name), \ + NULL, true, DOCSTRING(ptr), NULL); \ + fn->native = ptr; \ + fn->arity = arity_; \ + vmPushTempRef(vm, &fn->_super); /* fn. */ \ + pkClosureBufferWrite(&vm->builtin_classes[type]->methods, \ + vm, newClosure(vm, fn)); \ + vmPopTempRef(vm); /* fn. */ \ + } while (false) + + ADD_METHOD(PK_LIST, "append", _listAppend, 1); + ADD_METHOD(PK_FIBER, "run", _fiberRun, -1); + ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1); + +#undef ADD_METHOD +} + #undef IS_NUM_BYTE #undef DOCSTRING #undef DEF -/*****************************************************************************/ -/* PRIMITIVE TYPES CLASS */ -/*****************************************************************************/ - -static void initializePrimitiveClasses(PKVM* vm) { - // TODO -} - /*****************************************************************************/ /* OPERATORS */ /*****************************************************************************/ +Var preConstructSelf(PKVM* vm, Class* cls) { + +#define NO_INSTANCE(type_name) \ + VM_SET_ERROR(vm, newString(vm, \ + "Class '" type_name "' cannot be instanciated.")) + + switch (cls->class_of) { + case PK_OBJECT: + NO_INSTANCE("Object"); + return VAR_NULL; + + case PK_NULL: + case PK_BOOL: + case PK_NUMBER: + case PK_STRING: + case PK_LIST: + case PK_MAP: + case PK_RANGE: + return VAR_NULL; // Constructor will override the null. + + case PK_MODULE: + NO_INSTANCE("Module"); + return VAR_NULL; + + case PK_CLOSURE: + NO_INSTANCE("Closure"); + return VAR_NULL; + + case PK_FIBER: + return VAR_NULL; + + case PK_CLASS: + NO_INSTANCE("Class"); + return VAR_NULL; + + case PK_INSTANCE: + return VAR_OBJ(newInstance(vm, cls)); + } + + UNREACHABLE(); + return VAR_NULL; +} + +Class* getClass(PKVM* vm, Var instance) { + PkVarType type = getVarType(instance); + if (0 <= type && type < PK_INSTANCE) { + return vm->builtin_classes[type]; + } + ASSERT(IS_OBJ_TYPE(instance, OBJ_INST), OOPS); + Instance* inst = (Instance*)AS_OBJ(instance); + return inst->cls; +} + +Var getMethod(PKVM* vm, Var self, String* name, bool* is_method) { + + Class* cls = getClass(vm, self); + ASSERT(cls != NULL, OOPS); + + Class* cls_ = cls; + do { + for (int i = 0; i < (int)cls_->methods.count; i++) { + Closure* method = cls_->methods.data[i]; + if (IS_CSTR_EQ(name, method->fn->name, name->length)) { + if (is_method) *is_method = true; + return VAR_OBJ(method); + } + } + cls_ = cls_->super_class; + } while (cls_ != NULL); + + // If the attribute not found it'll set an error. + if (is_method) *is_method = false; + return varGetAttrib(vm, self, name); +} + #define UNSUPPORTED_OPERAND_TYPES(op) \ VM_SET_ERROR(vm, stringFormat(vm, "Unsupported operand types for " \ "operator '" op "' $ and $", varTypeName(v1), varTypeName(v2))) diff --git a/src/pk_core.h b/src/pk_core.h index cfe7fe3..6bf294e 100644 --- a/src/pk_core.h +++ b/src/pk_core.h @@ -17,6 +17,24 @@ void initializeCore(PKVM* vm); /* OPERATORS */ /*****************************************************************************/ +// This method is called just before constructing a type to initialize self +// and after that the constructor will be called. For builtin types this +// function will return VAR_NULL and the constructor will override self to +// it's instance (because for some classes we cannot create without argument +// example Fiber(fn), Range(from, to) etc). If the class cannot be +// instanciated (ex: Class 'Module') it'll set an error and return VAR_NULL. +// For other classes the return value will be an Instance. +Var preConstructSelf(PKVM* vm, Class* cls); + +// Returns the class of the [instance]. +Class* getClass(PKVM* vm, Var instance); + +// Returns the method (closure) in the instance [self]. If it's not an method +// but just an attribute the [is_method] pointer will be set to false and +// returns the value. +// If the method / attribute not found, it'll set a runtime error on the VM. +Var getMethod(PKVM* vm, Var self, String* name, bool* is_method); + Var varAdd(PKVM* vm, Var v1, Var v2); // Returns v1 + v2. Var varSubtract(PKVM* vm, Var v1, Var v2); // Returns v1 - v2. Var varMultiply(PKVM* vm, Var v1, Var v2); // Returns v1 * v2. diff --git a/src/pk_debug.c b/src/pk_debug.c index 3fb0088..2525731 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -126,25 +126,16 @@ void dumpFunctionCode(PKVM* vm, Function* func) { NO_ARGS(); break; - case OP_PUSH_LIST: SHORT_ARG(); break; - case OP_PUSH_INSTANCE: - { - int cls_index = READ_SHORT(); - ASSERT_INDEX((uint32_t)cls_index, func->owner->constants.count); - Var constant = func->owner->constants.data[cls_index]; - ASSERT(IS_OBJ_TYPE(constant, OBJ_CLASS), OOPS); - - // Prints: %5d [Class:%s]\n - PRINT_INT(cls_index); - PRINT(" [Class:"); - PRINT(func->owner->name->data); - PRINT("]\n"); + case OP_PUSH_LIST: + SHORT_ARG(); + break; + + case OP_PUSH_MAP: + case OP_PUSH_SELF: + case OP_LIST_APPEND: + case OP_MAP_INSERT: + NO_ARGS(); break; - } - case OP_PUSH_MAP: NO_ARGS(); break; - case OP_LIST_APPEND: NO_ARGS(); break; - case OP_MAP_INSERT: NO_ARGS(); break; - case OP_INST_APPEND: NO_ARGS(); break; case OP_PUSH_LOCAL_0: case OP_PUSH_LOCAL_1: @@ -236,6 +227,19 @@ void dumpFunctionCode(PKVM* vm, Function* func) { break; } + case OP_PUSH_BUILTIN_TY: + { + int index = READ_BYTE(); + ASSERT_INDEX(index, PK_INSTANCE); + const char* name = vm->builtin_classes[index]->name->data; + // Prints: %5d [Fn:%s]\n + PRINT_INT(index); + PRINT(" [Class:"); + PRINT(name); + PRINT("]\n"); + break; + } + case OP_PUSH_UPVALUE: case OP_STORE_UPVALUE: { @@ -278,6 +282,24 @@ void dumpFunctionCode(PKVM* vm, Function* func) { break; } + case OP_METHOD_CALL: + { + int argc = READ_BYTE(); + int index = READ_SHORT(); + String* name = moduleGetStringAt(func->owner, index); + ASSERT(name != NULL, OOPS); + + // Prints: %5d (argc) %d '%s'\n + PRINT_INT(argc); + PRINT(" (argc) "); + + _PRINT_INT(index, 0); + PRINT(" '"); + PRINT(name->data); + PRINT("'\n"); + break; + } + case OP_CALL: // Prints: %5d (argc)\n PRINT_INT(READ_BYTE()); diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index c6eaf38..bf6fd73 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -40,9 +40,8 @@ OPCODE(PUSH_LIST, 2, 1) // Push a new map to construct from literal. OPCODE(PUSH_MAP, 0, 1) -// Push a new instance to the stack. -// param: 1 byte index. -OPCODE(PUSH_INSTANCE, 1, 1) +// Push the self of the current method on the stack. +OPCODE(PUSH_SELF, 0, 1) // Pop the value on the stack the next stack top would be a list. Append the // value to the list. Used in literal array construction. @@ -52,10 +51,6 @@ OPCODE(LIST_APPEND, 0, -1) // Insert the key value pairs to the map. Used in literal map construction. OPCODE(MAP_INSERT, 0, -2) -// Pop the value on the stack, the next stack top would be an instance. Append -// the value to the instance. Used in instance construction. -OPCODE(INST_APPEND, 0, -1) - // Push stack local on top of the stack. Locals at 0 to 8 marked explicitly // since it's performance critical. // params: PUSH_LOCAL_N -> 1 byte count value. @@ -97,6 +92,10 @@ OPCODE(STORE_GLOBAL, 1, 0) // params: 1 bytes index. OPCODE(PUSH_BUILTIN_FN, 1, 1) +// Push a built in class. +// params: 1 bytes index. +OPCODE(PUSH_BUILTIN_TY, 1, 1) + // Push an upvalue of the current closure at the index which is the first one // byte argument. // params: 1 byte index. @@ -124,6 +123,11 @@ OPCODE(POP, 0, -1) // params: 2 byte name index. OPCODE(IMPORT, 2, 1) +// Call a method on the variable at the stack top. See opcode CALL for detail. +// params: 2 bytes method name index in the constant pool. +// 1 byte argc. +OPCODE(METHOD_CALL, 3, -0) //< Stack size will be calculated at compile time. + // Calls a function using stack's top N values as the arguments and once it // done the stack top should be stored otherwise it'll be disregarded. The // function should set the 0 th argment to return value. diff --git a/src/pk_value.c b/src/pk_value.c index 9a4c3d6..b388137 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -75,14 +75,6 @@ PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) { return handle; } -PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id) { - Instance* inst = newInstanceNative(vm, data, id); - vmPushTempRef(vm, &inst->_super); // inst - PkHandle* handle = vmNewHandle(vm, VAR_OBJ(inst)); - vmPopTempRef(vm); // inst - return handle; -} - /*****************************************************************************/ /* VAR INTERNALS */ /*****************************************************************************/ @@ -102,6 +94,7 @@ DEFINE_BUFFER(Uint, uint32_t) DEFINE_BUFFER(Byte, uint8_t) DEFINE_BUFFER(Var, Var) DEFINE_BUFFER(String, String*) +DEFINE_BUFFER(Closure, Closure*) void pkByteBufferAddString(pkByteBuffer* self, PKVM* vm, const char* str, uint32_t length) { @@ -154,6 +147,13 @@ void markStringBuffer(PKVM* vm, pkStringBuffer* self) { } } +void markClosureBuffer(PKVM* vm, pkClosureBuffer* self) { + if (self == NULL) return; + for (uint32_t i = 0; i < self->count; i++) { + markObject(vm, &self->data[i]->_super); + } +} + static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { // TODO: trace here. @@ -263,6 +263,7 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { // Mark call frames. for (int i = 0; i < fiber->frame_count; i++) { markObject(vm, (Object*)&fiber->frames[i].closure->_super); + markValue(vm, fiber->frames[i].self); } vm->bytes_allocated += sizeof(CallFrame) * fiber->frame_capacity; @@ -278,17 +279,18 @@ static void popMarkedObjectsInternal(Object* obj, PKVM* vm) { markObject(vm, &cls->owner->_super); markObject(vm, &cls->ctor->_super); markObject(vm, &cls->name->_super); - vm->bytes_allocated += sizeof(uint32_t) * cls->field_names.capacity; + + markClosureBuffer(vm, &cls->methods); + vm->bytes_allocated += sizeof(Closure) * cls->methods.capacity; + } break; case OBJ_INST: { Instance* inst = (Instance*)obj; - if (!inst->is_native) { - Inst* ins = inst->ins; - vm->bytes_allocated += sizeof(Inst); - vm->bytes_allocated += sizeof(Var*) * ins->fields.capacity; - } + markObject(vm, &inst->attribs->_super); + markObject(vm, &inst->cls->_super); + vm->bytes_allocated += sizeof(Instance); } break; } } @@ -493,6 +495,7 @@ Fiber* newFiber(PKVM* vm, Closure* closure) { } fiber->open_upvalues = NULL; + fiber->self = VAR_UNDEFINED; // Initialize the return value to null (doesn't really have to do that here // but if we're trying to debut it may crash when dumping the return value). @@ -501,80 +504,58 @@ Fiber* newFiber(PKVM* vm, Closure* closure) { return fiber; } -Class* newClass(PKVM* vm, Module* module, const char* name, uint32_t length, - int* cls_index, int* ctor_index) { +Class* newClass(PKVM* vm, const char* name, int length, + Class* super, Module* module, + const char* docstring, int* cls_index) { Class* cls = ALLOCATE(vm, Class); varInitObject(&cls->_super, vm, OBJ_CLASS); vmPushTempRef(vm, &cls->_super); // class. - uint32_t _cls_index = moduleAddConstant(vm, module, VAR_OBJ(cls)); - if (cls_index) *cls_index = (int)_cls_index; + pkClosureBufferInit(&cls->methods); - pkUintBufferInit(&cls->field_names); - cls->owner = module; + cls->class_of = PK_INSTANCE; + cls->owner = NULL; + cls->super_class = super; cls->docstring = NULL; - cls->name = moduleAddString(module, vm, name, length, NULL); + cls->ctor = NULL; + cls->new_fn = NULL; + cls->delete_fn = NULL; - // Since characters '@' and '$' are special in stringFormat, and they - // currently cannot be escaped (TODO), a string (char array) created - // for that character and passed as C string format. - char special[2] = { SPECIAL_NAME_CHAR, '\0' }; - String* ctor_name = stringFormat(vm, "$(Ctor:@)", special, cls->name); - - // Constructor. - vmPushTempRef(vm, &ctor_name->_super); // ctor_name. - { - Function* ctor_fn = newFunction(vm, ctor_name->data, ctor_name->length, - module, false, NULL, ctor_index); - vmPushTempRef(vm, &ctor_fn->_super); // ctor_fn. - cls->ctor = newClosure(vm, ctor_fn); - vmPopTempRef(vm); // ctor_fn. + // Builtin types doesn't belongs to a module. + if (module != NULL) { + cls->name = moduleAddString(module, vm, name, length, NULL); + int _cls_index = moduleAddConstant(vm, module, VAR_OBJ(cls)); + if (cls_index) *cls_index = _cls_index; + moduleAddGlobal(vm, module, name, length, VAR_OBJ(cls)); + } else { + cls->name = newStringLength(vm, name, (uint32_t)length); } - vmPopTempRef(vm); // ctor_name. vmPopTempRef(vm); // class. return cls; } -Instance* newInstance(PKVM* vm, Class* cls, bool initialize) { +Instance* newInstance(PKVM* vm, Class* cls) { + + ASSERT(cls->class_of == PK_INSTANCE, "Cannot create an instace of builtin " + "class with newInstance() function."); Instance* inst = ALLOCATE(vm, Instance); varInitObject(&inst->_super, vm, OBJ_INST); - vmPushTempRef(vm, &inst->_super); // inst. - inst->ty_name = cls->name->data; - inst->is_native = false; + inst->cls = cls; + inst->attribs = newMap(vm); - Inst* ins = ALLOCATE(vm, Inst); - inst->ins = ins; - ins->type = cls; - pkVarBufferInit(&ins->fields); - - if (initialize && cls->field_names.count != 0) { - pkVarBufferFill(&ins->fields, vm, VAR_NULL, cls->field_names.count); + if (cls->new_fn != NULL) { + inst->native = cls->new_fn(); + } else { + inst->native = NULL; } vmPopTempRef(vm); // inst. - - return inst; -} - -Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id) { - Instance* inst = ALLOCATE(vm, Instance); - varInitObject(&inst->_super, vm, OBJ_INST); - inst->is_native = true; - inst->native_id = id; - - if (vm->config.inst_name_fn != NULL) { - inst->ty_name = vm->config.inst_name_fn(id); - } else { - inst->ty_name = "$(?)"; - } - - inst->native = data; return inst; } @@ -798,7 +779,8 @@ List* listJoin(PKVM* vm, List* l1, List* l2) { return list; } -// Return a hash value for the object. +// Return a hash value for the object. Only String and Range objects can be +// hashable. static uint32_t _hashObject(Object* obj) { ASSERT(isObjectHashable(obj->type), @@ -809,28 +791,10 @@ static uint32_t _hashObject(Object* obj) { case OBJ_STRING: return ((String*)obj)->hash; - case OBJ_LIST: - case OBJ_MAP: - goto L_unhashable; - - case OBJ_RANGE: - { + case OBJ_RANGE: { Range* range = (Range*)obj; return utilHashNumber(range->from) ^ utilHashNumber(range->to); } - - case OBJ_MODULE: - case OBJ_FUNC: - case OBJ_FIBER: - case OBJ_CLASS: - case OBJ_INST: - TODO; - UNREACHABLE(); - - default: - L_unhashable: - UNREACHABLE(); - break; } UNREACHABLE(); @@ -1067,27 +1031,15 @@ void freeObject(PKVM* vm, Object* self) { case OBJ_CLASS: { Class* cls = (Class*)self; - pkUintBufferClear(&cls->field_names, vm); + pkClosureBufferClear(&cls->methods, vm); } break; - case OBJ_INST: - { + case OBJ_INST: { Instance* inst = (Instance*)self; - - if (inst->is_native) { - if (vm->config.inst_free_fn != NULL) { - // TODO: Allow user to set error when freeing the object. - vm->config.inst_free_fn(vm, inst->native, inst->native_id); - } - - } else { - Inst* ins = inst->ins; - pkVarBufferClear(&ins->fields, vm); - DEALLOCATE(vm, ins); + if (inst->cls->delete_fn != NULL) { + inst->cls->delete_fn(inst->native); } - - break; - } + } break; } DEALLOCATE(vm, self); @@ -1194,129 +1146,35 @@ void moduleAddMain(PKVM* vm, Module* module) { } bool instGetAttrib(PKVM* vm, Instance* inst, String* attrib, Var* value) { - ASSERT(inst != NULL, OOPS); - ASSERT(attrib != NULL, OOPS); - ASSERT(value != NULL, OOPS); + ASSERT((inst != NULL) && (attrib != NULL) && (value != NULL), OOPS); - // This function should only be called at runtime. - ASSERT(vm->fiber != NULL, OOPS); - - if (inst->is_native) { - - if (vm->config.inst_get_attrib_fn) { - // Temproarly change the fiber's "return address" to points to the - // below var 'val' so that the users can use 'pkReturn...()' function - // to return the attribute as well. - Var* temp = vm->fiber->ret; - Var val = VAR_UNDEFINED; - - vm->fiber->ret = &val; - PkStringPtr attr = { attrib->data, NULL, NULL, - attrib->length, attrib->hash }; - vm->config.inst_get_attrib_fn(vm, inst->native, inst->native_id, attr); - vm->fiber->ret = temp; - - if (IS_UNDEF(val)) { - - // FIXME: add a list of attribute overrides. - if ((CHECK_HASH("as_string", 0xbdef4147) == attrib->hash) && - IS_CSTR_EQ(attrib, "as_string", 9)) { - *value = VAR_OBJ(toRepr(vm, VAR_OBJ(inst))); - return true; - } - - // If we reached here, the native instance don't have the attribute - // and no overriden attributes found, return false to indicate that the - // attribute doesn't exists. - return false; - } - - // Attribute [val] updated by the hosting application. - *value = val; - return true; - } - - // If the hosting application doesn't provided a getter function, we treat - // it as if the instance don't has the attribute. - return false; - - } else { - - // TODO: Optimize this with binary search. - Class* cls = inst->ins->type; - for (uint32_t i = 0; i < cls->field_names.count; i++) { - ASSERT_INDEX(i, cls->field_names.count); - String* f_name = moduleGetStringAt(cls->owner, cls->field_names.data[i]); - ASSERT(f_name != NULL, OOPS); - if (IS_STR_EQ(f_name, attrib)) { - *value = inst->ins->fields.data[i]; - return true; - } - } - - // Couldn't find the attribute in it's type class, return false. - return false; + if (inst->native != NULL) { + TODO; } - UNREACHABLE(); - return false; + Var value_ = mapGet(inst->attribs, VAR_OBJ(attrib)); + if (IS_UNDEF(value_)) return false; + + *value = value_; + return true; } bool instSetAttrib(PKVM* vm, Instance* inst, String* attrib, Var value) { + ASSERT((inst != NULL) && (attrib != NULL), OOPS); - if (inst->is_native) { + if (inst->native != NULL) { + // Try setting the attribute from the native interface, and if success, we + // should return. otherwise the code will "fall through" and set on it's + // dynamic attributes map. + TODO; - if (vm->config.inst_set_attrib_fn) { - // Temproarly change the fiber's "return address" to points to the - // below var 'attrib_ptr' so that the users can use 'pkGetArg...()' - // function to validate and get the attribute (users should use 0 as the - // index of the argument since it's at the return address and we cannot - // ensure fiber->ret[1] will be in bounds). - Var* temp = vm->fiber->ret; - Var attrib_ptr = value; - - vm->fiber->ret = &attrib_ptr; - PkStringPtr attr = { attrib->data, NULL, NULL, - attrib->length, attrib->hash }; - bool exists = vm->config.inst_set_attrib_fn(vm, inst->native, - inst->native_id, attr); - vm->fiber->ret = temp; - - // If the type is incompatible there'll be an error by now, return false - // and the user of this function has to check VM_HAS_ERROR() as well. - if (VM_HAS_ERROR(vm)) return false; - - // If the attribute exists on the native type, the host application would - // returned true by now, return it. - return exists; - } - - // If the host application doesn't provided a setter we treat it as it - // doesn't has the attribute. - return false; - - } else { - - // TODO: Optimize this with binary search. - Class* ty = inst->ins->type; - for (uint32_t i = 0; i < ty->field_names.count; i++) { - ASSERT_INDEX(i, ty->field_names.count); - String* f_name = moduleGetStringAt(ty->owner, ty->field_names.data[i]); - ASSERT(f_name != NULL, OOPS); - if (f_name->hash == attrib->hash && - f_name->length == attrib->length && - memcmp(f_name->data, attrib->data, attrib->length) == 0) { - inst->ins->fields.data[i] = value; - return true; - } - } - - // Couldn't find the attribute in it's type class, return false. - return false; + // FIXME: + // Only return true if attribute have been set. + return true; } - UNREACHABLE(); - return false; + mapSet(vm, inst->attribs, VAR_OBJ(attrib), value); + return true; } /*****************************************************************************/ @@ -1407,6 +1265,16 @@ const char* varTypeName(Var v) { return getObjectTypeName(obj->type); } +PkVarType getVarType(Var v) { + if (IS_NULL(v)) return PK_NULL; + if (IS_BOOL(v)) return PK_BOOL; + if (IS_NUM(v)) return PK_NUMBER; + + ASSERT(IS_OBJ(v), OOPS); + Object* obj = AS_OBJ(v); + return getObjPkVarType(obj->type); +} + bool isValuesSame(Var v1, Var v2) { #if VAR_NAN_TAGGING // Bit representation of each values are unique so just compare the bits. @@ -1461,8 +1329,8 @@ bool isValuesEqual(Var v1, Var v2) { } bool isObjectHashable(ObjectType type) { - // Only list and map are un-hashable. - return type != OBJ_LIST && type != OBJ_MAP; + // Only String and Range are hashable (since they're immutable). + return type == OBJ_STRING || type == OBJ_RANGE; } // This will prevent recursive list/map from crash when calling to_string, by @@ -1702,35 +1570,17 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff, { const Instance* inst = (const Instance*)obj; pkByteBufferWrite(buff, vm, '['); - pkByteBufferAddString(buff, vm, inst->ty_name, - (uint32_t)strlen(inst->ty_name)); - pkByteBufferWrite(buff, vm, ':'); - - if (!inst->is_native) { - const Class* cls = inst->ins->type; - const Inst* ins = inst->ins; - ASSERT(ins->fields.count == cls->field_names.count, OOPS); - - for (uint32_t i = 0; i < cls->field_names.count; i++) { - if (i != 0) pkByteBufferWrite(buff, vm, ','); - - pkByteBufferWrite(buff, vm, ' '); - String* f_name = moduleGetStringAt(cls->owner, - cls->field_names.data[i]); - pkByteBufferAddString(buff, vm, f_name->data, f_name->length); - pkByteBufferWrite(buff, vm, '='); - _toStringInternal(vm, ins->fields.data[i], buff, outer, repr); - } - } else { - - char buff_addr[STR_HEX_BUFF_SIZE]; - char* ptr = (char*)buff_addr; - (*ptr++) = '0'; (*ptr++) = 'x'; - const int len = snprintf(ptr, sizeof(buff_addr) - 2, - "%08x", (unsigned int)(uintptr_t)inst->native); - pkByteBufferAddString(buff, vm, buff_addr, (uint32_t)len); - } + pkByteBufferWrite(buff, vm, '\''); + pkByteBufferAddString(buff, vm, inst->cls->name->data, + inst->cls->name->length); + pkByteBufferAddString(buff, vm, "' instance at ", 14); + char buff_addr[STR_HEX_BUFF_SIZE]; + char* ptr = (char*)buff_addr; + (*ptr++) = '0'; (*ptr++) = 'x'; + const int len = snprintf(ptr, sizeof(buff_addr) - 2, + "%08x", (unsigned int)(uintptr_t)inst); + pkByteBufferAddString(buff, vm, buff_addr, (uint32_t)len); pkByteBufferWrite(buff, vm, ']'); return; } diff --git a/src/pk_value.h b/src/pk_value.h index ba2d24c..efd4f3f 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -200,6 +200,7 @@ DECLARE_BUFFER(Uint, uint32_t) DECLARE_BUFFER(Byte, uint8_t) DECLARE_BUFFER(Var, Var) DECLARE_BUFFER(String, String*) +DECLARE_BUFFER(Closure, Closure*) // Add all the characters to the buffer, byte buffer can also be used as a // buffer to write string (like a string stream). Note that this will not @@ -428,6 +429,7 @@ typedef struct { const uint8_t* ip; //< Pointer to the next instruction byte code. const Closure* closure; //< Closure of the frame. Var* rbp; //< Stack base pointer. (%rbp) + Var self; //< Self reference of the current method. } CallFrame; typedef enum { @@ -464,6 +466,13 @@ struct Fiber { // overflowed. Var* ret; + // The self pointer to of the current method. It'll be updated before + // calling a native method. (Because native methods doesn't have a call + // frame we're doing it this way). Also updated just before calling a + // script method, and will be captured by the next allocated callframe + // 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. @@ -479,6 +488,9 @@ struct Fiber { struct Class { Object _super; + // The base class of this class. + Class* super_class; + // The module that owns this class. Module* owner; @@ -489,9 +501,21 @@ struct Class { // entry in it's owner module's constant pool. const char* docstring; + // For builtin type it'll be it's enum (ex: PK_STRING, PK_NUMBER, ...) for + // every other classes it'll be PK_INSTANCE to indicate that it's not a + // builtin type's class. + PkVarType class_of; + Closure* ctor; //< The constructor function. - pkUintBuffer field_names; //< Buffer of field names. - // TODO: ordered names buffer for binary search. + + // A buffer of methods of the class. + pkClosureBuffer methods; + + // Allocater and de-allocator functions for native types. + // For script/ builtin types it'll be NULL. + pkNewInstanceFn new_fn; + pkDeleteInstanceFn delete_fn; + }; typedef struct { @@ -502,15 +526,17 @@ typedef struct { struct Instance { Object _super; - const char* ty_name; //< Name of the type it belongs to. + Class* cls; //< Class of the instance. - bool is_native; //< True if it's a native type instance. - uint32_t native_id; //< Unique ID of this native instance. + // If the instance is native, the [native] pointer points to the user data + // (generally a heap allocated struct of that type) that contains it's + // attributes. We'll use it to access an attribute first with setters and + // getters and if the attribute not exists we'll continue search in the + // bellow attribs map. + void* native; - union { - void* native; //< C struct pointer. // TODO: - Inst* ins; //< Module instance pointer. - }; + // Dynamic attributes of an instance. + Map* attribs; }; /*****************************************************************************/ @@ -544,6 +570,12 @@ Range* newRange(PKVM* vm, double from, double to); Module* newModule(PKVM* vm); +Closure* newClosure(PKVM* vm, Function* fn); + +Upvalue* newUpvalue(PKVM* vm, Var* value); + +Fiber* newFiber(PKVM* vm, Closure* closure); + // FIXME: // The docstring should be allocated and stored in the module's constants // as a string if it's not a native function. (native function's docs are @@ -555,29 +587,15 @@ Function* newFunction(PKVM* vm, const char* name, int length, bool is_native, const char* docstring, int* fn_index); -Closure* newClosure(PKVM* vm, Function* fn); +// If the module is not NULL, the name and the class object will be added to +// the module's constant pool. The class will be added to the modules global +// as well. +Class* newClass(PKVM* vm, const char* name, int length, + Class* super, Module* module, + const char* docstring, int* cls_index); -Upvalue* newUpvalue(PKVM* vm, Var* value); - -Fiber* newFiber(PKVM* vm, Closure* closure); - -// FIXME: -// Same fix has to applied as newFunction() (see above). -// -// Allocate new Class object and return Class* with name [name]. -Class* newClass(PKVM* vm, Module* scr, const char* name, uint32_t length, - int* cls_index, int* ctor_index); - -// Allocate new instance with of the base [type]. Note that if [initialize] is -// false, the field value buffer of the instance would be un initialized (ie. -// the buffer count = 0). Otherwise they'll be set to VAR_NULL. -Instance* newInstance(PKVM* vm, Class* cls, bool initialize); - -// Allocate new native instance and with [data] as the native type handle and -// return Instance*. The [id] is the unique id of the instance, this would be -// used to check if two instances are equal and used to get the name of the -// instance using NativeTypeNameFn callback. -Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id); +// Allocate new instance with of the base [type]. +Instance* newInstance(PKVM* vm, Class* cls); /*****************************************************************************/ /* METHODS */ @@ -738,6 +756,9 @@ const char* getObjectTypeName(ObjectType type); // Returns the type name of the var [v]. const char* varTypeName(Var v); +// Returns the PkVarType of the first class varaible [v]. +PkVarType getVarType(Var v); + // Returns true if both variables are the same (ie v1 is v2). bool isValuesSame(Var v1, Var v2); diff --git a/src/pk_vm.c b/src/pk_vm.c index f92cd52..a9ab792 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -31,11 +31,6 @@ PkConfiguration pkNewConfiguration(void) { config.write_fn = NULL; config.read_fn = NULL; - config.inst_free_fn = NULL; - config.inst_name_fn = NULL; - config.inst_get_attrib_fn = NULL; - config.inst_set_attrib_fn = NULL; - config.load_script_fn = NULL; config.resolve_path_fn = NULL; config.user_data = NULL; @@ -555,6 +550,10 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) { frame->rbp = rbp; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; + + // Capture self. + frame->self = vm->fiber->self; + vm->fiber->self = VAR_UNDEFINED; } static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { @@ -569,6 +568,10 @@ static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; + // Capture self. + frame->self = vm->fiber->self; + vm->fiber->self = VAR_UNDEFINED; + ASSERT(*frame->rbp == VAR_NULL, OOPS); // Move all the argument(s) to the base of the current frame. @@ -709,6 +712,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber_) { register const uint8_t* ip; register Var* rbp; //< Stack base pointer register. + register Var* self; //< Points to the self in the current call frame. register CallFrame* frame; //< Current call frame. register Module* module; //< Currently executing module. register Fiber* fiber = fiber_; @@ -770,6 +774,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber_) { frame = &fiber->frames[fiber->frame_count-1]; \ ip = frame->ip; \ rbp = frame->rbp; \ + self = &frame->self; \ module = frame->closure->fn->owner; \ } while (false) @@ -793,12 +798,18 @@ L_vm_main_loop: // defined, the next line become a declaration (Opcode instruction;). NO_OP; +#define _DUMP_STACK() \ + do { \ + system("cls"); /* FIXME: */ \ + dumpGlobalValues(vm); \ + dumpStackFrame(vm); \ + DEBUG_BREAK(); \ + } while (false) + #if DUMP_STACK - system("cls"); // FIXME: - dumpGlobalValues(vm); - dumpStackFrame(vm); - DEBUG_BREAK(); + _DUMP_STACK(); #endif +#undef _DUMP_STACK SWITCH() { @@ -848,14 +859,9 @@ L_vm_main_loop: DISPATCH(); } - OPCODE(PUSH_INSTANCE): + OPCODE(PUSH_SELF): { - uint8_t index = READ_SHORT(); - ASSERT_INDEX(index, module->constants.count); - ASSERT(IS_OBJ_TYPE(module->constants.data[index], OBJ_CLASS), OOPS); - Instance* inst = newInstance(vm, - (Class*)AS_OBJ(module->constants.data[index]), false); - PUSH(VAR_OBJ(inst)); + PUSH(*self); DISPATCH(); } @@ -889,21 +895,6 @@ L_vm_main_loop: DISPATCH(); } - OPCODE(INST_APPEND): - { - Var value = PEEK(-1); // Don't pop yet, we need the reference for gc. - Var inst = PEEK(-2); - ASSERT(IS_OBJ_TYPE(inst, OBJ_INST), OOPS); - - Instance* inst_p = (Instance*)AS_OBJ(inst); - ASSERT(!inst_p->is_native, OOPS); - Inst* ins = inst_p->ins; - pkVarBufferWrite(&ins->fields, vm, value); - DROP(); // value - - DISPATCH(); - } - OPCODE(PUSH_LOCAL_0): OPCODE(PUSH_LOCAL_1): OPCODE(PUSH_LOCAL_2): @@ -971,6 +962,15 @@ L_vm_main_loop: DISPATCH(); } + OPCODE(PUSH_BUILTIN_TY): + { + uint8_t index = READ_BYTE(); + ASSERT_INDEX(index, PK_INSTANCE); + Class* cls = vm->builtin_classes[index]; + PUSH(VAR_OBJ(cls)); + DISPATCH(); + } + OPCODE(PUSH_UPVALUE): { uint8_t index = READ_BYTE(); @@ -1058,32 +1058,66 @@ L_vm_main_loop: DISPATCH(); } + { + uint8_t argc; + Var callable; + const Closure* closure; + + OPCODE(METHOD_CALL): + argc = READ_BYTE(); + fiber->ret = (fiber->sp - argc - 1); + fiber->self = *fiber->ret; //< Self for the next call. + + uint16_t index = READ_SHORT(); + bool is_method; + String* name = moduleGetStringAt(module, (int)index); + callable = getMethod(vm, fiber->self, name, &is_method); + CHECK_ERROR(); + goto L_do_call; + OPCODE(CALL): OPCODE(TAIL_CALL): - { - const uint8_t argc = READ_BYTE(); - Var* callable = fiber->sp - argc - 1; - - const Closure* closure = NULL; + argc = READ_BYTE(); + fiber->ret = fiber->sp - argc - 1; + callable = *fiber->ret; +L_do_call: // Raw functions cannot be on the stack, since they're not first class // citizens. - ASSERT(!IS_OBJ_TYPE(*callable, OBJ_FUNC), OOPS); + ASSERT(!IS_OBJ_TYPE(callable, OBJ_FUNC), OOPS); - if (IS_OBJ_TYPE(*callable, OBJ_CLOSURE)) { - closure = (const Closure*)AS_OBJ(*callable); + if (IS_OBJ_TYPE(callable, OBJ_CLOSURE)) { + closure = (const Closure*)AS_OBJ(callable); - } else if (IS_OBJ_TYPE(*callable, OBJ_CLASS)) { - closure = (const Closure*)((Class*)AS_OBJ(*callable))->ctor; + } else if (IS_OBJ_TYPE(callable, OBJ_CLASS)) { + Class* cls = (Class*)AS_OBJ(callable); + + // Allocate / create a new self before calling constructor on it. + fiber->self = preConstructSelf(vm, cls); + CHECK_ERROR(); + + closure = (const Closure*)(cls)->ctor; + + // No constructor is defined on the class. Just return self. + if (closure == NULL) { + if (argc != 0) { + String* msg = stringFormat(vm, "Expected exactly 0 argument(s)."); + RUNTIME_ERROR(msg); + } + + *fiber->ret = fiber->self; + fiber->self = VAR_UNDEFINED; + DISPATCH(); + } } else { RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a callable to " "call, instead got", - varTypeName(*callable), toString(vm, *callable))); - DISPATCH(); + varTypeName(callable), toString(vm, callable))); } // If we reached here it's a valid callable. + ASSERT(closure != NULL, OOPS); // -1 argument means multiple number of args. if (closure->fn->arity != -1 && closure->fn->arity != argc) { @@ -1093,8 +1127,6 @@ L_vm_main_loop: RUNTIME_ERROR(msg); } - // Next call frame starts here. (including return value). - fiber->ret = callable; *(fiber->ret) = VAR_NULL; //< Set the return value to null. if (closure->fn->is_native) { @@ -1129,9 +1161,9 @@ L_vm_main_loop: } else { - if (instruction == OP_CALL) { + if (instruction == OP_CALL || instruction == OP_METHOD_CALL) { UPDATE_FRAME(); //< Update the current frame's ip. - pushCallFrame(vm, closure, callable); + pushCallFrame(vm, closure, fiber->ret); LOAD_FRAME(); //< Load the top frame to vm's execution variables. } else { diff --git a/tests/examples/brainfuck.pk b/tests/examples/brainfuck.pk index dd63727..0f35837 100644 --- a/tests/examples/brainfuck.pk +++ b/tests/examples/brainfuck.pk @@ -67,7 +67,7 @@ def execute(expr) ## output the byte at the data pointer. else if c == '.' - write(str_chr(mem[ptr])) + write(chr(mem[ptr])) else if c == ',' assert(false, "Currently input isn't supported in pocketlang.") diff --git a/tests/examples/pi.pk b/tests/examples/pi.pk index 3440632..b6e4ff2 100644 --- a/tests/examples/pi.pk +++ b/tests/examples/pi.pk @@ -3,7 +3,11 @@ ## ## PI/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - ... -from math import abs, PI +from math import abs + +## Temproarly we cannot register variables on native modules +## will be implemented soon. So defining the PI here. +PI = 3.14159265358979323846 pi_by_4 = 0; sign = -1 for i in 1..100000 diff --git a/tests/lang/basics.pk b/tests/lang/basics.pk index 89e43d1..f415e0f 100644 --- a/tests/lang/basics.pk +++ b/tests/lang/basics.pk @@ -37,11 +37,9 @@ assert(!('foo' in {'bar':'baz'})) ## Builtin functions tests. assert(to_string(42) == '42') -## Core module test. -import math -h1 = math.hash("testing"); h2 = math.hash("test" + "ing") -assert(h1 == h2) -assert(math.ceil(1.1) == math.floor(2.9)) +## FIXME: add hash function. +##h1 = math.hash("testing"); h2 = math.hash("test" + "ing") +##assert(h1 == h2) ## Logical statement test val = 0; a = false; b = true diff --git a/tests/lang/class.pk b/tests/lang/class.pk index 93792b9..952b2ba 100644 --- a/tests/lang/class.pk +++ b/tests/lang/class.pk @@ -1,36 +1,33 @@ -## TODO: Implement ctor with va arg to -## initialize, fields. +class Vec2 + def _init(x, y) + self.x = x + self.y = y + end + + def add(other) + return Vec2(self.x + other.x, + self.y + other.y) + end + + ## Note that operator overloading / friend functions + ## haven't implemented at this point (to_string won't actually + ## override it). + def to_string + return "[${self.x}, ${self.y}]" + end -class _Vec - x = 0 - y = 0 end -def Vec(x, y) - ret = _Vec() - ret.x = x; ret.y = y - return ret -end +v1 = Vec2(1, 2); assert(v1.x == 1 and v1.y == 2) +print("v1 = ${v1.to_string()}") -def vecAdd(v1, v2) - return Vec(v1.x + v2.x, - v1.y + v2.y) -end +v2 = Vec2(3, 4); assert(v2.x == 3 and v2.y == 4) +print("v2 = ${v2.to_string()}") -v1 = Vec(1, 2); assert(v1.x == 1 and v1.y == 2) -v2 = Vec(3, 4); assert(v2.x == 3 and v2.y == 4) +v3 = v1.add(v2); assert(v3.x == 4 and v3.y == 6) +print("v3 = ${v3.to_string()}") -v3 = vecAdd(v1, v2) -assert(v3.x == 4 and v3.y == 6) +print('ALL TESTS PASSED') -class Test - fn = null - val = Vec(12, 32) -end - -test = Test() -test.fn = to_string -res = test.fn(test.val) -assert(res == "[_Vec: x=12, y=32]") diff --git a/tests/lang/core.pk b/tests/lang/core.pk index f2aae8c..1089351 100644 --- a/tests/lang/core.pk +++ b/tests/lang/core.pk @@ -1,8 +1,5 @@ ## Core builtin functions and attribute tests. -## Math functions -from math import * - assert(hex(12648430) == '0xc0ffee') assert(hex(255) == '0xff' and hex(10597059) == '0xa1b2c3') assert(hex(-4294967295) == '-0xffffffff') ## the largest. @@ -48,61 +45,6 @@ assert(r.as_list == [1, 2, 3, 4]) assert(r.first == 1) assert(r.last == 5) -assert(sin(0) == 0) -assert(sin(PI/2) == 1) - -threshold = 0.0000000000001 - -assert(abs(cos(PI/3) - 0.5) < threshold ) -assert(abs(tan(PI/4) - 1.0) < threshold ) -for i in 0..1000 - assert(abs(sin(i) / cos(i) - tan(i)) < threshold) -end - -assert((cosh(.5) - 1.1276259652063807) < threshold) -assert((tanh(0.5) - 1.127625965206) < threshold) -for i in 0..100 - assert(abs(sinh(i) / cosh(i) - tanh(i)) < threshold) -end - -assert(abs(acos(PI/4) - 0.5) < 0.35) -assert(abs(atan(PI/4) - 0.5) < 0.2) - -assert((acos(0.5) - 1.1276259652063807) < threshold) -assert((atan(0.3) - 1.1276259652063807) < threshold) - -x = -1; interval = 1000 -for i in 0..interval-1 - x += 2/interval - assert(abs(sin(asin(x)) - x) < threshold) - assert(abs(cos(acos(x)) - x) < threshold) - assert(abs(tan(atan(x)) - x) < threshold) -end - -assert(abs(log10(2) - 0.3010299956639812) < threshold) -assert(round(1.4) == 1) -assert(round(1.5) == 2) -assert(round(-1.5) == -2) - -## Note that these mathe functions are removed temproarly from the core -## For more information see PR #201 -## -##assert(abs(log2(2) - 1) < threshold) -##assert(abs(log2(1) - 0) < threshold) -##assert(abs(log2(5) - 2.321928094887362) < threshold) -## -##assert(abs(hypot(1,1) - 1.414213562373095) < threshold) -##assert(abs(hypot(3,5) - 5.830951894845301) < threshold) -## -##assert(abs(cbrt(27) - 3) < threshold) -##assert(abs(cbrt(9) - 2.080083823051904) < threshold) -## -##assert(abs(gamma(5) - 24) < threshold) -##assert(abs(gamma(2.2) - 1.101802490879713) < threshold) - - - - # If we got here, that means all test were passed. print('All TESTS PASSED') diff --git a/tests/lang/fibers.pk b/tests/lang/fibers.pk index aacaa2e..25f6435 100644 --- a/tests/lang/fibers.pk +++ b/tests/lang/fibers.pk @@ -3,10 +3,8 @@ def f0() yield("yield value") end -import Fiber - -fiber = Fiber.new(f0) -yield_value = Fiber.run(fiber) +fiber = Fiber(f0) +yield_value = fiber.run() assert(yield_value == "yield value") assert(!fiber.is_done) @@ -14,9 +12,10 @@ def f1() assert(yield("y") == "r") yield() end -fiber = Fiber.new(f1) -assert(Fiber.run(fiber) == "y") -Fiber.resume(fiber, "r") + +fiber = Fiber(f1) +assert(fiber.run() == "y") +fiber.resume("r") assert(!fiber.is_done) def f2(p1, p2, p3) @@ -27,11 +26,11 @@ def f2(p1, p2, p3) return p1 + p2 * p3 end -fiber = Fiber.new(f2) -p3 = Fiber.run(fiber, 1, 2, 3); assert(p3 == 3) -p2 = Fiber.resume(fiber, 'r1'); assert(p2 == 2) -p1 = Fiber.resume(fiber, 'r2'); assert(p1 == 1) -pa = Fiber.resume(fiber, 'r3'); assert(pa == 7) +fiber = Fiber(f2) +p3 = fiber.run(1, 2, 3); assert(p3 == 3) +p2 = fiber.resume('r1'); assert(p2 == 2) +p1 = fiber.resume('r2'); assert(p1 == 1) +pa = fiber.resume('r3'); assert(pa == 7) assert(fiber.is_done) # If we got here, that means all test were passed. diff --git a/tests/lang/import.pk b/tests/lang/import.pk index d023488..1dbdd47 100644 --- a/tests/lang/import.pk +++ b/tests/lang/import.pk @@ -13,10 +13,6 @@ import "basics.pk" ## will import all import "controlflow.pk" as if_test from "functions.pk" import f1, f2 as fn2, f3 -import "class.pk" as class_ -assert(class_.Vec(42, 3.14).y == 3.14) -(vec = class_._Vec()).x = 'a'; assert(vec.x == 'a') - ## If it has a module name it'll bind to that name. import 'import/module.pk' assert(module_name.get_module_name() == 'module_name') diff --git a/tests/modules/math.pk b/tests/modules/math.pk new file mode 100644 index 0000000..4356ba8 --- /dev/null +++ b/tests/modules/math.pk @@ -0,0 +1,64 @@ + +from math import * + +assert(ceil(1.1) == floor(2.9)) + +## FIXME: +## temproarly modules cannot define globals via native interface +## and it'll be fixed soon. +PI = 3.14159265358979323846 + +assert(sin(0) == 0) +assert(sin(PI/2) == 1) + +threshold = 0.0000000000001 + +assert(abs(cos(PI/3) - 0.5) < threshold ) +assert(abs(tan(PI/4) - 1.0) < threshold ) +for i in 0..1000 + assert(abs(sin(i) / cos(i) - tan(i)) < threshold) +end + +assert((cosh(.5) - 1.1276259652063807) < threshold) +assert((tanh(0.5) - 1.127625965206) < threshold) +for i in 0..100 + assert(abs(sinh(i) / cosh(i) - tanh(i)) < threshold) +end + +assert(abs(acos(PI/4) - 0.5) < 0.35) +assert(abs(atan(PI/4) - 0.5) < 0.2) + +assert((acos(0.5) - 1.1276259652063807) < threshold) +assert((atan(0.3) - 1.1276259652063807) < threshold) + +x = -1; interval = 1000 +for i in 0..interval-1 + x += 2/interval + assert(abs(sin(asin(x)) - x) < threshold) + assert(abs(cos(acos(x)) - x) < threshold) + assert(abs(tan(atan(x)) - x) < threshold) +end + +assert(abs(log10(2) - 0.3010299956639812) < threshold) +assert(round(1.4) == 1) +assert(round(1.5) == 2) +assert(round(-1.5) == -2) + +## Note that these mathe functions are removed temproarly from the core +## For more information see PR #201 +## +##assert(abs(log2(2) - 1) < threshold) +##assert(abs(log2(1) - 0) < threshold) +##assert(abs(log2(5) - 2.321928094887362) < threshold) +## +##assert(abs(hypot(1,1) - 1.414213562373095) < threshold) +##assert(abs(hypot(3,5) - 5.830951894845301) < threshold) +## +##assert(abs(cbrt(27) - 3) < threshold) +##assert(abs(cbrt(9) - 2.080083823051904) < threshold) +## +##assert(abs(gamma(5) - 24) < threshold) +##assert(abs(gamma(2.2) - 1.101802490879713) < threshold) + +# If we got here, that means all test were passed. +print('All TESTS PASSED') diff --git a/tests/native/README.md b/tests/native/README.md index 140b7e5..1ba5fc9 100644 --- a/tests/native/README.md +++ b/tests/native/README.md @@ -1,7 +1,7 @@ ## Example on how to integrate pocket VM with in your application. -- Including this example this repository contains several examples on how to integrate -pocket VM with your application +- Including this example this repository contains several examples on how to +integrate pocket VM with your application - These examples (currently 2 examples) - The `cli/` application - The `docs/try/main.c` web assembly version of pocketlang diff --git a/tests/native/example1.c b/tests/native/example1.c index c490613..12446fa 100644 --- a/tests/native/example1.c +++ b/tests/native/example1.c @@ -10,17 +10,17 @@ // The pocket script we're using to test. static const char* code = - " from YourModule import variableToC \n" - " a = 42 \n" - " b = variableToC(a) \n" - " print('[pocket] b =', b) \n" + " from my_module import cFunction \n" + " a = 42 \n" + " b = cFunction(a) \n" + " print('[pocket] b = $b') \n" ; /*****************************************************************************/ /* MODULE FUNCTION */ /*****************************************************************************/ -static void variableToC(PKVM* vm) { +static void cFunction(PKVM* vm) { // Get the parameter from pocket VM. double a; @@ -36,15 +36,7 @@ static void variableToC(PKVM* vm) { /* POCKET VM CALLBACKS */ /*****************************************************************************/ -// Error report callback. -static void reportError(PKVM* vm, PkErrorType type, - const char* file, int line, - const char* message) { - fprintf(stderr, "Error: %s\n", message); -} - -// print() callback to write stdout. -static void stdoutWrite(PKVM* vm, const char* text) { +static void stdoutCallback(PKVM* vm, const char* text) { fprintf(stdout, "%s", text); } @@ -56,21 +48,20 @@ int main(int argc, char** argv) { // Pocket VM configuration. PkConfiguration config = pkNewConfiguration(); - config.error_fn = reportError; - config.write_fn = stdoutWrite; - //config.read_fn = stdinRead; + config.write_fn = stdoutCallback; // Create a new pocket VM. PKVM* vm = pkNewVM(&config); - // Register your module. - PkHandle* your_module = pkNewModule(vm, "YourModule"); - pkModuleAddFunction(vm, your_module, "variableToC", variableToC, 1); - pkReleaseHandle(vm, your_module); + // Registering a native module. + PkHandle* my_module = pkNewModule(vm, "my_module"); + pkModuleAddFunction(vm, my_module, "cFunction", cFunction, 1); + pkRegisterModule(vm, my_module); + pkReleaseHandle(vm, my_module); // The path and the source code. - PkStringPtr source = { code, NULL, NULL, 0, 0 }; - PkStringPtr path = { "./some/path/", NULL, NULL, 0, 0 }; + PkStringPtr source = { .string = code }; + PkStringPtr path = { .string = "./some/path/" }; // Run the code. PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/); diff --git a/tests/native/example2.c b/tests/native/example2.c index 7a9e2ae..2d0b238 100644 --- a/tests/native/example2.c +++ b/tests/native/example2.c @@ -3,174 +3,85 @@ * Distributed Under The MIT License */ +#error Native interface is being refactored and will be completed soon. + // This is an example on how to write your own custom type (Vector here) and // bind it with with the pocket VM. #include -#include -#include -#include +#include /* For malloc */ +#include /* For printf */ +#include /* For strncmp */ +#include /* For sqrt */ // The script we're using to test the native Vector type. static const char* code = - " import Vector # The native module. \n" - " print('Module =', Vector) \n" - " \n" - " vec1 = Vector.new(1, 2) # Calling native method. \n" - " print('vec1 =', 'Vector.new(1, 2)') \n" - " print() \n" - " \n" - " # Using the native getter. \n" - " print('vec1.x =', vec1.x) \n" - " print('vec1.y =', vec1.y) \n" - " print('vec1.length =', vec1.length) \n" - " print() \n" - " \n" - " # Using the native setter. \n" - " vec1.x = 3; vec1.y = 4; \n" - " print('vec1.x =', vec1.x) \n" - " print('vec1.y =', vec1.y) \n" - " print('vec1.length =', vec1.length) \n" - " print() \n" - " \n" - " vec2 = Vector.new(5, 6) \n" - " vec3 = Vector.add(vec1, vec2) \n" - " print('vec3 =', 'Vector.add(vec1, vec2)') \n" - " print('vec3.x =', vec3.x) \n" - " print('vec3.y =', vec3.y) \n" - " \n" + " from vector import Vec2 \n" + " print('Class = $Vec2') \n" + " \n" + " v1 = Vec2(1, 2) \n" + " print('v1 = $v1') \n" + " print() \n" + " \n" + " print('v1.x = ${v1.x}') \n" + " print('v1.y = ${v1.y}') \n" + " print('v1.length = ${v1.length}') \n" + " print() \n" + " \n" + " v1.x = 3; v1.y = 4; \n" + " print('v1.x = ${v1.x}') \n" + " print('v1.y = ${v1.y}') \n" + " print('v1.length = ${v1.length}') \n" + " print() \n" + " \n" + " v2 = Vec2(5, 6) \n" + " v3 = v1.add(v2) \n" + " print('v3 = ${v3}') \n" + " print('v3.x = ${v3.x}') \n" + " print('v3.y = ${v3.y}') \n" + " \n" ; -/*****************************************************************************/ -/* NATIVE TYPE DEFINES & CALLBACKS */ -/*****************************************************************************/ - -// An enum value of native object, used as unique of the type in pocketlang. -typedef enum { - OBJ_VECTOR = 0, -} ObjType; - -typedef struct { - double x, y; // Vector variables. -} Vector; - -// Get name callback, will called from pocketlang to get the type name from -// the ID (the enum value). -const char* getObjName(uint32_t id) { - switch ((ObjType)id) { - case OBJ_VECTOR: return "Vector"; - } - return NULL; // Unreachable. -} - -// Instance getter callback to get a value from the native instance. -// The hash value and the length of the string are provided with the -// argument [attrib]. -void objGetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { - - switch ((ObjType)id) { - case OBJ_VECTOR: { - Vector* vector = ((Vector*)instance); - - if (strcmp(attrib.string, "x") == 0) { - pkReturnNumber(vm, vector->x); - return; - - } else if (strcmp(attrib.string, "y") == 0) { - pkReturnNumber(vm, vector->y); - return; - - } else if (strcmp(attrib.string, "length") == 0) { - double length = sqrt(pow(vector->x, 2) + pow(vector->y, 2)); - pkReturnNumber(vm, length); - return; - - } - } break; - } - - // If we reached here that means the attribute doesn't exists. - // Since we haven't used pkReturn...() function, pocket VM already - // know that the attribute doesn't exists. just return. - return; -} - -// Instance setter callback to set the value to the native instance. -// The hash value and the length of the string are provided with the -// argument [attrib]. -bool objSetAttrib(PKVM* vm, void* instance, uint32_t id, PkStringPtr attrib) { - - switch ((ObjType)id) { - case OBJ_VECTOR: { - Vector* vector = ((Vector*)instance); - - if (strcmp(attrib.string, "x") == 0) { - double x; // Get the number x. - if (!pkGetArgNumber(vm, 0, &x)) return false; - vector->x = x; - return true; - - } else if (strcmp(attrib.string, "y") == 0) { - double y; // Get the number x. - if (!pkGetArgNumber(vm, 0, &y)) return false; - vector->y = y; - return true; - - } - } break; - } - - // If we reached here that means the attribute doesn't exists. - // Return false to indicate it. - return false; -} - -// The free object callback, called just before the native instance, garbage -// collect. -void freeObj(PKVM* vm, void* instance, uint32_t id) { - free((void*)instance); // Your cleanups. -} - /*****************************************************************************/ /* VECTOR MODULE FUNCTIONS REGISTER */ /*****************************************************************************/ -// The Vector.new(x, y) function. -void _vecNew(PKVM* vm) { - double x, y; // The args. - - // Get the args from the stack, If it's not number, return. - if (!pkGetArgNumber(vm, 1, &x)) return; - if (!pkGetArgNumber(vm, 2, &y)) return; - - // Create a new vector. +typedef struct { + double x, y; +} Vector; + +// Native instance allocation callback. +void* _newVec() { Vector* vec = (Vector*)malloc(sizeof(Vector)); - vec->x = x, vec->y = y; - pkReturnInstNative(vm, (void*)vec, OBJ_VECTOR); + vec->x = 0; + vec->y = 0; + return vec; } -// The Vector.length(vec) function. -void _vecAdd(PKVM* vm) { - Vector *v1, *v2; - if (!pkGetArgInst(vm, 1, OBJ_VECTOR, (void**)&v1)) return; - if (!pkGetArgInst(vm, 2, OBJ_VECTOR, (void**)&v2)) return; - - // Create a new vector. - Vector* v3 = (Vector*)malloc(sizeof(Vector)); - v3->x = v1->x + v2->x; - v3->y = v1->y + v2->y; +// Native instance de-allocatoion callback. +void _deleteVec(void* vector) { + free(vector); +} - pkReturnInstNative(vm, (void*)v3, OBJ_VECTOR); +// Vec2 'add' method. +void _vec2Add(PKVM* vm) { + Vector* self = (Vector*)pkGetSelf(vm); + // FIXME: + // Temproarly it's not possible to get vector from the args since the native + // interface is being refactored. Will be implemented soon. } // Register the 'Vector' module and it's functions. void registerVector(PKVM* vm) { - PkHandle* vector = pkNewModule(vm, "Vector"); + PkHandle* vector = pkNewModule(vm, "vector"); - pkModuleAddFunction(vm, vector, "new", _vecNew, 2); - pkModuleAddFunction(vm, vector, "add", _vecAdd, 2); + PkHandle* Vec2 = pkNewClass(vm, "Vec2", NULL /*Base Class*/, + vector, _newVec, _deleteVec); + pkClassAddMethod(vm, Vec2, "add", _vec2Add, 1); + pkReleaseHandle(vm, Vec2); + pkRegisterModule(vm, vector); pkReleaseHandle(vm, vector); } @@ -178,35 +89,20 @@ void registerVector(PKVM* vm) { /* POCKET VM CALLBACKS */ /*****************************************************************************/ -// Error report callback. -void reportError(PKVM* vm, PkErrorType type, - const char* file, int line, - const char* message) { - fprintf(stderr, "Error: %s\n", message); -} - -// print() callback to write stdout. -void stdoutWrite(PKVM* vm, const char* text) { +void stdoutCallback(PKVM* vm, const char* text) { fprintf(stdout, "%s", text); } - int main(int argc, char** argv) { PkConfiguration config = pkNewConfiguration(); - config.error_fn = reportError; - config.write_fn = stdoutWrite; - //config.read_fn = stdinRead; - config.inst_free_fn = freeObj; - config.inst_name_fn = getObjName; - config.inst_get_attrib_fn = objGetAttrib; - config.inst_set_attrib_fn = objSetAttrib; + config.write_fn = stdoutCallback; PKVM* vm = pkNewVM(&config); registerVector(vm); - PkStringPtr source = { code, NULL, NULL, 0, 0 }; - PkStringPtr path = { "./some/path/", NULL, NULL, 0, 0 }; + PkStringPtr source = { .string = code }; + PkStringPtr path = { .string = "./some/path/" }; PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/); pkFreeVM(vm); diff --git a/tests/random/linked_list.pk b/tests/random/linked_list.pk new file mode 100644 index 0000000..26e3c60 --- /dev/null +++ b/tests/random/linked_list.pk @@ -0,0 +1,67 @@ + +class Node + def _init(val) + self.val = val + self.next = null + end + + def _to_string() + return "(${self.val})" + end +end + +class LinkedList + def _init() + self.head = null + end + + def append(node) + if self.head == null + self.head = node + else + last = self.head + while last.next + last = last.next + end + last.next = node + end + end + + def reverse() + curr = self.head + prev = null; next = null + while curr + next = curr.next + curr.next = prev + prev = curr + curr = next + end + self.head = prev + end + + def _to_string() + ret = "" + next = self.head + while next + ret += next._to_string() + ret += " --> " + next = next.next + end + ret += "null" + return ret + end +end + +ll = LinkedList() +ll.append(Node(4)) +ll.append(Node(6)) +ll.append(Node(3)) +ll.append(Node(9)) + +## FIXME: No override supported at the moment. +print(ll._to_string()) + +ll.reverse() + +print(ll._to_string()) + diff --git a/tests/random/lisp_eval.pk b/tests/random/lisp_eval.pk index fc3b403..3493668 100644 --- a/tests/random/lisp_eval.pk +++ b/tests/random/lisp_eval.pk @@ -65,7 +65,7 @@ def eval(expr, ind) return [r[1] / r[2], r[3]] else if isnum(c) - val = str_ord(c) - str_ord('0') + val = ord(c) - ord('0') assert(0 <= val and val < 10) return [val, ind] @@ -93,7 +93,7 @@ end ## Return true if c in numeric. def isnum(c) - k = str_ord(c) - str_ord('0') + k = ord(c) - ord('0') ## TODO: k in 0..10 return (0 <= k and k < 10) end diff --git a/tests/tests.py b/tests/tests.py index ffeded7..637ed1e 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -26,8 +26,13 @@ TEST_SUITE = { "lang/functions.pk", "lang/import.pk", ), + + "Modules Test" : ( + "modules/math.pk", + ), "Random Scripts" : ( + "random/linked_list.pk", "random/lisp_eval.pk", "random/string_algo.pk", ),