diff --git a/docs/TODO.txt b/docs/TODO.txt index 1be5078..7e9fe34 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -23,10 +23,10 @@ DONE: functions are first-class citizens DONE: functions take a set number of parameters DONE: functions last argument can be a rest parameter DONE: assert needs to kill the whole script, not just functions +DONE: native functions +DONE: global functions _get, _set, _push, _pop, _length, clear available - -TODO: functions return a set number of values - +TODO: slice and dot notation around the _index function TODO: ternary operator TODO: Nullish types TODO: A way to check the type of a variable (typeOf keyword) @@ -37,3 +37,5 @@ TODO: standard library TODO: external runner library TODO: document how it all works TODO: third output stream, for parser/compiler/interpreter errors + +NOPE: functions return a set number of values \ No newline at end of file diff --git a/docs/spec.md b/docs/spec.md index 3f39eec..455d65a 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -448,7 +448,7 @@ print dict.foo; //syntactic sugar, only works if the key is not a built-in funct ## Indexing, Slice and Dot Notation -Strings, arrays and dictionaries can be indexed in several ways, via the global `_get` and `_set` functions. Elements can be accessed using traditional bracket notation: +Strings, arrays and dictionaries can be indexed in several ways, via the globally available functions (see below). Elements can be accessed using traditional bracket notation: ``` str[0]; @@ -490,22 +490,42 @@ print str[::-2]; //drwolH 0 cannot be used as the third argument. -### _get() and _set() +### Globally available functions -The slice and dot notations (the latter of which only works on dictionaries) are simply syntactic sugar for the global `_get` and `_set` functions. These functions take a number of arguments, which correlate to the slice and dot notations: +The slice and dot notations (the latter of which only works on dictionaries) are simply syntactic sugar for the globally available functions. ``` -fn _get(self, first, second, third) { +//usable with arrays and dictionaries +fn _set(self, key, value) { //native code } -fn _set(self, first, second, third, value) { +//usable with arrays and dictionaries +fn _get(self, key) { + //native code +} + +//usable with arrays +fn push(self, val) { + //native code +} + +//usable with arrays +fn pop(self) { + //native code +} + +//usable with arrays, dictionaries and strings +fn length(self) { + //native code +} + +//usable with arrays and dictionaries +fn clear(self) { //native code } ``` -These functions can be overwritten. - ## Standard Library The standard library has a number of utility functions available, and is provided by default. @@ -517,7 +537,6 @@ import "standard"; The following functions are available in the standard library. -* clear(self: any) - This function removes the contents of the array, dictionary or string, leaving an empty array. This alters the memory. * clock() - This function returns the number of seconds since January 1st, 1970. * concat(self: any, x: any): any - This function requires an array or dictionary with a matching type as "x". This function returns a new dictionary instance which contains the contents of the current array or dictionary combined with the contents of "x". In the event of a dictionary key clash, the key-value pair in the current dictionary is included, and the key-value pair from "x" is discarded. * containsKey(self: [any, any], k: any): bool - This function returns true if the dictionary contains a key "k", otherwise it returns false. @@ -533,8 +552,6 @@ The following functions are available in the standard library. * lastIndexOf(self: string, str: string): int - This function returns the position of the last instance of "str" in the calling string "self". * length(self: any): int - This function returns the length of the array, dictionary or string. * map(self: any, cb: fn(k: any, v: any)(any)): any - This function calls "cb" once for every entry in the array or dictionary, with that key passed in as "k" and value passed in as "v". It returns a new array or dictionary with the keys copied from the current "self", and values replaced with the results of calls to "cb". -* pop(self: [any]): any - This function deletes the value at the end of the array, and returns that value. -* push(self: [any], x: type) - This function inserts the value of "x" at the end of the array. * reduce(self: any, default: any, cb: fn(acc: any, k: any, v: any): any): any - This function calls "cb" once for every element in the array or dictionary "self", with that element passed in as "k" and "v", and the value of the previous call passed in as "acc". For the first call to "cb", "default" is used for "acc". The final value of "acc" is returned by "reduce". * remove(self: any, k: any) - This function deletes the value at the index or key "k", shifting the remaining entries down 1 index if it's an array. This alters the memory. * replace(self: string, pat: string, rep: string): string - For each instance of "pat" that it finds in the calling string "self", it replaces it with "rep", then returns the new string. diff --git a/source/interpreter.c b/source/interpreter.c index cc9ec78..f7c4ecd 100644 --- a/source/interpreter.c +++ b/source/interpreter.c @@ -3,6 +3,7 @@ #include "common.h" #include "memory.h" +#include "keyword_types.h" #include #include @@ -18,6 +19,298 @@ static void stderrWrapper(const char* output) { fprintf(stderr, "\n" RESET); //default new line } +bool injectNativeFn(Interpreter* interpreter, char* name, NativeFn func) { + //reject reserved words + if (findTypeByKeyword(name) != TOKEN_EOF) { + printf("Can't override an existing keyword"); + return false; + } + + Literal identifier = TO_IDENTIFIER_LITERAL(name); + + //make sure the name isn't taken + if (existsLiteralDictionary(&interpreter->scope->variables, identifier)) { + printf("Can't override an existing variable"); + return false; + } + + Literal fn = TO_FUNCTION_LITERAL((void*)func, 0); + fn.type = LITERAL_FUNCTION_NATIVE; + + Literal type = TO_TYPE_LITERAL(fn.type, true); + + setLiteralDictionary(&interpreter->scope->variables, identifier, fn); + setLiteralDictionary(&interpreter->scope->types, identifier, type); + + return true; +} + +bool parseIdentifierToValue(Interpreter* interpreter, Literal* literalPtr) { + //this converts identifiers to values + if (IS_IDENTIFIER(*literalPtr)) { + if (!getScopeVariable(interpreter->scope, *literalPtr, literalPtr)) { + printf(ERROR "Error: Undeclared variable \"");; + printLiteral(*literalPtr); + printf("\"\n" RESET); + return false; + } + } + + return true; +} + +int _set(Interpreter* interpreter, LiteralArray* arguments) { + //if wrong number of arguments, fail + if (arguments->count != 3) { + (interpreter->printOutput)("Incorrect number of arguments to _set"); + return -1; + } + + Literal obj = arguments->literals[0]; + Literal key = arguments->literals[1]; + Literal val = arguments->literals[2]; + + parseIdentifierToValue(interpreter, &obj); + + switch(obj.type) { + case LITERAL_ARRAY: { + Literal typeLiteral = getScopeType(interpreter->scope, key); + + if (AS_TYPE(typeLiteral).typeOf == LITERAL_ARRAY) { + Literal subtypeLiteral = ((Literal*)(AS_TYPE(typeLiteral).subtypes))[0]; + + if (AS_TYPE(subtypeLiteral).typeOf != LITERAL_ANY && AS_TYPE(subtypeLiteral).typeOf != val.type) { + (interpreter->printOutput)("bad argument type in _set"); + return -1; + } + } + + if (!IS_INTEGER(key)) { + (interpreter->printOutput)("Expected integer index in _set"); + return -1; + } + + if (AS_ARRAY(obj)->count <= AS_INTEGER(key) || AS_INTEGER(key) < 0) { + (interpreter->printOutput)("Index out of bounds in _set"); + return -1; + } + + parseIdentifierToValue(interpreter, &val); + + //if it's a string or an identifier, make a local copy + if (IS_STRING(val)) { + val = TO_STRING_LITERAL(copyString(AS_STRING(val), STRLEN(val))); + } + if (IS_IDENTIFIER(val)) { + val = TO_IDENTIFIER_LITERAL(copyString(AS_IDENTIFIER(val), STRLEN_I(val))); + } + + //TODO: proper copy function for literals + + AS_ARRAY(obj)->literals[AS_INTEGER(key)] = val; + return 0; + } + + case LITERAL_DICTIONARY: { + Literal typeLiteral = getScopeType(interpreter->scope, key); + + if (AS_TYPE(typeLiteral).typeOf == LITERAL_DICTIONARY) { + Literal keySubtypeLiteral = ((Literal*)(AS_TYPE(typeLiteral).subtypes))[0]; + Literal valSubtypeLiteral = ((Literal*)(AS_TYPE(typeLiteral).subtypes))[1]; + + if (AS_TYPE(keySubtypeLiteral).typeOf != LITERAL_ANY && AS_TYPE(keySubtypeLiteral).typeOf != key.type) { + (interpreter->printOutput)("bad argument type in _set"); + return -1; + } + + if (AS_TYPE(valSubtypeLiteral).typeOf != LITERAL_ANY && AS_TYPE(valSubtypeLiteral).typeOf != val.type) { + (interpreter->printOutput)("bad argument type in _set"); + return -1; + } + } + + parseIdentifierToValue(interpreter, &key); + parseIdentifierToValue(interpreter, &val); + + setLiteralDictionary(AS_DICTIONARY(obj), key, val); + return 0; + } + + default: + (interpreter->printOutput)("Incorrect compound type in _set"); + printLiteral(obj); + return -1; + } +} + +int _get(Interpreter* interpreter, LiteralArray* arguments) { + //if wrong number of arguments, fail + if (arguments->count != 2) { + (interpreter->printOutput)("Incorrect number of arguments to _get"); + return -1; + } + + Literal obj = arguments->literals[0]; + Literal key = arguments->literals[1]; + + parseIdentifierToValue(interpreter, &obj); + + switch(obj.type) { + case LITERAL_ARRAY: { + if (!IS_INTEGER(key)) { + (interpreter->printOutput)("Expected integer index in _get"); + return -1; + } + + if (AS_ARRAY(obj)->count <= AS_INTEGER(key) || AS_INTEGER(key) < 0) { + (interpreter->printOutput)("Index out of bounds in _get"); + return -1; + } + + pushLiteralArray(&interpreter->stack, AS_ARRAY(obj)->literals[AS_INTEGER(key)]); + return 1; + } + + case LITERAL_DICTIONARY: + pushLiteralArray(&interpreter->stack, getLiteralDictionary(AS_DICTIONARY(obj), key)); + return 1; + + default: + (interpreter->printOutput)("Incorrect compound type in _get"); + printLiteral(obj); + return -1; + } +} + +int _push(Interpreter* interpreter, LiteralArray* arguments) { + //if wrong number of arguments, fail + if (arguments->count != 2) { + (interpreter->printOutput)("Incorrect number of arguments to _push"); + return -1; + } + + Literal obj = arguments->literals[0]; + Literal val = arguments->literals[1]; + + parseIdentifierToValue(interpreter, &obj); + + switch(obj.type) { + case LITERAL_ARRAY: { + Literal typeLiteral = getScopeType(interpreter->scope, val); + + if (AS_TYPE(typeLiteral).typeOf == LITERAL_ARRAY) { + Literal subtypeLiteral = ((Literal*)(AS_TYPE(typeLiteral).subtypes))[0]; + + if (AS_TYPE(subtypeLiteral).typeOf != LITERAL_ANY && AS_TYPE(subtypeLiteral).typeOf != val.type) { + (interpreter->printOutput)("bad argument type in _push"); + return -1; + } + } + + parseIdentifierToValue(interpreter, &val); + + pushLiteralArray(AS_ARRAY(obj), val); + return 0; + } + + default: + (interpreter->printOutput)("Incorrect compound type in _push"); + printLiteral(obj); + return -1; + } +} + +int _pop(Interpreter* interpreter, LiteralArray* arguments) { + //if wrong number of arguments, fail + if (arguments->count != 1) { + (interpreter->printOutput)("Incorrect number of arguments to _pop"); + return -1; + } + + Literal obj = arguments->literals[0]; + + parseIdentifierToValue(interpreter, &obj); + + switch(obj.type) { + case LITERAL_ARRAY: { + pushLiteralArray(&interpreter->stack, popLiteralArray(AS_ARRAY(obj))); + return 1; + } + + default: + (interpreter->printOutput)("Incorrect compound type in _pop"); + printLiteral(obj); + return -1; + } +} + +int _length(Interpreter* interpreter, LiteralArray* arguments) { + //if wrong number of arguments, fail + if (arguments->count != 1) { + (interpreter->printOutput)("Incorrect number of arguments to _get"); + return -1; + } + + Literal obj = arguments->literals[0]; + + parseIdentifierToValue(interpreter, &obj); + + switch(obj.type) { + case LITERAL_ARRAY: { + pushLiteralArray(&interpreter->stack, TO_INTEGER_LITERAL( AS_ARRAY(obj)->count )); + return 1; + } + + case LITERAL_DICTIONARY: + pushLiteralArray(&interpreter->stack, TO_INTEGER_LITERAL( AS_DICTIONARY(obj)->count )); + return 1; + + case LITERAL_STRING: + pushLiteralArray(&interpreter->stack, TO_INTEGER_LITERAL( STRLEN(obj) )); + return 1; + + default: + (interpreter->printOutput)("Incorrect compound type in _length"); + printLiteral(obj); + return -1; + } +} + +int _clear(Interpreter* interpreter, LiteralArray* arguments) { + //if wrong number of arguments, fail + if (arguments->count != 1) { + (interpreter->printOutput)("Incorrect number of arguments to _get"); + return -1; + } + + Literal obj = arguments->literals[0]; + + parseIdentifierToValue(interpreter, &obj); + + switch(obj.type) { + case LITERAL_ARRAY: { + while(AS_ARRAY(obj)->count) { + popLiteralArray(AS_ARRAY(obj)); + } + return 1; + } + + case LITERAL_DICTIONARY: { + for (int i = 0; i < AS_DICTIONARY(obj)->capacity; i++) { + if ( !IS_NULL(AS_DICTIONARY(obj)->entries[i].key) ) { + removeLiteralDictionary(AS_DICTIONARY(obj), AS_DICTIONARY(obj)->entries[i].key); + } + } + return 1; + } + + default: + (interpreter->printOutput)("Incorrect compound type in _get"); + printLiteral(obj); + return -1; + } +} + void initInterpreter(Interpreter* interpreter) { initLiteralArray(&interpreter->literalCache); interpreter->scope = pushScope(NULL); @@ -30,6 +323,14 @@ void initInterpreter(Interpreter* interpreter) { setInterpreterPrint(interpreter, stdoutWrapper); setInterpreterAssert(interpreter, stderrWrapper); + //globally available functions (tmp?) + injectNativeFn(interpreter, "_set", _set); + injectNativeFn(interpreter, "_get", _get); + injectNativeFn(interpreter, "_push", _push); + injectNativeFn(interpreter, "_pop", _pop); + injectNativeFn(interpreter, "_length", _length); + injectNativeFn(interpreter, "_clear", _clear); + interpreter->panic = false; } @@ -112,20 +413,6 @@ static void consumeShort(unsigned short bytes, unsigned char* tb, int* count) { *count += 2; } -static bool parseIdentifierToValue(Interpreter* interpreter, Literal* literalPtr) { - //this converts identifiers to values - if (IS_IDENTIFIER(*literalPtr)) { - if (!getScopeVariable(interpreter->scope, *literalPtr, literalPtr)) { - printf(ERROR "Error: Undeclared variable \"");; - printLiteral(*literalPtr); - printf("\"\n" RESET); - return false; - } - } - - return true; -} - //each available statement static bool execAssert(Interpreter* interpreter) { Literal rhs = popLiteralArray(&interpreter->stack); @@ -763,7 +1050,30 @@ static bool execFnCall(Interpreter* interpreter) { Literal identifier = popLiteralArray(&interpreter->stack); Literal func = identifier; - parseIdentifierToValue(interpreter, &func); + + if (!parseIdentifierToValue(interpreter, &func)) { + freeLiteralArray(&arguments); + return false; + } + + //check for side-loaded native functions + if (IS_FUNCTION_NATIVE(func)) { + //reverse the order to the correct order + LiteralArray correct; + initLiteralArray(&correct); + + while(arguments.count) { + pushLiteralArray(&correct, popLiteralArray(&arguments)); + } + + freeLiteralArray(&arguments); + + //call the native function + ((NativeFn) AS_FUNCTION(func) )(interpreter, &correct); + + freeLiteralArray(&correct); + return true; + } //set up a new interpreter Interpreter inner; diff --git a/source/interpreter.h b/source/interpreter.h index 020e55b..8fb76e0 100644 --- a/source/interpreter.h +++ b/source/interpreter.h @@ -29,6 +29,12 @@ typedef struct Interpreter { bool panic; } Interpreter; +//for native function API +typedef int (*NativeFn)(Interpreter* interpreter, LiteralArray* arguments); +bool injectNativeFn(Interpreter* interpreter, char* name, NativeFn func); +bool parseIdentifierToValue(Interpreter* interpreter, Literal* literalPtr); + +//init & free void initInterpreter(Interpreter* interpreter); void freeInterpreter(Interpreter* interpreter); diff --git a/source/keyword_types.c b/source/keyword_types.c index 0cd5740..7275ba8 100644 --- a/source/keyword_types.c +++ b/source/keyword_types.c @@ -2,6 +2,8 @@ #include "common.h" +#include + KeywordType keywordTypes[] = { //type keywords {TOKEN_NULL, "null"}, @@ -57,4 +59,16 @@ char* findKeywordByType(TokenType type) { } return NULL; +} + +TokenType findTypeByKeyword(const char* keyword) { + const int length = strlen(keyword); + + for (int i = 0; keywordTypes[i].keyword; i++) { + if (!strncmp(keyword, keywordTypes[i].keyword, length)) { + return keywordTypes[i].type; + } + } + + return TOKEN_EOF; } \ No newline at end of file diff --git a/source/keyword_types.h b/source/keyword_types.h index 62bcaab..5469f6a 100644 --- a/source/keyword_types.h +++ b/source/keyword_types.h @@ -10,3 +10,5 @@ typedef struct { extern KeywordType keywordTypes[]; char* findKeywordByType(TokenType type); + +TokenType findTypeByKeyword(const char* keyword); \ No newline at end of file diff --git a/source/literal.h b/source/literal.h index d7ff370..f67c89d 100644 --- a/source/literal.h +++ b/source/literal.h @@ -20,7 +20,7 @@ typedef enum { LITERAL_TYPE_INTERMEDIATE, //used to process types in the compiler only LITERAL_FUNCTION_INTERMEDIATE, //used to process functions in the compiler only LITERAL_FUNCTION_ARG_REST, //used to process function rest parameters - // LITERAL_FUNCTION_NATIVE, //for handling native functions + LITERAL_FUNCTION_NATIVE, //for handling native functions LITERAL_ANY, //used by the type system only } LiteralType; @@ -68,6 +68,7 @@ typedef struct { #define IS_ARRAY(value) ((value).type == LITERAL_ARRAY) #define IS_DICTIONARY(value) ((value).type == LITERAL_DICTIONARY) #define IS_FUNCTION(value) ((value).type == LITERAL_FUNCTION) +#define IS_FUNCTION_NATIVE(value) ((value).type == LITERAL_FUNCTION_NATIVE) #define IS_IDENTIFIER(value) ((value).type == LITERAL_IDENTIFIER) #define IS_TYPE(value) ((value).type == LITERAL_TYPE) diff --git a/source/literal_dictionary.c b/source/literal_dictionary.c index d37c6ac..0e133d3 100644 --- a/source/literal_dictionary.c +++ b/source/literal_dictionary.c @@ -113,9 +113,9 @@ static void adjustEntryCapacity(_entry** dictionaryHandle, int oldCapacity, int *dictionaryHandle = newEntries; } -static bool setEntryArray(_entry** dictionaryHandle, int* capacityPtr, int count, Literal key, Literal value, int hash) { +static bool setEntryArray(_entry** dictionaryHandle, int* capacityPtr, int contains, Literal key, Literal value, int hash) { //expand array if needed - if (count + 1 > *capacityPtr * DICTIONARY_MAX_LOAD) { + if (contains + 1 > *capacityPtr * DICTIONARY_MAX_LOAD) { int oldCapacity = *capacityPtr; *capacityPtr = GROW_CAPACITY(*capacityPtr); adjustEntryCapacity(dictionaryHandle, oldCapacity, *capacityPtr); //custom rather than automatic reallocation @@ -138,7 +138,7 @@ static bool setEntryArray(_entry** dictionaryHandle, int* capacityPtr, int count value = TO_IDENTIFIER_LITERAL(copyString(AS_IDENTIFIER(value), STRLEN_I(value))); } - //true = count increase + //true = contains increase if (IS_NULL(entry->key)) { setEntryValues(entry, key, value); return true; @@ -177,6 +177,7 @@ void initLiteralDictionary(LiteralDictionary* dictionary) { //HACK: because modulo by 0 is undefined, set the capacity to a non-zero value (and allocate the arrays) dictionary->entries = NULL; dictionary->capacity = GROW_CAPACITY(0); + dictionary->contains = 0; dictionary->count = 0; adjustEntryCapacity(&dictionary->entries, 0, dictionary->capacity); } @@ -184,7 +185,7 @@ void initLiteralDictionary(LiteralDictionary* dictionary) { void freeLiteralDictionary(LiteralDictionary* dictionary) { freeEntryArray(dictionary->entries, dictionary->capacity); dictionary->capacity = 0; - dictionary->count = 0; + dictionary->contains = 0; } void setLiteralDictionary(LiteralDictionary* dictionary, Literal key, Literal value) { @@ -193,9 +194,10 @@ void setLiteralDictionary(LiteralDictionary* dictionary, Literal key, Literal va return; } - const int increment = setEntryArray(&dictionary->entries, &dictionary->capacity, dictionary->count, key, value, hashLiteral(key)); + const int increment = setEntryArray(&dictionary->entries, &dictionary->capacity, dictionary->contains, key, value, hashLiteral(key)); if (increment) { + dictionary->contains++; dictionary->count++; } } @@ -227,6 +229,7 @@ void removeLiteralDictionary(LiteralDictionary* dictionary, Literal key) { if (entry != NULL) { freeEntry(entry); entry->value = TO_BOOLEAN_LITERAL(true); //tombstone + dictionary->count--; } } diff --git a/source/literal_dictionary.h b/source/literal_dictionary.h index cfe02e9..f6cb34a 100644 --- a/source/literal_dictionary.h +++ b/source/literal_dictionary.h @@ -13,6 +13,7 @@ typedef struct _entry { typedef struct LiteralDictionary { _entry* entries; int capacity; + int contains; //count + tombstones int count; } LiteralDictionary; diff --git a/source/scope.c b/source/scope.c index cf11ecd..856d7ef 100644 --- a/source/scope.c +++ b/source/scope.c @@ -198,3 +198,17 @@ bool getScopeVariable(Scope* scope, Literal key, Literal* valueHandle) { *valueHandle = getLiteralDictionary(&scope->variables, key); return true; } + +Literal getScopeType(Scope* scope, Literal key) { + //dead end + if (scope == NULL) { + return TO_NULL_LITERAL; + } + + //if it's not in this scope, keep searching up the chain + if (!existsLiteralDictionary(&scope->types, key)) { + return getScopeType(scope->ancestor, key); + } + + return getLiteralDictionary(&scope->types, key); +} diff --git a/source/scope.h b/source/scope.h index dddb9b1..91af0db 100644 --- a/source/scope.h +++ b/source/scope.h @@ -21,3 +21,4 @@ bool isDelcaredScopeVariable(Scope* scope, Literal key); bool setScopeVariable(Scope* scope, Literal key, Literal value, bool constCheck); bool getScopeVariable(Scope* scope, Literal key, Literal* value); +Literal getScopeType(Scope* scope, Literal key); \ No newline at end of file diff --git a/test/native-functions.toy b/test/native-functions.toy new file mode 100644 index 0000000..4a82652 --- /dev/null +++ b/test/native-functions.toy @@ -0,0 +1,87 @@ + +{ + //test arrays without types + var array = []; + + _push(array, 1); + _push(array, 2); + _push(array, 3); + _push(array, 4); + _push(array, "foo"); + + assert _length(array) == 5, "_push or _length failed with array"; + assert _pop(array) == "foo", "_pop failed with array"; + + _set(array, 2, "bar"); + assert array == [1, 2, "bar", 4], "_set failed with array"; + assert _get(array, 3) == 4, "_get failed with array"; + + + //test dictionaries without types + var dict = [:]; + + _set(dict, "key", "value"); + _set(dict, 1, 2); + + assert dict == ["key":"value", 1:2], "_set failed with dictionaries"; + assert _get(dict, "key") == "value", "_get failed with dictionaries"; + + + //test _length + assert _length(array) == 4 && _length(dict) == 2, "_length failed with array or dictionaries"; + + + //test clear + _clear(array); + _clear(dict); + + assert _length(array) == 0 && _length(dict) == 0, "_clear failed with array or dictionaries"; +} + + +{ + //test arrays with types + var array: [int] = []; + + _push(array, 1); + _push(array, 2); + _push(array, 3); + _push(array, 4); + _push(array, 10); + + assert _length(array) == 5, "_push or _length failed with array (+ types)"; + assert _pop(array) == 10, "_pop failed with array (+ types)"; + + _set(array, 2, 70); + assert array == [1, 2, 70, 4], "_set failed with array (+ types)"; + assert _get(array, 3) == 4, "_get failed with array (+ types)"; + + + //test dictionaries with types + var dict: [string, string] = [:]; + + _set(dict, "key", "value"); + + assert dict == ["key":"value"], "_set failed with dictionaries (+ types)"; + assert _get(dict, "key") == "value", "_get failed with dictionaries (+ types)"; + + + //test length with types + assert _length(array) == 4 && _length(dict) == 1, "_length failed with array or dictionaries (+ types)"; + + + //test clear with types + _clear(array); + _clear(dict); + + assert _length(array) == 0 && _length(dict) == 0, "_clear failed with array or dictionaries (+ types)"; +} + +{ + var str = "hello world"; + + assert _length(str) == 11, "_length failed with string"; +} + + +print "All good";