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:
parent
99a9b9a2b6
commit
deca6045ff
|
@ -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__)
|
||||
|
|
18
src/object.c
18
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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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);
|
||||
|
|
66
src/plfunc.h
66
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"
|
||||
);
|
||||
|
|
|
@ -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.*"
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue