diff --git a/scripts/run_tests.py b/scripts/run_tests.py index 8904858..6c8b354 100644 --- a/scripts/run_tests.py +++ b/scripts/run_tests.py @@ -33,7 +33,7 @@ TEST_SUITE = { "Modules Test" : ( "modules/dummy.pk", "modules/math.pk", - "modules/io.File.pk", + "modules/io.pk", ), "Random Scripts" : ( diff --git a/src/core/core.c b/src/core/core.c index a39ba95..cffaedf 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -79,7 +79,7 @@ static inline bool validateNumeric(PKVM* vm, Var var, double* value, static inline bool validateInteger(PKVM* vm, Var var, int64_t* value, const char* name) { if (isInteger(var, value)) return true; - VM_SET_ERROR(vm, stringFormat(vm, "$ must be a whole number.", name)); + VM_SET_ERROR(vm, stringFormat(vm, "$ must be an Integer.", name)); return false; } @@ -273,6 +273,87 @@ DEF(coreHelp, } } +// Add all the methods recursively to the lits used for generating a list of +// attributes for the 'dir()' function. +static void _collectMethods(PKVM* vm, List* list, Class* cls) { + if (cls == NULL) return; + + for (uint32_t i = 0; i < cls->methods.count; i++) { + listAppend(vm, list, + VAR_OBJ(newString(vm, cls->methods.data[i]->fn->name))); + } + _collectMethods(vm, list, cls->super_class); +} + +DEF(coreDir, + "dir(v:var) -> List[String]\n" + "It'll return all the elements of the variable [v]. If [v] is a module " + "it'll return the names of globals, functions, and classes. If it's an " + "instance it'll return all the attributes and methods.") { + + Var v = ARG(1); + switch (getVarType(v)) { + + case PK_NULL: + case PK_BOOL: + case PK_NUMBER: + case PK_STRING: + case PK_LIST: + case PK_MAP: + case PK_RANGE: + case PK_CLOSURE: + case PK_FIBER: { + List* list = newList(vm, 8); + vmPushTempRef(vm, &list->_super); // list. + _collectMethods(vm, list, getClass(vm, v)); + vmPopTempRef(vm); // list. + RET(VAR_OBJ(list)); + } + + case PK_MODULE: { + Module* m = (Module*) AS_OBJ(v); + List* list = newList(vm, 8); + vmPushTempRef(vm, &list->_super); // list. + for (uint32_t i = 0; i < m->globals.count; i++) { + Var name = m->constants.data[m->global_names.data[i]]; + ASSERT(IS_OBJ_TYPE(name, OBJ_STRING), OOPS); + listAppend(vm, list, name); + } + vmPopTempRef(vm); // list. + RET(VAR_OBJ(list)); + } break; + + case PK_CLASS: { + Class* cls = (Class*) AS_OBJ(v); + List* list = newList(vm, 8); + vmPushTempRef(vm, &list->_super); // list. + _collectMethods(vm, list, cls); + // TODO: if we add static variables to classes it should be + // added here as well. + vmPopTempRef(vm); // list. + RET(VAR_OBJ(list)); + } break; + + case PK_INSTANCE: { + Instance* inst = (Instance*) AS_OBJ(v); + List* list = newList(vm, 8); + vmPushTempRef(vm, &list->_super); // list. + for (uint32_t i = 0; i < inst->attribs->capacity; i++) { + Var key = (inst->attribs->entries + i)->key; + if (!IS_UNDEF(key)) { + ASSERT(IS_OBJ_TYPE(key, OBJ_STRING), OOPS); + listAppend(vm, list, key); + } + } + _collectMethods(vm, list, inst->cls); + vmPopTempRef(vm); // list. + RET(VAR_OBJ(list)); + } break; + } + + UNREACHABLE(); +} + DEF(coreAssert, "assert(condition:bool [, msg:string]) -> void\n" "If the condition is false it'll terminate the current fiber with the " @@ -539,6 +620,7 @@ static void initializeBuiltinFunctions(PKVM* vm) { (int)strlen(name), argc, fn, DOCSTRING(fn)); // General functions. INITIALIZE_BUILTIN_FN("help", coreHelp, -1); + INITIALIZE_BUILTIN_FN("dir", coreDir, 1); INITIALIZE_BUILTIN_FN("assert", coreAssert, -1); INITIALIZE_BUILTIN_FN("bin", coreBin, 1); INITIALIZE_BUILTIN_FN("hex", coreHex, 1); diff --git a/src/core/public.c b/src/core/public.c index a55caf5..d479b37 100644 --- a/src/core/public.c +++ b/src/core/public.c @@ -858,6 +858,74 @@ bool pkNewInstance(PKVM* vm, int cls, int index, int argc, int argv) { return !VM_HAS_ERROR(vm); } +void pkNewRange(PKVM* vm, int index, double first, double last) { + CHECK_FIBER_EXISTS(vm); + VALIDATE_SLOT_INDEX(index); + + SET_SLOT(index, VAR_OBJ(newRange(vm, first, last))); +} + +void pkNewList(PKVM* vm, int index) { + CHECK_FIBER_EXISTS(vm); + VALIDATE_SLOT_INDEX(index); + + SET_SLOT(index, VAR_OBJ(newList(vm, 0))); +} + +void pkNewMap(PKVM* vm, int index) { + CHECK_FIBER_EXISTS(vm); + VALIDATE_SLOT_INDEX(index); + + SET_SLOT(index, VAR_OBJ(newMap(vm))); +} + +bool pkListInsert(PKVM* vm, int list, int32_t index, int value) { + CHECK_FIBER_EXISTS(vm); + VALIDATE_SLOT_INDEX(list); + VALIDATE_SLOT_INDEX(value); + + ASSERT(IS_OBJ_TYPE(SLOT(list), OBJ_LIST), "Slot value wasn't a List"); + List* l = (List*) AS_OBJ(SLOT(list)); + if (index < 0) index = l->elements.count + index + 1; + + if (index < 0 || (uint32_t) index > l->elements.count) { + VM_SET_ERROR(vm, newString(vm, "Index out of bounds.")); + return false; + } + + listInsert(vm, l, (uint32_t) index, SLOT(value)); + return true; +} + +bool pkListPop(PKVM* vm, int list, int32_t index, int popped) { + CHECK_FIBER_EXISTS(vm); + VALIDATE_SLOT_INDEX(list); + if (popped >= 0) VALIDATE_SLOT_INDEX(popped); + + ASSERT(IS_OBJ_TYPE(SLOT(list), OBJ_LIST), "Slot value wasn't a List"); + List* l = (List*) AS_OBJ(SLOT(list)); + if (index < 0) index += l->elements.count; + + if (index < 0 || (uint32_t) index >= l->elements.count) { + VM_SET_ERROR(vm, newString(vm, "Index out of bounds.")); + return false; + } + + Var p = listRemoveAt(vm, l, index); + if (popped >= 0) SET_SLOT(popped, p); + return true; +} + +uint32_t pkListLength(PKVM* vm, int list) { + CHECK_FIBER_EXISTS(vm); + VALIDATE_SLOT_INDEX(list); + + ASSERT(IS_OBJ_TYPE(SLOT(list), OBJ_LIST), "Slot value wasn't a List"); + List* l = (List*)AS_OBJ(SLOT(list)); + + return l->elements.count; +} + bool pkCallFunction(PKVM* vm, int fn, int argc, int argv, int ret) { CHECK_FIBER_EXISTS(vm); ASSERT(IS_OBJ_TYPE(SLOT(fn), OBJ_CLOSURE), "Slot value wasn't a function"); diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 328af25..700bda5 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -391,24 +391,9 @@ PK_PUBLIC void pkSetSlotHandle(PKVM* vm, int index, PkHandle* handle); /* POCKET FFI */ /*****************************************************************************/ -// Get the attribute with [name] of the instance at the [instance] slot and -// place it at the [index] slot. Return true on success. -PK_PUBLIC bool pkGetAttribute(PKVM* vm, int instance, const char* name, - int index); - -// Set the attribute with [name] of the instance at the [instance] slot to -// the value at the [value] index slot. Return true on success. -PK_PUBLIC bool pkSetAttribute(PKVM* vm, int instance, - const char* name, int value); - // Place the [self] instance at the [index] slot. PK_PUBLIC void pkPlaceSelf(PKVM* vm, int index); -// Import a module with the [path] and place it at [index] slot. The path -// sepearation should be '/'. Example: to import module "foo.bar" the [path] -// should be "foo/bar". On failure, it'll set an error and return false. -PK_PUBLIC bool pkImportModule(PKVM* vm, const char* path, int index); - // Set the [index] slot's value as the class of the [instance]. PK_PUBLIC void pkGetClass(PKVM* vm, int instance, int index); @@ -420,6 +405,29 @@ PK_PUBLIC void pkGetClass(PKVM* vm, int instance, int index); // is the first argument slot's index. PK_PUBLIC bool pkNewInstance(PKVM* vm, int cls, int index, int argc, int argv); +// Create a new Range object and place it at [index] slot. +PK_PUBLIC void pkNewRange(PKVM* vm, int index, double first, double last); + +// Create a new List object and place it at [index] slot. +PK_PUBLIC void pkNewList(PKVM* vm, int index); + +// Create a new Map object and place it at [index] slot. +PK_PUBLIC void pkNewMap(PKVM* vm, int index); + +// Insert [value] to the [list] at the [index], if the index is less than zero, +// it'll count from backwards. ie. insert[-1] == insert[list.length]. +// Note that slot [list] must be a valid list otherwise it'll fail an +// assertion. +PK_PUBLIC bool pkListInsert(PKVM* vm, int list, int32_t index, int value); + +// Pop an element from [list] at [index] and place it at the [popped] slot, if +// [popped] is negative, the popped value will be ignored. +PK_PUBLIC bool pkListPop(PKVM* vm, int list, int32_t index, int popped); + +// Returns the length of the list at the [list] slot, it the slot isn't a list +// an assertion will fail. +PK_PUBLIC uint32_t pkListLength(PKVM* vm, int list); + // Calls a function at the [fn] slot, with [argc] argument where [argv] is the // slot of the first argument. [ret] is the slot index of the return value. if // [ret] < 0 the return value will be discarded. @@ -431,6 +439,21 @@ PK_PUBLIC bool pkCallFunction(PKVM* vm, int fn, int argc, int argv, int ret); PK_PUBLIC bool pkCallMethod(PKVM* vm, int instance, const char* method, int argc, int argv, int ret); +// Get the attribute with [name] of the instance at the [instance] slot and +// place it at the [index] slot. Return true on success. +PK_PUBLIC bool pkGetAttribute(PKVM* vm, int instance, const char* name, + int index); + +// Set the attribute with [name] of the instance at the [instance] slot to +// the value at the [value] index slot. Return true on success. +PK_PUBLIC bool pkSetAttribute(PKVM* vm, int instance, + const char* name, int value); + +// Import a module with the [path] and place it at [index] slot. The path +// sepearation should be '/'. Example: to import module "foo.bar" the [path] +// should be "foo/bar". On failure, it'll set an error and return false. +PK_PUBLIC bool pkImportModule(PKVM* vm, const char* path, int index); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/libs/std_path.c b/src/libs/std_path.c index 130c089..ee9a38b 100644 --- a/src/libs/std_path.c +++ b/src/libs/std_path.c @@ -189,16 +189,22 @@ char* pathResolveImport(PKVM* vm, const char* from, const char* path) { static inline bool pathIsFile(const char* path) { struct stat path_stat; - stat(path, &path_stat); + if (stat(path, &path_stat)) return false; // Error: might be path not exists. return (path_stat.st_mode & S_IFMT) == S_IFREG; } static inline bool pathIsDir(const char* path) { struct stat path_stat; - stat(path, &path_stat); + if (stat(path, &path_stat)) return false; // Error: might be path not exists. return (path_stat.st_mode & S_IFMT) == S_IFDIR; } +static inline time_t pathMtime(const char* path) { + struct stat path_stat; + if (stat(path, &path_stat)) return 0; // Error: might be path not exists. + return path_stat.st_mtime; +} + static inline bool pathIsExists(const char* path) { return access(path, F_OK) == 0; } @@ -339,6 +345,64 @@ DEF(_pathIsDir, "") { pkSetSlotBool(vm, 0, pathIsDir(path)); } +DEF(_pathListDir, "") { + + int argc = pkGetArgc(vm); + if (!pkCheckArgcRange(vm, argc, 0, 1)) return; + + const char* path = "."; + if (argc == 1) if (!pkValidateSlotString(vm, 1, &path, NULL)) return; + + if (!pathIsExists(path)) { + pkSetRuntimeErrorFmt(vm, "Path '%s' does not exists.", path); + return; + } + + // We create a new list at slot[0] and use slot[1] as our working memory + // overriding our parameter. + pkNewList(vm, 0); + + DIR* dirstream = opendir(path); + if (dirstream) { + struct dirent* dir; + while ((dir = readdir(dirstream)) != NULL) { + if (!strcmp(dir->d_name, ".")) continue; + if (!strcmp(dir->d_name, "..")) continue; + + pkSetSlotString(vm, 1, dir->d_name); + if (!pkListInsert(vm, 0, -1, 1)) return; + } + closedir(dirstream); + } +} + +// Returns the modified time of the file. +DEF(_pathMtime, "") { + const char* path; + if (!pkValidateSlotString(vm, 1, &path, NULL)) return; + + double mtime = 0; + struct stat path_stat; + if (stat(path, &path_stat) == 0) mtime = (double) path_stat.st_mtime; + pkSetSlotNumber(vm, 0, mtime); +} + +// Returns the file size in bytes. +DEF(_pathSize, "") { + const char* path; + + if (!pkValidateSlotString(vm, 1, &path, NULL)) return; + + struct stat path_stat; + if (stat(path, &path_stat) || ((path_stat.st_mode & S_IFMT) != S_IFREG)) { + pkSetRuntimeErrorFmt(vm, "Path '%s' wasn't a file.", path); + return; + } + + pkSetSlotNumber(vm, 0, path_stat.st_size); + +} + /*****************************************************************************/ /* MODULE REGISTER */ /*****************************************************************************/ @@ -358,6 +422,9 @@ void registerModulePath(PKVM* vm) { pkModuleAddFunction(vm, path, "exists", _pathExists, 1); pkModuleAddFunction(vm, path, "isfile", _pathIsFile, 1); pkModuleAddFunction(vm, path, "isdir", _pathIsDir, 1); + pkModuleAddFunction(vm, path, "listdir", _pathListDir, -1); + pkModuleAddFunction(vm, path, "mtime", _pathMtime, 1); + pkModuleAddFunction(vm, path, "size", _pathSize, 1); pkRegisterModule(vm, path); pkReleaseHandle(vm, path); diff --git a/tests/lang/builtin_fn.pk b/tests/lang/builtin_fn.pk index 31541c8..eb2cac6 100644 --- a/tests/lang/builtin_fn.pk +++ b/tests/lang/builtin_fn.pk @@ -1,6 +1,9 @@ ## TODO: add more test of built in functions. +assert("append" in dir([])) +assert("_repr" in dir(null)) + assert(list_join([1, 2, 3]) == "123") assert(list_join(["hello", " world"]) == "hello world") assert(list_join([[], []]) == "[][]") diff --git a/tests/modules/io.File.pk b/tests/modules/io.pk similarity index 84% rename from tests/modules/io.File.pk rename to tests/modules/io.pk index 6e04ee0..a628d93 100644 --- a/tests/modules/io.File.pk +++ b/tests/modules/io.pk @@ -16,9 +16,10 @@ def read_file() 'line4 : qux\n', ] - line = '' + line = ''; i = 0 while line = f.getline() - assert(line in LINES) + assert(LINES[i] == line) + i += 1 end f.close() diff --git a/tests/modules/path.pk b/tests/modules/path.pk new file mode 100644 index 0000000..c909441 --- /dev/null +++ b/tests/modules/path.pk @@ -0,0 +1,5 @@ +import path + +THIS_PATH = path.dirname(__file__) + +assert('path.pk' in path.listdir(THIS_PATH))