From 66a07e395c2e5b5ffc2b295cbdbd78bfabea0527 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Mon, 28 Mar 2022 13:19:34 -0400 Subject: [PATCH] Add some runtime type-checking. Adjust simple-ops structure. --- src/env.c | 4 +- src/object.c | 25 +++++- src/object.h | 15 ++-- src/pebblisp.c | 4 +- src/pebblisp.h | 3 +- src/plfunc.c | 231 ++++++++++++++++++++++++++++--------------------- src/plfunc.h | 204 ++++++++++++++++++++++--------------------- src/tests.sh | 15 ++-- 8 files changed, 276 insertions(+), 225 deletions(-) diff --git a/src/env.c b/src/env.c index cdb1c45..dc8e7c8 100644 --- a/src/env.c +++ b/src/env.c @@ -393,8 +393,8 @@ struct Environment defaultEnv() {"/", &dvi}, {"%", &mod}, {"=", &equ}, - {">", >h}, - {"<", <h}, + {">", &greaterThan}, + {"<", &lessThan}, {"&", &and}, {"|", &or}, pf("cat", catObjects), diff --git a/src/object.c b/src/object.c index 3a03e4d..f8c6040 100644 --- a/src/object.c +++ b/src/object.c @@ -178,7 +178,7 @@ static const char* errorText[] = {"MISMATCHED_PARENS", "LAMBDA_ARGS_NOT_LIST", "DID_NOT_FIND_SYMBOL", "BAD_TYPE", - "LISTS_NOT_SAME_SIZE", + "BAD_PARAMS_ON", "BAD_NUMBER", "UNSUPPORTED_NUMBER_TYPE", "NOT_ENOUGH_ARGUMENTS", @@ -299,7 +299,7 @@ int stringNObj(char* dest, const Object* obj, const size_t len) dest += stringf(dest, len, "E[%d]", (int)code); #else if (obj->error->context && obj->error->context[0] != '\0') { - dest += stringf(dest, len, "'%s': %s", errorText[code], + dest += stringf(dest, len, "%s: %s", errorText[code], obj->error->context); } else if (code >= 0 && code <= INDEX_PAST_END) { dest += stringf(dest, len, "%s", errorText[code]); @@ -602,6 +602,11 @@ inline Object startList(const Object start) return list; } +inline int isNumber(const Object test) +{ + return test.type == TYPE_NUMBER; +} + inline int isListy(const Object test) { return test.type == TYPE_LIST || test.type == TYPE_SLIST; @@ -612,6 +617,16 @@ inline int isStringy(const Object test) return test.type == TYPE_STRING || test.type == TYPE_SYMBOL; } +inline int isBool(const Object test) +{ + return test.type == TYPE_BOOL; +} + +inline int isFuncy(const Object test) +{ + return test.type == TYPE_LAMBDA || test.type == TYPE_FUNC; +} + inline int isValidType(const Object test) { switch (test.type) { @@ -823,7 +838,11 @@ inline enum errorCode getErrorCode(const Object obj) inline void errorAddContext(Object* o, const char* context, int lineNo, const char* fileName) { o->error->context = calloc(sizeof(char), RESULT_LENGTH); - sprintf(o->error->context, "%s %s:%d", context, fileName, lineNo); + char* cursor = o->error->context; + cursor += sprintf(cursor, "%s", context); + if (fileName) { + sprintf(cursor, " %s:%d", fileName, lineNo); + } } inline Object errorWithContextLineNo(enum errorCode code, const char* context, int lineNo, const char* fileName) diff --git a/src/object.h b/src/object.h index 0d80522..b99f6b9 100644 --- a/src/object.h +++ b/src/object.h @@ -25,13 +25,6 @@ _element = _element->forward) #define POINTER _element -#define FOR_POINTERS_IN_LISTS(_list, _list2) \ - for(Object *_element = (_list)->list, *_element2 = (_list2)->list; \ - _element != NULL && _element2 != NULL; \ - _element = _element->forward, _element2 = _element2->forward) -#define P1 POINTER -#define P2 _element2 - #ifdef PBL_PLATFORM_APLITE #define LOW_MEM #endif @@ -50,7 +43,7 @@ enum errorCode { LAMBDA_ARGS_NOT_LIST, DID_NOT_FIND_SYMBOL, BAD_TYPE, - LISTS_NOT_SAME_SIZE, + BAD_PARAMS_ON, BAD_NUMBER, UNSUPPORTED_NUMBER_TYPE, NOT_ENOUGH_ARGUMENTS, @@ -169,10 +162,16 @@ void allocObject(Object** spot, Object src); void appendList(Object* dest, const Object* src); +int isNumber(Object test); + int isListy(Object test); int isStringy(Object test); +int isBool(Object test); + +int isFuncy(Object test); + int isValidType(Object test); int isError(Object obj, enum errorCode err); diff --git a/src/pebblisp.c b/src/pebblisp.c index 8a7dedc..9d95bf4 100644 --- a/src/pebblisp.c +++ b/src/pebblisp.c @@ -714,8 +714,8 @@ int main(int argc, const char* argv[]) } deleteEnv(&env); shredDictionary(); - // fprintf(stderr, "TOTAL ALLOCATIONS: %d\n", getAllocations()); - // fprintf(stderr, "TOTAL BYTES: %zu\n", getBytes()); + // fprintf(stderr, "\nHEAP-ALLOCATED OBJECTS: %d\n", getAllocations()); + // fprintf(stderr, "TOTAL OBJECT.C ALLOC: %zu\n", getBytes()); } #endif diff --git a/src/pebblisp.h b/src/pebblisp.h index a2627c8..619688d 100644 --- a/src/pebblisp.h +++ b/src/pebblisp.h @@ -18,8 +18,9 @@ static const char * const _name ## Tests[] = {__VA_ARGS__}; \ static_assert(array_length(_name ## Tests) % 2 == 0, "Array of test strings must have exactly one expected result for each test."); \ Object _name(Object* params, int length, struct Environment* env) +// GCC warns without the attribute, even when typeChecks are used #define tfn(_name, _type, _docs, ...) \ -static const Type _name ## Type[] = UNPACK _type; \ +__attribute__((unused)) static int (*_name ## TypeChecks[])(Object) = UNPACK _type; \ fn(_name, _docs, __VA_ARGS__) #define fnn(_name, _symbol, _docs, ...) \ diff --git a/src/plfunc.c b/src/plfunc.c index 4724047..c88a141 100644 --- a/src/plfunc.c +++ b/src/plfunc.c @@ -3,8 +3,33 @@ #include "plfunc.h" +Object typeCheck(const char* funcName, Object* params, int length, int (*typeChecks[])(Object), int typeLength, int* failed) +{ + *failed = 1; + if ((typeLength - 1) > length ) { + return errorObject(NOT_ENOUGH_ARGUMENTS); + } + for (int i = 0; i < typeLength - 1; i++) { + if (typeChecks[i] && !typeChecks[i](params[i])) { // TODO: Use pl func name instead of C function name. + return /*errorObject(BAD_TYPE); */ errorWithContextLineNo(BAD_PARAMS_ON, funcName, 0, NULL); + } + } + *failed = 0; + return numberObject(0); +} + +#ifndef DISABLE_TYPE_CHECKS +#define checkTypes(FUNC) int FAILED; Object ERROR = typeCheck(#FUNC, params, length, FUNC ## TypeChecks, array_length(FUNC ## TypeChecks), &FAILED); \ +if (FAILED) { \ + return ERROR; \ +} +#else +#define checkTypes(FUNC) ; +#endif + Object reduce(Object* params, int length, struct Environment* env) { + checkTypes(reduce) Object list = params[0]; const Object func = params[1]; Object total = params[2]; @@ -22,6 +47,7 @@ Object reduce(Object* params, int length, struct Environment* env) Object charAt(Object* params, int length, struct Environment* env) { + checkTypes(charAt) Object string = params[0]; Object at = params[1]; @@ -41,6 +67,7 @@ Object charAt(Object* params, int length, struct Environment* env) Object filter(Object* params, int length, struct Environment* env) { + checkTypes(filter) Object condition = params[0]; Object list = params[1]; @@ -61,6 +88,7 @@ Object filter(Object* params, int length, struct Environment* env) Object append(Object* params, int length, struct Environment* env) { + checkTypes(append) Object list = params[0]; Object newElement = params[1]; @@ -71,6 +99,7 @@ Object append(Object* params, int length, struct Environment* env) Object prepend(Object* params, int length, struct Environment* env) { + checkTypes(prepend) Object list = params[0]; Object newElement = params[1]; @@ -82,6 +111,7 @@ Object prepend(Object* params, int length, struct Environment* env) Object at(Object* params, int length, struct Environment* env) { + checkTypes(at) Object index = params[0]; Object list = params[1]; @@ -95,6 +125,7 @@ Object at(Object* params, int length, struct Environment* env) Object rest(Object* params, int length, struct Environment* env) { + checkTypes(rest) Object list = params[0]; if (!isListy(list)) { return errorObject(NOT_A_LIST); @@ -113,11 +144,9 @@ Object rest(Object* params, int length, struct Environment* env) Object reverse(Object* params, int length, struct Environment* ignore2) { + checkTypes(reverse) Object _list = params[0]; - if (!isListy(_list)) { - return errorObject(NOT_A_LIST); - } const Object* list = &_list; Object rev = listObject(); @@ -136,6 +165,7 @@ Object reverse(Object* params, int length, struct Environment* ignore2) Object isNum(Object* params, int length, struct Environment* env) { + checkTypes(isNum) Object test = params[0]; return test.type == TYPE_NUMBER ? boolObject(1) : boolObject(0); @@ -143,6 +173,7 @@ Object isNum(Object* params, int length, struct Environment* env) Object isList(Object* params, int length, struct Environment* env) { + checkTypes(isList) Object test = params[0]; return test.type == TYPE_LIST ? boolObject(1) : boolObject(0); @@ -150,6 +181,7 @@ Object isList(Object* params, int length, struct Environment* env) Object isString(Object* params, int length, struct Environment* env) { + checkTypes(isString) Object test = params[0]; return test.type == TYPE_STRING ? boolObject(1) : boolObject(0); @@ -157,6 +189,7 @@ Object isString(Object* params, int length, struct Environment* env) Object charVal(Object* params, int length, struct Environment* env) { + checkTypes(charVal) Object test = params[0]; return numberObject(test.string[0]); @@ -164,6 +197,7 @@ Object charVal(Object* params, int length, struct Environment* env) Object isErr(Object* params, int length, struct Environment* env) { + checkTypes(isErr) Object test = params[0]; return test.type == TYPE_ERROR ? boolObject(1) : boolObject(0); @@ -189,17 +223,11 @@ Object parseEvalO(Object* params, int length, struct Environment* env) } } -Object listEquality(const Object* list1, const Object* list2) -{ - FOR_POINTERS_IN_LISTS(list1, list2) { - if (P1->type != P2->type || P1->number != P2->number) { - return boolObject(0); - } - } - return boolObject(1); -} - +#ifdef STANDALONE #define CAT_MAX 1024 +#else +#define CAT_MAX 64 +#endif Object _catObjects(Object obj1, Object obj2, struct Environment* env) { @@ -222,6 +250,8 @@ Object _catObjects(Object obj1, Object obj2, struct Environment* env) Object catObjects(Object* params, int length, struct Environment* env) { + checkTypes(catObjects) + Object string = stringFromSlice("", 0); if (length == 0) { return string; @@ -234,89 +264,12 @@ Object catObjects(Object* params, int length, struct Environment* env) return string; } -Object _basicOp(const Object* obj1, const Object* obj2, const char op, - struct Environment* env) -{ - const int n1 = obj1->number; - const int n2 = obj2->number; - - switch (op) { - case '&': - return boolObject(n1 != 0 && n2 != 0); - case '|': - return boolObject(n1 != 0 || n2 != 0); - - case '=': - if (bothAre(TYPE_STRING, obj1, obj2)) { - return boolObject(!strcmp(obj1->string, obj2->string)); - } - if (bothAre(TYPE_LIST, obj1, obj2)) { - return listEquality(obj1, obj2); - } - return boolObject(n1 == n2 && areSameType(obj1, obj2)); - case '>': - return boolObject(n1 > n2); - case '<': - return boolObject(n1 < n2); - default: - return *obj1; - } -} - -Object basicOp(const Object* obj1, const Object* obj2, const char op, - struct Environment* env) -{ - if (isError(*obj2, NOT_ENOUGH_ARGUMENTS)) { - return *obj2; - } - - int lists = (obj1->type == TYPE_LIST) + (obj2->type == TYPE_LIST); - if (lists == 0) { - return _basicOp(obj1, obj2, op, env); - - } else if (lists == 1) { // Single operand is applied to each element in list - const Object* listObj = (obj1->type == TYPE_LIST) ? obj1 : obj2; - const Object* singleObj = (obj1->type == TYPE_LIST) ? obj2 : obj1; - - Object newList = listObject(); - FOR_POINTER_IN_LIST(listObj) { - Object adding = eval(POINTER, env); - nf_addToList(&newList, _basicOp(&adding, singleObj, op, env)); - } - return newList; - - } else { // 2 lists with the op applied to matching indices of both lists - if (listLength(obj1) == listLength(obj2)) { - Object newList = listObject(); - FOR_POINTERS_IN_LISTS(obj1, obj2) { - const Object ev1 = eval(P1, env); - const Object ev2 = eval(P2, env); - nf_addToList(&newList, _basicOp(&ev1, &ev2, op, env)); - } - return newList; - } else { - return errorObject(LISTS_NOT_SAME_SIZE); - } - } -} Object len(Object* params, int length, struct Environment* env) { - Object obj1 = params[0]; + checkTypes(len) - if (!isListy(obj1)) { - return errorObject(NOT_A_LIST); - } - Object o = numberObject(listLength(&obj1)); - return o; -} - -#define BASIC_OP(_name, _char) \ -Object _name(Object* params, int length, struct Environment* env) \ -{ \ - Object obj1 = params[0]; \ - Object obj2 = params[1]; \ - return basicOp(&obj1, &obj2, _char, env); \ + return numberObject(listLength(¶ms[0])); } #define BASIC_MATH(_name, _op) \ @@ -342,17 +295,86 @@ BASIC_MATH(dvi, /=) BASIC_MATH(mod, %=) -BASIC_OP(equ, '=') +int areEqual(const Object* obj1, const Object* obj2); -BASIC_OP(gth, '>') +int listEquality(const Object* list1, const Object* list2) +{ + Object* element1, *element2; + for (element1 = (list1)->list, element2 = (list2)->list; + element1 != ((void*) 0) && element2 != ((void*) 0); + element1 = element1->forward, element2 = element2->forward) { + if (!areEqual(element1, element2)) { + return 0; + } + } + return (element1 == NULL && element2 == NULL); +} -BASIC_OP(lth, '<') +int areEqual(const Object* obj1, const Object* obj2) +{ + const int n1 = obj1->number; + const int n2 = obj2->number; -BASIC_OP(and, '&') + if (bothAre(TYPE_STRING, obj1, obj2)) { + return !strcmp(obj1->string, obj2->string); + } + if (bothAre(TYPE_LIST, obj1, obj2)) { + return listEquality(obj1, obj2); + } + return n1 == n2 && areSameType(obj1, obj2); +} -BASIC_OP(or, '|') +Object equ(Object* params, int length, struct Environment* env) +{ + if (length < 2) { + return errorObject(NOT_ENOUGH_ARGUMENTS); + } + int bool = 1; + for (int i = 0; i < length - 1; i++) { + if (!areEqual(¶ms[i], ¶ms[i + 1])) { + bool = 0; + break; + } + } + return boolObject(bool); +} -#undef BASIC_OP +Object or(Object* params, int length, struct Environment* env) +{ + if (length < 2) { + return errorObject(NOT_ENOUGH_ARGUMENTS); + } + int bool = 0; + for (int i = 0; i < length - 1; i++) { + if (params[i].number || params[i + 1].number) { + bool = 1; + break; + } + } + return boolObject(bool); +} + +#define BASIC_COMPARISON(NAME, OP)\ +Object NAME(Object* params, int length, struct Environment* env) \ +{ \ + if (length < 2) { \ + return errorObject(NOT_ENOUGH_ARGUMENTS); \ + } \ + int bool = 1; \ + for (int i = 0; i < length - 1; i++) { \ + if (!(params[i].number OP params[i + 1].number)) { \ + bool = 0; \ + break; \ + } \ + } \ + return boolObject(bool); \ +} + +BASIC_COMPARISON(greaterThan, >) + +BASIC_COMPARISON(lessThan, <) + +BASIC_COMPARISON(and, &&) #ifdef STANDALONE @@ -366,6 +388,7 @@ Object print(Object* params, int length, struct Environment* env) Object numToChar(Object* params, int length, struct Environment* env) { + checkTypes(numToChar) Object c = params[0]; if (c.type != TYPE_NUMBER) { @@ -396,6 +419,7 @@ Object takeInput(Object* params, int length, struct Environment* env) Object loadFile(Object* params, int length, struct Environment* env) { + checkTypes(loadFile) Object filename = params[0]; if (isStringy(filename)) { @@ -407,6 +431,7 @@ Object loadFile(Object* params, int length, struct Environment* env) Object systemCall(Object* params, int length, struct Environment* env) { + checkTypes(systemCall) Object process = params[0]; if (isStringy(process)) { @@ -430,8 +455,9 @@ char* readFileToString(FILE* input) size_t capacity = 128; char* string = malloc(sizeof(char) * capacity); int c; - int i = 0; + int i = 1; // Skip refCount + string[0] = 1; // Set refCount while ((c = fgetc(input)) != EOF) { string[i] = c; i++; @@ -439,16 +465,19 @@ char* readFileToString(FILE* input) char* prev = string; capacity *= 2; string = malloc(sizeof(char) * capacity); - memcpy(string, prev, sizeof(char) * capacity / 2); + string += 1; + memcpy(string, prev, sizeof(char) * (capacity / 2)); free(prev); } } + string[i] = '\0'; - return string; + return string + 1; // Offset past refCount } Object readFileToObject(Object* params, int length, struct Environment* env) { + checkTypes(readFileToObject) Object filename = params[0]; if (filename.type != TYPE_STRING) { diff --git a/src/plfunc.h b/src/plfunc.h index 3087596..ece3db5 100644 --- a/src/plfunc.h +++ b/src/plfunc.h @@ -18,9 +18,9 @@ BASIC_OP(mod); BASIC_OP(equ); -BASIC_OP(gth); +BASIC_OP(greaterThan); -BASIC_OP(lth); +BASIC_OP(lessThan); BASIC_OP(and); @@ -28,119 +28,119 @@ BASIC_OP(or); #undef BASIC_OP -/// ANY => STRING -fn(catObjects, - "Concatenate string versions of the given objects.", - "(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )", +tfn(catObjects, + ({ NULL, isStringy }), + "Concatenate string versions of the given objects.", + "(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )", ); -/// FUNCY, LIST => LIST -fn(filter, - "Filter a list based on the given condition.", - "(fil (fn (a) (< 50 a)) (25 60 100))", "( 60 100 )", - "(fil (fn (a) (< 0 (len a))) ( () (1) (1 2) () ))", "( ( 1 ) ( 1 2 ) )", +tfn(filter, + ({ isFuncy, isListy, isListy }), + "Filter a list based on the given condition.", + "(fil (fn (a) (< 50 a)) (25 60 100))", "( 60 100 )", + "(fil (fn (a) (< 0 (len a))) ( () (1) (1 2) () ))", "( ( 1 ) ( 1 2 ) )", ); -/// LIST, ANY => LIST -fn(append, - "Append the given element. Creates a new list.", - "(ap (1 2) 3)", "( 1 2 3 )", +tfn(append, + ({ isListy, NULL, isListy }), + "Append the given element. Creates a new list.", + "(ap (1 2) 3)", "( 1 2 3 )", ); -/// LIST, ANY => LIST -fn(prepend, - "Prepend the given element. Creates a new list", - "(pre (2 3) 1)", "( 1 2 3 )", +tfn(prepend, + ({ isListy, NULL, isListy }), + "Prepend the given element. Creates a new list", + "(pre (2 3) 1)", "( 1 2 3 )", ); tfn(len, - ({ TYPE_LIST, TYPE_NUMBER }), + ({ isListy, isNumber }), "Returns the length of the given list, or a NOT_A_LIST error if the expression is not a list.", "(len (2 3))", "2", "(len ())", "0", - "(len \"string\")", "NOT_A_LIST", + "(len \"string\")", "BAD_PARAMS_ON: len", ); -/// LIST, FUNCY, ANY => ANY -fn(reduce, - "Performs a simple reduction. Does not currently work with lambdas.\n" - "Takes three arguments:\n" - " - Values\n" - " - A function to apply to each value\n" - " - An initial value", - "(reduce 5 + 6)", "11", - "(reduce (1 2 3) + 0)", "6", +tfn(reduce, + ({ NULL, isFuncy, NULL, NULL }), + "Performs a simple reduction. Does not currently work with lambdas.\n" + "Takes three arguments:\n" + " - Values\n" + " - A function to apply to each value\n" + " - An initial value", + "(reduce 5 + 6)", "11", + "(reduce (1 2 3) + 0)", "6", ); -/// NUMBER, LIST => ANY -fn(at, - "Get item at the given index in the given list.", - "(at 1 (1 2 3))", "2", - "(at 99 (1 2 3))", "INDEX_PAST_END", - "(at 99 \"string\")", "INDEX_PAST_END", +tfn(at, + ({ isNumber, isListy, NULL }), + "Get item at the given index in the given list.", + "(at 1 (1 2 3))", "2", + "(at 99 (1 2 3))", "INDEX_PAST_END", + "(at 99 \"string\")", "BAD_PARAMS_ON: at", ); -/// LIST => LIST -fn(rest, - "Get the tail of a list. All but the first element.", - "(rest (1 2 3))", "( 2 3 )", - "(rest ())", "( )", - "(rest \"string\")", "NOT_A_LIST", +tfn(rest, + ({ isListy, isListy }), + "Get the tail of a list. All but the first element.", + "(rest (1 2 3))", "( 2 3 )", + "(rest ())", "( )", + "(rest \"string\")", "BAD_PARAMS_ON: rest", ); -/// LIST => LIST -fn(reverse, - "Reverse a list.", - "(rev (1 2 3))", "( 3 2 1 )", - "(rev \"string\")", "NOT_A_LIST", +tfn(reverse, + ({ isListy, isListy }), + "Reverse a list.", + "(rev (1 2 3))", "( 3 2 1 )", + "(rev \"string\")", "BAD_PARAMS_ON: reverse", ); -/// ANY => BOOL -fn(isNum, - "Returns `T` only if the argument evaluates to a number.", - "(isnum 1)", "T", - "(isnum (+ 5 5))", "T", - "(isnum '(+ 5 5))", "F", - "(isnum \"Hello\")", "F", +tfn(isNum, + ({ NULL, isBool }), + "Returns `T` only if the argument evaluates to a number.", + "(isnum 1)", "T", + "(isnum (+ 5 5))", "T", + "(isnum '(+ 5 5))", "F", + "(isnum \"Hello\")", "F", ); -/// ANY => BOOL -fn(isList, - "Returns `T` only if the argument is a list.", - "(islist (1 2 3))", "T", - "(islist ())", "T", - "(islist \"Stringy\")", "F", +tfn(isList, + ({ NULL, isBool }), + "Returns `T` only if the argument is a list.", + "(islist (1 2 3))", "T", + "(islist ())", "T", + "(islist \"Stringy\")", "F", ); -/// ANY => BOOL -fn(isString, - "Returns `T` only if the argument is a string.", - "(isstr \"Heyo\")", "T", - "(isstr \"\")", "T", - "(isstr (cat 5 5))", "T", - "(isstr 10)", "F", +tfn(isString, + ({ NULL, isBool }), + "Returns `T` only if the argument is a string.", + "(isstr \"Heyo\")", "T", + "(isstr \"\")", "T", + "(isstr (cat 5 5))", "T", + "(isstr 10)", "F", ); -/// ANY => BOOL -fn(isErr, - "Check if the argument is an error.", - "(iserr (at 10 ()))", "T", - "(iserr 5)", "F", +tfn(isErr, + ({ NULL, isBool }), + "Check if the argument is an error.", + "(iserr (at 10 ()))", "T", + "(iserr 5)", "F", ); -/// STRING => STRING -fn(charAt, - "Get the char in the given string at the given index.", - "(chat \"Hello\" 1)", "e", - "(chat \"Hello\" 10)", "", +tfn(charAt, + ({isStringy, isNumber, isStringy}), + "Get the char in the given string at the given index.", + "(chat \"Hello\" 1)", "e", + "(chat \"Hello\" 10)", "", ); -/// STRING => NUMBER -fn(charVal, - "Get the ascii integer representaton of the given character.", - "(char \"h\")", "104", - "(char \"hello\")", "104", - "(char \"\")", "0", +tfn(charVal, + ({ isStringy, isNumber }), + "Get the ascii integer representaton of the given character.", + "(char \"h\")", "104", + "(char \"hello\")", "104", + "(char \"\")", "0", ); /// STRING/SLIST => ANY @@ -161,27 +161,29 @@ fn(possessive, fn(print, "Prints the string representation of the given object to stdout."); -fn(numToChar, - "Gets a string containing the ascii character for the given number value.", - "(ch 107)", "k", - "(ch 0x21)", "!", +tfn(numToChar, + ({ isNumber, isStringy }), + "Gets a string containing the ascii character for the given number value.", + "(ch 107)", "k", + "(ch 0x21)", "!", ); fn(printEnvO, "Prints out the current scoped environment."); -fn(systemCall, - "Opens a shell and runs the given command, returning 0 if successful.\n" - "If the argument is not a string, returns 255.\n", - "(sys \"echo yee > /dev/null\")", "0", - "(sys 5)", "255", +tfn(systemCall, + ({ isStringy, isNumber }), + "Opens a shell and runs the given command, returning 0 if successful.\n" + "If the argument is not a string, returns 255.\n", + "(sys \"echo yee > /dev/null\")", "0", ); -fn(loadFile, - "Loads and parses the given file.\n" - "Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n" - "(loadfile \"printdate.pl\")\n" - "Mon 21 Mar 2022 10:35:03 AM EDT\n" - "=> 0" +tfn(loadFile, + ({ isStringy, NULL }), + "Loads and parses the given file.\n" + "Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n" + "(loadfile \"printdate.pl\")\n" + "Mon 21 Mar 2022 10:35:03 AM EDT\n" + "=> 0" ); /// @code @@ -201,9 +203,9 @@ fn(help, "(? \"+\") => \"(+ 1 2) => 3\"" ); -/// STRING => STRING -fn(readFileToObject, - "Read a file into a string object." +tfn(readFileToObject, + ({ isStringy, isStringy }), + "Read a file into a string object." ); #endif // STANDALONE diff --git a/src/tests.sh b/src/tests.sh index d0a31fb..2077e58 100755 --- a/src/tests.sh +++ b/src/tests.sh @@ -107,6 +107,7 @@ check "ChainDiv" "(/ 1493856 741 96 7)" "3" title "Comparison" check "GratrThn" "(> 23847123 19375933)" "T" +check "GratrThnMulti" "(> 9999 55 1 0)" "T" check "LessThan" "(< 23847123 19375933)" "F" check "Equality" "(= 987654321 987654321 )" "T" check "StringEquality" '(= "Bean" "Bean" )' "T" @@ -215,18 +216,18 @@ check "FuncReturningAFunc" "(def plusser (fn (outer) (fn (inner) (+ outer inner) (plusFive 10)" "15" title "ShouldError" -check "LenOfNotList" "(len 5)" "NOT_A_LIST" +check "LenOfNotList" "(len 5)" regex "BAD_PARAMS_ON.*" check "NoMapList" "(map sq)" "( )" check "BadNumber" "(5df)" regex "BAD_NUMBER.*" check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*" check "BadBinary" "(0b01120)" regex "BAD_NUMBER.*" check "UnsupportedNumber" "(00000)" regex "UNSUPPORTED_NUMBER.*" -check "BadParens1" "(hey()" regex "'MISMATCHED_PARENS.*" -check "BadParens2" "(hey)(" regex "'MISMATCHED_PARENS.*" -check "BadParens3" "((hey(" regex "'MISMATCHED_PARENS.*" -check "BadParens4" ")))hey" regex "'MISMATCHED_PARENS.*" -check "BadParens5" "hey))(" regex "'MISMATCHED_PARENS.*" -check "BadParens6" '(ey")"' regex "'MISMATCHED_PARENS.*" +check "BadParens1" "(hey()" regex "MISMATCHED_PARENS.*" +check "BadParens2" "(hey)(" regex "MISMATCHED_PARENS.*" +check "BadParens3" "((hey(" regex "MISMATCHED_PARENS.*" +check "BadParens4" ")))hey" regex "MISMATCHED_PARENS.*" +check "BadParens5" "hey))(" regex "MISMATCHED_PARENS.*" +check "BadParens6" '(ey")"' regex "MISMATCHED_PARENS.*" title "ListArithmetic" disabled check "UnevenLists" "(+ (1 2) (1 2 3))" "LISTS_NOT_SAME_SIZE"