From 097cbf6a5c1b52f785a8e95faed8c2f663e25256 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Mon, 21 Mar 2022 12:33:35 -0400 Subject: [PATCH] Start some internal testing frameworking. --- src/Makefile | 2 +- src/env.c | 91 ++++++++++++++++++++++++++++++++++-------- src/env.h | 3 ++ src/examples/webby.pl | 5 ++- src/object.c | 9 ++++- src/object.h | 2 +- src/pebblisp.c | 47 ++++++++++++++++++++++ src/pebblisp.h | 19 +++++---- src/plfunc.h | 93 +++++++++++++++++++++++++++++-------------- src/tests.sh | 27 ++++++++----- src/tokens.c | 11 +++++ src/web.c | 4 +- src/web.h | 6 +-- 13 files changed, 245 insertions(+), 74 deletions(-) diff --git a/src/Makefile b/src/Makefile index 3c8d26d..c4b14d8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -12,7 +12,7 @@ mkfile_dir := $(dir $(mkfile_path)) GCC_COM ?= gcc -g -O0 -Wall -o $(exe) -D WEBSERVER -D STANDALONE -DSCRIPTDIR=\"$(SCRIPTDIR)\" all: - $(GCC_COM) $(files) $(libs) && echo && ./tests.sh + $(GCC_COM) $(files) $(libs) && echo && ./tests.sh && ./pl --run-tests notest: $(GCC_COM) $(file_libs) diff --git a/src/env.c b/src/env.c index 150e97a..154468a 100644 --- a/src/env.c +++ b/src/env.c @@ -52,6 +52,7 @@ struct Environment envForLambda(const Object* params, const Object* arg_forms, } struct Environment env = { + .name = "lambdaEnv", .outer = outer, .strings = NULL, .objects = NULL, @@ -240,17 +241,24 @@ void setGlobal(struct Environment* env) struct helpText { const char* symbol; const char* help; + const char*const* tests; }; int currentHelp = 0; struct helpText helpTexts[100]; -#define pf(_name, _func) buildHelp(_name, &(_func), _func ## Doc) +#define pf(_name, _func) buildFuncSym(_name, &(_func), _func ## Doc, NULL) +#define pfn(_func) buildFuncSym(_func ## Symbol, &(_func), _func ## Doc, NULL) +#define pft(_name, _func) buildFuncSym(_name, &(_func), _func ## Doc, _func ## Tests) -struct symFunc buildHelp(const char* symbol, Object (* func)(Object, Object, struct Environment*), const char* help) +int helpInitialized = 0; +struct symFunc buildFuncSym(const char* symbol, Object (* func)(Object, Object, struct Environment*), const char* help, const char*const* tests) { - helpTexts[currentHelp].help = help; - helpTexts[currentHelp].symbol = symbol; - currentHelp += 1; + if (!helpInitialized) { + helpTexts[currentHelp].help = help; + helpTexts[currentHelp].symbol = symbol; + helpTexts[currentHelp].tests = tests; + currentHelp += 1; + } return (struct symFunc) { .func = func, @@ -269,6 +277,54 @@ const char* getHelp(const char* symbol) return "Help not found!"; } +fnn(segfault, "seg", "Induces a segfault.") +(Object ignore1, Object ignore2, struct Environment* env) +{ + int* p = NULL; + return numberObject(*p); +} + +// Returns number of failures +int runTests() +{ + printf("Running tests...\n"); + int failureCount = 0; + int passCount = 0; + for (int hi = 0; hi < currentHelp; hi++) { + struct helpText h = helpTexts[hi]; + if (h.tests) { + int ti = 0; + char result[1024]; + while (h.tests[ti]) { + const char* test = h.tests[ti]; + const char* expected = h.tests[ti + 1]; + struct Environment env = defaultEnv(); + Object o = parseEval(test, &env); + stringObj(result, &o); + cleanObject(&o); + if (strcmp(result, expected) != 0) { + failureCount++; + printf("Test failed!\n"); + printf("%s\n", test); + printf("Expected '%s' but received '%s'\n", expected, result); + } else { + passCount++; + } + ti += 2; + } + } + } + if (passCount > 0) { + printf(""); + } + printf("%d tests passed!\n", passCount); + if (failureCount > 0) { + printf(""); + } + printf("%d tests failed!\n", failureCount); + return failureCount; +} + struct Environment defaultEnv() { char** strings = calloc(sizeof(char*), MAX_ENV_ELM); @@ -276,6 +332,7 @@ struct Environment defaultEnv() char size = MAX_ENV_ELM; struct Environment e = { + .name = "defaultEnv", .outer = NULL, .strings = strings, .objects = objects, @@ -297,8 +354,8 @@ struct Environment defaultEnv() {"<", <h}, {"&", &and}, {"|", &or}, - pf("cat", catObjects), - pf("fil", filter), + pft("cat", catObjects), + pft("fil", filter), pf("len", len), pf("ap", append), pf("pre", prepend), @@ -312,22 +369,23 @@ struct Environment defaultEnv() pf("isnum", isNum), pf("islist", isList), pf("isstr", isString), - {"iserr", &isErr}, + pf("iserr", isErr), pf("char", charVal), - {"eval", &parseEvalO}, - {"poss", &possessive}, + pf("eval", parseEvalO), + pf("poss", possessive), #ifdef WEBSERVER pf("get", addGetRoute), pf("post", addPostRoute), pf("serve", startServer), #endif #ifdef STANDALONE - {"prn", &print}, - {"pch", &pChar}, - {"penv", &printEnvO}, - //{"sys", &systemCall}, - {"loadfile", &loadFile}, - {"inp", &takeInput}, + pfn(segfault), + pf("prn", print), + pf("pch", pChar), + pf("penv", printEnvO), + pf("sys", systemCall), + pf("loadfile", loadFile), + pf("inp", takeInput), pf("?", help) #endif }; @@ -336,6 +394,7 @@ struct Environment defaultEnv() addFunc(symFuncs[i].sym, symFuncs[i].func, &e); } + helpInitialized = 1; return e; } diff --git a/src/env.h b/src/env.h index eccef68..379b225 100644 --- a/src/env.h +++ b/src/env.h @@ -15,6 +15,7 @@ struct Environment { struct StructDef* structDefs; int refs; + const char* name; }; struct Environment* global(); @@ -44,4 +45,6 @@ int getStructIndex(struct Environment* env, const char* name); const char* getHelp(const char* symbol); +int runTests(); + #endif diff --git a/src/examples/webby.pl b/src/examples/webby.pl index 2580f92..a831f20 100755 --- a/src/examples/webby.pl +++ b/src/examples/webby.pl @@ -36,12 +36,13 @@ and the interpreter won't even instantly crash over it! It's truly astounding stuff, when you think about it." )) -(def homepage (fn () (html ( +(def homepage (fn (req) (html ( (head ( (link ((rel "stylesheet") (href "styles.css"))) )) (body ( - (h1 "This is a sweet PebbLisp blog") + (h1 "This is a sweet PebbLisp site") + (p (cat "" req)) (htmlize p1) (htmlize p2))) )))) diff --git a/src/object.c b/src/object.c index c57be72..3a47abe 100644 --- a/src/object.c +++ b/src/object.c @@ -1,4 +1,5 @@ #include "object.h" +#include "pebblisp.h" #include "env.h" #include @@ -476,11 +477,16 @@ void _printObj(const Object* obj, int newline) printObj(&obj->lambda->body); return; } - char temp[200] = ""; stringObj(temp, obj); if (newline) { printf("%s\n", temp); + if (obj->type == TYPE_ERROR) { + if (obj->error && obj->error->plContext) { + printf("%s\n", obj->error->plContext->text); + return; + } + } } else { printf("%s", temp); } @@ -922,6 +928,7 @@ inline Object errorObject(enum errorCode err) o.error = malloc(sizeof(struct Error)); o.error->code = err; o.error->context = NULL; + o.error->plContext = NULL; #endif return o; diff --git a/src/object.h b/src/object.h index 5640f10..dacff48 100644 --- a/src/object.h +++ b/src/object.h @@ -85,11 +85,11 @@ typedef struct Object Object; struct Lambda; struct Environment; -struct Slice; struct Other; struct Error { enum errorCode code; char* context; + struct Slice* plContext; }; struct Object { diff --git a/src/pebblisp.c b/src/pebblisp.c index 9eb9613..9be72a7 100644 --- a/src/pebblisp.c +++ b/src/pebblisp.c @@ -1,3 +1,9 @@ +#ifdef STANDALONE +#define _GNU_SOURCE +#include +#include +#endif + #include "pebblisp.h" #include @@ -590,6 +596,7 @@ Result parseAtom(struct Slice* s) } } +struct Slice* lastOpen = NULL; Object parseEval(const char* input, struct Environment* env) { struct Error err = noError(); @@ -623,6 +630,7 @@ Object parseEval(const char* input, struct Environment* env) struct Slice* tok = tokens; while (tok[i].text != NULL) { if (tok[i].text[0] == '(') { + lastOpen = &tok[i]; parens++; } else if (tok[i].text[0] == ')') { parens--; @@ -633,6 +641,7 @@ Object parseEval(const char* input, struct Environment* env) Object parsed = parse(tok).obj; if (parsed.type == TYPE_ERROR) { obj = parsed; // TODO Check necessity + obj.error->plContext = lastOpen; break; } if (tok[i].text[0] == ')') { @@ -733,10 +742,48 @@ void loadArgsIntoEnv(int argc, const char* argv[], struct Environment* env) addToEnv(env, "args", args); } +int nestedSegfault = 0; +void handler(int nSignum, siginfo_t* si, void* vcontext) +{ + if (nestedSegfault) { + printf("Nested segfault!!!\n"); + exit(139); + } + nestedSegfault = 1; + + printf("Segfaulted!\n"); + if (lastOpen) { + printf("line: %d\n%s\n", lastOpen->lineNumber, lastOpen->text); + } else { + printf("Happened before token processing.\n"); + } + struct Environment *e = global(); + *e = defaultEnv(); + ucontext_t* context = vcontext; + context->uc_mcontext.gregs[REG_RIP]++; + exit(139); +} + int main(int argc, const char* argv[]) { struct Environment env = defaultEnv(); setGlobal(&env); + + if (argc == 2 && strcmp(argv[1], "--run-tests") == 0) { + runTests(); + return 0; + } + + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + action.sa_flags = SA_SIGINFO; + action.sa_sigaction = handler; + sigaction(SIGSEGV, &action, NULL); + + // struct Environment* e = &env; + // e += 10000; + // printEnv(e); + readFile(SCRIPTDIR "/lib.pbl", &env); if (argc >= 2) { FILE* file = fopen(argv[1], "r"); diff --git a/src/pebblisp.h b/src/pebblisp.h index 906feeb..d5fa281 100644 --- a/src/pebblisp.h +++ b/src/pebblisp.h @@ -6,11 +6,22 @@ #include "env.h" #include "object.h" -#define F(_name, _docs) static const char * const _name ## Doc = _docs; Object _name +#define fn(_name, _docs, ...) static const char * const _name ## Doc = _docs; \ +Object _name + +#define fnn(_name, _symbol, _docs)\ +static const char * const _name ## Doc = _docs;\ +static const char * const _name ## Symbol = _symbol;\ +Object _name + +#define fnt(_name, _docs, ...) static const char * const _name ## Doc = _docs;\ + static const char * const _name ## Tests[] = {__VA_ARGS__, NULL}; \ + Object _name struct Slice { const char* text; unsigned char length; + int lineNumber; }; typedef struct Result { @@ -43,12 +54,6 @@ Object simpleFuncEval(Object func, Object arg1, Object arg2, struct Environment* #ifdef STANDALONE -Object takeInput(Object i1, Object i2, struct Environment* i3); - -Object systemCall(Object call, Object _, struct Environment* i3); - -Object loadFile(Object filename, Object _, struct Environment* env); - int _readFile(FILE* input, struct Environment* env); int readFile(const char* filename, struct Environment* env); diff --git a/src/plfunc.h b/src/plfunc.h index 3023949..6f8307d 100644 --- a/src/plfunc.h +++ b/src/plfunc.h @@ -4,7 +4,7 @@ #include "pebblisp.h" #define BASIC_OP(_name) \ - Object _name(Object obj1, Object obj2, struct Environment *env); + Object _name(Object obj1, Object obj2, struct Environment *env) BASIC_OP(add); @@ -28,85 +28,118 @@ BASIC_OP(or); #undef BASIC_OP -F(catObjects, - "Concatenate string versions of the given objects.\n" - "(cat \"Stuff: \" (1 2 3)) => \"Stuff: ( 1 2 3 )\"" +fnt(catObjects, + "Concatenate string versions of the given objects.\n", + "(cat \"Stuff: \" (1 2 3))", "Stuff: ( 1 2 3 )" )(Object obj1, Object obj2, struct Environment* env); -F(filter, "(filter (< 50) (25 60 100)) => ( 60 100 )") -(Object condition, Object list, struct Environment* env); +fnt(filter, + "Filter a list based on the given condition.\n", + "(fil (< 50) (25 60 100))", "( 60 100 )" +)(Object condition, Object list, struct Environment* env); -F(append, "(ap (1 2) 3) => ( 1 2 3 )") +fn(append, + "Append the given element", + "(ap (1 2) 3)", "( 1 2 3 )") (Object list, Object newElement, struct Environment* env); -F(prepend, "(pre (2 3) 1) => ( 1 2 3 )") +fn(prepend, "(pre (2 3) 1) => ( 1 2 3 )") (Object list, Object newElement, struct Environment* env); -F(len, "(len (2 3)) => 2") +fn(len, "(len (2 3)) => 2") (Object obj1, Object o_ignore, struct Environment* e_ignore); -F(reduce, "(reduce ((1 2 3) 0) +) => 6") +fn(reduce, "(reduce ((1 2 3) 0) +) => 6") (Object listInitial, Object func, struct Environment* env); -F(at, "(at 1 (1 2 3)) => 2") +fn(at, "(at 1 (1 2 3)) => 2") (Object index, Object list, struct Environment* env); -F(rest, "(rest (1 2 3)) => ( 2 3 )") +fn(rest, "(rest (1 2 3)) => ( 2 3 )") (Object list, Object ignore, struct Environment* env); -F(reverse, "(rev (1 2 3)) => ( 3 2 1 )") +fn(reverse, "(rev (1 2 3)) => ( 3 2 1 )") (Object _list, Object ignore, struct Environment* ignore2); -F(isNum, "(isnum 1) => T\n(isnum \"Hello\") => F") +fn(isNum, "(isnum 1) => T\n(isnum \"Hello\") => F") (Object test, Object ignore, struct Environment* ignore2); -F(isList, +fn(isList, "(islist (1 2 3)) => T\n" "(islist ()) => T\n" "(islist \"Stringy\") => F" )(Object test, Object ignore, struct Environment* ignore2); -F(isString, +fn(isString, "(isstr \"Heyo\") => T\n" "(isstr \"\") => T\n" "(isstr 10) => F" )(Object test, Object ignore, struct Environment* ignore2); -F(isErr, "") -(Object test, Object ignore, struct Environment* ignore2); +fn(isErr, + "(iserr (eval \"(((\") => T\n" + "(iserr 5) => F" +)(Object test, Object ignore, struct Environment* ignore2); -F(charAt, +fn(charAt, "(chat \"Hello\" 1) => \"e\"\n" "(chat \"Hello\" 10) => \"\"" )(Object string, Object at, struct Environment* ignore); -F(charVal, +fn(charVal, "(char \"h\") => 104\n" "(char \"hello\") => 104\n" "(char \"\") => 0" )(Object test, Object ignore, struct Environment* ignore2); -F(parseEvalO, "(eval \"(1 2 3)\") => (1 2 3)") +fn(parseEvalO, "(eval \"(1 2 3)\") => (1 2 3)") (Object text, Object ignore, struct Environment* env); -F(possessive, - "(struct Post (title body)) (def p (Post \"TI\" \"BO\")) p's title => TI" +fn(possessive, + "(struct Post (title body))\n" + "(def p (Post \"TI\" \"BO\"))\n" + "p's title => TI" )(Object structo, Object field, struct Environment* env); #ifdef STANDALONE -F(print, "Prints the string representation of the given object to stdout.") +fn(print, "Prints the string representation of the given object to stdout.") (Object p, Object ignore, struct Environment* ignore2); -F(pChar, "Prints the ascii character for the given number value.") +fn(pChar, "Prints the ascii character for the given number value.") (Object c, Object i1, struct Environment* i2); -F(printEnvO, "Prints out the current scoped environment.") +fn(printEnvO, "Prints out the current scoped environment.") (Object i1, Object i2, struct Environment* env); -F(help, "(? +) => \"(+ 1 2) => 3\"") -(Object symbol, Object ignore, struct Environment* ignore2); +fn(systemCall, + "Opens a shell and runs the given command, returning the command's exit code.\n" + "(sys \"echo yee\")\n" + "yee\n" + "=> 0" +)(Object process, Object _, struct Environment* env); -#endif +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" +)(Object filename, Object _, struct Environment* env); -#endif //PEBBLISP_PLFUNC_H +fn(takeInput, + "Take console input with an optional prompt. For example:\n" + "`(def x (input))` will wait for user input with no prompt.\n" + "`(def x (input \">> \"))` wait for input, but prompt the user with '>> '.\n" +)(Object i1, Object i2, struct Environment* i3); + +fn(help, + "Displays help text for the given function.\n" + "Currently requires the function name as a string, but future syntactic sugar may\n" + "loosen this requirement.\n" + "(? \"+\") => \"(+ 1 2) => 3\"" +)(Object symbol, Object ignore, struct Environment* ignore2); + +#endif // STANDALONE + +#endif // PEBBLISP_PLFUNC_H diff --git a/src/tests.sh b/src/tests.sh index f7fdabd..2e87563 100755 --- a/src/tests.sh +++ b/src/tests.sh @@ -55,21 +55,26 @@ check() { return 1 fi + local output if $VALGRIND; then echo -ne "\n $1\r " - local output="$($VALCOM ./pl "(loadfile \"examples/lib.pbl\") $2" | grep -v PLT)" + output="$($VALCOM ./pl "(loadfile \"examples/lib.pbl\") $2" | grep -v PLT)" else - local output="$(./pl "(loadfile \"examples/lib.pbl\") $2" | grep -v PLT)" + output="$(./pl "(loadfile \"examples/lib.pbl\") $2" | grep -v PLT)" fi - if [ "$output" == "$3" ]; then + if [ "$3" == "$regex" ]; then + if [[ "$output" =~ ^$4$ ]]; then + pass "$1" + return + fi + elif [ "$output" == "$3" ]; then pass "$1" - elif [ "$3" == "$regex" ] && [[ "$output" =~ $4 ]]; then - pass "$1" - else - fail "$1" "$2" - FAIL_OUTPUT="${FAIL_OUTPUT}\n  expected '$3' but received '$output'\n" + return fi + + fail "$1" "$2" + FAIL_OUTPUT="${FAIL_OUTPUT}\n  expected '$3' but received '$output'\n" } echo "STARTING TESTS" @@ -206,9 +211,9 @@ title "ShouldError" check "LenOfNotList" "(len 5)" "NOT_A_LIST" check "NoMapList" "(map sq)" "( )" check "UnevenLists" "(+ (1 2) (1 2 3))" "LISTS_NOT_SAME_SIZE" -check "BadNumber" "(5df)" "BAD_NUMBER" -check "BadHex" "(0x0zf)" "BAD_NUMBER" -check "BadBinary" "(0b01120)" "BAD_NUMBER" +check "BadNumber" "(5df)" regex "BAD_NUMBER.*" +check "BadHex" "(0x0zf)" regex "BAD_NUMBER.*" +check "BadBinary" "(0b01120)" regex "BAD_NUMBER.*" check "BadParens1" "(hey()" regex "'MISMATCHED_PARENS.*" check "BadParens2" "(hey)(" regex "'MISMATCHED_PARENS.*" check "BadParens3" "((hey(" regex "'MISMATCHED_PARENS.*" diff --git a/src/tokens.c b/src/tokens.c index a42fbf1..7f5216a 100644 --- a/src/tokens.c +++ b/src/tokens.c @@ -66,6 +66,7 @@ struct Slice* nf_tokenize(const char* input, struct Error* err) int i = 0; int slice = 0; + int lineNumber = 1; int parens = 0; while (input[i] != '\0') { @@ -73,6 +74,9 @@ struct Slice* nf_tokenize(const char* input, struct Error* err) // printd("input: '%c'\n", input[i]); if (isWhitespace(input[i]) || input[i] == ';') { + if (input[i] == '\n') { + lineNumber++; + } i++; continue; } @@ -92,6 +96,7 @@ struct Slice* nf_tokenize(const char* input, struct Error* err) } slices[slice].text = &input[i]; + slices[slice].lineNumber = lineNumber; if (isSingle(input[i])) { i++; @@ -105,6 +110,9 @@ struct Slice* nf_tokenize(const char* input, struct Error* err) if (input[i] == '"' && input[i + 1] == '"' && input[i + 2] == '"') { break; } + if (input[i] == '\n') { + lineNumber++; + } l++; if (input[i] == '\0' || input[i + 1] == '\0' || input[i + 2] == '\0') { err->context = malloc(sizeof(char) * ERR_LEN + 1); @@ -118,6 +126,9 @@ struct Slice* nf_tokenize(const char* input, struct Error* err) } else { // Simple string while (input[++i] != '"' && input[i] != '\0') { + if (input[i] == '\n') { + lineNumber++; + } l++; } } diff --git a/src/web.c b/src/web.c index e525bb4..02cba44 100644 --- a/src/web.c +++ b/src/web.c @@ -80,8 +80,8 @@ answer_to_connection(void* cls, struct MHD_Connection* connection, Object queryParams = listObject(); MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, add_query_param, &queryParams); - Object res = structObject(requestDefinition); - res.structObject->fields[0] = queryParams; + Object res = numberObject(1010);//structObject(requestDefinition); + //res.structObject->fields[0] = queryParams; Object route = cloneObject(routes[i].routeAction); Object result = listEvalLambda(&route, &res, routes[i].env); diff --git a/src/web.h b/src/web.h index 14619bb..9ff7724 100644 --- a/src/web.h +++ b/src/web.h @@ -16,21 +16,21 @@ int addRoute(struct Route route); int start(int port); -F(startServer, +fn(startServer, "(serve) => 0\n" "Starts a simple web server with routes that have been added with (get) and (post).\n" "Note: This is a non-blocking call. It is recommended to wait for user input before exiting.\n" " A simple way would be to use (inp) immediately after the (serve) call." )(Object path, Object textFunc, struct Environment* env); -F(addGetRoute, +fn(addGetRoute, "Note: Implementation bugs currently prevent using an inline lambda.\n" " (def homepage (fn () (\"Hello, world!\")));(get \"/\" homepage)\n" " (def queryPage (fn (req) (req's queryParams)));(get \"/x\" queryPage)\n" " (serve)\n" )(Object path, Object textFunc, struct Environment* env); -F(addPostRoute, +fn(addPostRoute, "Note: Implementation bugs currently prevent using an inline lambda.\n" " (def homepage (fn () (\"Hello, world!\")));(post \"/\" homepage)\n" " (serve)\n"