More useful type-error messages.

BAD_PARAMS_ON -> BAD_PARAMS
Replace printType() with getTypeName().
Use --ignore-config in testing.
Add type-checking (and missing clone) to (await).
This commit is contained in:
Sage Vaillancourt 2022-04-06 09:51:53 -04:00 committed by Sage Vaillancourt
parent 99a9b9a2b6
commit deca6045ff
10 changed files with 76 additions and 66 deletions

View File

@ -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__)

View File

@ -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)

View File

@ -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);

View File

@ -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(&params[i]));
return errorWithContextLineNo(BAD_PARAMS, context, 0, NULL);
}
}
*failed = 0;

View File

@ -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 "

View File

@ -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);

View File

@ -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"
);

View File

@ -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.*"

View File

@ -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;

View File

@ -11,13 +11,16 @@ 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", "<PENDING>",
"(def sleepy (fn () ((sys \"sleep 0.01\") \"Hiya\"))) (def x (async sleepy)) (await x)", "Hiya",
);
fn(await, "await",
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",
);