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
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')
-