Add some runtime type-checking.
Adjust simple-ops structure.
This commit is contained in:
parent
e22e022cd8
commit
66a07e395c
|
@ -393,8 +393,8 @@ struct Environment defaultEnv()
|
|||
{"/", &dvi},
|
||||
{"%", &mod},
|
||||
{"=", &equ},
|
||||
{">", >h},
|
||||
{"<", <h},
|
||||
{">", &greaterThan},
|
||||
{"<", &lessThan},
|
||||
{"&", &and},
|
||||
{"|", &or},
|
||||
pf("cat", catObjects),
|
||||
|
|
25
src/object.c
25
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 [33m%s:%d[0m", context, fileName, lineNo);
|
||||
char* cursor = o->error->context;
|
||||
cursor += sprintf(cursor, "%s", context);
|
||||
if (fileName) {
|
||||
sprintf(cursor, " [33m%s:%d[0m", fileName, lineNo);
|
||||
}
|
||||
}
|
||||
|
||||
inline Object errorWithContextLineNo(enum errorCode code, const char* context, int lineNo, const char* fileName)
|
||||
|
|
15
src/object.h
15
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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ...) \
|
||||
|
|
231
src/plfunc.c
231
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) {
|
||||
|
|
204
src/plfunc.h
204
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
|
||||
|
|
15
src/tests.sh
15
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"
|
||||
|
|
Loading…
Reference in New Issue