From 7579d0b30ca1b8494e0be58af549e6c85212ceb0 Mon Sep 17 00:00:00 2001 From: Michael E Date: Mon, 18 Apr 2016 11:25:36 +0300 Subject: [PATCH] Initial Commit --- README.md | 4 + appinfo.json | 32 ++++ resources/images/appicon.png | Bin 0 -> 230 bytes src/calc.c | 163 +++++++++++++++++++ src/calc.h | 24 +++ src/fixed.c | 299 +++++++++++++++++++++++++++++++++++ src/fixed.h | 55 +++++++ src/pebblisp.c | 288 +++++++++++++++++++++++++++++++++ src/pebblisp.h | 78 +++++++++ src/tags | 98 ++++++++++++ src/tokens.c | 83 ++++++++++ src/tokens.h | 9 ++ wscript | 41 +++++ 13 files changed, 1174 insertions(+) create mode 100644 appinfo.json create mode 100644 resources/images/appicon.png create mode 100644 src/calc.c create mode 100644 src/calc.h create mode 100644 src/fixed.c create mode 100644 src/fixed.h create mode 100644 src/pebblisp.c create mode 100644 src/pebblisp.h create mode 100644 src/tags create mode 100644 src/tokens.c create mode 100644 src/tokens.h create mode 100644 wscript diff --git a/README.md b/README.md index 02a53a1..dcb854b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # PebbleCalc A simple calculator app for the Pebble smartwatch +Support addition, subtraction, multiplication, division and exponentiation. +Supports up to 2 digits of decimals using fixed-point arithmetic. + +Credit to Wojciech Siewierski for his fixed point arithmetic implementation. It was made for his [GravCalc](https://github.com/Vifon/GravCalc) Pebble app. diff --git a/appinfo.json b/appinfo.json new file mode 100644 index 0000000..ba6d12f --- /dev/null +++ b/appinfo.json @@ -0,0 +1,32 @@ +{ + "appKeys": {}, + "capabilities": [ + "" + ], + "companyName": "Michael E.", + "enableMultiJS": true, + "longName": "Calculator", + "projectType": "native", + "resources": { + "media": [ + { + "file": "images/appicon.png", + "menuIcon": true, + "name": "APP_ICON_IDENTIFIER", + "targetPlatforms": null, + "type": "png" + } + ] + }, + "sdkVersion": "3", + "shortName": "Calculator", + "targetPlatforms": [ + "aplite", + "basalt" + ], + "uuid": "3a239ae0-adc5-4d40-b871-6fbcd8c37e7c", + "versionLabel": "1.1", + "watchapp": { + "watchface": false + } +} diff --git a/resources/images/appicon.png b/resources/images/appicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3948a495f73128d15208260991d78b9d02998307 GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*s1SJ3FdmIK*oCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#0gDg^pGxIq0YRXUY-UJAiF1B#Zfaf$kjuc}T$GwvlA5AWo>`Ki;O^-gkfN8$ z4it~^ba4#vIG+6H|9^Ytt&GCVybRn%?GqSJ_^btc`=4>y1TDTN|MlOB@9ulgJ0-4d-&-VWq$|R3g`-P& T^`|6LkOMqj{an^LB{Ts5e}hS7 literal 0 HcmV?d00001 diff --git a/src/calc.c b/src/calc.c new file mode 100644 index 0000000..6aa1365 --- /dev/null +++ b/src/calc.c @@ -0,0 +1,163 @@ +#include +#include +#include "fixed.h" +#include "calc.h" +#include "pebblisp.h" + +static inline char* getToken(int n) { + const int length = sizeof(tokens) / sizeof(tokens[0]); + return tokens[n % length]; +} + +static int8_t selected_token = 1; //Currently selected button, starts on '5' + +// static bool operator_entered = false; //Has the operator been entered yet +// static bool num1_is_ans = false; //Is the previous result in num1? Used to allow further calculations on result. +// static char num1[MAX_LENGTH] = ""; //First operand +// static char num2[MAX_LENGTH] = ""; //Second operand +// static char result_text[MAX_LENGTH] = ""; //Results text layer buffer string +// static uint8_t operator = 0; //Operator, where 0 is +, 1 is -, 2 is *, 3 is /, 4 is ^ +static struct Environment env; + +static void updateText() +{ + strcpy(temptext, mytext); + if(getToken(selected_token)[0] == ' ') { + strcat(temptext, "_"); + } else { + strcat(temptext, getToken(selected_token)); + } + + text_layer_set_text(s_input_text_layer, temptext); +} + +//Up or Down button handler +static void up_down_handler(ClickRecognizerRef recognizer, void *context){ + //Move selected button down if down is pressed, and up if up is pressed + selected_token += (click_recognizer_get_button_id(recognizer) == BUTTON_ID_DOWN) ? 1 : -1; + //If selected button is outside button range, wrap around + selected_token = selected_token < 0 ? TOKEN_END : selected_token > TOKEN_END ? 0 : selected_token; + + updateText(); +} + +// Adds the current string to the main string +static void enter(){ + strcat(mytext, getToken(selected_token)); + selected_token = 0; + updateText(); +} + +// // Backspace. Clears whole number if full is true +// static void clear(bool full){ +// char *num = operator_entered ? num2 : num1; //Create a pointer to the currently edited number +// if(full) +// *num = 0; +// else +// num[strlen(num)-1] = 0; +// text_layer_set_text(s_input_text_layer, num); +// } + +//Calculate result, display it and reset +static void calculate(){ + // Object obj = parseEval(mytext, &env); + // snprintf(mytext, MAX_LENGTH, "R:%d", obj.number); + // selected_token = 0; + strcpy(mytext, "heythere"); + updateText(); + if(1) + return; + + /* + bool overflow = false; //Overflow flag + // Convert operands to numbers + fixed lhs = str_to_fixed(num1, &overflow); + fixed rhs = str_to_fixed(num2, &overflow); + fixed result = 0; + //Calculate the result + switch(operator){ + case 0: + result = fixed_add(lhs, rhs, &overflow); + break; + case 1: + result = fixed_subt(lhs, rhs, &overflow); + break; + case 2: + result = fixed_mult(lhs, rhs, &overflow); + break; + case 3: + result = fixed_div(lhs, rhs); + break; + case 4: + result = fixed_pow(lhs, fixed_to_int(rhs), &overflow); //Exponent must be an int + break; + default: + result = 0; + } + + APP_LOG(APP_LOG_LEVEL_DEBUG, "num1: %d num2: %d result: %d", lhs, rhs, result); + + //Reset operands, operator_entered and entering_decimal + *num1 = 0; + *num2 = 0; + operator_entered = false; + + if(overflow){ + text_layer_set_text(s_input_text_layer, "Overflow Error"); //Display message on overflow + } + else{ + fixed_repr(result, result_text, MAX_LENGTH); //Convert result to string + text_layer_set_text(s_input_text_layer, result_text); //Display result + strcpy(num1, result_text); //Copy result into num1 + num1_is_ans = true; + } + */ +} + +// Button press handler +static void select_handler(ClickRecognizerRef recognizer, void *context){ + if(selected_token == sizeof(tokens) / sizeof(tokens[0]) - 1) + calculate(); + else + enter(); +} + +static void click_config_provider(void *context) { + // Register click handlers + window_single_repeating_click_subscribe(BUTTON_ID_UP, 100, up_down_handler); + window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 100, up_down_handler); + window_single_click_subscribe(BUTTON_ID_SELECT, select_handler); +} + +static void init(void) { + // Create a window and get information about the window + s_window = window_create(); + Layer *window_layer = window_get_root_layer(s_window); + // Register click config provider + window_set_click_config_provider(s_window, click_config_provider); + + // Input text layer setup + GRect text_bounds = GRect(6, 6, 132, 132); + s_input_text_layer = text_layer_create(text_bounds); + text_layer_set_text(s_input_text_layer, getToken(1)); + text_layer_set_font(s_input_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); + layer_add_child(window_get_root_layer(s_window), text_layer_get_layer(s_input_text_layer)); + + // Push the window, setting the window animation to 'true' + window_stack_push(s_window, true); + env = defaultEnv(); +} + +static void deinit(void) { + // Destroy the text layer + text_layer_destroy(s_input_text_layer); + + // Destroy the window + window_destroy(s_window); +} + +int main(void) { + init(); + app_event_loop(); + deinit(); +} diff --git a/src/calc.h b/src/calc.h new file mode 100644 index 0000000..e89c215 --- /dev/null +++ b/src/calc.h @@ -0,0 +1,24 @@ +#include +#include "pebblisp.h" + +#define SMAX_LENGTH 256 +#define MAX_LENGTH 11 +#define END_PHRASE "END" + +// Layers +Window *s_window; +TextLayer *s_input_text_layer; + +char mytext[SMAX_LENGTH] = ""; +char temptext[SMAX_LENGTH] = ""; + +char *tokens[] = { + " ", "(", ")", + "+", "-", "*", "/", + "1","2","3", + "4","5","6", + "7","8","9", "0", + END_PHRASE +}; + +#define TOKEN_END 17 diff --git a/src/fixed.c b/src/fixed.c new file mode 100644 index 0000000..a3ee97b --- /dev/null +++ b/src/fixed.c @@ -0,0 +1,299 @@ +/** @file fixed.c + * @brief A fixed point numbers implementation. + * @author Wojciech 'vifon' Siewierski + */ + +/***********************************************************************************/ +/* Copyright (C) 2015 Wojciech Siewierski */ +/* */ +/* Author: Wojciech Siewierski */ +/* */ +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 3 */ +/* of the License, or (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ +/***********************************************************************************/ + +#include "fixed.h" + +#include +#include +#include + +/** Sum two fixed point numbers. + * + * @param lhs + * @param rhs + * @param[out] overflow Indicate whether the addition would + * result in an overflow. If the initial value is @p true, it will + * stay @p true. The returned value is unspecified if it is true. + * + * @return The result. + */ +fixed fixed_add(fixed lhs, fixed rhs, bool* overflow) +{ + // If both arguments have the same sign... + if ((rhs > 0) == (lhs > 0)) { + // ...check for the overflow. + *overflow = *overflow || abs(lhs) > FIXED_MAX - abs(rhs); + } + + if (*overflow) { + return lhs; + } + + return lhs + rhs; +} + +/** Subtract two fixed point numbers. + * + * @param lhs + * @param rhs + * @param[out] overflow Indicate whether the subtraction would + * result in an overflow. If the initial value is @p true, it will + * stay @p true. The returned value is unspecified if it is true. + * + * @return The result. + */ +fixed fixed_subt(fixed lhs, fixed rhs, bool* overflow) +{ + return fixed_add(lhs, -rhs, overflow); +} + +/** Multiply two fixed point numbers. + * + * @param lhs + * @param rhs + * @param[out] overflow Indicate whether the multiplication would + * result in an overflow. If the initial value is @p true, it will + * stay @p true. The returned value is unspecified if it is true. + * + * @return The result. + */ +fixed fixed_mult(fixed lhs, fixed rhs, bool* overflow) +{ + if (fixed_to_int(rhs) != 0) { + *overflow = *overflow || abs(lhs) > abs(fixed_div(FIXED_MAX, rhs)); + } + + if (*overflow) { + return lhs; + } + + return (lhs / FIXED_SCALE) * rhs + + ((lhs % FIXED_SCALE) * rhs) / FIXED_SCALE; +} + +/** Divide two fixed point numbers. + * + * @param lhs + * @param rhs + * + * @note The fractional part of @p rhs is ignored for large @p lhs + * due to a change in the order of performed operations made to avoid + * overflows. + * + * @return The result. + */ +fixed fixed_div(fixed lhs, fixed rhs) +{ + /* Check if it's safe to normalize lhs instead of rhs for precision. */ + if (lhs < FIXED_MAX / FIXED_SCALE) { + /* Keep precision whether possible. */ + lhs = lhs * FIXED_SCALE; + } else { + /* Sacrifice the fractional part of rhs when the another order + * of operations would cause an overflow. */ + rhs = fixed_to_int(rhs); + } + + if (rhs == 0) { + return 0; /* TODO: handle properly */ + } else { + return lhs / rhs; + } +} + +/** Create the textual representation of the fixed point number. + * + * @param fixed A number to represent. + * @param buffer A buffer to store the representation. + * @param size Size of @p buffer. + * + * @return A pointer to the @p buffer parameter. + */ +char* fixed_repr(fixed fixed, char* buffer, size_t size) +{ + const char* sign = (fixed < 0 ? "-" : ""); + int integal_part = abs(fixed) / FIXED_SCALE; + int fractional_part = abs(fixed) % FIXED_SCALE; + + if (fractional_part != 0) { + int n = snprintf(buffer, size, + "%s%d.%02u", + sign, integal_part, fractional_part); + + /* Remove the trailing zeros. */ + if (fractional_part >= 10 && + fractional_part % 10 == 0) { + + buffer[n-1] = '\0'; + } + } else { + snprintf(buffer, size, + "%s%d", + sign, integal_part); + } + + return buffer; +} + +/** Convert a string to integer. + * + * @param str String to convert. + * @param[out] endptr If non-NULL, set to the first invalid character. + * @param maxnum Max number of digits to read. Pass -1 for unlimited. + * + * @return The converted integer. + */ +int str_to_int(const char *str, char **endptr, int maxnum) { + int result = 0; + + int sign = 1; + if (*str == '-') { + ++str; + sign = -1; + } + + while (*str >= '0' && *str <= '9' && maxnum-- != 0) { + result *= 10; + result += *str++ - '0'; + } + + /* save the position of the first invalid character */ + if (endptr != NULL) { + /* http://stackoverflow.com/questions/993700/are-strtol-strtod-unsafe */ + *endptr = (char*)str; + } + + return sign * result; +} + +/** Convert a string to a fixed point number. + * + * @param str String to convert. + * @param[out] overflow Indicate whether the conversion would + * result in an overflow. If the initial value is @p true, it will + * stay @p true. The returned value is unspecified if it is true. + * + * @return The converted fixed point number. + */ +fixed str_to_fixed(const char* str, bool* overflow) +{ + char* fractional_start; + char* endptr; + + int sign = 1; + if (*str == '-') { + ++str; + sign = -1; + } + + // Detect a potential overflow. + static const int FIXED_MAX_digits = 8; + static const char* FIXED_MAX_char = "21474836"; /* FIXED_MAX/FIXED_SCALE */ + const char* integral_end = strchr(str, '.'); + if (integral_end == NULL) { + integral_end = str + strlen(str); + } + + if (integral_end - str > FIXED_MAX_digits) { + *overflow = true; + return 0; + } else if (integral_end - str == FIXED_MAX_digits) { + /* strcmp will return a positive number for string + * lexicographically greater than FIXED_MAX_digits. For + * strings of the same length (which is the case in this if + * branch) it is the same as being numerically greater. */ + if (strncmp(str, FIXED_MAX_char, FIXED_MAX_digits) > 0) { + *overflow = true; + return 0; + } + } + + + int integral_part = str_to_int(str, &fractional_start, -1) * FIXED_SCALE; + if (*fractional_start != '\0') { + ++fractional_start; + } + int fractional_part = str_to_int(fractional_start, &endptr, 2); + + if (endptr - fractional_start == 1) { + /* There was only one digit -- higher order of magnitude. */ + fractional_part *= 10; + } + + return sign * (integral_part + fractional_part); +} + +/** Convert the fixed point value to a regular integer. + * + * @param n + * + * @return The integral part of the fixed point number. + */ +int fixed_to_int(fixed n) +{ + return n / FIXED_SCALE; +} + +/** Convert the integer to a fixed point value. + * + * @param n + * + * @return The converted fixed point number. + */ +fixed int_to_fixed(int n) +{ + return n * FIXED_SCALE; +} + +/** A simple implementation of the pow(3) standard function + * for fixed point numbers. + * + * @param base + * @param exponent + * @param[out] overflow Indicate whether the exponentiation would + * result in an overflow. If the initial value is @p true, it will + * stay @p true. The returned value is unspecified if it is true. + * + * @return The exponentiation result. + * + * @note The exponent is an integer, not a fixed point number. + */ +fixed fixed_pow(fixed base, int exponent, bool* overflow) +{ + fixed result = FIXED_SCALE; + + bool negative = exponent < 0; + exponent = abs(exponent); + + while (exponent-- && *overflow == false) { + result = fixed_mult(result, base, overflow); + } + + if (negative) { + return fixed_div(int_to_fixed(1), + result); + } else { + return result; + } +} \ No newline at end of file diff --git a/src/fixed.h b/src/fixed.h new file mode 100644 index 0000000..a2a0487 --- /dev/null +++ b/src/fixed.h @@ -0,0 +1,55 @@ +/** @file fixed.h + * @brief A fixed point numbers implementation. + * @author Wojciech 'vifon' Siewierski + */ + +/***********************************************************************************/ +/* Copyright (C) 2015 Wojciech Siewierski */ +/* */ +/* Author: Wojciech Siewierski */ +/* */ +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 3 */ +/* of the License, or (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ +/***********************************************************************************/ + +#ifndef _h_FIXED_ +#define _h_FIXED_ + +/* Do not include pebble.h when compiling the unittests. */ +#ifndef __cplusplus +# include /* for bool */ +#endif + +#include +#include + +/** The underlying fixed point representation. */ +typedef int fixed; + +/** The scaling factor of the fixed point numbers. */ +#define FIXED_SCALE 100 + +/** Maximum representable value. */ +static const fixed FIXED_MAX = INT_MAX; + +fixed fixed_add(fixed lhs, fixed rhs, bool* overflow); +fixed fixed_subt(fixed lhs, fixed rhs, bool* overflow); +fixed fixed_mult(fixed lhs, fixed rhs, bool* overflow); +fixed fixed_div(fixed lhs, fixed rhs); +char* fixed_repr(fixed fixed, char* buffer, size_t size); +fixed str_to_fixed(const char* str, bool* overflow); +int fixed_to_int(fixed n); +fixed int_to_fixed(int n); +fixed fixed_pow(fixed base, int exponent, bool* overflow); + +#endif \ No newline at end of file diff --git a/src/pebblisp.c b/src/pebblisp.c new file mode 100644 index 0000000..20898e4 --- /dev/null +++ b/src/pebblisp.c @@ -0,0 +1,288 @@ +#include "pebblisp.h" +#include "tokens.h" +#include +#include +#include + +#ifndef STANDALONE +#define printf(...) copySlice(NULL, NULL) +#else +#endif + +void copySlice(char * dest, struct Slice *src) +{ + if(!dest || !src) + return; + strncpy(dest, src->text, src->length); + dest[src->length] = '\0'; +} + +void debugSlice(struct Slice *s) +{ + //printf("Debug Slice\n text:'"); + for(int i = 0; i < s->length; i++) { + //printf("%c", s->text[i]); + } + // printf("'\n"); + // printf(" length: %d\n", s->length); +} + +Object fetchFromEnvironment(const char *name, struct Environment *env) +{ + int i = 0; + const char *next = env->strings[i]; + while(next != NULL) { + // printf("fetching '%s' against '%s'\n", name, next); + if(strcmp(name, next) == 0) { + return env->objects[i]; + } + next = env->strings[++i]; + } + printf("DID NOT FIND SYMBOL\n"); + Object o; + o.type = TYPE_ERROR; + return o; +} + +Result parse(struct Slice *slices) +{ + // printf("parse()\n"); + struct Slice *token = slices; + struct Slice *rest; + if(token->text != NULL) { + rest = &slices[1]; + } else { + // printf("Assigning null...\n"); + rest = NULL; + } + + if(token->text[0] == '(') { + // todo check for null rest + return readSeq(rest); + } else { // todo error on closed paren + return R(parseAtom(token), rest); + } +} + +Result readSeq(struct Slice *tokens) +{ + // printf("readSeq()\n"); + Object res; + res.forward = NULL; + res.type = TYPE_LIST; + //res.list = malloc(sizeof(Object)); + + Object *march = &res; + for(;;) { + struct Slice *next = &tokens[0]; + struct Slice *rest = next->text? &next[1] : NULL; + if(next->text[0] == ')') { + return R(res, rest); + } + Result r = parse(tokens); + march->forward = malloc(sizeof(Object)); + *march->forward = r.obj; + // char out[MAX_TOK_LEN]; + // printf("stringObj: %s\n", stringObj(out, &r.obj)); + tokens = r.slices; + march = march->forward; + } +} + +Object parseAtom(struct Slice *s) +{ + // printf("parseAtom()\n"); + Object o; + o.forward = NULL; + if(isDigit(s->text[0])) { + o.type = TYPE_NUMBER; + o.number = 0; + for(int i = 0; i < s->length; i++) { + o.number *= 10; + o.number += s->text[i] - '0'; + } + + } else { + o.type = TYPE_SYMBOL; + copySlice(o.name, s); + } + return o; +} + +Object eval(Object *obj, struct Environment *env) +{ + Object o = *obj; + switch(obj->type) { + case TYPE_NUMBER: + { + return *obj; + } + case TYPE_LIST: + { + // printf("TYPE_LIST\n"); + Object first_form = *obj->forward; + Object first_eval = eval(&first_form, env); + return first_eval.func(*first_form.forward, *first_form.forward->forward); + } + case TYPE_SYMBOL: + { + return fetchFromEnvironment(obj->name, env); + } + default: + ; + } + return o; +} + +char* stringObj(char *dest, Object *obj) +{ + if(obj->type == TYPE_NUMBER) { + snprintf(dest, MAX_TOK_LEN, "%d", obj->number); + } else if(obj->type == TYPE_SYMBOL) { + return obj->name; + } + + return dest; +} + +Result resultFromObjAndSlices(Object obj, struct Slice *slices) +{ + Result r; + r.obj = obj; + r.slices = slices; + return r; +} + +Object basicOp(Object *obj1, Object *obj2, const char op) +{ + Object o; + o.forward = NULL; + o.type = TYPE_NUMBER; + switch(op){ + case '+': + o.number = obj1->number + obj2->number; + break; + case '-': + o.number = obj1->number - obj2->number; + break; + case '*': + o.number = obj1->number * obj2->number; + break; + case '/': + o.number = obj1->number / obj2->number; + break; + default: + o = *obj1; + } + return o; +} + +Object add(Object obj1, Object obj2) + { return basicOp(&obj1, &obj2, '+'); } + +Object sub(Object obj1, Object obj2) + { return basicOp(&obj1, &obj2, '-'); } + +Object mul(Object obj1, Object obj2) + { return basicOp(&obj1, &obj2, '*'); } + +Object divi(Object obj1, Object obj2) + { return basicOp(&obj1, &obj2, '/'); } + +void addFunc(const char *name, Object (*func)(Object, Object), + struct Environment *env) +{ + int i; + for(i = 0; i < MAX_ENV_ELM; i++) { + if(env->strings[i] == NULL) { + //printf("Adding at %d\n", i); + env->strings[i] = malloc(sizeof(name)); + strncpy(env->strings[i], name, MAX_TOK_LEN); + Object o; + o.type = TYPE_FUNC; + o.forward = NULL; + o.func = func; + env->objects[i] = o; + break; + } + } +} + +struct Environment defaultEnv() { + struct Environment e; + e.strings = malloc(sizeof(char*) * MAX_ENV_ELM); + e.objects = malloc(sizeof(Object) * MAX_ENV_ELM); + addFunc("+", &add, &e); + addFunc("-", &sub, &e); + addFunc("*", &mul, &e); + addFunc("/", &divi, &e); + + /* + e.strings[0] = malloc(sizeof("+")); + strncpy(e.strings[0], "+", MAX_TOK_LEN); + Object o; + o.type = TYPE_FUNC; + o.forward = NULL; + o.func = &add; + e.objects[0] = o; + */ + return e; +} + +Object parseEval(const char *input, struct Environment *env) +{ + struct Slice *tokens = tokenize(input); + struct Slice *debug = tokens; + if(debug) { + while(debug->text) { + char tok[10]; + copySlice(tok, debug); + // printf("'%s', ", tok); + debug++; + } + // printf("\n"); + } else { + // printf("parse error\n"); + } + // printf("parseEval() parse()\n"); + Object parsed = parse(tokens).obj; + return eval(&parsed, env); +} + +#ifdef STANDALONE +int repl(struct Environment *env) +{ + char input[100] = ""; + while(input[0] != 'q') { + printf("pebblisp>> "); + fgets(input, 100, stdin); + Object obj = parseEval(input, env); + printf("eval: %d\n", obj.number); + break; + } +} + +int main(void) +{ + struct Environment env = defaultEnv(); + if(1) { + repl(&env); + } else { + struct Slice *tokens = tokenize("(+ 10 5)"); + struct Slice *debug = tokens; + + if(debug) { + while(debug->text) { + char tok[10]; + copySlice(tok, debug); + printf("'%s', ", tok); + debug++; + } + printf("\n"); + } else { + printf("parse error\n"); + } + parse(tokens); + } +} +#endif diff --git a/src/pebblisp.h b/src/pebblisp.h new file mode 100644 index 0000000..4c25cd1 --- /dev/null +++ b/src/pebblisp.h @@ -0,0 +1,78 @@ +#ifndef PEBBLISP_H +#define PEBBLISP_H + +// #define STANDALONE + +#define MAX_TOK_LEN 6 +#define MAX_TOK_CNT 128 +#define MAX_ENV_ELM 50 + +// static const char* tokenFail = "Missing ')'\n"; + +typedef struct Object Object; + +struct Slice { + const char *text; + int length; +}; + +typedef enum Type { + TYPE_NUMBER, + TYPE_LIST, + TYPE_FUNC, + //TYPE_STRING, + TYPE_SYMBOL, + //TYPE_CONS, + //TYPE_LAMBDA, + //TYPE_MACRO, + //TYPE_PRIMITIVE, + TYPE_ENV, + TYPE_ERROR +} Type; + +struct Object { + Type type; + Object *forward; + union { + int number; + Object *list; + char name[MAX_TOK_LEN]; + Object (*func)(Object, Object); + + /* + struct { double number; }; // number + struct { char string[sizeof (Object *[3])]; }; // string, symbol + struct { Object *car, *cdr; }; // cons + struct { Object *params, *body, *env; }; // lambda, macro + struct { int primitive; char *name; }; // primitive + struct { Object *parent, *vars, *vals; }; // env + struct { Object *forward; }; // forwarding pointer + */ + }; +}; + +typedef struct Result { + Object obj; + struct Slice *slices; +} Result; + +struct Environment { + char **strings; + Object *objects; +}; + +Object eval(Object *obj, struct Environment *env); +char* stringObj(char *dest, Object *obj); +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); +struct Environment defaultEnv(); + +Result resultFromObjAndSlices(Object obj, struct Slice *slices); +Object add(Object obj1, Object obj2); + +#define R(_obj, _slices) resultFromObjAndSlices(_obj, _slices) + +#endif diff --git a/src/tags b/src/tags new file mode 100644 index 0000000..8d44738 --- /dev/null +++ b/src/tags @@ -0,0 +1,98 @@ +!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ +!_TAG_PROGRAM_NAME Exuberant Ctags // +!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ +!_TAG_PROGRAM_VERSION 5.9~svn20110310 // +END_PHRASE calc.h 6;" d +Environment pebblisp.h /^struct Environment {$/;" s +FIXED_MAX fixed.h /^static const fixed FIXED_MAX = INT_MAX;$/;" v +FIXED_SCALE fixed.h 40;" d +MAX_ENV_ELM pebblisp.h 8;" d +MAX_LENGTH calc.h 5;" d +MAX_TOK_CNT pebblisp.h 7;" d +MAX_TOK_LEN pebblisp.h 6;" d +Object pebblisp.h /^struct Object {$/;" s +Object pebblisp.h /^typedef struct Object Object;$/;" t typeref:struct:Object +PEBBLISP_H pebblisp.h 2;" d +R pebblisp.h 74;" d +Result pebblisp.h /^typedef struct Result {$/;" s +Result pebblisp.h /^} Result;$/;" t typeref:struct:Result +SMAX_LENGTH calc.h 4;" d +STANDALONE pebblisp.h 4;" d +Slice pebblisp.h /^struct Slice {$/;" s +TOKENS_H tokens.h 2;" d +TYPE_ENV pebblisp.h /^ TYPE_ENV,$/;" e enum:Type +TYPE_ERROR pebblisp.h /^ TYPE_ERROR$/;" e enum:Type +TYPE_FUNC pebblisp.h /^ TYPE_FUNC,$/;" e enum:Type +TYPE_LIST pebblisp.h /^ TYPE_LIST,$/;" e enum:Type +TYPE_NUMBER pebblisp.h /^ TYPE_NUMBER,$/;" e enum:Type +TYPE_SYMBOL pebblisp.h /^ TYPE_SYMBOL,$/;" e enum:Type +Type pebblisp.h /^typedef enum Type {$/;" g +Type pebblisp.h /^} Type;$/;" t typeref:enum:Type +_h_FIXED_ fixed.h 26;" d +add pebblisp.c /^Object add(Object obj1, Object obj2)$/;" f +calculate calc.c /^static void calculate(){$/;" f file: +clear calc.c /^static void clear(bool full){$/;" f file: +click_config_provider calc.c /^static void click_config_provider(void *context) {$/;" f file: +copySlice pebblisp.c /^void copySlice(char * dest, struct Slice *src)$/;" f +debugSlice pebblisp.c /^void debugSlice(struct Slice *s)$/;" f +defaultEnv pebblisp.c /^struct Environment defaultEnv() {$/;" f +deinit calc.c /^static void deinit(void) {$/;" f file: +enter calc.c /^static void enter(){$/;" f file: +eval pebblisp.c /^Object eval(Object *obj, struct Environment *env)$/;" f +fixed fixed.h /^typedef int fixed;$/;" t +fixed_add fixed.c /^fixed fixed_add(fixed lhs, fixed rhs, bool* overflow)$/;" f +fixed_div fixed.c /^fixed fixed_div(fixed lhs, fixed rhs)$/;" f +fixed_mult fixed.c /^fixed fixed_mult(fixed lhs, fixed rhs, bool* overflow)$/;" f +fixed_pow fixed.c /^fixed fixed_pow(fixed base, int exponent, bool* overflow)$/;" f +fixed_repr fixed.c /^char* fixed_repr(fixed fixed, char* buffer, size_t size)$/;" f +fixed_subt fixed.c /^fixed fixed_subt(fixed lhs, fixed rhs, bool* overflow)$/;" f +fixed_to_int fixed.c /^int fixed_to_int(fixed n)$/;" f +forward pebblisp.h /^ Object *forward;$/;" m struct:Object +func pebblisp.h /^ Object (*func)(Object, Object);$/;" m union:Object::__anon1 +getToken calc.c /^static inline char* getToken(int n) {$/;" f file: +init calc.c /^static void init(void) {$/;" f file: +int_to_fixed fixed.c /^fixed int_to_fixed(int n)$/;" f +isDigit tokens.c /^int isDigit(const char c) {$/;" f +isSingle tokens.c /^int isSingle(const char c) {$/;" f +length pebblisp.h /^ int length;$/;" m struct:Slice +list pebblisp.h /^ Object *list;$/;" m union:Object::__anon1 +main calc.c /^int main(void) {$/;" f +main pebblisp.c /^int main(void)$/;" f +mytext calc.h /^char mytext[SMAX_LENGTH] = "";$/;" v +name pebblisp.h /^ char name[MAX_TOK_LEN];$/;" m union:Object::__anon1 +num1 calc.c /^static char num1[MAX_LENGTH] = ""; \/\/First operand$/;" v file: +num1_is_ans calc.c /^static bool num1_is_ans = false; \/\/Is the previous result in num1? Used to allow further calculations on result.$/;" v file: +num2 calc.c /^static char num2[MAX_LENGTH] = ""; \/\/Second operand$/;" v file: +number pebblisp.h /^ int number;$/;" m union:Object::__anon1 +obj pebblisp.h /^ Object obj;$/;" m struct:Result +objects pebblisp.h /^ Object *objects;$/;" m struct:Environment +operator calc.c /^static uint8_t operator = 0; \/\/Operator, where 0 is +, 1 is -, 2 is *, 3 is \/, 4 is ^$/;" v file: +operator_entered calc.c /^static bool operator_entered = false; \/\/Has the operator been entered yet$/;" v file: +parse pebblisp.c /^Result parse(struct Slice *slices)$/;" f +parseAtom pebblisp.c /^Object parseAtom(struct Slice *s)$/;" f +parseEval pebblisp.c /^Object parseEval(const char *input, struct Environment *env)$/;" f +printf pebblisp.c 7;" d file: +readSeq pebblisp.c /^Result readSeq(struct Slice *tokens)$/;" f +repl pebblisp.c /^int repl(struct Environment *env)$/;" f +resultFromObjAndSlices pebblisp.c /^Result resultFromObjAndSlices(Object obj, struct Slice *slices)$/;" f +result_text calc.c /^static char result_text[MAX_LENGTH] = ""; \/\/Results text layer buffer string$/;" v file: +s_input_text_layer calc.h /^TextLayer *s_input_text_layer;$/;" v +s_window calc.h /^Window *s_window;$/;" v +select_handler calc.c /^static void select_handler(ClickRecognizerRef recognizer, void *context){$/;" f file: +selected_token calc.c /^static int8_t selected_token = 1; \/\/Currently selected button, starts on '5'$/;" v file: +singleTokens tokens.c /^static const char singleTokens[] = "()+-*\/";$/;" v file: +slices pebblisp.h /^ struct Slice *slices;$/;" m struct:Result typeref:struct:Result::Slice +str_to_fixed fixed.c /^fixed str_to_fixed(const char* str, bool* overflow)$/;" f +str_to_int fixed.c /^int str_to_int(const char *str, char **endptr, int maxnum) {$/;" f +stringObj pebblisp.c /^char* stringObj(char *dest, Object *obj)$/;" f +strings pebblisp.h /^ char **strings;$/;" m struct:Environment +temptext calc.h /^char temptext[SMAX_LENGTH] = "";$/;" v +text pebblisp.h /^ const char *text;$/;" m struct:Slice +tokenFail pebblisp.h /^static const char* tokenFail = "Missing ')'\\n";$/;" v +tokenize tokens.c /^struct Slice *tokenize(const char *input)$/;" f +tokens calc.h /^char *tokens[] = {$/;" v +type pebblisp.h /^ Type type;$/;" m struct:Object +up_down_handler calc.c /^static void up_down_handler(ClickRecognizerRef recognizer, void *context){$/;" f file: +updateText calc.c /^static void updateText()$/;" f file: diff --git a/src/tokens.c b/src/tokens.c new file mode 100644 index 0000000..1e69075 --- /dev/null +++ b/src/tokens.c @@ -0,0 +1,83 @@ +#include "tokens.h" +#include + +#ifdef STANDALONE +#include +#else +#define printf(...) tokenize(NULL) +#endif + +/* + * Grammar: + * token + * expr + * (op expr expr) + * (list expr expr ... ) + */ + +// Is the char a standalone token? +static const char singleTokens[] = "()+-*/"; +int isSingle(const char c) { + int i = 0; + while(singleTokens[i] != '\0'){ + if(singleTokens[i] == c) + return singleTokens[i]; + i++; + } + return 0; +} + +int isDigit(const char c) { + return c >= '0' && c <= '9'; +} + +struct Slice *tokenize(const char *input) +{ + if(!input) + return NULL; + + struct Slice *slices = malloc(sizeof(struct Slice) * MAX_TOK_CNT); + + int i = 0; + int slice = 0; + + int parens = 0; + while(input[i] != '\0') { + printf("input: '%c'\n", input[i]); + if(input[i] == '(') { + parens++; + } else if (input[i] == ')') { + parens--; + } + + if(isSingle(input[i])) { + slices[slice].text = &input[i]; + slices[slice].length = 1; + slice++; + i++; + + } else if(isDigit(input[i])) { + slices[slice].text = &input[i]; + + int l = 1; + while(isDigit(input[++i])) + l++; + + slices[slice].length = l; + slice++; + + } else { // Whitespace or other uncaught + i++; + } + } + + if(parens){ + free(slices); + return NULL; + } + + slices[slice].text = NULL; + slices[slice].length = 0; + + return slices; +} diff --git a/src/tokens.h b/src/tokens.h new file mode 100644 index 0000000..0ec7d8c --- /dev/null +++ b/src/tokens.h @@ -0,0 +1,9 @@ +#ifndef TOKENS_H +#define TOKENS_H + +#include "pebblisp.h" +int isSingle(const char c); +int isDigit(const char c); +struct Slice *tokenize(const char *input); + +#endif diff --git a/wscript b/wscript new file mode 100644 index 0000000..78aad52 --- /dev/null +++ b/wscript @@ -0,0 +1,41 @@ +# +# This file is the default set of rules to compile a Pebble project. +# +# Feel free to customize this to your needs. +# + +import os.path + +top = '.' +out = 'build' + + +def options(ctx): + ctx.load('pebble_sdk') + + +def configure(ctx): + ctx.load('pebble_sdk') + + +def build(ctx): + ctx.load('pebble_sdk') + + build_worker = os.path.exists('worker_src') + binaries = [] + + for p in ctx.env.TARGET_PLATFORMS: + ctx.set_env(ctx.all_envs[p]) + ctx.set_group(ctx.env.PLATFORM_NAME) + app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) + ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), target=app_elf) + + if build_worker: + worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) + binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) + ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), target=worker_elf) + else: + binaries.append({'platform': p, 'app_elf': app_elf}) + + ctx.set_group('bundle') + ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js'), js_entry_file='src/js/app.js')