diff --git a/src/env.h b/src/env.h index e0b4322..90b4935 100644 --- a/src/env.h +++ b/src/env.h @@ -68,11 +68,6 @@ unused 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) -#define tfn(_name, _symbol, _type, _docs, ...) \ -unused static int (*_name ## TypeChecks[])(Object) = UNPACK _type; \ -static const char * const _name ## Symbol = _symbol; \ -fnn(_name, _docs, __VA_ARGS__) - #define fn(_name, _symbol, _docs, ...) \ static const char * const _name ## Symbol = _symbol; \ fnn(_name, _docs, __VA_ARGS__) diff --git a/src/object.c b/src/object.c index 79ef2ba..df12bf9 100644 --- a/src/object.c +++ b/src/object.c @@ -142,7 +142,7 @@ static const char* errorText[] = { "LAMBDA_ARGS_NOT_LIST", "DID_NOT_FIND_SYMBOL", "BAD_TYPE", - "BAD_PARAMS_ON", + "BAD_PARAMS", "BAD_NUMBER", "UNSUPPORTED_NUMBER_TYPE", "NOT_ENOUGH_ARGUMENTS", @@ -332,14 +332,12 @@ char* stringObj(const Object* obj, size_t* length) #if defined(DEBUG) || defined(STANDALONE) #define SIMPLE_TYPE(_type) case _type:\ - printf(#_type);\ - return + return #_type; -void printType(const Object* obj) +const char* getTypeName(const Object* obj) { if (!obj) { - printf("NULL OBJECT"); - return; + return "NULL_OBJECT"; } switch (obj->type) { SIMPLE_TYPE(TYPE_NUMBER); @@ -353,14 +351,10 @@ void printType(const Object* obj) SIMPLE_TYPE(TYPE_PROMISE); SIMPLE_TYPE(TYPE_OTHER); SIMPLE_TYPE(TYPE_ERROR); - case TYPE_LAMBDA: - printf("TYPE_LAMBDA Params:\n"); - return; + SIMPLE_TYPE(TYPE_LAMBDA); } - if (!isValidType(*obj)) { - printf("UNKNOWN TYPE (%d)", obj->type); - } + return "UNKNOWN_TYPE"; } void _printObj(const Object* obj, int newline) diff --git a/src/object.h b/src/object.h index a0a78e2..83e615d 100644 --- a/src/object.h +++ b/src/object.h @@ -46,7 +46,7 @@ enum errorCode { LAMBDA_ARGS_NOT_LIST, DID_NOT_FIND_SYMBOL, BAD_TYPE, - BAD_PARAMS_ON, + BAD_PARAMS, BAD_NUMBER, UNSUPPORTED_NUMBER_TYPE, NOT_ENOUGH_ARGUMENTS, @@ -147,7 +147,7 @@ struct string { */ char* stringObj(const Object* obj, size_t* length); -void printType(const Object* obj); +const char* getTypeName(const Object* obj); void printObj(const Object* obj); diff --git a/src/pebblisp.c b/src/pebblisp.c index 6fb5006..2676a40 100644 --- a/src/pebblisp.c +++ b/src/pebblisp.c @@ -520,15 +520,18 @@ Object parseEval(const char* input, struct Environment* env) } Object typeCheck(const char* funcName, Object* params, int length, - int (* typeChecks[])(Object), int typeLength, int* failed) + struct TypeCheck typeChecks[], int typeLength, int* failed) { *failed = 1; if ((typeLength - 1) > length) { return errorWithContext(NOT_ENOUGH_ARGUMENTS, funcName); } + 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); + if (typeChecks[i].checkFunc && !typeChecks[i].checkFunc(params[i])) { // TODO: Use pl func name instead of C function name. + char context[128]; + sprintf(context, "When calling %s, expected %s, but received %s", funcName, typeChecks[i].name, getTypeName(¶ms[i])); + return errorWithContextLineNo(BAD_PARAMS, context, 0, NULL); } } *failed = 0; diff --git a/src/pebblisp.h b/src/pebblisp.h index af3d3ac..f4ff740 100644 --- a/src/pebblisp.h +++ b/src/pebblisp.h @@ -19,8 +19,17 @@ unused 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) +struct TypeCheck { + int (*checkFunc)(Object); + const char* name; +}; + +#define expect(_checker) {.checkFunc = (_checker), .name = #_checker} +#define returns(_checker) {.checkFunc = (_checker), .name = #_checker} +#define anyType {.checkFunc = NULL, .name = "AnyType"} + #define tfn(_name, _symbol, _type, _docs, ...) \ -unused static int (*_name ## TypeChecks[])(Object) = UNPACK _type; \ +unused static struct TypeCheck _name ## TypeChecks[] = UNPACK _type; \ static const char * const _name ## Symbol = _symbol; \ fnn(_name, _docs, __VA_ARGS__) @@ -76,7 +85,7 @@ Object listEvalLambda(Object* lambda, const Object* remaining, int evalLength, Object simpleFuncEval(Object func, Object arg1, Object arg2, struct Environment* env); Object typeCheck(const char* funcName, Object* params, int length, - int (* typeChecks[])(Object), int typeLength, int* failed); + struct TypeCheck typeChecks[], int typeLength, int* failed); #ifndef STANDALONE #define DISABLE_TYPE_CHECKS @@ -112,7 +121,7 @@ fn(def, "def", ); tfn(structAccess, "poss", - ({ isStruct, isStringy, NULL }), + ({ expect(isStruct), expect(isStringy), anyType }), "Get the value of a struct's field", "(struct Post (title body))\n " "(def p (Post \"TI\" \"BO\"))\n " diff --git a/src/plfunc.c b/src/plfunc.c index d14e3e0..602c1bc 100644 --- a/src/plfunc.c +++ b/src/plfunc.c @@ -251,7 +251,7 @@ Object substring(Object* params, int length, unused struct Environment* env) int len = end.number - start.number; if (len < 0 || start.number >= strlen(string.string)) { - return errorWithContext(BAD_PARAMS_ON, string.string); + return errorWithContext(BAD_PARAMS, string.string); } Object substr = withLen(len, TYPE_STRING); snprintf(substr.string, len + 1, "%s", string.string + start.number); diff --git a/src/plfunc.h b/src/plfunc.h index 56c7feb..cddb1d2 100644 --- a/src/plfunc.h +++ b/src/plfunc.h @@ -68,40 +68,40 @@ fn(or, "|", ); tfn(catObjects, "cat", - ({ NULL, isStringy }), + ({ anyType, returns(isStringy) }), "Concatenate string representations of the given objects.", "(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )", ); tfn(filter, "fil", - ({ isFuncy, isListy, isListy }), + ({ expect(isFuncy), expect(isListy), returns(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 ) )", ); tfn(append, "ap", - ({ isListy, NULL, isListy }), + ({ expect(isListy), anyType, returns(isListy) }), "Append the given element. Creates a new list.", "(ap (1 2) 3)", "( 1 2 3 )", ); tfn(prepend, "pre", - ({ isListy, NULL, isListy }), + ({ expect(isListy), anyType, returns(isListy) }), "Prepend the given element. Creates a new list", "(pre (2 3) 1)", "( 1 2 3 )", ); tfn(len, "len", - ({ isListy, isNumber }), + ({ expect(isListy), returns(isNumber) }), "Returns the length of the given list, or an error if the expression is not a list.", "(len (2 3))", "2", "(len ())", "0", - "(len \"string\")", "BAD_PARAMS_ON: len", + "(len \"string\")", "BAD_PARAMS;", ); tfn(reduce, "reduce", - ({ NULL, isFuncy, NULL, NULL }), + ({ anyType, expect(isFuncy), anyType, anyType }), "Performs a simple reduction. Does not currently work with lambdas.\n" "Takes three arguments:\n" " - Values\n" @@ -112,30 +112,30 @@ tfn(reduce, "reduce", ); tfn(at, "at", - ({ isNumber, isListy, NULL }), + ({ expect(isNumber), expect(isListy), anyType }), "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", + "(at 99 \"string\")", "BAD_PARAMS;", ); tfn(rest, "rest", - ({ isListy, isListy }), + ({ expect(isListy), returns(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", + "(rest \"string\")", "BAD_PARAMS;", ); tfn(reverse, "rev", - ({ isListy, isListy }), + ({ expect(isListy), returns(isListy) }), "Reverse a list.", "(rev (1 2 3))", "( 3 2 1 )", - "(rev \"string\")", "BAD_PARAMS_ON: reverse", + "(rev \"string\")", "BAD_PARAMS;", ); tfn(isNum, "isnum", - ({ NULL, isBool }), + ({ anyType, returns(isBool) }), "Returns `T` only if the argument evaluates to a number.", "(isnum 1)", "T", "(isnum (+ 5 5))", "T", @@ -144,7 +144,7 @@ tfn(isNum, "isnum", ); tfn(isList, "islist", - ({ NULL, isBool }), + ({ anyType, returns(isBool) }), "Returns `T` only if the argument is a list.", "(islist (1 2 3))", "T", "(islist ())", "T", @@ -152,7 +152,7 @@ tfn(isList, "islist", ); tfn(isString, "isstr", - ({ NULL, isBool }), + ({ anyType, returns(isBool) }), "Returns `T` only if the argument is a string.", "(isstr \"Heyo\")", "T", "(isstr \"\")", "T", @@ -161,21 +161,21 @@ tfn(isString, "isstr", ); tfn(isErr, "iserr", - ({ NULL, isBool }), + ({ anyType, returns(isBool) }), "Check if the argument is an error.", "(iserr (at 10 ()))", "T", "(iserr 5)", "F", ); tfn(charAt, "chat", - ({ isStringy, isNumber, isStringy }), + ({ expect(isStringy), expect(isNumber), returns(isStringy) }), "Get the char in the given string at the given index.", "(chat \"Hello\" 1)", "e", "(chat \"Hello\" 10)", "", ); tfn(charVal, "char", - ({ isStringy, isNumber }), + ({ expect(isStringy), returns(isNumber) }), "Get the ascii integer representaton of the given character.", "(char \"h\")", "104", "(char \"hello\")", "104", @@ -183,21 +183,21 @@ tfn(charVal, "char", ); tfn(slen, "slen", - ({ isStringy, isNumber }), + ({ expect(isStringy), returns(isNumber) }), "Returns the length of the given string", "(slen \"string\")", "6", "(slen \"\")", "0", ); tfn(chars, "chars", - ({ isStringy, isListy }), + ({ expect(isStringy), returns(isListy) }), "Get a list of all chars in the given string", "(chars \"hello\")", "( h e l l o )", "(chars \"\")", "( )", ); tfn(matches, "matches", - ({ isStringy, isStringy, isBool }), + ({ expect(isStringy), expect(isStringy), returns(isBool) }), "Check that a string matches a basic wildcard pattern\n" "Note: Currently there is no way to match a literal asterisk", "(matches \"Hiya\" \"Hiya\")", "T", @@ -214,11 +214,11 @@ tfn(matches, "matches", ); tfn(substring, "substr", - ({ isNumber, isNumber, isStringy, isStringy }), + ({ expect(isNumber), expect(isNumber), expect(isStringy), returns(isStringy) }), "Get a substring from the given string.", "(substr 1 3 \"Hello\")", "el", - "(substr 99 3 \"Hello\")", "BAD_PARAMS_ON: Hello;", - "(substr 98 99 \"Hello\")", "BAD_PARAMS_ON: Hello;", + "(substr 99 3 \"Hello\")", "BAD_PARAMS;", + "(substr 98 99 \"Hello\")", "BAD_PARAMS;", "(substr 0 1 \"\")", "", "(substr 0 99 \"Hey\")", "Hey", "(substr 1 99 \"Heyyyy\")", "eyyyy", @@ -232,7 +232,7 @@ fn(parseEvalO, "eval", ); tfn(getTime, "time", - ({ isStruct }), + ({ returns(isStruct) }), "Get a struct of the current time with fields (minute hour sec)." ); @@ -241,7 +241,7 @@ tfn(getTime, "time", fn(print, "prn", "Prints the string representation of the given object to stdout."); tfn(numToChar, "ch", - ({ isNumber, isStringy }), + ({ expect(isNumber), returns(isStringy) }), "Gets a string containing the ascii character for the given number value.", "(ch 107)", "k", "(ch 0x21)", "!", @@ -255,14 +255,14 @@ fn(printEnvO, "penv", ); tfn(systemCall, "sys", - ({ isStringy, isNumber }), + ({ expect(isStringy), returns(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", ); tfn(loadFile, "loadfile", - ({ isStringy, NULL }), + ({ expect(isStringy), anyType }), "Loads and parses the given file.\n" "Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n" "(loadfile \"printdate.pl\")\n" @@ -271,12 +271,12 @@ tfn(loadFile, "loadfile", ); tfn(cd, "cd", - ({ isStringy, NULL }), + ({ expect(isStringy), anyType }), "Change the current directory." ); tfn(cwd, "cwd", - ({ isStringy }), + ({ returns(isStringy) }), "Get the current directory." ); @@ -290,12 +290,12 @@ fn(takeInput, "inp", ); tfn(readFileToObject, "rf", - ({ isStringy, isStringy }), + ({ expect(isStringy), returns(isStringy) }), "Read a file into a string object." ); tfn(getEnvVar, "env", - ({ isStringy, isStringy }), + ({ expect(isStringy), returns(isStringy) }), "Get a variable from the current environment\n" "(env HOME) => /home/sagevaillancourt" ); diff --git a/src/tests.sh b/src/tests.sh index f72d655..e65e84a 100755 --- a/src/tests.sh +++ b/src/tests.sh @@ -65,13 +65,13 @@ check() { local exit_code=0 if $VALGRIND; then echo -ne "\n $1" - output="$($VALCOM ./pl "(loadfile \"examples/lib.pbl\") $2")" + output="$($VALCOM ./pl --ignore-config "(loadfile \"examples/lib.pbl\") $2")" exit_code=$? if [[ "$exit_code" == "0" ]]; then echo -ne "\r " fi else - output="$(./pl "(loadfile \"examples/lib.pbl\") $2")" + output="$(./pl --ignore-config "(loadfile \"examples/lib.pbl\") $2")" fi @@ -217,7 +217,7 @@ check "FuncReturningAFunc" "(def plusser (fn (outer) (fn (inner) (+ outer inner) (plusFive 10)" "15" title "ShouldError" -check "LenOfNotList" "(len 5)" regex "BAD_PARAMS_ON.*" +check "LenOfNotList" "(len 5)" regex "BAD_PARAMS.*" check "NoMapList" "(map sq)" "NULL_MAP_ARGS" check "BadNumber" "(5df)" regex "BAD_NUMBER.*" check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*" diff --git a/src/threads.c b/src/threads.c index 815e623..d89dc5f 100644 --- a/src/threads.c +++ b/src/threads.c @@ -25,6 +25,11 @@ int isDone(struct Promise* promise) return promise->done; } +int isPromise(Object src) +{ + return src.type == TYPE_PROMISE; +} + void cleanPromise(struct Promise* promise) { promise->refs -= 1; @@ -36,6 +41,7 @@ void cleanPromise(struct Promise* promise) Object await(Object* params, int length, struct Environment* env) { + checkTypes(await) struct Promise* promise = params[0].promise; if (!promise->done) { // TODO: Does `done` need a mutex or other lock? pthread_join(promise->thread, NULL); @@ -67,7 +73,7 @@ Object async(Object* params, int length, struct Environment* env) .refs = 2, .done = 0, .env = env, - .object = params[0], // TODO: Clone? + .object = cloneObject(params[0]), }; env->refs += 1; diff --git a/src/threads.h b/src/threads.h index 22de236..8e198a7 100644 --- a/src/threads.h +++ b/src/threads.h @@ -11,15 +11,18 @@ void cleanPromise(struct Promise* promise); int isDone(struct Promise* promise); +int isPromise(Object src); + fn(async, "async", "Run the given lambda on a separate thread, returning a promise.", "(def sleepy (fn () ((sys \"sleep 0.01\") \"Hiya\"))) (def x (async sleepy)) x", "", "(def sleepy (fn () ((sys \"sleep 0.01\") \"Hiya\"))) (def x (async sleepy)) (await x)", "Hiya", ); -fn(await, "await", - "Waits for a promise to resolve before proceeding.", - "(def sleepy (fn () ((sys \"sleep 0.01\") \"Hiya\"))) (def x (async sleepy)) (await x)", "Hiya", +tfn(await, "await", + ({ expect(isPromise), anyType }), + "Waits for a promise to resolve before proceeding.", + "(def sleepy (fn () ((sys \"sleep 0.01\") \"Hiya\"))) (def x (async sleepy)) (await x)", "Hiya", ); #endif // PEBBLISP_THREADS_H