From 8e04748f86f63a24440eb73aba28a6a617163df4 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Mon, 23 May 2022 12:22:45 +0530 Subject: [PATCH] some builtin class methods were added - check a string contains another with `in` keyword - Number: isint, isbyte - String: find, replace, split, startswith, endswith, strip - Map: clear, has, get, pop - list: insert, clear, find, pop - removed str_sub builtin function as its redunent since we have range slice over strings. - moved lower, upper attributes of strings as methods --- docs/README.md | 2 +- docs/{tryonline.html => try-online.html} | 0 scripts/run_tests.py | 1 - src/core/buffers.h | 2 +- src/core/core.c | 388 ++++++++++++++++++----- src/core/value.c | 154 ++++++++- src/core/value.h | 15 +- tests/lang/builtin_ty.pk | 115 +++++++ tests/lang/core.pk | 50 --- 9 files changed, 590 insertions(+), 137 deletions(-) rename docs/{tryonline.html => try-online.html} (100%) delete mode 100644 tests/lang/core.pk diff --git a/docs/README.md b/docs/README.md index 3b8fccd..c38385a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,7 +11,7 @@ which is less than 300KB and the language itself can be compile

- Try Online + Try Online GitHub
diff --git a/docs/tryonline.html b/docs/try-online.html similarity index 100% rename from docs/tryonline.html rename to docs/try-online.html diff --git a/scripts/run_tests.py b/scripts/run_tests.py index 2c099d3..b9d79d1 100644 --- a/scripts/run_tests.py +++ b/scripts/run_tests.py @@ -24,7 +24,6 @@ TEST_SUITE = { "lang/builtin_ty.pk", "lang/class.pk", "lang/closure.pk", - "lang/core.pk", "lang/controlflow.pk", "lang/fibers.pk", "lang/functions.pk", diff --git a/src/core/buffers.h b/src/core/buffers.h index 3b30a66..1d4450e 100644 --- a/src/core/buffers.h +++ b/src/core/buffers.h @@ -71,7 +71,7 @@ if (self->capacity < size) { \ int capacity = utilPowerOf2Ceil((int)size); \ if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; \ - self->data = (m_type*)vmRealloc(vm, self->data, \ + self->data = (m_type*) vmRealloc(vm, self->data, \ self->capacity * sizeof(m_type), capacity * sizeof(m_type)); \ self->capacity = capacity; \ } \ diff --git a/src/core/core.c b/src/core/core.c index 928fe04..7178b38 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -489,34 +489,6 @@ DEF(coreExit, exit((int)value); } -// String functions. -// ----------------- - -DEF(coreStrSub, - "str_sub(str:string, pos:num, len:num) -> string\n" - "Returns a substring from a given string supplied. In addition, " - "the position and length of the substring are provided when this " - "function is called. For example: `str_sub(str, pos, len)`.") { - - String* str; - int64_t pos, len; - - if (!validateArgString(vm, 1, &str)) return; - if (!validateInteger(vm, ARG(2), &pos, "Argument 2")) return; - if (!validateInteger(vm, ARG(3), &len, "Argument 3")) return; - - if (pos < 0 || str->length < pos) - RET_ERR(newString(vm, "Index out of range.")); - - if (str->length < pos + len) - RET_ERR(newString(vm, "Substring length exceeded the limit.")); - - // Edge case, empty string. - if (len == 0) RET(VAR_OBJ(newStringLength(vm, "", 0))); - - RET(VAR_OBJ(newStringLength(vm, str->data + pos, (uint32_t)len))); -} - // List functions. // --------------- @@ -557,21 +529,6 @@ DEF(coreListJoin, RET(VAR_OBJ(str)); } -// Map functions. -// -------------- - -DEF(coreMapRemove, - "map_remove(self:map, key:var) -> var\n" - "Remove the [key] from the map [self] and return it's value if the key " - "exists, otherwise it'll return null.") { - - Map* map; - if (!validateArgMap(vm, 1, &map)) return; - Var key = ARG(2); - - RET(mapRemoveKey(vm, map, key)); -} - static void initializeBuiltinFN(PKVM* vm, Closure** bfn, const char* name, int length, int arity, pkNativeFn ptr, const char* docstring) { @@ -601,19 +558,10 @@ static void initializeBuiltinFunctions(PKVM* vm) { 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); - // List functions. INITIALIZE_BUILTIN_FN("list_append", coreListAppend, 2); INITIALIZE_BUILTIN_FN("list_join", coreListJoin, 1); - // Map functions. - INITIALIZE_BUILTIN_FN("map_remove", coreMapRemove, 2); - #undef INITIALIZE_BUILTIN_FN } @@ -850,23 +798,293 @@ DEF(_numberIsbyte, RET(VAR_BOOL((floor(n) == n) && (0x00 <= n && n <= 0xff))); } +DEF(_stringFind, + "String.find(sub:String[, start:Number=0]) -> Number\n" + "Returns the first index of the substring [sub] found from the " + "[start] index") { + + if (!pkCheckArgcRange(vm, ARGC, 1, 2)) return; + + String* sub; + if (!validateArgString(vm, 1, &sub)) return; + + int64_t start = 0; + if (ARGC == 2) { + if (!validateInteger(vm, ARG(2), &start, "Argument 1")) return; + } + + String* self = (String*) AS_OBJ(SELF); + + if (self->length <= start) { + RET(VAR_NUM((double) -1)); + } + + // FIXME: Pocketlang strings can contain \x00 ie. NULL byte and strstr + // doesn't support them. However pocketlang strings always ends with a null + // byte so the match won't go outside of the string. + const char* match = strstr(self->data + start, sub->data); + + if (match == NULL) RET(VAR_NUM((double) -1)); + + ASSERT_INDEX(match - self->data, self->capacity); + RET(VAR_NUM((double) (match - self->data))); +} + +DEF(_stringReplace, + "String.replace(old:Sttring, new:String[, count:Number=-1]) -> String\n" + "Returns a copy of the string where [count] occurrence of the substring " + "[old] will be replaced with [new]. If [count] == -1 all the occurrence " + "will be replaced.") { + + if (!pkCheckArgcRange(vm, ARGC, 2, 3)) return; + + String *old, *new_; + if (!validateArgString(vm, 1, &old)) return; + if (!validateArgString(vm, 2, &new_)) return; + + String* self = (String*) AS_OBJ(SELF); + + int64_t count = -1; + if (ARGC == 3) { + if (!validateInteger(vm, ARG(3), &count, "Argument 3")) return; + if (count < 0 && count != -1) { + RET_ERR(newString(vm, "count should either be >= 0 or -1")); + } + } + + RET(VAR_OBJ(stringReplace(vm, self, old, new_, (int32_t) count))); +} + +DEF(_stringSplit, + "String.split(sep:String) -> List\n" + "Split the string into a list of string seperated by [sep] delimeter.") { + + String* sep; + if (!validateArgString(vm, 1, &sep)) return; + + if (sep->length == 0) { + RET_ERR(newString(vm, "Cannot use empty string as a seperator.")); + } + + RET(VAR_OBJ(stringSplit(vm, (String*)AS_OBJ(SELF), sep))); +} + +DEF(_stringStrip, + "String.strip() -> String\n" + "Returns a copy of the string where the leading and trailing whitespace " + "removed.") { + RET(VAR_OBJ(stringStrip(vm, (String*) AS_OBJ(SELF)))); +} + +DEF(_stringLower, + "String.lower() -> String\n" + "Returns a copy of the string where all the characters are converted to " + "lower case letters.") { + RET(VAR_OBJ(stringLower(vm, (String*) AS_OBJ(SELF)))); +} + +DEF(_stringUpper, + "String.lower() -> String\n" + "Returns a copy of the string where all the characters are converted to " + "upper case letters.") { + RET(VAR_OBJ(stringUpper(vm, (String*) AS_OBJ(SELF)))); +} + +DEF(_stingStartswith, + "String.startswith(prefix: String | List) -> Bool\n" + "Returns true if the string starts the specified prefix.") { + + Var prefix = ARG(1); + String* self = (String*) AS_OBJ(SELF); + + if (IS_OBJ_TYPE(prefix, OBJ_STRING)) { + String* pre = (String*) AS_OBJ(prefix); + if (pre->length > self->length) RET(VAR_FALSE); + RET(VAR_BOOL((strncmp(self->data, pre->data, pre->length) == 0))); + + } else if (IS_OBJ_TYPE(prefix, OBJ_LIST)) { + + List* prefixes = (List*) AS_OBJ(prefix); + for (uint32_t i = 0; i < prefixes->elements.count; i++) { + Var pre_var = prefixes->elements.data[i]; + if (!IS_OBJ_TYPE(pre_var, OBJ_STRING)) { + RET_ERR(newString(vm, "Expected a String for prefix.")); + } + String* pre = (String*) AS_OBJ(pre_var); + if (pre->length > self->length) RET(VAR_FALSE); + if (strncmp(self->data, pre->data, pre->length) == 0) RET(VAR_TRUE); + } + RET(VAR_FALSE); + + } else { + RET_ERR(newString(vm, "Expected a String or a List of prifiexes.")); + } +} + +DEF(_stingEndswith, + "String.endswith(suffix: String | List) -> Bool\n" + "Returns true if the string ends with the specified suffix.") { + + Var suffix = ARG(1); + String* self = (String*)AS_OBJ(SELF); + + if (IS_OBJ_TYPE(suffix, OBJ_STRING)) { + String* suf = (String*)AS_OBJ(suffix); + if (suf->length > self->length) RET(VAR_FALSE); + + const char* start = (self->data + (self->length - suf->length)); + RET(VAR_BOOL((strncmp(start, suf->data, suf->length) == 0))); + + } else if (IS_OBJ_TYPE(suffix, OBJ_LIST)) { + + List* suffixes = (List*)AS_OBJ(suffix); + for (uint32_t i = 0; i < suffixes->elements.count; i++) { + Var suff_var = suffixes->elements.data[i]; + if (!IS_OBJ_TYPE(suff_var, OBJ_STRING)) { + RET_ERR(newString(vm, "Expected a String for suffix.")); + } + String* suf = (String*)AS_OBJ(suff_var); + if (suf->length > self->length) RET(VAR_FALSE); + + const char* start = (self->data + (self->length - suf->length)); + if (strncmp(start, suf->data, suf->length) == 0) RET(VAR_TRUE); + } + RET(VAR_FALSE); + + } else { + RET_ERR(newString(vm, "Expected a String or a List of suffixes.")); + } +} + DEF(_listAppend, "List.append(value:var) -> List\n" - "Append the [value] to the list and return the list.") { + "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)); + listAppend(vm, ((List*) AS_OBJ(SELF)), ARG(1)); RET(SELF); } +DEF(_listInsert, + "List.insert(index:Number, value:var) -> null\n" + "Insert the element at the given index. The index should be " + "0 <= index <= list.length.") { + + List* self = (List*)AS_OBJ(SELF); + + int64_t index; + if (!validateInteger(vm, ARG(1), &index, "Argument 1")) return; + + if (index < 0 || index > self->elements.count) { + RET_ERR(newString(vm, "List.insert index out of bounds.")); + } + + listInsert(vm, self, (uint32_t) index, ARG(2)); +} + +DEF(_listPop, + "List.pop(index=-1) -> var\n" + "Removes the last element of the list and return it.") { + + ASSERT(IS_OBJ_TYPE(SELF, OBJ_LIST), OOPS); + List* self = (List*) AS_OBJ(SELF); + + if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return; + + if (self->elements.count == 0) { + RET_ERR(newString(vm, "Cannot pop from an empty list.")); + } + + int64_t index = -1; + if (ARGC == 1) { + if (!validateInteger(vm, ARG(1), &index, "Argument 1")) return; + } + if (index < 0) index = self->elements.count + index; + + if (index < 0 || index >= self->elements.count) { + RET_ERR(newString(vm, "List.pop index out of bounds.")); + } + RET(listRemoveAt(vm, self, (uint32_t) index)); +} + +DEF(_listFind, + "List.find(value:var) -> Number\n" + "Find the value and return its index. If the vlaue not exists " + "it'll return -1.") { + + ASSERT(IS_OBJ_TYPE(SELF, OBJ_LIST), OOPS); + List* self = (List*)AS_OBJ(SELF); + + Var* it = self->elements.data; + if (it == NULL) RET(VAR_NUM(-1)); // Empty list. + + for (; it < self->elements.data + self->elements.count; it++) { + if (isValuesEqual(*it, ARG(1))) { + RET(VAR_NUM((double) (it - self->elements.data))); + } + } + + RET(VAR_NUM(-1)); +} + +DEF(_listClear, + "List.clear() -> null\n" + "Removes all the entries in the list.") { + listClear(vm, (List*) AS_OBJ(SELF)); +} + +DEF(_mapClear, + "Map.clear() -> null\n" + "Removes all the entries in the map.") { + Map* self = (Map*) AS_OBJ(SELF); + mapClear(vm, self); +} + +DEF(_mapGet, + "Map.get(key:var, default=null) -> var\n" + "Returns the key if its in the map, otherwise the default value will " + "be returned.") { + + if (!pkCheckArgcRange(vm, ARGC, 1, 2)) return; + + Var default_ = (ARGC == 1) ? VAR_NULL : ARG(2); + + Map* self = (Map*) AS_OBJ(SELF); + + Var value = mapGet(self, ARG(1)); + if (IS_UNDEF(value)) RET(default_); + RET(value); +} + +DEF(_mapHas, + "Map.has(key:var) -> Bool\n" + "Returns true if the key exists.") { + + Map* self = (Map*)AS_OBJ(SELF); + Var value = mapGet(self, ARG(1)); + RET(VAR_BOOL(!IS_UNDEF(value))); +} + +DEF(_mapPop, + "Map.pop(key:var) -> var\n" + "Pops the value at the key and return it.") { + + Map* self = (Map*)AS_OBJ(SELF); + Var value = mapRemoveKey(vm, self, ARG(1)); + if (IS_UNDEF(value)) { + RET_ERR(stringFormat(vm, "Key '@' does not exists.", toRepr(vm, ARG(1)))); + } + RET(value); +} + 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); + Fiber* self = (Fiber*) AS_OBJ(SELF); // Switch fiber and start execution. New fibers are marked as running in // either it's stats running with vmRunFiber() or here -- inserting a @@ -884,7 +1102,7 @@ DEF(_fiberResume, "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); + Fiber* self = (Fiber*) AS_OBJ(SELF); if (!pkCheckArgcRange(vm, ARGC, 0, 1)) return; @@ -948,12 +1166,32 @@ static void initializePrimitiveClasses(PKVM* vm) { } while (false) // TODO: write docs. - ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1); - ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0); - ADD_METHOD(PK_NUMBER, "isbyte", _numberIsbyte, 0); - ADD_METHOD(PK_LIST, "append", _listAppend, 1); - ADD_METHOD(PK_FIBER, "run", _fiberRun, -1); - ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1); + ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1); + ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0); + ADD_METHOD(PK_NUMBER, "isbyte", _numberIsbyte, 0); + + ADD_METHOD(PK_STRING, "strip", _stringStrip, 0); + ADD_METHOD(PK_STRING, "lower", _stringLower, 0); + ADD_METHOD(PK_STRING, "upper", _stringUpper, 0); + ADD_METHOD(PK_STRING, "find", _stringFind, -1); + ADD_METHOD(PK_STRING, "replace", _stringReplace, -1); + ADD_METHOD(PK_STRING, "split", _stringSplit, 1); + ADD_METHOD(PK_STRING, "startswith", _stingStartswith, 1); + ADD_METHOD(PK_STRING, "endswith", _stingEndswith, 1); + + ADD_METHOD(PK_LIST, "clear", _listClear, 0); + ADD_METHOD(PK_LIST, "find", _listFind, 1); + ADD_METHOD(PK_LIST, "append", _listAppend, 1); + ADD_METHOD(PK_LIST, "pop", _listPop, -1); + ADD_METHOD(PK_LIST, "insert", _listInsert, 2); + + ADD_METHOD(PK_MAP, "clear", _mapClear, 0); + ADD_METHOD(PK_MAP, "get", _mapGet, -1); + ADD_METHOD(PK_MAP, "has", _mapHas, 1); + ADD_METHOD(PK_MAP, "pop", _mapPop, 1); + + ADD_METHOD(PK_FIBER, "run", _fiberRun, -1); + ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1); #undef ADD_METHOD } @@ -1347,11 +1585,15 @@ bool varContains(PKVM* vm, Var elem, Var container) { return false; } - String* sub = (String*)AS_OBJ(elem); - String* str = (String*)AS_OBJ(container); + String* sub = (String*) AS_OBJ(elem); + String* str = (String*) AS_OBJ(container); if (sub->length > str->length) return false; - TODO; + // FIXME: strstr function can only be used for null terminated strings. + // But pocket lang strings can contain any byte values including a null + // byte \x00. + const char* match = strstr(str->data, sub->data); + return match != NULL; } break; @@ -1419,15 +1661,6 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { case CHECK_HASH("length", 0x83d03615): return VAR_NUM((double)(str->length)); - - case CHECK_HASH("lower", 0xb51d04ba): - return VAR_OBJ(stringLower(vm, str)); - - case CHECK_HASH("upper", 0xa8c6a47): - return VAR_OBJ(stringUpper(vm, str)); - - case CHECK_HASH("strip", 0xfd1b18d1): - return VAR_OBJ(stringStrip(vm, str)); } } break; @@ -1441,10 +1674,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { } break; case OBJ_MAP: { - // map = { "foo" : 42, "can't access" : 32 } - // val = map.foo ## <-- This should be error - // Only the map's attributes are accessed here. - TODO; + // TODO: } break; case OBJ_RANGE: { @@ -1509,7 +1739,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { } break; case OBJ_CLASS: - TODO; + // TODO: break; case OBJ_INST: { diff --git a/src/core/value.c b/src/core/value.c index 58ecf63..cec132b 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -23,6 +23,9 @@ // capacity by the GROW_FACTOR. #define GROW_FACTOR 2 +#define _MAX(a,b) ((a) > (b) ? (a) : (b)) +#define _MIN(a,b) ((a) < (b) ? (a) : (b)) + // Buffer implementations. DEFINE_BUFFER(Uint, uint32_t) DEFINE_BUFFER(Byte, uint8_t) @@ -544,7 +547,7 @@ String* stringLower(PKVM* vm, String* self) { // Start where the first upper case letter found. char* _c = lower->data + (c - self->data); - for (; *_c != '\0'; _c++) *_c = (char)tolower(*_c); + for (; *_c != '\0'; _c++) *_c = (char) tolower(*_c); // Since the string is modified re-hash it. lower->hash = utilHashString(lower->data); @@ -565,7 +568,7 @@ String* stringUpper(PKVM* vm, String* self) { // Start where the first lower case letter found. char* _c = upper->data + (c - self->data); - for (; *_c != '\0'; _c++) *_c = (char)toupper(*_c); + for (; *_c != '\0'; _c++) *_c = (char) toupper(*_c); // Since the string is modified re-hash it. upper->hash = utilHashString(upper->data); @@ -608,6 +611,139 @@ String* stringStrip(PKVM* vm, String* self) { return newStringLength(vm, start, (uint32_t)(end - start + 1)); } +String* stringReplace(PKVM* vm, String* self, + String* old, String* new_, int32_t count) { + // The algorithm: + // + // We'll first deduce the maximum possible occurence of the old string. + // + // max_count = floor(self.length / old.length) + // + // If count == -1 we'll set it to max_count, otherwise we can update our + // count as follows. + // + // count = min(count, min_count) + // + // Now we know the maximum possible length of the new string. + // + // length = max(self.length, + // self.length + (new.length - old.length) * count) + // + // Finally we use "C" functions strstr() and memcpy() to find and replace. + + // FIXME: this function use strstr() which only supports null terminated + // strings, if our string contain any \x00 the replacement will stop. + + ASSERT(count >= 0 || count == -1, OOPS); + + // Optimize case. + if (self->length == 0 || old->length == 0 || count == 0) return self; + if (IS_STR_EQ(old, new_)) return self; + + int32_t max_count = self->length / old->length; + count = (count == -1) + ? max_count + : _MIN(count, max_count); + + // TODO: New length can be overflow if the string is too large + // we should handle it here. + + uint32_t length = _MAX(self->length, + self->length + (new_->length - old->length) * count); + + String* replaced = self; // Will be allocated if any match found. + int32_t replacedc = 0; // Replaced count so far. + + const char* s = self->data; // Source: current position in self. + char* d = NULL; // Destination pointer in replaced. + + do { + if (replacedc == count) break; + + const char* match = strstr(s, old->data); + if (match == NULL) break; + + // Note that since we're not allocating anything else here, this string + // doesn't needs to pushed to VM's temp references. + if (replacedc == 0) { + replaced = newStringLength(vm, "", length); + d = replaced->data; + } + + // Copy everything from [s] till [match]. + memcpy(d, s, match - s); + d += match - s; + s = match; + + // Copy the replace string. + memcpy(d, new_->data, new_->length); + d += new_->length; + s += old->length; + replacedc++; + + } while (true); + + // Copy the rest of the string from [s] till the end. + if (d != NULL) { + uint32_t tail_length = self->length - (int32_t) (s - self->data); + memcpy(d, s, tail_length); + d += tail_length; + + // Update the string. + replaced->length = (int32_t) (d - replaced->data); + ASSERT(replaced->length < replaced->capacity, OOPS); + replaced->data[replaced->length] = '\0'; + replaced->hash = utilHashString(replaced->data); + + } else { + ASSERT(self == replaced, OOPS); + } + + return replaced; +} + +List* stringSplit(PKVM* vm, String* self, String* sep) { + + ASSERT(sep->length != 0, OOPS); + + const char* s = self->data; // Current position in self. + + List* list = newList(vm, 0); + vmPushTempRef(vm, &list->_super); // list. + do { + const char* match = strstr(s, sep->data); + if (match == NULL) { + + // Add the tail string from [s] till the end. Optimize case: if the + // string doesn't have any match we can reuse self. + if (s == self->data) { + ASSERT(list->elements.count == 0, OOPS); + listAppend(vm, list, VAR_OBJ(self)); + + } else { + String* tail = newStringLength(vm, s, + (uint32_t)(self->length - (s - self->data))); + vmPushTempRef(vm, &tail->_super); // tail. + listAppend(vm, list, VAR_OBJ(tail)); + vmPopTempRef(vm); // tail. + } + + break; // We're done. + } + + String* split = newStringLength(vm, s, (uint32_t)(match - s)); + vmPushTempRef(vm, &split->_super); // split. + listAppend(vm, list, VAR_OBJ(split)); + vmPopTempRef(vm); // split. + + s = match + sep->length; + + } while (true); + vmPopTempRef(vm); // list. + + return list; +} + String* stringFormat(PKVM* vm, const char* fmt, ...) { va_list arg_list; @@ -698,6 +834,8 @@ void listInsert(PKVM* vm, List* self, uint32_t index, Var value) { } Var listRemoveAt(PKVM* vm, List* self, uint32_t index) { + ASSERT_INDEX(index, self->elements.count); + Var removed = self->elements.data[index]; if (IS_OBJ(removed)) vmPushTempRef(vm, AS_OBJ(removed)); @@ -708,7 +846,7 @@ Var listRemoveAt(PKVM* vm, List* self, uint32_t index) { // Shrink the size if it's too much excess. if (self->elements.capacity / GROW_FACTOR >= self->elements.count) { - self->elements.data = (Var*)vmRealloc(vm, self->elements.data, + self->elements.data = (Var*) vmRealloc(vm, self->elements.data, sizeof(Var) * self->elements.capacity, sizeof(Var) * self->elements.capacity / GROW_FACTOR); self->elements.capacity /= GROW_FACTOR; @@ -720,6 +858,10 @@ Var listRemoveAt(PKVM* vm, List* self, uint32_t index) { return removed; } +void listClear(PKVM* vm, List* self) { + pkVarBufferClear(&self->elements, vm); +} + List* listAdd(PKVM* vm, List* l1, List* l2) { // Optimize end case. @@ -898,7 +1040,7 @@ void mapClear(PKVM* vm, Map* self) { Var mapRemoveKey(PKVM* vm, Map* self, Var key) { MapEntry* entry; - if (!_mapFindEntry(self, key, &entry)) return VAR_NULL; + if (!_mapFindEntry(self, key, &entry)) return VAR_UNDEFINED; // Set the key as VAR_UNDEFINED to mark is as an available slow and set it's // value to VAR_TRUE for tombstone. @@ -1609,3 +1751,7 @@ bool toBool(Var v) { UNREACHABLE(); return false; } + +// Undefining for amalgamation to let othre libraries also define _MIN, _MAX. +#undef _MAX +#undef _MIN diff --git a/src/core/value.h b/src/core/value.h index 8489585..144e1ad 100644 --- a/src/core/value.h +++ b/src/core/value.h @@ -643,6 +643,16 @@ String* stringUpper(PKVM* vm, String* self); // If the string is already trimmed it'll return the same string. String* stringStrip(PKVM* vm, String* self); +// Replace the [count] occurence of the [old] string with [new_] string. If +// [count] == -1. It'll replace all the occurences. If nothing is replaced +// the original string will be returned. +String* stringReplace(PKVM* vm, String* self, + String* old, String* new_, int count); + +// Split the string into a list of string separated by [sep]. String [sep] must +// not be of length 0 otherwise an assertion will fail. +List* stringSplit(PKVM* vm, String* self, String* sep); + // Creates a new string from the arguments. This is intended for internal // usage and it has 2 formated characters (just like wren does). // $ - a C string @@ -672,6 +682,9 @@ void listInsert(PKVM* vm, List* self, uint32_t index, Var value); // Remove and return element at [index]. Var listRemoveAt(PKVM* vm, List* self, uint32_t index); +// Remove all the elements of the list. +void listClear(PKVM* vm, List* self); + // Create a new list by joining the 2 given list and return the result. List* listAdd(PKVM* vm, List* l1, List* l2); @@ -686,7 +699,7 @@ void mapSet(PKVM* vm, Map* self, Var key, Var value); void mapClear(PKVM* vm, Map* self); // Remove the [key] from the map. If the key exists return it's value -// otherwise return VAR_NULL. +// otherwise return VAR_UNDEFINED. Var mapRemoveKey(PKVM* vm, Map* self, Var key); // Returns true if the fiber has error, and if it has any the fiber cannot be diff --git a/tests/lang/builtin_ty.pk b/tests/lang/builtin_ty.pk index d581854..45e668a 100644 --- a/tests/lang/builtin_ty.pk +++ b/tests/lang/builtin_ty.pk @@ -5,6 +5,10 @@ ## Number ############################################################################### +assert(hex(12648430) == '0xc0ffee') +assert(hex(255) == '0xff' and hex(10597059) == '0xa1b2c3') +assert(hex(-4294967295) == '-0xffffffff') ## the largest. + sum = 0 5.times fn(i) sum += i @@ -15,10 +19,27 @@ assert(Number("-.23e+6") == -.23e6) assert(Number("0b10100101") == 0b10100101) assert(Number("-0Xabcdef123") == -0xabcdef123) +############################################################################### +## RANGE +############################################################################### + +r = 1..5 +assert(r.as_list == [1, 2, 3, 4]) +assert(r.first == 1) +assert(r.last == 5) + ############################################################################### ## STRING ############################################################################### +assert(''.length == 0) +assert('test'.length == 4) +assert(''.lower() == '' and ''.upper() == '') +assert('already+lower '.lower() == 'already+lower ') +assert('ALREADY+UPPER '.upper() == 'ALREADY+UPPER ') +assert('tEST+InG'.lower() == 'test+ing') +assert('tEST+InG'.upper() == 'TEST+ING') + s = "foobar" assert(s[-3] == 'b') @@ -33,6 +54,39 @@ assert("abcd"[-1..-3] == "dcb") assert(""[0..-1] == "") assert(""[-1..0] == "") +assert(' trim '.strip() == 'trim') +assert(''.strip() == '') + +lorem = "\ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor +incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nost +rud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis +aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fug +iat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in cu +lpa qui officia deserunt mollit anim id est laborum." + +assert("sint" in lorem) +ut = lorem.find('ut'); assert(ut != -1) +assert(lorem[ut+3 .. ut+8] == 'labore') + +assert("foobar".replace("foo", "baz") == "bazbar") +assert("abcdefabdcefabaeef".replace("ab", "") == "cdefdcefaeef") +assert("xx.xx.xx.xx".replace("xx", "yy", 2) == "yy.yy.xx.xx") +assert("aaaaaaaaaaaaa".replace('a', '!b', 5) == "!b!b!b!b!baaaaaaaa") +assert("aaaaaaaaaaaaa".replace('a', 'b', 1000) == "bbbbbbbbbbbbb") + +assert("a,b,c".split(',') == ['a', 'b', 'c']) +assert('a,'.split(',') == ['a', '']) +assert('foo!!bar!!baz'.split('!!') == ['foo', 'bar', 'baz']) + +assert('abcdef'.startswith('abc')) +assert(not 'x'.startswith('abc')) +assert(not 'pqr'.startswith(['abc', 'def', 'gh'])) + +assert('foobar'.endswith('obar')) +assert(not 'foobar'.endswith('rfoo')) +assert('image.png'.endswith(['.jpg', '.jpeg', '.png', '.ppm'])) + ############################################################################### ## LIST ############################################################################### @@ -52,6 +106,67 @@ assert(l[-1..-3] == [4, 3, 2]) assert([][0..0] == []) assert([][-1..-1] == []) +assert([].length == 0) +assert([1, 2, 3].length == 3) + +assert(['a'].append('b') == ['a', 'b']) + +assert(l.find(2) == 1) +assert(l.find(4) == 3) +assert(l.find(7) == -1) + +assert(l.pop(-2) == 3 and l == [1, 2, 4]) +assert(l.pop() == 4) +assert(l.pop() == 2) +assert(l == [1]) + +l = [] +l.insert(0, 'l') +l.insert(1, 'l') +l.insert(0, 'h') +l.insert(3, 'o') +l.insert(1, 'e') +assert(l == ['h', 'e', 'l', 'l', 'o']) + +############################################################################### +## MAP +############################################################################### + +swtich = { + 0 : fn return 1 end, + 1 : fn return 2 end, + 2 : fn return 3 end, + 3 : fn return 4 end, + 4 : fn return 5 end, +} + +for i in 0..5 + assert(swtich.get(i)() == i+1) +end + +vars = { + 'os' : 'mint', + 'version' : '20.3', + 'name' : 'Una', +} + +assert(vars.get('os') == 'mint') +assert(vars.get('family', 'linux') == 'linux') +assert(vars.has('version')) +assert(!vars.has('build')) + + +############################################################################### +## FUNCTIONS +############################################################################### + +assert(print.arity == -1) +assert(hex.arity == 1) +assert(fn(a, b)end .arity == 2) +assert(print.name == "print") +def foo(p1, p2, p3) end +assert(foo.name == "foo") +assert(foo.arity == 3) ## If we got here, that means all test were passed. print('All TESTS PASSED') diff --git a/tests/lang/core.pk b/tests/lang/core.pk deleted file mode 100644 index a0913f6..0000000 --- a/tests/lang/core.pk +++ /dev/null @@ -1,50 +0,0 @@ -## Core builtin functions and attribute tests. - -assert(hex(12648430) == '0xc0ffee') -assert(hex(255) == '0xff' and hex(10597059) == '0xa1b2c3') -assert(hex(-4294967295) == '-0xffffffff') ## the largest. - -## string attributes. -assert(''.length == 0) -assert('test'.length == 4) -assert(''.lower == '' and ''.upper == '') -assert('already+lower '.lower == 'already+lower ') -assert('ALREADY+UPPER '.upper == 'ALREADY+UPPER ') -assert('tEST+InG'.lower == 'test+ing') -assert('tEST+InG'.upper == 'TEST+ING') - -assert(' trim '.strip == 'trim') -assert(''.strip == '') - -## List attribute -assert([].length == 0) -assert([1, 2, 3].length == 3) - -## Function -assert(print.arity == -1) -assert(hex.arity == 1) -assert(fn(a, b)end .arity == 2) -assert(print.name == "print") -def foo(p1, p2, p3) end -assert(foo.name == "foo") -assert(foo.arity == 3) - -## String functions -assert(str_sub('c programming', 2, 11) == 'programming') -assert(str_sub('foobarbaz', 2, 3) == 'oba') -assert(str_sub('abcdef', 1, 2) == 'bc') -assert(str_sub('I am a boy', 0, 4) == 'I am') -assert(str_sub('programming', 2, 0) == '') -assert(str_sub('foobar', 5, 0) == '') -assert(str_sub('foobar', 0, 6) == 'foobar') -assert(str_sub('', 0, 0) == '') - -## range -r = 1..5 -assert(r.as_list == [1, 2, 3, 4]) -assert(r.first == 1) -assert(r.last == 5) - -# If we got here, that means all test were passed. -print('All TESTS PASSED') -