diff --git a/README.md b/README.md index 9fab8e8..1f9d4ba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # PebbLisp A very basic LISP implementation with an interpreter and editor written for the Pebble. +# TODO +- Call scripts from scripts +- Call more pebble functions +- Maybe hard-code more built-in functions to ease up on memory use + # Writing in PebbLisp PebbLisp includes several built-ins functionalities. @@ -78,4 +83,4 @@ Each element in the candidate list is compared against the partial condition. If `(fil (< 100) (20 150 30 200))` -would return `( 150 200 )`, as no other elements fit the condition `(< 100 n)`. \ No newline at end of file +would return `( 150 200 )`, as no other elements fit the condition `(< 100 n)`. diff --git a/src/calc.c b/src/calc.c index 16d1e35..215ae51 100644 --- a/src/calc.c +++ b/src/calc.c @@ -52,6 +52,27 @@ static void cycle_tokens(ClickRecognizerRef recognizer, void *context) updateText(); } +static const char *fonts[] = { + FONT_KEY_GOTHIC_28_BOLD, + FONT_KEY_GOTHIC_24_BOLD, + FONT_KEY_GOTHIC_18_BOLD, + FONT_KEY_GOTHIC_14_BOLD +}; + +static const int smallestFont = 3; + +static void adjustFont() +{ + int i = 0; + while(temptext[i++]) {} + int f = i < 50 ? smallestFont - 3 : + i < 100 ? smallestFont - 2 : + i < 130 ? smallestFont - 1 : + smallestFont; + text_layer_set_font(s_input_text_layer, + fonts_get_system_font(fonts[f])); +} + /** * In normal token list, backspace if possible, otherwise return to script list * In function token list, return to normal token list @@ -69,6 +90,7 @@ static void click_backspace(ClickRecognizerRef recognizer, void *context) using_func_tokens = false; updateText(); } + adjustFont(); } // Adds the current string to the main string @@ -79,7 +101,9 @@ static void add_token() if(using_func_tokens) { using_func_tokens = false; } + updateText(); + adjustFont(); } /** Code running and saving **/ @@ -160,6 +184,7 @@ static void code_window_load(Window *window) if(persist_exists(current_code)) { persist_read_string(current_code, mytext, SMAX_LENGTH); updateText(); + adjustFont(); } } diff --git a/src/calc.h b/src/calc.h index 3bac1f1..933125a 100644 --- a/src/calc.h +++ b/src/calc.h @@ -31,7 +31,6 @@ int current_code; // Currently selected button, starts on '(' static int8_t selected_token = 1; - // PebbLisp environment static struct Environment env; @@ -49,7 +48,7 @@ const char *tokens[] = { "+ ", "- ", "* ", "/ ", "1","2","3", "4","5","6", - "7","8","9", "0", + "7","8","9", "0", "x", "a", "b", "c", "d", "e", "= ", "< ", "> ", "\"", @@ -63,7 +62,6 @@ const char *func_tokens[] = { "spent ", "window ", "max ", "min ", "sq ", "cube ", "exp " - }; bool using_func_tokens = false; diff --git a/src/env.c b/src/env.c index c909557..d51272b 100644 --- a/src/env.c +++ b/src/env.c @@ -62,6 +62,7 @@ struct Environment envForLambda(const Object *params, const Object *arg_forms, const Object *march = arg_forms; for(int i = 0; i < paramCount; i++) { const char *newObjName = itemAt(params, i)->string; + // Eval the `march` list const Object newEnvObj = eval(march, outer); addToEnv(&env, newObjName, newEnvObj); // Could use eval_forms? march = march->forward; @@ -172,6 +173,10 @@ const char *codes[] = { ) \ ))", + "(def r (fn (L) \ + (if (= L () \ + ", + NULL }; diff --git a/src/object.c b/src/object.c index ad2952e..23bdf30 100644 --- a/src/object.c +++ b/src/object.c @@ -3,7 +3,7 @@ #include #include -#define RESULT_LENGTH 20 +#define RESULT_LENGTH 30 #ifdef DEBUG #define printd(...) printf(__VA_ARGS__) @@ -211,7 +211,7 @@ static const char *errorText[] = { "BAD_TYPE", "UNEXPECTED_FORM", "LISTS_NOT_SAME_SIZE", - "SYMBOLS_CANT_START_WITH_DIGITS", + "BAD_NUMBER", "UNSUPPORTED_NUMBER_TYPE", "NOT_A_SYMBOL", "ONLY_ONE_ARGUMENT", diff --git a/src/object.h b/src/object.h index 46b64f5..ed4fa2f 100644 --- a/src/object.h +++ b/src/object.h @@ -5,6 +5,7 @@ #define MAX_ENV_ELM 50 // 50 #define FOR_POINTER_IN_LIST(_list) \ + if(_list && _list->type == TYPE_LIST) \ for(Object *_element = _list->list; \ _element != NULL;\ _element = _element->forward) @@ -32,7 +33,7 @@ enum errorCode { BAD_TYPE, UNEXPECTED_FORM, LISTS_NOT_SAME_SIZE, - SYMBOLS_CANT_START_WITH_DIGITS, + BAD_NUMBER, UNSUPPORTED_NUMBER_TYPE, NOT_A_SYMBOL, ONLY_ONE_ARGUMENT, @@ -65,12 +66,11 @@ struct Object { Object *list; char *string; Object (*func)(Object, Object, struct Environment *); - struct Lambda *lambda; // Maybe better as not a pointer? + struct Lambda *lambda; enum errorCode err; }; }; -// Maybe better as pointers? struct Lambda { Object params; Object body; @@ -81,7 +81,6 @@ void printList(const Object *list); void printObj(const Object *obj); void debugObj(const Object *obj); void printErr(const Object *obj); -int getType(const Object *obj); int isEmpty(const Object *obj); Object *tail(const Object *listObj); diff --git a/src/pebblisp.c b/src/pebblisp.c index 6ebab27..9c154a3 100644 --- a/src/pebblisp.c +++ b/src/pebblisp.c @@ -24,15 +24,17 @@ Object evalDefArgs(const Object *argForms, struct Environment *env) Object evalIfArgs(const Object *argForms, struct Environment *env) { - return eval(argForms, env).number? + return eval(argForms, env).number ? eval(argForms->forward, env) : eval(argForms->forward->forward, env); } Object evalLambdaArgs(const Object *argForms) { - // params // body - return constructLambda(argForms, argForms? argForms->forward : NULL); + return constructLambda( + argForms, // Params + argForms ? argForms->forward : NULL // Body + ); } Object evalMapArgs(const Object *argForms, struct Environment *env) @@ -42,12 +44,11 @@ Object evalMapArgs(const Object *argForms, struct Environment *env) Object lambda = eval(argForms, env); const Object *inputList = argForms->forward; + Object outputList = listObject(); - if(lambda.type != TYPE_LAMBDA || inputList->type != TYPE_LIST) + if(lambda.type != TYPE_LAMBDA) return errorObject(BAD_TYPE); - Object list = listObject(); - FOR_POINTER_IN_LIST(inputList) { // Create a new list for each element, // since lambda evaluation looks for a list @@ -57,13 +58,13 @@ Object evalMapArgs(const Object *argForms, struct Environment *env) struct Environment newEnv = envForLambda(params, &tempList, env); // Add the lambda evaluation to the list - nf_addToList(&list, eval(&lambda.lambda->body, &newEnv)); + nf_addToList(&outputList, eval(&lambda.lambda->body, &newEnv)); deleteEnv(&newEnv); cleanObject(&tempList); } cleanObject(&lambda); - return list; + return outputList; } Object evalBuiltIns(const Object *first, const Object *rest, @@ -72,6 +73,7 @@ Object evalBuiltIns(const Object *first, const Object *rest, if(first->type != TYPE_SYMBOL) { return errorObject(NOT_A_SYMBOL); } + if(strcmp(first->string, "def") == 0) { return evalDefArgs(rest, env); } else if(strcmp(first->string, "if") == 0) { @@ -85,7 +87,7 @@ Object evalBuiltIns(const Object *first, const Object *rest, return errorObject(BUILT_IN_NOT_FOUND); } -void eval_forms(Object *destList, const Object *src, struct Environment *env) +void evalForms(Object *destList, const Object *src, struct Environment *env) { int length = listLength(src) - 1; // Not counting first_form for(int i = 0; i < length; i++) { // Evaluates all in list @@ -94,9 +96,82 @@ void eval_forms(Object *destList, const Object *src, struct Environment *env) } } +Object evalList(const Object *obj, struct Environment *env) +{ + const int evalLength = listLength(obj); + + if(evalLength == 0) + return cloneObject(*obj); + + if(evalLength == 1) { + if(listLength(obj->list) == 0) + return cloneObject(*obj); + return eval(obj->list, env); + } + + Object *first_form = obj->list; + + { // Try to eval built-ins + const Object builtIn = + evalBuiltIns(first_form, first_form->forward, env); + + if(!isError(builtIn, BUILT_IN_NOT_FOUND) && + !isError(builtIn, NOT_A_SYMBOL)) { + return builtIn; + } + } + + Object first_eval = eval(first_form, env); + if(first_eval.type == TYPE_FUNC) { + int length = evalLength - 1; // Not counting first_form + Object rest[length]; + evalForms(rest, obj, env); + + Object func_eval = rest[0]; + if(length == 1) { + func_eval = first_eval.func( + func_eval, errorObject(ONLY_ONE_ARGUMENT), env); + // Consider it a partial function if only one arg + if(isError(func_eval, ONLY_ONE_ARGUMENT)) + return cloneObject(*obj); + } else { + for(int i = 1; i < length; i++) { + Object toClean = func_eval; + func_eval = first_eval.func(func_eval, rest[i], env); + cleanObject(&toClean); + cleanObject(&rest[i]); + } + } + + return func_eval; + + } else if (first_eval.type == TYPE_LAMBDA) { + struct Environment newEnv = envForLambda( + &first_eval.lambda->params, + first_form->forward, + env + ); + Object ret = eval(&first_eval.lambda->body, &newEnv); + deleteEnv(&newEnv); + cleanObject(&first_eval); + return ret; + + } else { + Object list = listObject(); + FOR_POINTER_IN_LIST(obj) { + nf_addToList(&list, eval(POINTER, env)); + } + return list; + } +} + Object eval(const Object *obj, struct Environment *env) { switch(obj->type) { + case TYPE_ERROR: + case TYPE_FUNC: + return *obj; + case TYPE_NUMBER: case TYPE_BOOL: case TYPE_STRING: @@ -106,79 +181,13 @@ Object eval(const Object *obj, struct Environment *env) return fetchFromEnvironment(obj->string, env); case TYPE_LIST: - { - if(listLength(obj) == 0) - return *obj; - - if(listLength(obj) == 1) - return eval(obj->list, env); - - Object *first_form = obj->list; - - { // Try to eval built-ins - const Object builtIn = - evalBuiltIns(first_form, first_form->forward, env); - - if(builtIn.type != TYPE_ERROR) { - return builtIn; - } - } - - Object first_eval = eval(first_form, env); - if(first_eval.type == TYPE_FUNC) { - int length = listLength(obj) - 1; // Not counting first_form - Object rest[length]; - eval_forms(rest, obj, env); - - Object func_eval = rest[0]; - if(length == 1) { - func_eval = first_eval.func( - func_eval, errorObject(ONLY_ONE_ARGUMENT), env); - if(isError(func_eval, ONLY_ONE_ARGUMENT)) // Consider it a partial function - return cloneObject(*obj); - } else { - for(int i = 1; i < length; i++) { - Object toClean = func_eval; - func_eval = first_eval.func(func_eval, rest[i], env); - //if(i != length - 1) - cleanObject(&toClean); - cleanObject(&rest[i]); - } - } - - return func_eval; - - } else if (first_eval.type == TYPE_LAMBDA) { - struct Environment newEnv = - envForLambda(&first_eval.lambda->params, first_form->forward, env); - Object ret = eval(&first_eval.lambda->body, &newEnv); - deleteEnv(&newEnv); - cleanObject(&first_eval); - return ret; - - } else { - Object list = listObject(); - FOR_POINTER_IN_LIST(obj) { - nf_addToList(&list, eval(POINTER, env)); - } - return list; - } - } + return evalList(obj, env); case TYPE_LAMBDA: return errorObject(UNEXPECTED_FORM); - - default: - return *obj; } -} -Result result(Object obj, struct Slice *slices) -{ - return (Result) { - .obj = obj, - .slices = slices - }; + return errorObject(BAD_TYPE); } Object catObjects(const Object obj1, const Object obj2, struct Environment *env) @@ -202,6 +211,15 @@ Object catObjects(const Object obj1, const Object obj2, struct Environment *env) return o; } +Object listEquality(const Object *list1, const Object *list2) +{ + FOR_POINTERS_IN_LISTS(list1, list2) { + if(P1->type != P2->type || P1->number != P2->number) + return boolObject(0); + } + return boolObject(1); +} + Object _basicOp(const Object *obj1, const Object *obj2, const char op, struct Environment *env) { @@ -225,7 +243,9 @@ Object _basicOp(const Object *obj1, const Object *obj2, const char op, case '=': if(obj1->type == TYPE_STRING || obj2->type == TYPE_STRING) return boolObject(!strcmp(obj1->string, obj2->string)); - return boolObject(n1 == n2); + if(obj1->type == TYPE_LIST && obj2->type == TYPE_LIST) + return listEquality(obj1, obj2); + return boolObject(n1 == n2 && obj1->type == obj2->type); case '>': return boolObject(n1 > n2); case '<': @@ -376,10 +396,10 @@ Result parse(struct Slice *slices) // todo check for null rest return readSeq(rest); } else { // todo error on missing close paren - return result(parseAtom(token), rest); + return (Result){parseAtom(token), rest}; } } else { - return result(errorObject(NULL_PARSE), NULL); + return (Result){errorObject(NULL_PARSE), NULL}; } } @@ -390,7 +410,7 @@ Result readSeq(struct Slice *tokens) struct Slice *next = tokens; struct Slice *rest = next->text? &next[1] : NULL; if(next->text[0] == ')') { - return result(res, rest); + return (Result){res, rest}; } Result r = parse(tokens); if(r.obj.type == TYPE_ERROR) @@ -406,7 +426,7 @@ Object parseDecimal(struct Slice *s) int num = 0; for(int i = 0; i < s->length; i++) { if(!isDigit(s->text[i])) { - return errorObject(SYMBOLS_CANT_START_WITH_DIGITS); + return errorObject(BAD_NUMBER); } num *= 10; num += s->text[i] - '0'; @@ -414,22 +434,56 @@ Object parseDecimal(struct Slice *s) return numberObject(num); } +Object parseHex(struct Slice *s) +{ + int num = 0; + for(int i = 2; i < s->length; i++) { + const char c = s->text[i]; + if(!isHex(c)) { + return errorObject(BAD_NUMBER); + } + num *= 16; + if(isDigit(c)) { + num += c - '0'; + } else /* is hex */ { + num += c - 'a' + 10; + } + } + return numberObject(num); +} + +Object parseBin(struct Slice *s) +{ + int num = 0; + for(int i = 2; i < s->length; i++) { + const char c = s->text[i]; + if(c != '0' && c != '1') { + return errorObject(BAD_NUMBER); + } + num *= 2; + num += c - '0'; + } + return numberObject(num); +} + Object parseAtom(struct Slice *s) { - if(isDigit(s->text[0])) { - if(s->text[0] != '0' || s->length == 1) + const char c = s->text[0]; + if(isDigit(c)) { + if(c != '0' || s->length == 1) { return parseDecimal(s); - else { + } else if(c == '0' && s->text[1] == 'x') { + return parseHex(s); + } else if(c == '0' && s->text[1] == 'b') { + return parseBin(s); + } else { return errorObject(UNSUPPORTED_NUMBER_TYPE); } - - } else if (s->length == 1 && (s->text[0] == 'T' || s->text[0] == 't')) { + } else if (s->length == 1 && (c == 'T' || c == 't')) { return boolObject(1); - - } else if (s->length == 1 && (s->text[0] == 'F' || s->text[0] == 'f')) { + } else if (s->length == 1 && (c == 'F' || c == 'f')) { return boolObject(0); - - } else if (s->text[0] == '"' || s->text[0] == '\'') { + } else if (c == '"' || c == '\'') { return objFromSlice(s->text, s->length); } else { return symFromSlice(s->text, s->length); @@ -449,7 +503,7 @@ Object parseEval(const char *input, struct Environment *env) printd("start slice\n"); if(debug) { while(debug->text) { - char tok[MAX_TOK_LEN]; + char tok[100]; copySlice(tok, debug); printd("slice: '%s'\n", tok); debug++; @@ -461,32 +515,27 @@ Object parseEval(const char *input, struct Environment *env) int parens = 0; Object obj = numberObject(0); struct Slice *tok = tokens; - if(tok[i].text[0] != '(') { - Object parsed = parse(tok).obj; - if(parsed.type == TYPE_ERROR) - return parsed; - obj = eval(&parsed, env); - cleanObject(&parsed); - } else { - while(tok[i].text != NULL) { - if(tok[i].text[0] == '(') { - parens++; - } else if(tok[i].text[0] == ')') { - parens--; - if(parens == 0) { - cleanObject(&obj); - Object parsed = parse(tok).obj; - if(parsed.type == TYPE_ERROR) - return parsed; - tok = &tok[i + 1]; - i = -1; - //printObj(&parsed); - obj = eval(&parsed, env); - cleanObject(&parsed); - } - } - i++; + while(tok[i].text != NULL) { + if(tok[i].text[0] == '(') { + parens++; + } else if(tok[i].text[0] == ')') { + parens--; } + + if(parens == 0) { + cleanObject(&obj); + Object parsed = parse(tok).obj; + if(parsed.type == TYPE_ERROR) + return parsed; + if(tok[i].text[0] == ')') { + tok = &tok[i + 1]; + i = -1; + } + obj = eval(&parsed, env); + cleanObject(&parsed); + } + + i++; } free(tokens); return obj; diff --git a/src/pebblisp.h b/src/pebblisp.h index 65a3753..a76d7ca 100644 --- a/src/pebblisp.h +++ b/src/pebblisp.h @@ -29,14 +29,12 @@ Object eval(const Object *obj, struct Environment *env); Result parse(struct Slice *slices); Result readSeq(struct Slice *slices); Object parseAtom(struct Slice *slice); -void copySlice(char * dest, struct Slice *src); Object parseEval(const char *input, struct Environment *env); -void eval_forms(Object *destList, const Object *src, struct Environment *env); +void evalForms(Object *destList, const Object *src, struct Environment *env); +void copySlice(char * dest, struct Slice *src); Object evalLambdaArgs(const Object *arg_forms); -Result result(Object obj, struct Slice *slices); - // Slices void copySlice(char * dest, struct Slice *src); void debugSlice(struct Slice *s); @@ -48,6 +46,7 @@ BASIC_OP(mul); BASIC_OP(dvi); BASIC_OP(mod); BASIC_OP(equ); BASIC_OP(gth); BASIC_OP(lth); #undef BASIC_OP + Object catObjects(const Object obj1, const Object obj2, struct Environment *env); Object filter(Object obj1, Object obj2, struct Environment *env); Object append(Object list, Object newElement, struct Environment *env); diff --git a/src/tests.sh b/src/tests.sh index 7c4dd18..cbae38f 100755 --- a/src/tests.sh +++ b/src/tests.sh @@ -1,5 +1,7 @@ #!/bin/bash +VALCOM="valgrind -q --leak-check=full --track-origins=yes" + TOTAL_PASSES=0 TOTAL_FAILS=0 PASSES=0 @@ -24,11 +26,9 @@ endBlock() { title() { echo "$1" - # echo "$1" | sed 's/./-/g' | sed 's/$/-/g' } pass() { - # echo "$1 test PASSED" ((PASSES++)) ((TOTAL_PASSES++)) } @@ -42,7 +42,7 @@ fail() { check() { local output if (($VALGRIND == 1)); then - local output=$(valgrind -q --leak-check=full --track-origins=yes ./pebblisp "$2") + local output=$($VALCOM ./pebblisp "$2") else local output=$(./pebblisp "$2") fi @@ -62,6 +62,8 @@ title "Plain return tests" check "PlainRet" "10" "10" check "StrRetrn" "\"hey\"" "hey" check "SingleStrRetrn" "'hey'" "hey" +check "HexReturn" "0x0f0f0" "61680" +check "BinaryReturn" "0b011010011010011" "13523" endBlock title "Arithmetic" @@ -99,7 +101,7 @@ check "EmptLst2" "( )" "( )" check "ListIndex" "(at (+ 1 1) (1 2 1000 4 5))" "1000" check "EvalElems" "((* 10 10) 7)" "( 100 7 )" check "AppendToList" "(ap (1 20 300 4000 50000) 600000)" "( 1 20 300 4000 50000 600000 )" -check "AppndToEnvList" "(def l (50000 4000 300 20)) (def l (ap l 1)) (l)" "( 50000 4000 300 20 1 )" +check "AppndToEnvList" "(def l (50000 4000 300 20)) (def l (ap l 1)) l" "( 50000 4000 300 20 1 )" check "Rest(Tail)OfList" "(def l (50000 4000 300 20)) (rest l)" "( 4000 300 20 )" check "ListLength" "(def l (1 20 3 'abc' 'banana' (+ 10 5))) (len l)" "6" endBlock @@ -158,7 +160,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 "BadSymbol" "(5df)" "SYMBOLS_START_WITH_LETTERS" +check "BadNumber" "(5df)" "BAD_NUMBER" +check "BadHex" "(0x0zf)" "BAD_NUMBER" +check "BadBinary" "(0b01120)" "BAD_NUMBER" check "BadParens" "(hey()" "MISMATCHED_PARENS" check "BadParens2" "(hey)(" "MISMATCHED_PARENS" check "BadParens3" "((hey(" "MISMATCHED_PARENS" @@ -175,10 +179,13 @@ endBlock echo "" if [ "$TOTAL_FAILS" -ne "0" ]; then - echo "$TOTAL_FAILS Tests Failed" -else - echo "$TOTAL_FAILS Tests Failed" + echo -n "" fi +echo "$TOTAL_FAILS Tests Failed" + +if [ "$TOTAL_PASSES" -ne "0" ]; then + echo -n "" +fi +echo "$TOTAL_PASSES Tests Passed" -echo "$TOTAL_PASSES Tests Passed" echo "" diff --git a/src/tokens.c b/src/tokens.c index 8e6b8bd..29d4771 100644 --- a/src/tokens.c +++ b/src/tokens.c @@ -35,6 +35,10 @@ int isDigit(const char c) { return c >= '0' && c <= '9'; } +int isHex(const char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); +} + int isWhitespace(const char c) { return c == ' ' || c == '\t' || c == '\n'; } diff --git a/src/tokens.h b/src/tokens.h index c73685b..18483f8 100644 --- a/src/tokens.h +++ b/src/tokens.h @@ -5,6 +5,7 @@ int isSingle(const char c); int isDigit(const char c); +int isHex(const char c); struct Slice *nf_tokenize(const char *input); #endif