Add some runtime type-checking.

Adjust simple-ops structure.
This commit is contained in:
Sage Vaillancourt 2022-03-28 13:19:34 -04:00 committed by Sage Vaillancourt
parent e22e022cd8
commit 66a07e395c
8 changed files with 276 additions and 225 deletions

View File

@ -393,8 +393,8 @@ struct Environment defaultEnv()
{"/", &dvi}, {"/", &dvi},
{"%", &mod}, {"%", &mod},
{"=", &equ}, {"=", &equ},
{">", &gth}, {">", &greaterThan},
{"<", &lth}, {"<", &lessThan},
{"&", &and}, {"&", &and},
{"|", &or}, {"|", &or},
pf("cat", catObjects), pf("cat", catObjects),

View File

@ -178,7 +178,7 @@ static const char* errorText[] = {"MISMATCHED_PARENS",
"LAMBDA_ARGS_NOT_LIST", "LAMBDA_ARGS_NOT_LIST",
"DID_NOT_FIND_SYMBOL", "DID_NOT_FIND_SYMBOL",
"BAD_TYPE", "BAD_TYPE",
"LISTS_NOT_SAME_SIZE", "BAD_PARAMS_ON",
"BAD_NUMBER", "BAD_NUMBER",
"UNSUPPORTED_NUMBER_TYPE", "UNSUPPORTED_NUMBER_TYPE",
"NOT_ENOUGH_ARGUMENTS", "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); dest += stringf(dest, len, "E[%d]", (int)code);
#else #else
if (obj->error->context && obj->error->context[0] != '\0') { 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); obj->error->context);
} else if (code >= 0 && code <= INDEX_PAST_END) { } else if (code >= 0 && code <= INDEX_PAST_END) {
dest += stringf(dest, len, "%s", errorText[code]); dest += stringf(dest, len, "%s", errorText[code]);
@ -602,6 +602,11 @@ inline Object startList(const Object start)
return list; return list;
} }
inline int isNumber(const Object test)
{
return test.type == TYPE_NUMBER;
}
inline int isListy(const Object test) inline int isListy(const Object test)
{ {
return test.type == TYPE_LIST || test.type == TYPE_SLIST; 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; 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) inline int isValidType(const Object test)
{ {
switch (test.type) { 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) inline void errorAddContext(Object* o, const char* context, int lineNo, const char* fileName)
{ {
o->error->context = calloc(sizeof(char), RESULT_LENGTH); 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) inline Object errorWithContextLineNo(enum errorCode code, const char* context, int lineNo, const char* fileName)

View File

@ -25,13 +25,6 @@
_element = _element->forward) _element = _element->forward)
#define POINTER _element #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 #ifdef PBL_PLATFORM_APLITE
#define LOW_MEM #define LOW_MEM
#endif #endif
@ -50,7 +43,7 @@ enum errorCode {
LAMBDA_ARGS_NOT_LIST, LAMBDA_ARGS_NOT_LIST,
DID_NOT_FIND_SYMBOL, DID_NOT_FIND_SYMBOL,
BAD_TYPE, BAD_TYPE,
LISTS_NOT_SAME_SIZE, BAD_PARAMS_ON,
BAD_NUMBER, BAD_NUMBER,
UNSUPPORTED_NUMBER_TYPE, UNSUPPORTED_NUMBER_TYPE,
NOT_ENOUGH_ARGUMENTS, NOT_ENOUGH_ARGUMENTS,
@ -169,10 +162,16 @@ void allocObject(Object** spot, Object src);
void appendList(Object* dest, const Object* src); void appendList(Object* dest, const Object* src);
int isNumber(Object test);
int isListy(Object test); int isListy(Object test);
int isStringy(Object test); int isStringy(Object test);
int isBool(Object test);
int isFuncy(Object test);
int isValidType(Object test); int isValidType(Object test);
int isError(Object obj, enum errorCode err); int isError(Object obj, enum errorCode err);

View File

@ -714,8 +714,8 @@ int main(int argc, const char* argv[])
} }
deleteEnv(&env); deleteEnv(&env);
shredDictionary(); shredDictionary();
// fprintf(stderr, "TOTAL ALLOCATIONS: %d\n", getAllocations()); // fprintf(stderr, "\nHEAP-ALLOCATED OBJECTS: %d\n", getAllocations());
// fprintf(stderr, "TOTAL BYTES: %zu\n", getBytes()); // fprintf(stderr, "TOTAL OBJECT.C ALLOC: %zu\n", getBytes());
} }
#endif #endif

View File

@ -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."); \ 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) Object _name(Object* params, int length, struct Environment* env)
// GCC warns without the attribute, even when typeChecks are used
#define tfn(_name, _type, _docs, ...) \ #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__) fn(_name, _docs, __VA_ARGS__)
#define fnn(_name, _symbol, _docs, ...) \ #define fnn(_name, _symbol, _docs, ...) \

View File

@ -3,8 +3,33 @@
#include "plfunc.h" #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) Object reduce(Object* params, int length, struct Environment* env)
{ {
checkTypes(reduce)
Object list = params[0]; Object list = params[0];
const Object func = params[1]; const Object func = params[1];
Object total = params[2]; 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) Object charAt(Object* params, int length, struct Environment* env)
{ {
checkTypes(charAt)
Object string = params[0]; Object string = params[0];
Object at = params[1]; 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) Object filter(Object* params, int length, struct Environment* env)
{ {
checkTypes(filter)
Object condition = params[0]; Object condition = params[0];
Object list = params[1]; 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) Object append(Object* params, int length, struct Environment* env)
{ {
checkTypes(append)
Object list = params[0]; Object list = params[0];
Object newElement = params[1]; 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) Object prepend(Object* params, int length, struct Environment* env)
{ {
checkTypes(prepend)
Object list = params[0]; Object list = params[0];
Object newElement = params[1]; 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) Object at(Object* params, int length, struct Environment* env)
{ {
checkTypes(at)
Object index = params[0]; Object index = params[0];
Object list = params[1]; 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) Object rest(Object* params, int length, struct Environment* env)
{ {
checkTypes(rest)
Object list = params[0]; Object list = params[0];
if (!isListy(list)) { if (!isListy(list)) {
return errorObject(NOT_A_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) Object reverse(Object* params, int length, struct Environment* ignore2)
{ {
checkTypes(reverse)
Object _list = params[0]; Object _list = params[0];
if (!isListy(_list)) {
return errorObject(NOT_A_LIST);
}
const Object* list = &_list; const Object* list = &_list;
Object rev = listObject(); 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) Object isNum(Object* params, int length, struct Environment* env)
{ {
checkTypes(isNum)
Object test = params[0]; Object test = params[0];
return test.type == TYPE_NUMBER ? boolObject(1) : boolObject(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) Object isList(Object* params, int length, struct Environment* env)
{ {
checkTypes(isList)
Object test = params[0]; Object test = params[0];
return test.type == TYPE_LIST ? boolObject(1) : boolObject(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) Object isString(Object* params, int length, struct Environment* env)
{ {
checkTypes(isString)
Object test = params[0]; Object test = params[0];
return test.type == TYPE_STRING ? boolObject(1) : boolObject(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) Object charVal(Object* params, int length, struct Environment* env)
{ {
checkTypes(charVal)
Object test = params[0]; Object test = params[0];
return numberObject(test.string[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) Object isErr(Object* params, int length, struct Environment* env)
{ {
checkTypes(isErr)
Object test = params[0]; Object test = params[0];
return test.type == TYPE_ERROR ? boolObject(1) : boolObject(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) #ifdef STANDALONE
{
FOR_POINTERS_IN_LISTS(list1, list2) {
if (P1->type != P2->type || P1->number != P2->number) {
return boolObject(0);
}
}
return boolObject(1);
}
#define CAT_MAX 1024 #define CAT_MAX 1024
#else
#define CAT_MAX 64
#endif
Object _catObjects(Object obj1, Object obj2, struct Environment* env) 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) Object catObjects(Object* params, int length, struct Environment* env)
{ {
checkTypes(catObjects)
Object string = stringFromSlice("", 0); Object string = stringFromSlice("", 0);
if (length == 0) { if (length == 0) {
return string; return string;
@ -234,89 +264,12 @@ Object catObjects(Object* params, int length, struct Environment* env)
return string; 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 len(Object* params, int length, struct Environment* env)
{ {
Object obj1 = params[0]; checkTypes(len)
if (!isListy(obj1)) { return numberObject(listLength(&params[0]));
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); \
} }
#define BASIC_MATH(_name, _op) \ #define BASIC_MATH(_name, _op) \
@ -342,17 +295,86 @@ BASIC_MATH(dvi, /=)
BASIC_MATH(mod, %=) 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(&params[i], &params[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 #ifdef STANDALONE
@ -366,6 +388,7 @@ Object print(Object* params, int length, struct Environment* env)
Object numToChar(Object* params, int length, struct Environment* env) Object numToChar(Object* params, int length, struct Environment* env)
{ {
checkTypes(numToChar)
Object c = params[0]; Object c = params[0];
if (c.type != TYPE_NUMBER) { 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) Object loadFile(Object* params, int length, struct Environment* env)
{ {
checkTypes(loadFile)
Object filename = params[0]; Object filename = params[0];
if (isStringy(filename)) { 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) Object systemCall(Object* params, int length, struct Environment* env)
{ {
checkTypes(systemCall)
Object process = params[0]; Object process = params[0];
if (isStringy(process)) { if (isStringy(process)) {
@ -430,8 +455,9 @@ char* readFileToString(FILE* input)
size_t capacity = 128; size_t capacity = 128;
char* string = malloc(sizeof(char) * capacity); char* string = malloc(sizeof(char) * capacity);
int c; int c;
int i = 0; int i = 1; // Skip refCount
string[0] = 1; // Set refCount
while ((c = fgetc(input)) != EOF) { while ((c = fgetc(input)) != EOF) {
string[i] = c; string[i] = c;
i++; i++;
@ -439,16 +465,19 @@ char* readFileToString(FILE* input)
char* prev = string; char* prev = string;
capacity *= 2; capacity *= 2;
string = malloc(sizeof(char) * capacity); string = malloc(sizeof(char) * capacity);
memcpy(string, prev, sizeof(char) * capacity / 2); string += 1;
memcpy(string, prev, sizeof(char) * (capacity / 2));
free(prev); free(prev);
} }
} }
string[i] = '\0';
return string; return string + 1; // Offset past refCount
} }
Object readFileToObject(Object* params, int length, struct Environment* env) Object readFileToObject(Object* params, int length, struct Environment* env)
{ {
checkTypes(readFileToObject)
Object filename = params[0]; Object filename = params[0];
if (filename.type != TYPE_STRING) { if (filename.type != TYPE_STRING) {

View File

@ -18,9 +18,9 @@ BASIC_OP(mod);
BASIC_OP(equ); BASIC_OP(equ);
BASIC_OP(gth); BASIC_OP(greaterThan);
BASIC_OP(lth); BASIC_OP(lessThan);
BASIC_OP(and); BASIC_OP(and);
@ -28,119 +28,119 @@ BASIC_OP(or);
#undef BASIC_OP #undef BASIC_OP
/// ANY => STRING tfn(catObjects,
fn(catObjects, ({ NULL, isStringy }),
"Concatenate string versions of the given objects.", "Concatenate string versions of the given objects.",
"(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )", "(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )",
); );
/// FUNCY, LIST => LIST tfn(filter,
fn(filter, ({ isFuncy, isListy, isListy }),
"Filter a list based on the given condition.", "Filter a list based on the given condition.",
"(fil (fn (a) (< 50 a)) (25 60 100))", "( 60 100 )", "(fil (fn (a) (< 50 a)) (25 60 100))", "( 60 100 )",
"(fil (fn (a) (< 0 (len a))) ( () (1) (1 2) () ))", "( ( 1 ) ( 1 2 ) )", "(fil (fn (a) (< 0 (len a))) ( () (1) (1 2) () ))", "( ( 1 ) ( 1 2 ) )",
); );
/// LIST, ANY => LIST tfn(append,
fn(append, ({ isListy, NULL, isListy }),
"Append the given element. Creates a new list.", "Append the given element. Creates a new list.",
"(ap (1 2) 3)", "( 1 2 3 )", "(ap (1 2) 3)", "( 1 2 3 )",
); );
/// LIST, ANY => LIST tfn(prepend,
fn(prepend, ({ isListy, NULL, isListy }),
"Prepend the given element. Creates a new list", "Prepend the given element. Creates a new list",
"(pre (2 3) 1)", "( 1 2 3 )", "(pre (2 3) 1)", "( 1 2 3 )",
); );
tfn(len, 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.", "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 (2 3))", "2",
"(len ())", "0", "(len ())", "0",
"(len \"string\")", "NOT_A_LIST", "(len \"string\")", "BAD_PARAMS_ON: len",
); );
/// LIST, FUNCY, ANY => ANY tfn(reduce,
fn(reduce, ({ NULL, isFuncy, NULL, NULL }),
"Performs a simple reduction. Does not currently work with lambdas.\n" "Performs a simple reduction. Does not currently work with lambdas.\n"
"Takes three arguments:\n" "Takes three arguments:\n"
" - Values\n" " - Values\n"
" - A function to apply to each value\n" " - A function to apply to each value\n"
" - An initial value", " - An initial value",
"(reduce 5 + 6)", "11", "(reduce 5 + 6)", "11",
"(reduce (1 2 3) + 0)", "6", "(reduce (1 2 3) + 0)", "6",
); );
/// NUMBER, LIST => ANY tfn(at,
fn(at, ({ isNumber, isListy, NULL }),
"Get item at the given index in the given list.", "Get item at the given index in the given list.",
"(at 1 (1 2 3))", "2", "(at 1 (1 2 3))", "2",
"(at 99 (1 2 3))", "INDEX_PAST_END", "(at 99 (1 2 3))", "INDEX_PAST_END",
"(at 99 \"string\")", "INDEX_PAST_END", "(at 99 \"string\")", "BAD_PARAMS_ON: at",
); );
/// LIST => LIST tfn(rest,
fn(rest, ({ isListy, isListy }),
"Get the tail of a list. All but the first element.", "Get the tail of a list. All but the first element.",
"(rest (1 2 3))", "( 2 3 )", "(rest (1 2 3))", "( 2 3 )",
"(rest ())", "( )", "(rest ())", "( )",
"(rest \"string\")", "NOT_A_LIST", "(rest \"string\")", "BAD_PARAMS_ON: rest",
); );
/// LIST => LIST tfn(reverse,
fn(reverse, ({ isListy, isListy }),
"Reverse a list.", "Reverse a list.",
"(rev (1 2 3))", "( 3 2 1 )", "(rev (1 2 3))", "( 3 2 1 )",
"(rev \"string\")", "NOT_A_LIST", "(rev \"string\")", "BAD_PARAMS_ON: reverse",
); );
/// ANY => BOOL tfn(isNum,
fn(isNum, ({ NULL, isBool }),
"Returns `T` only if the argument evaluates to a number.", "Returns `T` only if the argument evaluates to a number.",
"(isnum 1)", "T", "(isnum 1)", "T",
"(isnum (+ 5 5))", "T", "(isnum (+ 5 5))", "T",
"(isnum '(+ 5 5))", "F", "(isnum '(+ 5 5))", "F",
"(isnum \"Hello\")", "F", "(isnum \"Hello\")", "F",
); );
/// ANY => BOOL tfn(isList,
fn(isList, ({ NULL, isBool }),
"Returns `T` only if the argument is a list.", "Returns `T` only if the argument is a list.",
"(islist (1 2 3))", "T", "(islist (1 2 3))", "T",
"(islist ())", "T", "(islist ())", "T",
"(islist \"Stringy\")", "F", "(islist \"Stringy\")", "F",
); );
/// ANY => BOOL tfn(isString,
fn(isString, ({ NULL, isBool }),
"Returns `T` only if the argument is a string.", "Returns `T` only if the argument is a string.",
"(isstr \"Heyo\")", "T", "(isstr \"Heyo\")", "T",
"(isstr \"\")", "T", "(isstr \"\")", "T",
"(isstr (cat 5 5))", "T", "(isstr (cat 5 5))", "T",
"(isstr 10)", "F", "(isstr 10)", "F",
); );
/// ANY => BOOL tfn(isErr,
fn(isErr, ({ NULL, isBool }),
"Check if the argument is an error.", "Check if the argument is an error.",
"(iserr (at 10 ()))", "T", "(iserr (at 10 ()))", "T",
"(iserr 5)", "F", "(iserr 5)", "F",
); );
/// STRING => STRING tfn(charAt,
fn(charAt, ({isStringy, isNumber, isStringy}),
"Get the char in the given string at the given index.", "Get the char in the given string at the given index.",
"(chat \"Hello\" 1)", "e", "(chat \"Hello\" 1)", "e",
"(chat \"Hello\" 10)", "", "(chat \"Hello\" 10)", "",
); );
/// STRING => NUMBER tfn(charVal,
fn(charVal, ({ isStringy, isNumber }),
"Get the ascii integer representaton of the given character.", "Get the ascii integer representaton of the given character.",
"(char \"h\")", "104", "(char \"h\")", "104",
"(char \"hello\")", "104", "(char \"hello\")", "104",
"(char \"\")", "0", "(char \"\")", "0",
); );
/// STRING/SLIST => ANY /// STRING/SLIST => ANY
@ -161,27 +161,29 @@ fn(possessive,
fn(print, "Prints the string representation of the given object to stdout."); fn(print, "Prints the string representation of the given object to stdout.");
fn(numToChar, tfn(numToChar,
"Gets a string containing the ascii character for the given number value.", ({ isNumber, isStringy }),
"(ch 107)", "k", "Gets a string containing the ascii character for the given number value.",
"(ch 0x21)", "!", "(ch 107)", "k",
"(ch 0x21)", "!",
); );
fn(printEnvO, "Prints out the current scoped environment."); fn(printEnvO, "Prints out the current scoped environment.");
fn(systemCall, tfn(systemCall,
"Opens a shell and runs the given command, returning 0 if successful.\n" ({ isStringy, isNumber }),
"If the argument is not a string, returns 255.\n", "Opens a shell and runs the given command, returning 0 if successful.\n"
"(sys \"echo yee > /dev/null\")", "0", "If the argument is not a string, returns 255.\n",
"(sys 5)", "255", "(sys \"echo yee > /dev/null\")", "0",
); );
fn(loadFile, tfn(loadFile,
"Loads and parses the given file.\n" ({ isStringy, NULL }),
"Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n" "Loads and parses the given file.\n"
"(loadfile \"printdate.pl\")\n" "Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n"
"Mon 21 Mar 2022 10:35:03 AM EDT\n" "(loadfile \"printdate.pl\")\n"
"=> 0" "Mon 21 Mar 2022 10:35:03 AM EDT\n"
"=> 0"
); );
/// @code /// @code
@ -201,9 +203,9 @@ fn(help,
"(? \"+\") => \"(+ 1 2) => 3\"" "(? \"+\") => \"(+ 1 2) => 3\""
); );
/// STRING => STRING tfn(readFileToObject,
fn(readFileToObject, ({ isStringy, isStringy }),
"Read a file into a string object." "Read a file into a string object."
); );
#endif // STANDALONE #endif // STANDALONE

View File

@ -107,6 +107,7 @@ check "ChainDiv" "(/ 1493856 741 96 7)" "3"
title "Comparison" title "Comparison"
check "GratrThn" "(> 23847123 19375933)" "T" check "GratrThn" "(> 23847123 19375933)" "T"
check "GratrThnMulti" "(> 9999 55 1 0)" "T"
check "LessThan" "(< 23847123 19375933)" "F" check "LessThan" "(< 23847123 19375933)" "F"
check "Equality" "(= 987654321 987654321 )" "T" check "Equality" "(= 987654321 987654321 )" "T"
check "StringEquality" '(= "Bean" "Bean" )' "T" check "StringEquality" '(= "Bean" "Bean" )' "T"
@ -215,18 +216,18 @@ check "FuncReturningAFunc" "(def plusser (fn (outer) (fn (inner) (+ outer inner)
(plusFive 10)" "15" (plusFive 10)" "15"
title "ShouldError" title "ShouldError"
check "LenOfNotList" "(len 5)" "NOT_A_LIST" check "LenOfNotList" "(len 5)" regex "BAD_PARAMS_ON.*"
check "NoMapList" "(map sq)" "( )" check "NoMapList" "(map sq)" "( )"
check "BadNumber" "(5df)" regex "BAD_NUMBER.*" check "BadNumber" "(5df)" regex "BAD_NUMBER.*"
check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*" check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*"
check "BadBinary" "(0b01120)" regex "BAD_NUMBER.*" check "BadBinary" "(0b01120)" regex "BAD_NUMBER.*"
check "UnsupportedNumber" "(00000)" regex "UNSUPPORTED_NUMBER.*" check "UnsupportedNumber" "(00000)" regex "UNSUPPORTED_NUMBER.*"
check "BadParens1" "(hey()" regex "'MISMATCHED_PARENS.*" check "BadParens1" "(hey()" regex "MISMATCHED_PARENS.*"
check "BadParens2" "(hey)(" regex "'MISMATCHED_PARENS.*" check "BadParens2" "(hey)(" regex "MISMATCHED_PARENS.*"
check "BadParens3" "((hey(" regex "'MISMATCHED_PARENS.*" check "BadParens3" "((hey(" regex "MISMATCHED_PARENS.*"
check "BadParens4" ")))hey" regex "'MISMATCHED_PARENS.*" check "BadParens4" ")))hey" regex "MISMATCHED_PARENS.*"
check "BadParens5" "hey))(" regex "'MISMATCHED_PARENS.*" check "BadParens5" "hey))(" regex "MISMATCHED_PARENS.*"
check "BadParens6" '(ey")"' regex "'MISMATCHED_PARENS.*" check "BadParens6" '(ey")"' regex "MISMATCHED_PARENS.*"
title "ListArithmetic" disabled title "ListArithmetic" disabled
check "UnevenLists" "(+ (1 2) (1 2 3))" "LISTS_NOT_SAME_SIZE" check "UnevenLists" "(+ (1 2) (1 2 3))" "LISTS_NOT_SAME_SIZE"