From b419826b8ee5098cbfd6b60f8db895abc7649125 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Tue, 5 Apr 2022 15:27:41 -0400 Subject: [PATCH] More robust addToTable(). Add several pl string functions: `slen`, `chars`, `matches`, and `substr`. Add support for comments in native tests. --- src/calc.c | 1 + src/env.c | 49 ++++++++++++------------ src/examples/pebblisp.pbl | 27 ++++++++++++-- src/hash.c | 25 +++++++------ src/pebblisp.c | 1 + src/plfunc.c | 78 +++++++++++++++++++++++++++++++++++++++ src/plfunc.h | 42 +++++++++++++++++++++ 7 files changed, 184 insertions(+), 39 deletions(-) diff --git a/src/calc.c b/src/calc.c index c6f87f4..76a4b2e 100644 --- a/src/calc.c +++ b/src/calc.c @@ -1,6 +1,7 @@ #include #include +#define HASHLESS_ENV #include "pebcom.h" #include "object.h" #include "pebbleobject.h" diff --git a/src/env.c b/src/env.c index bfc8f07..516d5fa 100644 --- a/src/env.c +++ b/src/env.c @@ -8,10 +8,6 @@ #include "web.h" #include "plfunc.h" -#define printd(...) ; - -#define HASH_ENV - struct symFunc buildFuncSym(const char* symbol, Object (* func)(Object*, int, struct Environment*), const char* help, const char* const* tests, size_t testLength); @@ -60,7 +56,7 @@ struct symFunc { #define pf(_func) buildFuncSym(_func ## Symbol, &(_func)) #endif -#ifdef HASH_ENV // Hash-based env +#ifndef HASHLESS_ENV // Hash-based env Object* fetch(const char* name, struct Environment* env) { while (env) { @@ -104,18 +100,10 @@ struct Environment envForLambda(const Object* params, const Object* arg_forms, i struct Environment env = { .outer = NULL, - .table = buildTable(4), + .table = buildTable(paramCount * 2), .refs = 1, }; - if (paramCount == 0) { - if (outer) { - outer->refs += 1; - return *outer; - } - return env; - } - const Object* march = arg_forms; for (int i = 0; i < paramCount; i++) { const char* newObjName = itemAt(params, i)->string; @@ -186,6 +174,10 @@ struct Environment defaultEnv() #ifndef PBL_PLATFORM_APLITE pf(rest), pf(charAt), + pf(chars), + pf(matches), + pf(slen), + pf(substring), pf(isNum), pf(isList), pf(isString), @@ -447,6 +439,10 @@ struct Environment defaultEnvPreAllocated(struct EnvElement* elements) #ifndef PBL_PLATFORM_APLITE pf(rest), pf(charAt), + pf(chars), + pf(matches), + pf(slen), + pf(substring), pf(isNum), pf(isList), pf(isString), @@ -488,12 +484,6 @@ struct Environment defaultEnvPreAllocated(struct EnvElement* elements) e.elements[j].symbol = NULL; } -#ifdef CACHE - for (i = 0; i < CACHE_SIZE + 1; i++) { - cache[i] = NULL; - } -#endif - helpInitialized = 1; return e; } @@ -618,12 +608,12 @@ void printColored(const char* code) int isQuote = 0; printf("[%dm", getColor(depth)); while (code[c]) { - if (code[c] == '(' && !isQuote) { + if ((code[c] == '(' || code[c] == '{') && !isQuote) { depth += 1; printf("[%dm", getColor(depth)); - } else if (code[c] == ')' && !isQuote) { + } else if ((code[c] == ')' || code[c] == '}') && !isQuote) { depth -= 1; - printf(")[%dm", getColor(depth)); + printf("%c[%dm", code[c], getColor(depth)); c++; continue; } else if (code[c] == '"') { @@ -668,6 +658,10 @@ Object help(Object* params, int length, struct Environment* env) for (int ti = 0; ti < h.testCount; ti += 2) { const char* test = h.tests[ti]; const char* expected = h.tests[ti + 1]; + if (test[0] == ';') { + textCursor += sprintf(textCursor, "\n%s %s", test + 1, expected); + continue; + } textCursor += sprintf(textCursor, "\n %s => ", test); if (expected[0] == '\0') { textCursor += sprintf(textCursor, ""); @@ -697,12 +691,19 @@ int runTests(int detailed) for (int ti = 0; ti < h.testCount; ti += 2) { const char* test = h.tests[ti]; const char* expected = h.tests[ti + 1]; + if (test[0] == ';') { + continue; + } struct Environment env = defaultEnv(); Object o = parseEval(test, &env); size_t length; char* result = stringObj(&o, &length); cleanObject(&o); - if (strcmp(result, expected) != 0) { + int expectedLen = 0; + while(expected[expectedLen] && expected[expectedLen] != ';') { + expectedLen++; + } + if (strncmp(result, expected, expectedLen) != 0) { failureCount++; printf("Test failed!\n"); printf("%s\n", test); diff --git a/src/examples/pebblisp.pbl b/src/examples/pebblisp.pbl index 0f54b0d..a6a4793 100644 --- a/src/examples/pebblisp.pbl +++ b/src/examples/pebblisp.pbl @@ -14,6 +14,8 @@ (def nl (ch 10)) +(def fore (fn (f list) ((map f list) ""))) + (def first (fn (list) (at 0 list))) (def up "..") @@ -21,7 +23,7 @@ (def config (cat ~ "/.pebblisp.pbl")) -(def r (fn () (loadfile config))) +(def reloadConfig (fn () (loadfile config))) (def hour (fn (ti) ( (def h (% ti.hour 12)) @@ -30,25 +32,42 @@ (def zero (fn (num) (cat (if (< num 10) "0" "") num))) -(def clock (fn (ti) (cat (hour ti) ":" (zero ti.minute) ":" (zero ti.sec)))) +(def string (fn (a) (cat "" a))) (struct Alias (name value)) (def aliases ( (Alias "ls" "ls --color") + (Alias "alias" "(alias)") + (Alias "pbl" "vim ~/.pebblisp.pbl") + (Alias "r" "(reloadConfig)") )) +(def alias (fn () ( + (reduce (map (fn (a) (cat nl a.name " -> " a.value)) aliases) cat "") +))) + (def preprocess (fn (text) ( (def matches (fil (fn (a) (= text a.name)) aliases)) (def match (first matches)) (if (iserr match) text match.value) ))) +(def clock (fn (ti) (cat (hour ti) ":" (zero ti.minute) ":" (zero ti.sec)))) + +(def cleanDir (fn () ( + (def di (cwd)) + (if (matches di (cat ~ "*")) + (cat "~" (substr (slen ~) 999 di)) + di + ) +))) + (def prompt (fn (a) ( (def ti (time)) - (def d (cwd)) + (def d (cleanDir)) (cat nl esc "]0; " d (ch 7) - bold red "[sage] " blue (clock) " " reset cyan d nl + bold red "[sage] " blue (clock ti) " " reset cyan d nl bold green "pebblisp ~> " reset) ))) diff --git a/src/hash.c b/src/hash.c index 6cff466..3212f43 100644 --- a/src/hash.c +++ b/src/hash.c @@ -21,7 +21,7 @@ struct ObjectTable buildTable(size_t capacity) struct ObjectTable table = { .count = 0, .capacity = capacity, - .elements = malloc(sizeof(struct EnvElement) * capacity), + .elements = capacity ? malloc(sizeof(struct EnvElement) * capacity) : NULL, }; for (int i = 0; i < table.capacity; i++) { table.elements[i].symbol = NULL; @@ -35,16 +35,8 @@ struct ObjectTable buildTable(size_t capacity) /// \param object Should already be cloned void addToTable(struct ObjectTable* table, char* name, Object object) { - size_t h = hash(name) % table->capacity; - while(table->elements[h].symbol) { - h = (h + 1) % table->capacity; - } - table->elements[h].symbol = name; - table->elements[h].object = object; - table->count += 1; - - if (table->capacity < table->count * 2) { - struct ObjectTable newTable = buildTable(table->capacity * 2); + if (table->capacity < (table->count + 1) * 2) { + struct ObjectTable newTable = buildTable(table->capacity ? table->capacity * 2 : 4); for (int i = 0; i < table->capacity; i++) { if (table->elements[i].symbol) { addToTable(&newTable, table->elements[i].symbol, table->elements[i].object); @@ -53,10 +45,21 @@ void addToTable(struct ObjectTable* table, char* name, Object object) free(table->elements); *table = newTable; } + + size_t h = hash(name) % table->capacity; + while(table->elements[h].symbol) { + h = (h + 1) % table->capacity; + } + table->elements[h].symbol = name; + table->elements[h].object = object; + table->count += 1; } Object* getFromTable(struct ObjectTable* table, const char* name) { + if (table->capacity == 0) { + return NULL; + } size_t h = hash(name) % table->capacity; while(table->elements[h].symbol) { if (strcmp(name, table->elements[h].symbol) == 0) { diff --git a/src/pebblisp.c b/src/pebblisp.c index 4303b78..5e44f6c 100644 --- a/src/pebblisp.c +++ b/src/pebblisp.c @@ -698,6 +698,7 @@ void handler(int nSignum, siginfo_t* si, void* vcontext) exit(139); } +// TODO: Track crashes in ~/.plcrash or some such int main(int argc, const char* argv[]) { struct Environment env = defaultEnv(); diff --git a/src/plfunc.c b/src/plfunc.c index 3de4522..8c32741 100644 --- a/src/plfunc.c +++ b/src/plfunc.c @@ -180,6 +180,84 @@ Object charVal(Object* params, unused int length, unused struct Environment* env return numberObject(test.string[0]); } +Object chars(Object* params, unused int length, unused struct Environment* env) +{ + checkTypes(chars) + char c[2]; + c[1] = '\0'; + int i = 0; + Object list = listObject(); + while(params[0].string[i]) { + c[0] = params[0].string[i]; + nf_addToList(&list, stringFromSlice(c, 1)); + i++; + } + return list; +} + +size_t literalLen(const char* pattern) +{ + const char* cursor = pattern; + while(*cursor != '*' && *cursor != '\0') { + cursor++; + } + return cursor - pattern; +} + +/// Returns 1 on match, 0 otherwise +int stringComp(const char* string, const char* pattern) +{ + const char* cursor = string; + const char* patternCursor = pattern; + + while (*patternCursor) { + size_t len = literalLen(patternCursor); + if (len == 0) { + patternCursor += 1; + while (!stringComp(cursor, patternCursor)) { + cursor++; + if (*cursor == '\0') { + return 0; + } + } + } else { + if (strncmp(patternCursor, cursor, len) != 0) { + return 0; + } + patternCursor += len; + } + } + return 1; +} + +Object matches(Object* params, int length, unused struct Environment* env) +{ + checkTypes(matches) + return boolObject(stringComp(params[0].string, params[1].string)); +} + +Object slen(Object* params, int length, unused struct Environment* env) +{ + checkTypes(slen) + return numberObject(strlen(params[0].string)); +} + +Object substring(Object* params, int length, unused struct Environment* env) +{ + checkTypes(substring) + Object start = params[0]; // First char to include + Object end = params[1]; // First char to exclude + Object string = params[2]; + + int len = end.number - start.number; + if (len < 0 || start.number >= strlen(string.string)) { + return errorWithContext(BAD_PARAMS_ON, string.string); + } + Object substr = withLen(len, TYPE_STRING); + snprintf(substr.string, len + 1, "%s", string.string + start.number); + return substr; +} + Object printEnvO(Object* params, int length, unused struct Environment* env) { int printPointers = 0; diff --git a/src/plfunc.h b/src/plfunc.h index 01f9ab0..56c7feb 100644 --- a/src/plfunc.h +++ b/src/plfunc.h @@ -182,6 +182,48 @@ tfn(charVal, "char", "(char \"\")", "0", ); +tfn(slen, "slen", + ({ isStringy, isNumber }), + "Returns the length of the given string", + "(slen \"string\")", "6", + "(slen \"\")", "0", +); + +tfn(chars, "chars", + ({ isStringy, 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 }), + "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", + "(matches \"Howdy\" \"H*y\")", "T", + "(matches \"Yello\" \"*\")", "T", + "(matches \"S\" \"*S\")", "T", + "(matches \"S\" \"S*\")", "T", + "(matches \"\" \"*\")", "T", + "(matches \"ThisIsALongerOne\" \"This*ALong*One\")", "T", + "(matches \"ThisIsALongerOne\" \"*This*ALong*One*\")", "T", + ";", "", + "(matches \"Howdy\" \"*llo\")", "F", + "(matches \"Stink\" \"Stank\")", "F", +); + +tfn(substring, "substr", + ({ isNumber, isNumber, isStringy, 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 0 1 \"\")", "", + "(substr 0 99 \"Hey\")", "Hey", + "(substr 1 99 \"Heyyyy\")", "eyyyy", +); + /// STRING/SLIST => ANY fn(parseEvalO, "eval", "Evaluate the given string or quoted list.",