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
This commit is contained in:
Thakee Nathees 2022-05-23 12:22:45 +05:30
parent b2c68d6064
commit 8e04748f86
9 changed files with 590 additions and 137 deletions

View File

@ -11,7 +11,7 @@ which is less than <strong>300KB</strong> and the language itself can be compile
</p> </p>
<div class="center"> <div class="center">
<a class="button" target="_blank" href="./tryonline.html"> Try Online </a> <a class="button" target="_blank" href="./try-online.html"> Try Online </a>
<a class="button" target="_blank" href="https://www.github.com/ThakeeNathees/pocketlang/"> GitHub </a> <a class="button" target="_blank" href="https://www.github.com/ThakeeNathees/pocketlang/"> GitHub </a>
</div> </div>

View File

@ -24,7 +24,6 @@ TEST_SUITE = {
"lang/builtin_ty.pk", "lang/builtin_ty.pk",
"lang/class.pk", "lang/class.pk",
"lang/closure.pk", "lang/closure.pk",
"lang/core.pk",
"lang/controlflow.pk", "lang/controlflow.pk",
"lang/fibers.pk", "lang/fibers.pk",
"lang/functions.pk", "lang/functions.pk",

View File

@ -489,34 +489,6 @@ DEF(coreExit,
exit((int)value); 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. // List functions.
// --------------- // ---------------
@ -557,21 +529,6 @@ DEF(coreListJoin,
RET(VAR_OBJ(str)); 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, static void initializeBuiltinFN(PKVM* vm, Closure** bfn, const char* name,
int length, int arity, pkNativeFn ptr, int length, int arity, pkNativeFn ptr,
const char* docstring) { const char* docstring) {
@ -601,19 +558,10 @@ static void initializeBuiltinFunctions(PKVM* vm) {
INITIALIZE_BUILTIN_FN("input", coreInput, -1); INITIALIZE_BUILTIN_FN("input", coreInput, -1);
INITIALIZE_BUILTIN_FN("exit", coreExit, -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. // List functions.
INITIALIZE_BUILTIN_FN("list_append", coreListAppend, 2); INITIALIZE_BUILTIN_FN("list_append", coreListAppend, 2);
INITIALIZE_BUILTIN_FN("list_join", coreListJoin, 1); INITIALIZE_BUILTIN_FN("list_join", coreListJoin, 1);
// Map functions.
INITIALIZE_BUILTIN_FN("map_remove", coreMapRemove, 2);
#undef INITIALIZE_BUILTIN_FN #undef INITIALIZE_BUILTIN_FN
} }
@ -850,9 +798,167 @@ DEF(_numberIsbyte,
RET(VAR_BOOL((floor(n) == n) && (0x00 <= n && n <= 0xff))); 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, DEF(_listAppend,
"List.append(value:var) -> List\n" "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); ASSERT(IS_OBJ_TYPE(SELF, OBJ_LIST), OOPS);
@ -860,6 +966,118 @@ DEF(_listAppend,
RET(SELF); 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, DEF(_fiberRun,
"Fiber.run(...) -> var\n" "Fiber.run(...) -> var\n"
"Runs the fiber's function with the provided arguments and returns it's " "Runs the fiber's function with the provided arguments and returns it's "
@ -951,7 +1169,27 @@ static void initializePrimitiveClasses(PKVM* vm) {
ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1); ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1);
ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0); ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0);
ADD_METHOD(PK_NUMBER, "isbyte", _numberIsbyte, 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, "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, "run", _fiberRun, -1);
ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1); ADD_METHOD(PK_FIBER, "resume", _fiberResume, -1);
@ -1351,7 +1589,11 @@ bool varContains(PKVM* vm, Var elem, Var container) {
String* str = (String*) AS_OBJ(container); String* str = (String*) AS_OBJ(container);
if (sub->length > str->length) return false; 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; } break;
@ -1419,15 +1661,6 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
case CHECK_HASH("length", 0x83d03615): case CHECK_HASH("length", 0x83d03615):
return VAR_NUM((double)(str->length)); 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; } break;
@ -1441,10 +1674,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
} break; } break;
case OBJ_MAP: { case OBJ_MAP: {
// map = { "foo" : 42, "can't access" : 32 } // TODO:
// val = map.foo ## <-- This should be error
// Only the map's attributes are accessed here.
TODO;
} break; } break;
case OBJ_RANGE: { case OBJ_RANGE: {
@ -1509,7 +1739,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
} break; } break;
case OBJ_CLASS: case OBJ_CLASS:
TODO; // TODO:
break; break;
case OBJ_INST: { case OBJ_INST: {

View File

@ -23,6 +23,9 @@
// capacity by the GROW_FACTOR. // capacity by the GROW_FACTOR.
#define GROW_FACTOR 2 #define GROW_FACTOR 2
#define _MAX(a,b) ((a) > (b) ? (a) : (b))
#define _MIN(a,b) ((a) < (b) ? (a) : (b))
// Buffer implementations. // Buffer implementations.
DEFINE_BUFFER(Uint, uint32_t) DEFINE_BUFFER(Uint, uint32_t)
DEFINE_BUFFER(Byte, uint8_t) DEFINE_BUFFER(Byte, uint8_t)
@ -608,6 +611,139 @@ String* stringStrip(PKVM* vm, String* self) {
return newStringLength(vm, start, (uint32_t)(end - start + 1)); 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, ...) { String* stringFormat(PKVM* vm, const char* fmt, ...) {
va_list arg_list; 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) { Var listRemoveAt(PKVM* vm, List* self, uint32_t index) {
ASSERT_INDEX(index, self->elements.count);
Var removed = self->elements.data[index]; Var removed = self->elements.data[index];
if (IS_OBJ(removed)) vmPushTempRef(vm, AS_OBJ(removed)); if (IS_OBJ(removed)) vmPushTempRef(vm, AS_OBJ(removed));
@ -720,6 +858,10 @@ Var listRemoveAt(PKVM* vm, List* self, uint32_t index) {
return removed; return removed;
} }
void listClear(PKVM* vm, List* self) {
pkVarBufferClear(&self->elements, vm);
}
List* listAdd(PKVM* vm, List* l1, List* l2) { List* listAdd(PKVM* vm, List* l1, List* l2) {
// Optimize end case. // Optimize end case.
@ -898,7 +1040,7 @@ void mapClear(PKVM* vm, Map* self) {
Var mapRemoveKey(PKVM* vm, Map* self, Var key) { Var mapRemoveKey(PKVM* vm, Map* self, Var key) {
MapEntry* entry; 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 // Set the key as VAR_UNDEFINED to mark is as an available slow and set it's
// value to VAR_TRUE for tombstone. // value to VAR_TRUE for tombstone.
@ -1609,3 +1751,7 @@ bool toBool(Var v) {
UNREACHABLE(); UNREACHABLE();
return false; return false;
} }
// Undefining for amalgamation to let othre libraries also define _MIN, _MAX.
#undef _MAX
#undef _MIN

View File

@ -643,6 +643,16 @@ String* stringUpper(PKVM* vm, String* self);
// If the string is already trimmed it'll return the same string. // If the string is already trimmed it'll return the same string.
String* stringStrip(PKVM* vm, String* self); 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 // Creates a new string from the arguments. This is intended for internal
// usage and it has 2 formated characters (just like wren does). // usage and it has 2 formated characters (just like wren does).
// $ - a C string // $ - a C string
@ -672,6 +682,9 @@ void listInsert(PKVM* vm, List* self, uint32_t index, Var value);
// Remove and return element at [index]. // Remove and return element at [index].
Var listRemoveAt(PKVM* vm, List* self, uint32_t 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. // Create a new list by joining the 2 given list and return the result.
List* listAdd(PKVM* vm, List* l1, List* l2); 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); void mapClear(PKVM* vm, Map* self);
// Remove the [key] from the map. If the key exists return it's value // 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); Var mapRemoveKey(PKVM* vm, Map* self, Var key);
// Returns true if the fiber has error, and if it has any the fiber cannot be // Returns true if the fiber has error, and if it has any the fiber cannot be

View File

@ -5,6 +5,10 @@
## Number ## Number
############################################################################### ###############################################################################
assert(hex(12648430) == '0xc0ffee')
assert(hex(255) == '0xff' and hex(10597059) == '0xa1b2c3')
assert(hex(-4294967295) == '-0xffffffff') ## the largest.
sum = 0 sum = 0
5.times fn(i) 5.times fn(i)
sum += i sum += i
@ -15,10 +19,27 @@ assert(Number("-.23e+6") == -.23e6)
assert(Number("0b10100101") == 0b10100101) assert(Number("0b10100101") == 0b10100101)
assert(Number("-0Xabcdef123") == -0xabcdef123) 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 ## 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" s = "foobar"
assert(s[-3] == 'b') assert(s[-3] == 'b')
@ -33,6 +54,39 @@ assert("abcd"[-1..-3] == "dcb")
assert(""[0..-1] == "") assert(""[0..-1] == "")
assert(""[-1..0] == "") 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 ## LIST
############################################################################### ###############################################################################
@ -52,6 +106,67 @@ assert(l[-1..-3] == [4, 3, 2])
assert([][0..0] == []) assert([][0..0] == [])
assert([][-1..-1] == []) 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. ## If we got here, that means all test were passed.
print('All TESTS PASSED') print('All TESTS PASSED')

View File

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