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."); \ _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)
#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, ...) \ #define fn(_name, _symbol, _docs, ...) \
static const char * const _name ## Symbol = _symbol; \ static const char * const _name ## Symbol = _symbol; \
fnn(_name, _docs, __VA_ARGS__) fnn(_name, _docs, __VA_ARGS__)

View File

@ -142,7 +142,7 @@ static const char* errorText[] = {
"LAMBDA_ARGS_NOT_LIST", "LAMBDA_ARGS_NOT_LIST",
"DID_NOT_FIND_SYMBOL", "DID_NOT_FIND_SYMBOL",
"BAD_TYPE", "BAD_TYPE",
"BAD_PARAMS_ON", "BAD_PARAMS",
"BAD_NUMBER", "BAD_NUMBER",
"UNSUPPORTED_NUMBER_TYPE", "UNSUPPORTED_NUMBER_TYPE",
"NOT_ENOUGH_ARGUMENTS", "NOT_ENOUGH_ARGUMENTS",
@ -332,14 +332,12 @@ char* stringObj(const Object* obj, size_t* length)
#if defined(DEBUG) || defined(STANDALONE) #if defined(DEBUG) || defined(STANDALONE)
#define SIMPLE_TYPE(_type) case _type:\ #define SIMPLE_TYPE(_type) case _type:\
printf(#_type);\ return #_type;
return
void printType(const Object* obj) const char* getTypeName(const Object* obj)
{ {
if (!obj) { if (!obj) {
printf("NULL OBJECT"); return "NULL_OBJECT";
return;
} }
switch (obj->type) { switch (obj->type) {
SIMPLE_TYPE(TYPE_NUMBER); SIMPLE_TYPE(TYPE_NUMBER);
@ -353,14 +351,10 @@ void printType(const Object* obj)
SIMPLE_TYPE(TYPE_PROMISE); SIMPLE_TYPE(TYPE_PROMISE);
SIMPLE_TYPE(TYPE_OTHER); SIMPLE_TYPE(TYPE_OTHER);
SIMPLE_TYPE(TYPE_ERROR); SIMPLE_TYPE(TYPE_ERROR);
case TYPE_LAMBDA: SIMPLE_TYPE(TYPE_LAMBDA);
printf("TYPE_LAMBDA Params:\n");
return;
} }
if (!isValidType(*obj)) { return "UNKNOWN_TYPE";
printf("UNKNOWN TYPE (%d)", obj->type);
}
} }
void _printObj(const Object* obj, int newline) void _printObj(const Object* obj, int newline)

View File

@ -46,7 +46,7 @@ enum errorCode {
LAMBDA_ARGS_NOT_LIST, LAMBDA_ARGS_NOT_LIST,
DID_NOT_FIND_SYMBOL, DID_NOT_FIND_SYMBOL,
BAD_TYPE, BAD_TYPE,
BAD_PARAMS_ON, BAD_PARAMS,
BAD_NUMBER, BAD_NUMBER,
UNSUPPORTED_NUMBER_TYPE, UNSUPPORTED_NUMBER_TYPE,
NOT_ENOUGH_ARGUMENTS, NOT_ENOUGH_ARGUMENTS,
@ -147,7 +147,7 @@ struct string {
*/ */
char* stringObj(const Object* obj, size_t* length); char* stringObj(const Object* obj, size_t* length);
void printType(const Object* obj); const char* getTypeName(const Object* obj);
void printObj(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, 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; *failed = 1;
if ((typeLength - 1) > length) { if ((typeLength - 1) > length) {
return errorWithContext(NOT_ENOUGH_ARGUMENTS, funcName); return errorWithContext(NOT_ENOUGH_ARGUMENTS, funcName);
} }
for (int i = 0; i < typeLength - 1; i++) { 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. if (typeChecks[i].checkFunc && !typeChecks[i].checkFunc(params[i])) { // TODO: Use pl func name instead of C function name.
return /*errorObject(BAD_TYPE); */ errorWithContextLineNo(BAD_PARAMS_ON, funcName, 0, NULL); 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; *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."); \ _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)
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, ...) \ #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; \ static const char * const _name ## Symbol = _symbol; \
fnn(_name, _docs, __VA_ARGS__) 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 simpleFuncEval(Object func, Object arg1, Object arg2, struct Environment* env);
Object typeCheck(const char* funcName, Object* params, int length, 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 #ifndef STANDALONE
#define DISABLE_TYPE_CHECKS #define DISABLE_TYPE_CHECKS
@ -112,7 +121,7 @@ fn(def, "def",
); );
tfn(structAccess, "poss", tfn(structAccess, "poss",
({ isStruct, isStringy, NULL }), ({ expect(isStruct), expect(isStringy), anyType }),
"Get the value of a struct's field", "Get the value of a struct's field",
"(struct Post (title body))\n " "(struct Post (title body))\n "
"(def p (Post \"TI\" \"BO\"))\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; int len = end.number - start.number;
if (len < 0 || start.number >= strlen(string.string)) { 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); Object substr = withLen(len, TYPE_STRING);
snprintf(substr.string, len + 1, "%s", string.string + start.number); snprintf(substr.string, len + 1, "%s", string.string + start.number);

View File

@ -68,40 +68,40 @@ fn(or, "|",
); );
tfn(catObjects, "cat", tfn(catObjects, "cat",
({ NULL, isStringy }), ({ anyType, returns(isStringy) }),
"Concatenate string representations of the given objects.", "Concatenate string representations of the given objects.",
"(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )", "(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )",
); );
tfn(filter, "fil", tfn(filter, "fil",
({ isFuncy, isListy, isListy }), ({ expect(isFuncy), expect(isListy), returns(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 ) )",
); );
tfn(append, "ap", tfn(append, "ap",
({ isListy, NULL, isListy }), ({ expect(isListy), anyType, returns(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 )",
); );
tfn(prepend, "pre", tfn(prepend, "pre",
({ isListy, NULL, isListy }), ({ expect(isListy), anyType, returns(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, "len", 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.", "Returns the length of the given list, or an error if the expression is not a list.",
"(len (2 3))", "2", "(len (2 3))", "2",
"(len ())", "0", "(len ())", "0",
"(len \"string\")", "BAD_PARAMS_ON: len", "(len \"string\")", "BAD_PARAMS;",
); );
tfn(reduce, "reduce", tfn(reduce, "reduce",
({ NULL, isFuncy, NULL, NULL }), ({ anyType, expect(isFuncy), anyType, anyType }),
"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"
@ -112,30 +112,30 @@ tfn(reduce, "reduce",
); );
tfn(at, "at", tfn(at, "at",
({ isNumber, isListy, NULL }), ({ expect(isNumber), expect(isListy), anyType }),
"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\")", "BAD_PARAMS_ON: at", "(at 99 \"string\")", "BAD_PARAMS;",
); );
tfn(rest, "rest", tfn(rest, "rest",
({ isListy, isListy }), ({ expect(isListy), returns(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\")", "BAD_PARAMS_ON: rest", "(rest \"string\")", "BAD_PARAMS;",
); );
tfn(reverse, "rev", tfn(reverse, "rev",
({ isListy, isListy }), ({ expect(isListy), returns(isListy) }),
"Reverse a list.", "Reverse a list.",
"(rev (1 2 3))", "( 3 2 1 )", "(rev (1 2 3))", "( 3 2 1 )",
"(rev \"string\")", "BAD_PARAMS_ON: reverse", "(rev \"string\")", "BAD_PARAMS;",
); );
tfn(isNum, "isnum", tfn(isNum, "isnum",
({ NULL, isBool }), ({ anyType, returns(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",
@ -144,7 +144,7 @@ tfn(isNum, "isnum",
); );
tfn(isList, "islist", tfn(isList, "islist",
({ NULL, isBool }), ({ anyType, returns(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",
@ -152,7 +152,7 @@ tfn(isList, "islist",
); );
tfn(isString, "isstr", tfn(isString, "isstr",
({ NULL, isBool }), ({ anyType, returns(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",
@ -161,21 +161,21 @@ tfn(isString, "isstr",
); );
tfn(isErr, "iserr", tfn(isErr, "iserr",
({ NULL, isBool }), ({ anyType, returns(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",
); );
tfn(charAt, "chat", tfn(charAt, "chat",
({ isStringy, isNumber, isStringy }), ({ expect(isStringy), expect(isNumber), returns(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)", "",
); );
tfn(charVal, "char", tfn(charVal, "char",
({ isStringy, isNumber }), ({ expect(isStringy), returns(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",
@ -183,21 +183,21 @@ tfn(charVal, "char",
); );
tfn(slen, "slen", tfn(slen, "slen",
({ isStringy, isNumber }), ({ expect(isStringy), returns(isNumber) }),
"Returns the length of the given string", "Returns the length of the given string",
"(slen \"string\")", "6", "(slen \"string\")", "6",
"(slen \"\")", "0", "(slen \"\")", "0",
); );
tfn(chars, "chars", tfn(chars, "chars",
({ isStringy, isListy }), ({ expect(isStringy), returns(isListy) }),
"Get a list of all chars in the given string", "Get a list of all chars in the given string",
"(chars \"hello\")", "( h e l l o )", "(chars \"hello\")", "( h e l l o )",
"(chars \"\")", "( )", "(chars \"\")", "( )",
); );
tfn(matches, "matches", tfn(matches, "matches",
({ isStringy, isStringy, isBool }), ({ expect(isStringy), expect(isStringy), returns(isBool) }),
"Check that a string matches a basic wildcard pattern\n" "Check that a string matches a basic wildcard pattern\n"
"Note: Currently there is no way to match a literal asterisk", "Note: Currently there is no way to match a literal asterisk",
"(matches \"Hiya\" \"Hiya\")", "T", "(matches \"Hiya\" \"Hiya\")", "T",
@ -214,11 +214,11 @@ tfn(matches, "matches",
); );
tfn(substring, "substr", tfn(substring, "substr",
({ isNumber, isNumber, isStringy, isStringy }), ({ expect(isNumber), expect(isNumber), expect(isStringy), returns(isStringy) }),
"Get a substring from the given string.", "Get a substring from the given string.",
"(substr 1 3 \"Hello\")", "el", "(substr 1 3 \"Hello\")", "el",
"(substr 99 3 \"Hello\")", "BAD_PARAMS_ON: Hello;", "(substr 99 3 \"Hello\")", "BAD_PARAMS;",
"(substr 98 99 \"Hello\")", "BAD_PARAMS_ON: Hello;", "(substr 98 99 \"Hello\")", "BAD_PARAMS;",
"(substr 0 1 \"\")", "", "(substr 0 1 \"\")", "",
"(substr 0 99 \"Hey\")", "Hey", "(substr 0 99 \"Hey\")", "Hey",
"(substr 1 99 \"Heyyyy\")", "eyyyy", "(substr 1 99 \"Heyyyy\")", "eyyyy",
@ -232,7 +232,7 @@ fn(parseEvalO, "eval",
); );
tfn(getTime, "time", tfn(getTime, "time",
({ isStruct }), ({ returns(isStruct) }),
"Get a struct of the current time with fields (minute hour sec)." "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."); fn(print, "prn", "Prints the string representation of the given object to stdout.");
tfn(numToChar, "ch", tfn(numToChar, "ch",
({ isNumber, isStringy }), ({ expect(isNumber), returns(isStringy) }),
"Gets a string containing the ascii character for the given number value.", "Gets a string containing the ascii character for the given number value.",
"(ch 107)", "k", "(ch 107)", "k",
"(ch 0x21)", "!", "(ch 0x21)", "!",
@ -255,14 +255,14 @@ fn(printEnvO, "penv",
); );
tfn(systemCall, "sys", tfn(systemCall, "sys",
({ isStringy, isNumber }), ({ expect(isStringy), returns(isNumber) }),
"Opens a shell and runs the given command, returning 0 if successful.\n" "Opens a shell and runs the given command, returning 0 if successful.\n"
"If the argument is not a string, returns 255.\n", "If the argument is not a string, returns 255.\n",
"(sys \"echo yee > /dev/null\")", "0", "(sys \"echo yee > /dev/null\")", "0",
); );
tfn(loadFile, "loadfile", tfn(loadFile, "loadfile",
({ isStringy, NULL }), ({ expect(isStringy), anyType }),
"Loads and parses the given file.\n" "Loads and parses the given file.\n"
"Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n" "Returns 0 if the file was loaded and parsed successfully. Otherwise 1.\n"
"(loadfile \"printdate.pl\")\n" "(loadfile \"printdate.pl\")\n"
@ -271,12 +271,12 @@ tfn(loadFile, "loadfile",
); );
tfn(cd, "cd", tfn(cd, "cd",
({ isStringy, NULL }), ({ expect(isStringy), anyType }),
"Change the current directory." "Change the current directory."
); );
tfn(cwd, "cwd", tfn(cwd, "cwd",
({ isStringy }), ({ returns(isStringy) }),
"Get the current directory." "Get the current directory."
); );
@ -290,12 +290,12 @@ fn(takeInput, "inp",
); );
tfn(readFileToObject, "rf", tfn(readFileToObject, "rf",
({ isStringy, isStringy }), ({ expect(isStringy), returns(isStringy) }),
"Read a file into a string object." "Read a file into a string object."
); );
tfn(getEnvVar, "env", tfn(getEnvVar, "env",
({ isStringy, isStringy }), ({ expect(isStringy), returns(isStringy) }),
"Get a variable from the current environment\n" "Get a variable from the current environment\n"
"(env HOME) => /home/sagevaillancourt" "(env HOME) => /home/sagevaillancourt"
); );

View File

@ -65,13 +65,13 @@ check() {
local exit_code=0 local exit_code=0
if $VALGRIND; then if $VALGRIND; then
echo -ne "\n $1" echo -ne "\n $1"
output="$($VALCOM ./pl "(loadfile \"examples/lib.pbl\") $2")" output="$($VALCOM ./pl --ignore-config "(loadfile \"examples/lib.pbl\") $2")"
exit_code=$? exit_code=$?
if [[ "$exit_code" == "0" ]]; then if [[ "$exit_code" == "0" ]]; then
echo -ne "\r " echo -ne "\r "
fi fi
else else
output="$(./pl "(loadfile \"examples/lib.pbl\") $2")" output="$(./pl --ignore-config "(loadfile \"examples/lib.pbl\") $2")"
fi fi
@ -217,7 +217,7 @@ check "FuncReturningAFunc" "(def plusser (fn (outer) (fn (inner) (+ outer inner)
(plusFive 10)" "15" (plusFive 10)" "15"
title "ShouldError" 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 "NoMapList" "(map sq)" "NULL_MAP_ARGS"
check "BadNumber" "(5df)" regex "BAD_NUMBER.*" check "BadNumber" "(5df)" regex "BAD_NUMBER.*"
check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*" check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*"

View File

@ -25,6 +25,11 @@ int isDone(struct Promise* promise)
return promise->done; return promise->done;
} }
int isPromise(Object src)
{
return src.type == TYPE_PROMISE;
}
void cleanPromise(struct Promise* promise) void cleanPromise(struct Promise* promise)
{ {
promise->refs -= 1; promise->refs -= 1;
@ -36,6 +41,7 @@ void cleanPromise(struct Promise* promise)
Object await(Object* params, int length, struct Environment* env) Object await(Object* params, int length, struct Environment* env)
{ {
checkTypes(await)
struct Promise* promise = params[0].promise; struct Promise* promise = params[0].promise;
if (!promise->done) { // TODO: Does `done` need a mutex or other lock? if (!promise->done) { // TODO: Does `done` need a mutex or other lock?
pthread_join(promise->thread, NULL); pthread_join(promise->thread, NULL);
@ -67,7 +73,7 @@ Object async(Object* params, int length, struct Environment* env)
.refs = 2, .refs = 2,
.done = 0, .done = 0,
.env = env, .env = env,
.object = params[0], // TODO: Clone? .object = cloneObject(params[0]),
}; };
env->refs += 1; env->refs += 1;

View File

@ -11,13 +11,16 @@ void cleanPromise(struct Promise* promise);
int isDone(struct Promise* promise); int isDone(struct Promise* promise);
int isPromise(Object src);
fn(async, "async", fn(async, "async",
"Run the given lambda on a separate thread, returning a promise.", "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)) x", "<PENDING>",
"(def sleepy (fn () ((sys \"sleep 0.01\") \"Hiya\"))) (def x (async sleepy)) (await x)", "Hiya", "(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.", "Waits for a promise to resolve before proceeding.",
"(def sleepy (fn () ((sys \"sleep 0.01\") \"Hiya\"))) (def x (async sleepy)) (await x)", "Hiya", "(def sleepy (fn () ((sys \"sleep 0.01\") \"Hiya\"))) (def x (async sleepy)) (await x)", "Hiya",
); );