Quellcode durchsuchen

Add calculator from https://github.com/xMasterX/all-the-plugins

git-subtree-dir: calculator
git-subtree-mainline: da9eb804fd9cc634881b023368dd04912415f591
git-subtree-split: fd88197e403ba315f12863842be7b8ed117e06bd
Willy-JL vor 2 Jahren
Ursprung
Commit
e5a46c4f37

+ 1 - 0
calculator/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/xMasterX/all-the-plugins dev apps_source_code/calculator

+ 16 - 0
calculator/application.fam

@@ -0,0 +1,16 @@
+App(
+    appid="calculator",
+    name="Calculator",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="calculator_app",
+    cdefines=["APP_CALCULATOR"],
+    requires=["gui"],
+    stack_size=1 * 1024,
+    order=45,
+    fap_icon="calcIcon.png",
+    fap_category="Tools",
+    fap_author="@n-o-T-I-n-s-a-n-e",
+    fap_weburl="https://github.com/n-o-T-I-n-s-a-n-e",
+    fap_version="1.1",
+    fap_description="Calculator, that can calculate simple expressions",
+)

BIN
calculator/calcIcon.png


+ 458 - 0
calculator/calculator.c

@@ -0,0 +1,458 @@
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <stdbool.h> // Header-file for boolean data-type.
+#include <string.h> // Header-file for string functions.
+#include "tinyexpr.h" // Header-file for the TinyExpr library.
+
+#include <stdio.h>
+#include <stdlib.h>
+
+const short MAX_TEXT_LENGTH = 20;
+
+typedef struct {
+    short x;
+    short y;
+} selectedPosition;
+
+typedef struct {
+    FuriMutex* mutex;
+    selectedPosition position;
+    //string with the inputted calculator text
+    char text[20];
+    short textLength;
+    char log[20];
+} Calculator;
+
+char getKeyAtPosition(short x, short y) {
+    if(x == 0 && y == 0) {
+        return 'C';
+    }
+    if(x == 1 && y == 0) {
+        return '<';
+    }
+    if(x == 2 && y == 0) {
+        return '%';
+    }
+    if(x == 3 && y == 0) {
+        return '/';
+    }
+    if(x == 0 && y == 1) {
+        return '1';
+    }
+    if(x == 1 && y == 1) {
+        return '2';
+    }
+    if(x == 2 && y == 1) {
+        return '3';
+    }
+    if(x == 3 && y == 1) {
+        return '*';
+    }
+    if(x == 0 && y == 2) {
+        return '4';
+    }
+    if(x == 1 && y == 2) {
+        return '5';
+    }
+    if(x == 2 && y == 2) {
+        return '6';
+    }
+    if(x == 3 && y == 2) {
+        return '-';
+    }
+    if(x == 0 && y == 3) {
+        return '7';
+    }
+    if(x == 1 && y == 3) {
+        return '8';
+    }
+    if(x == 2 && y == 3) {
+        return '9';
+    }
+    if(x == 3 && y == 3) {
+        return '+';
+    }
+    if(x == 0 && y == 4) {
+        return '(';
+    }
+    if(x == 1 && y == 4) {
+        return '0';
+    }
+    if(x == 2 && y == 4) {
+        return '.';
+    }
+    if(x == 3 && y == 4) {
+        return '=';
+    }
+    return ' ';
+}
+
+short calculateStringWidth(const char* str, short lenght) {
+    /* widths:
+        1 = 2
+        2, 3, 4, 5, 6, 7, 8, 9, 0, X, -, +, . =  = 5
+        %, / = 7
+        S = 5
+        (, ) = 3
+
+    */
+    short width = 0;
+    for(short i = 0; i < lenght; i++) {
+        switch(str[i]) {
+        case '1':
+            width += 2;
+            break;
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+        case '0':
+        case '*':
+        case '-':
+        case '+':
+        case '.':
+            width += 5;
+            break;
+        case '%':
+        case '/':
+            width += 7;
+            break;
+        case 'S':
+            width += 5;
+            break;
+        case '(':
+        case ')':
+            width += 3;
+            break;
+        default:
+            break;
+        }
+        width += 1;
+    }
+
+    return width;
+}
+
+void generate_calculator_layout(Canvas* canvas) {
+    //draw dotted lines
+    for(int i = 0; i <= 64; i++) {
+        if(i % 2 == 0) {
+            canvas_draw_dot(canvas, i, 14);
+            canvas_draw_dot(canvas, i, 33);
+        }
+        if(i % 2 == 1) {
+            canvas_draw_dot(canvas, i, 15);
+            canvas_draw_dot(canvas, i, 34);
+        }
+    }
+
+    //draw horizontal lines
+    canvas_draw_box(canvas, 0, 41, 64, 2);
+    canvas_draw_box(canvas, 0, 57, 64, 2);
+    canvas_draw_box(canvas, 0, 73, 64, 2);
+    canvas_draw_box(canvas, 0, 89, 64, 2);
+    canvas_draw_box(canvas, 0, 105, 64, 2);
+    canvas_draw_box(canvas, 0, 121, 64, 2);
+
+    //draw vertical lines
+    canvas_draw_box(canvas, 0, 43, 1, 80);
+    canvas_draw_box(canvas, 15, 43, 2, 80);
+    canvas_draw_box(canvas, 31, 43, 2, 80);
+    canvas_draw_box(canvas, 47, 43, 2, 80);
+    canvas_draw_box(canvas, 63, 43, 1, 80);
+
+    //draw buttons
+    //row 1 (C, ;, %, ÷)
+    canvas_draw_str(canvas, 5, 54, "C");
+    canvas_draw_str(canvas, 19, 54, " <-");
+    canvas_draw_str(canvas, 35, 54, " %");
+    canvas_draw_str(canvas, 51, 54, " /");
+
+    //row 2 (1, 2, 3, X)
+    canvas_draw_str(canvas, 5, 70, " 1");
+    canvas_draw_str(canvas, 19, 70, " 2");
+    canvas_draw_str(canvas, 35, 70, " 3");
+    canvas_draw_str(canvas, 51, 70, " X");
+
+    //row 3 (4, 5, 6, -)
+    canvas_draw_str(canvas, 5, 86, " 4");
+    canvas_draw_str(canvas, 19, 86, " 5");
+    canvas_draw_str(canvas, 35, 86, " 6");
+    canvas_draw_str(canvas, 51, 86, " -");
+
+    //row 4 (7, 8, 9, +)
+    canvas_draw_str(canvas, 5, 102, " 7");
+    canvas_draw_str(canvas, 19, 102, " 8");
+    canvas_draw_str(canvas, 35, 102, " 9");
+    canvas_draw_str(canvas, 51, 102, " +");
+
+    //row 5 (+/-, 0, ., =)
+    canvas_draw_str(canvas, 3, 118, "( )");
+    canvas_draw_str(canvas, 19, 118, " 0");
+    canvas_draw_str(canvas, 35, 118, "   .");
+    canvas_draw_str(canvas, 51, 118, " =");
+}
+
+void calculator_draw_callback(Canvas* canvas, void* ctx) {
+    furi_assert(ctx);
+    const Calculator* calculator_state = ctx;
+    furi_mutex_acquire(calculator_state->mutex, FuriWaitForever);
+
+    canvas_clear(canvas);
+
+    //show selected button
+    short startX = 1;
+    short startY = 43;
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_box(
+        canvas,
+        startX + (calculator_state->position.x) * 16,
+        (startY) + (calculator_state->position.y) * 16,
+        16,
+        16);
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_box(
+        canvas,
+        startX + (calculator_state->position.x) * 16 + 2,
+        (startY) + (calculator_state->position.y) * 16 + 2,
+        10,
+        10);
+
+    canvas_set_color(canvas, ColorBlack);
+    generate_calculator_layout(canvas);
+
+    //draw text
+    short stringWidth = calculateStringWidth(calculator_state->text, calculator_state->textLength);
+    short startingPosition = 5;
+    if(stringWidth > 60) {
+        startingPosition += 60 - (stringWidth + 5);
+    }
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_str(canvas, startingPosition, 28, calculator_state->text);
+    //canvas_draw_str(canvas, 10, 10, calculator_state->log);
+
+    //draw cursor
+    canvas_draw_box(canvas, stringWidth + 5, 29, 5, 1);
+
+    furi_mutex_release(calculator_state->mutex);
+}
+
+void calculator_input_callback(InputEvent* input_event, void* ctx) {
+    furi_assert(ctx);
+    FuriMessageQueue* event_queue = ctx;
+    furi_message_queue_put(event_queue, input_event, FuriWaitForever);
+}
+
+void calculate(Calculator* calculator_state) {
+    double result;
+    result = te_interp(calculator_state->text, 0);
+
+    calculator_state->textLength = 0;
+    calculator_state->text[0] = '\0';
+    // sprintf(calculator_state->text, "%f", result);
+
+    //invert sign if negative
+    if(result < 0) {
+        calculator_state->text[calculator_state->textLength++] = '-';
+        result = -result;
+    }
+
+    //get numbers before and after decimal
+    int beforeDecimal = result;
+    int afterDecimal = (result - beforeDecimal) * 100;
+
+    char beforeDecimalString[10];
+    char afterDecimalString[10];
+    int i = 0;
+    //parse to a string
+    while(beforeDecimal > 0) {
+        beforeDecimalString[i++] = beforeDecimal % 10 + '0';
+        beforeDecimal /= 10;
+    }
+    // invert string
+    for(int j = 0; j < i / 2; j++) {
+        char temp = beforeDecimalString[j];
+        beforeDecimalString[j] = beforeDecimalString[i - j - 1];
+        beforeDecimalString[i - j - 1] = temp;
+    }
+    //add it to the answer
+    for(int j = 0; j < i; j++) {
+        calculator_state->text[calculator_state->textLength++] = beforeDecimalString[j];
+    }
+
+    i = 0;
+    if(afterDecimal > 0) {
+        while(afterDecimal > 0) {
+            afterDecimalString[i++] = afterDecimal % 10 + '0';
+            afterDecimal /= 10;
+        }
+        // invert string
+        for(int j = 0; j < i / 2; j++) {
+            char temp = afterDecimalString[j];
+            afterDecimalString[j] = afterDecimalString[i - j - 1];
+            afterDecimalString[i - j - 1] = temp;
+        }
+
+        //add decimal point
+        calculator_state->text[calculator_state->textLength++] = '.';
+
+        //add numbers after decimal
+        for(int j = 0; j < i; j++) {
+            calculator_state->text[calculator_state->textLength++] = afterDecimalString[j];
+        }
+    }
+    calculator_state->text[calculator_state->textLength] = '\0';
+}
+
+int32_t calculator_app(void* p) {
+    UNUSED(p);
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
+
+    Calculator* calculator_state = malloc(sizeof(Calculator));
+    calculator_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!calculator_state->mutex) {
+        //FURI_LOG_E("calculator", "cannot create mutex\r\n");
+        free(calculator_state);
+        return -1;
+    }
+
+    // Configure view port
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, calculator_draw_callback, calculator_state);
+    view_port_input_callback_set(view_port, calculator_input_callback, event_queue);
+    view_port_set_orientation(view_port, ViewPortOrientationVertical);
+
+    // Register view port in GUI
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    //NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
+
+    InputEvent event;
+
+    while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
+        //break out of the loop if the back key is pressed
+        if(event.type == InputTypeShort && event.key == InputKeyBack) {
+            break;
+        }
+
+        if(event.type == InputTypeShort) {
+            switch(event.key) {
+            case InputKeyUp:
+                if(calculator_state->position.y > 0) {
+                    calculator_state->position.y--;
+                }
+                break;
+            case InputKeyDown:
+                if(calculator_state->position.y < 4) {
+                    calculator_state->position.y++;
+                }
+                break;
+            case InputKeyLeft:
+                if(calculator_state->position.x > 0) {
+                    calculator_state->position.x--;
+                }
+                break;
+            case InputKeyRight:
+                if(calculator_state->position.x < 3) {
+                    calculator_state->position.x++;
+                }
+                break;
+            case InputKeyOk: {
+                //add the selected button to the text
+                //char* text = calculator_state->text;
+                // short* textLength = &calculator_state->textLength;
+
+                char key =
+                    getKeyAtPosition(calculator_state->position.x, calculator_state->position.y);
+
+                switch(key) {
+                case 'C':
+                    while(calculator_state->textLength > 0) {
+                        calculator_state->text[calculator_state->textLength--] = '\0';
+                    }
+                    calculator_state->text[0] = '\0';
+                    calculator_state->log[2] = key;
+                    break;
+                case '<':
+                    calculator_state->log[2] = key;
+                    if(calculator_state->textLength > 0) {
+                        calculator_state->text[--calculator_state->textLength] = '\0';
+                    } else {
+                        calculator_state->text[0] = '\0';
+                    }
+                    break;
+                case '=':
+                    calculator_state->log[2] = key;
+                    calculate(calculator_state);
+                    break;
+                case '%':
+                case '/':
+                case '*':
+                case '-':
+                case '+':
+                case '.':
+                case '(':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                case '0':
+                    if(calculator_state->textLength < MAX_TEXT_LENGTH) {
+                        calculator_state->text[calculator_state->textLength++] = key;
+                        calculator_state->text[calculator_state->textLength] = '\0';
+                    }
+                    //calculator_state->log[1] = calculator_state->text[*textLength];
+                    break;
+                default:
+                    break;
+                }
+            }
+            default:
+                break;
+            }
+
+            view_port_update(view_port);
+        }
+
+        if(event.type == InputTypeLong) {
+            switch(event.key) {
+            case InputKeyOk:
+                if(calculator_state->position.x == 0 && calculator_state->position.y == 4) {
+                    if(calculator_state->textLength < MAX_TEXT_LENGTH) {
+                        calculator_state->text[calculator_state->textLength++] = ')';
+                        calculator_state->text[calculator_state->textLength] = '\0';
+                    }
+                    view_port_update(view_port);
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    }
+    gui_remove_view_port(gui, view_port);
+    view_port_free(view_port);
+    furi_mutex_free(calculator_state->mutex);
+    furi_message_queue_free(event_queue);
+
+    furi_record_close(RECORD_NOTIFICATION);
+    furi_record_close(RECORD_GUI);
+    free(calculator_state);
+
+    return 0;
+}

BIN
calculator/img/1.png


+ 786 - 0
calculator/tinyexpr.c

@@ -0,0 +1,786 @@
+// SPDX-License-Identifier: Zlib
+/*
+ * TINYEXPR - Tiny recursive descent parser and evaluation engine in C
+ *
+ * Copyright (c) 2015-2020 Lewis Van Winkle
+ *
+ * http://CodePlea.com
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgement in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+/* COMPILE TIME OPTIONS */
+
+/* Exponentiation associativity:
+For a^b^c = (a^b)^c and -a^b = (-a)^b do nothing.
+For a^b^c = a^(b^c) and -a^b = -(a^b) uncomment the next line.*/
+/* #define TE_POW_FROM_RIGHT */
+
+/* Logarithms
+For log = base 10 log do nothing
+For log = natural log uncomment the next line. */
+/* #define TE_NAT_LOG */
+
+#include "tinyexpr.h"
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <limits.h>
+
+#ifndef NAN
+#define NAN (0.0 / 0.0)
+#endif
+
+#ifndef INFINITY
+#define INFINITY (1.0 / 0.0)
+#endif
+
+typedef double (*te_fun2)(double, double);
+
+enum {
+    TOK_NULL = TE_CLOSURE7 + 1,
+    TOK_ERROR,
+    TOK_END,
+    TOK_SEP,
+    TOK_OPEN,
+    TOK_CLOSE,
+    TOK_NUMBER,
+    TOK_VARIABLE,
+    TOK_INFIX
+};
+
+enum { TE_CONSTANT = 1 };
+
+typedef struct state {
+    const char* start;
+    const char* next;
+    int type;
+    union {
+        double value;
+        const double* bound;
+        const void* function;
+    };
+    void* context;
+
+    const te_variable* lookup;
+    int lookup_len;
+} state;
+
+#define TYPE_MASK(TYPE) ((TYPE)&0x0000001F)
+
+#define IS_PURE(TYPE) (((TYPE)&TE_FLAG_PURE) != 0)
+#define IS_FUNCTION(TYPE) (((TYPE)&TE_FUNCTION0) != 0)
+#define IS_CLOSURE(TYPE) (((TYPE)&TE_CLOSURE0) != 0)
+#define ARITY(TYPE) (((TYPE) & (TE_FUNCTION0 | TE_CLOSURE0)) ? ((TYPE)&0x00000007) : 0)
+#define NEW_EXPR(type, ...) new_expr((type), (const te_expr*[]){__VA_ARGS__})
+
+static te_expr* new_expr(const int type, const te_expr* parameters[]) {
+    const int arity = ARITY(type);
+    const int psize = sizeof(void*) * arity;
+    const int size =
+        (sizeof(te_expr) - sizeof(void*)) + psize + (IS_CLOSURE(type) ? sizeof(void*) : 0);
+    te_expr* ret = malloc(size);
+    memset(ret, 0, size);
+    if(arity && parameters) {
+        memcpy(ret->parameters, parameters, psize);
+    }
+    ret->type = type;
+    ret->bound = 0;
+    return ret;
+}
+
+void te_free_parameters(te_expr* n) {
+    if(!n) return;
+    switch(TYPE_MASK(n->type)) {
+    case TE_FUNCTION7:
+    case TE_CLOSURE7:
+        te_free(n->parameters[6]); /* Falls through. */
+    case TE_FUNCTION6:
+    case TE_CLOSURE6:
+        te_free(n->parameters[5]); /* Falls through. */
+    case TE_FUNCTION5:
+    case TE_CLOSURE5:
+        te_free(n->parameters[4]); /* Falls through. */
+    case TE_FUNCTION4:
+    case TE_CLOSURE4:
+        te_free(n->parameters[3]); /* Falls through. */
+    case TE_FUNCTION3:
+    case TE_CLOSURE3:
+        te_free(n->parameters[2]); /* Falls through. */
+    case TE_FUNCTION2:
+    case TE_CLOSURE2:
+        te_free(n->parameters[1]); /* Falls through. */
+    case TE_FUNCTION1:
+    case TE_CLOSURE1:
+        te_free(n->parameters[0]);
+    }
+}
+
+void te_free(te_expr* n) {
+    if(!n) return;
+    te_free_parameters(n);
+    free(n);
+}
+
+static double pi(void) {
+    return 3.14159265358979323846;
+}
+static double e(void) {
+    return 2.71828182845904523536;
+}
+static double fac(double a) { /* simplest version of fac */
+    if(a < (double)0.0) return NAN;
+    if(a > UINT_MAX) return INFINITY;
+    unsigned int ua = (unsigned int)(a);
+    unsigned long int result = 1, i;
+    for(i = 1; i <= ua; i++) {
+        if(i > ULONG_MAX / result) return INFINITY;
+        result *= i;
+    }
+    return (double)result;
+}
+static double ncr(double n, double r) {
+    if(n < (double)0.0 || r < (double)0.0 || n < r) return NAN;
+    if(n > UINT_MAX || r > UINT_MAX) return INFINITY;
+    unsigned long int un = (unsigned int)(n), ur = (unsigned int)(r), i;
+    unsigned long int result = 1;
+    if(ur > un / 2) ur = un - ur;
+    for(i = 1; i <= ur; i++) {
+        if(result > ULONG_MAX / (un - ur + i)) return INFINITY;
+        result *= un - ur + i;
+        result /= i;
+    }
+    return result;
+}
+static double npr(double n, double r) {
+    return ncr(n, r) * fac(r);
+}
+
+#ifdef _MSC_VER
+#pragma function(ceil)
+#pragma function(floor)
+#endif
+
+static const te_variable functions[] = {
+    /* must be in alphabetical order */
+    {"abs", fabs, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"acos", acos, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"asin", asin, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"atan", atan, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"atan2", atan2, TE_FUNCTION2 | TE_FLAG_PURE, 0},
+    {"ceil", ceil, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"cos", cos, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"cosh", cosh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"e", e, TE_FUNCTION0 | TE_FLAG_PURE, 0},
+    {"exp", exp, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"fac", fac, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"floor", floor, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"ln", log, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+#ifdef TE_NAT_LOG
+    {"log", log, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+#else
+    {"log", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+#endif
+    {"log10", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"ncr", ncr, TE_FUNCTION2 | TE_FLAG_PURE, 0},
+    {"npr", npr, TE_FUNCTION2 | TE_FLAG_PURE, 0},
+    {"pi", pi, TE_FUNCTION0 | TE_FLAG_PURE, 0},
+    {"pow", pow, TE_FUNCTION2 | TE_FLAG_PURE, 0},
+    {"sin", sin, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"sinh", sinh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"sqrt", sqrt, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"tan", tan, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {"tanh", tanh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
+    {0, 0, 0, 0}};
+
+static const te_variable* find_builtin(const char* name, int len) {
+    int imin = 0;
+    int imax = sizeof(functions) / sizeof(te_variable) - 2;
+
+    /*Binary search.*/
+    while(imax >= imin) {
+        const int i = (imin + ((imax - imin) / 2));
+        int c = strncmp(name, functions[i].name, len);
+        if(!c) c = '\0' - functions[i].name[len];
+        if(c == 0) {
+            return functions + i;
+        } else if(c > 0) {
+            imin = i + 1;
+        } else {
+            imax = i - 1;
+        }
+    }
+
+    return 0;
+}
+
+static const te_variable* find_lookup(const state* s, const char* name, int len) {
+    int iters;
+    const te_variable* var;
+    if(!s->lookup) return 0;
+
+    for(var = s->lookup, iters = s->lookup_len; iters; ++var, --iters) {
+        if(strncmp(name, var->name, len) == 0 && var->name[len] == '\0') {
+            return var;
+        }
+    }
+    return 0;
+}
+
+static double add(double a, double b) {
+    return a + b;
+}
+static double sub(double a, double b) {
+    return a - b;
+}
+static double mul(double a, double b) {
+    return a * b;
+}
+static double divide(double a, double b) {
+    return a / b;
+}
+static double negate(double a) {
+    return -a;
+}
+static double comma(double a, double b) {
+    (void)a;
+    return b;
+}
+
+void next_token(state* s) {
+    s->type = TOK_NULL;
+
+    do {
+        if(!*s->next) {
+            s->type = TOK_END;
+            return;
+        }
+
+        /* Try reading a number. */
+        if((s->next[0] >= '0' && s->next[0] <= '9') || s->next[0] == '.') {
+            s->value = strtof(s->next, (char**)&s->next);
+            s->type = TOK_NUMBER;
+        } else {
+            /* Look for a variable or builtin function call. */
+            if(isalpha((int)s->next[0])) {
+                const char* start;
+                start = s->next;
+                while(isalpha((int)s->next[0]) || isdigit((int)s->next[0]) || (s->next[0] == '_'))
+                    s->next++;
+
+                const te_variable* var = find_lookup(s, start, s->next - start);
+                if(!var) var = find_builtin(start, s->next - start);
+
+                if(!var) {
+                    s->type = TOK_ERROR;
+                } else {
+                    switch(TYPE_MASK(var->type)) {
+                    case TE_VARIABLE:
+                        s->type = TOK_VARIABLE;
+                        s->bound = var->address;
+                        break;
+
+                    case TE_CLOSURE0:
+                    case TE_CLOSURE1:
+                    case TE_CLOSURE2:
+                    case TE_CLOSURE3: /* Falls through. */
+                    case TE_CLOSURE4:
+                    case TE_CLOSURE5:
+                    case TE_CLOSURE6:
+                    case TE_CLOSURE7: /* Falls through. */
+                        s->context = var->context; /* Falls through. */
+
+                    case TE_FUNCTION0:
+                    case TE_FUNCTION1:
+                    case TE_FUNCTION2:
+                    case TE_FUNCTION3: /* Falls through. */
+                    case TE_FUNCTION4:
+                    case TE_FUNCTION5:
+                    case TE_FUNCTION6:
+                    case TE_FUNCTION7: /* Falls through. */
+                        s->type = var->type;
+                        s->function = var->address;
+                        break;
+                    }
+                }
+
+            } else {
+                /* Look for an operator or special character. */
+                switch(s->next++[0]) {
+                case '+':
+                    s->type = TOK_INFIX;
+                    s->function = add;
+                    break;
+                case '-':
+                    s->type = TOK_INFIX;
+                    s->function = sub;
+                    break;
+                case '*':
+                    s->type = TOK_INFIX;
+                    s->function = mul;
+                    break;
+                case '/':
+                    s->type = TOK_INFIX;
+                    s->function = divide;
+                    break;
+                case '^':
+                    s->type = TOK_INFIX;
+                    s->function = pow;
+                    break;
+                case '%':
+                    s->type = TOK_INFIX;
+                    s->function = fmod;
+                    break;
+                case '(':
+                    s->type = TOK_OPEN;
+                    break;
+                case ')':
+                    s->type = TOK_CLOSE;
+                    break;
+                case ',':
+                    s->type = TOK_SEP;
+                    break;
+                case ' ':
+                case '\t':
+                case '\n':
+                case '\r':
+                    break;
+                default:
+                    s->type = TOK_ERROR;
+                    break;
+                }
+            }
+        }
+    } while(s->type == TOK_NULL);
+}
+
+static te_expr* list(state* s);
+static te_expr* expr(state* s);
+static te_expr* power(state* s);
+
+static te_expr* base(state* s) {
+    /* <base>      =    <constant> | <variable> | <function-0> {"(" ")"} | <function-1> <power> | <function-X> "(" <expr> {"," <expr>} ")" | "(" <list> ")" */
+    te_expr* ret;
+    int arity;
+
+    switch(TYPE_MASK(s->type)) {
+    case TOK_NUMBER:
+        ret = new_expr(TE_CONSTANT, 0);
+        ret->value = s->value;
+        next_token(s);
+        break;
+
+    case TOK_VARIABLE:
+        ret = new_expr(TE_VARIABLE, 0);
+        ret->bound = s->bound;
+        next_token(s);
+        break;
+
+    case TE_FUNCTION0:
+    case TE_CLOSURE0:
+        ret = new_expr(s->type, 0);
+        ret->function = s->function;
+        if(IS_CLOSURE(s->type)) ret->parameters[0] = s->context;
+        next_token(s);
+        if(s->type == TOK_OPEN) {
+            next_token(s);
+            if(s->type != TOK_CLOSE) {
+                s->type = TOK_ERROR;
+            } else {
+                next_token(s);
+            }
+        }
+        break;
+
+    case TE_FUNCTION1:
+    case TE_CLOSURE1:
+        ret = new_expr(s->type, 0);
+        ret->function = s->function;
+        if(IS_CLOSURE(s->type)) ret->parameters[1] = s->context;
+        next_token(s);
+        ret->parameters[0] = power(s);
+        break;
+
+    case TE_FUNCTION2:
+    case TE_FUNCTION3:
+    case TE_FUNCTION4:
+    case TE_FUNCTION5:
+    case TE_FUNCTION6:
+    case TE_FUNCTION7:
+    case TE_CLOSURE2:
+    case TE_CLOSURE3:
+    case TE_CLOSURE4:
+    case TE_CLOSURE5:
+    case TE_CLOSURE6:
+    case TE_CLOSURE7:
+        arity = ARITY(s->type);
+
+        ret = new_expr(s->type, 0);
+        ret->function = s->function;
+        if(IS_CLOSURE(s->type)) ret->parameters[arity] = s->context;
+        next_token(s);
+
+        if(s->type != TOK_OPEN) {
+            s->type = TOK_ERROR;
+        } else {
+            int i;
+            for(i = 0; i < arity; i++) {
+                next_token(s);
+                ret->parameters[i] = expr(s);
+                if(s->type != TOK_SEP) {
+                    break;
+                }
+            }
+            if(s->type != TOK_CLOSE || i != arity - 1) {
+                s->type = TOK_ERROR;
+            } else {
+                next_token(s);
+            }
+        }
+
+        break;
+
+    case TOK_OPEN:
+        next_token(s);
+        ret = list(s);
+        if(s->type != TOK_CLOSE) {
+            s->type = TOK_ERROR;
+        } else {
+            next_token(s);
+        }
+        break;
+
+    default:
+        ret = new_expr(0, 0);
+        s->type = TOK_ERROR;
+        ret->value = NAN;
+        break;
+    }
+
+    return ret;
+}
+
+static te_expr* power(state* s) {
+    /* <power>     =    {("-" | "+")} <base> */
+    int sign = 1;
+    while(s->type == TOK_INFIX && (s->function == add || s->function == sub)) {
+        if(s->function == sub) sign = -sign;
+        next_token(s);
+    }
+
+    te_expr* ret;
+
+    if(sign == 1) {
+        ret = base(s);
+    } else {
+        ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, base(s));
+        ret->function = negate;
+    }
+
+    return ret;
+}
+
+#ifdef TE_POW_FROM_RIGHT
+static te_expr* factor(state* s) {
+    /* <factor>    =    <power> {"^" <power>} */
+    te_expr* ret = power(s);
+
+    int neg = 0;
+
+    if(ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate) {
+        te_expr* se = ret->parameters[0];
+        free(ret);
+        ret = se;
+        neg = 1;
+    }
+
+    te_expr* insertion = 0;
+
+    while(s->type == TOK_INFIX && (s->function == pow)) {
+        te_fun2 t = s->function;
+        next_token(s);
+
+        if(insertion) {
+            /* Make exponentiation go right-to-left. */
+            te_expr* insert =
+                NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, insertion->parameters[1], power(s));
+            insert->function = t;
+            insertion->parameters[1] = insert;
+            insertion = insert;
+        } else {
+            ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s));
+            ret->function = t;
+            insertion = ret;
+        }
+    }
+
+    if(neg) {
+        ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, ret);
+        ret->function = negate;
+    }
+
+    return ret;
+}
+#else
+static te_expr* factor(state* s) {
+    /* <factor>    =    <power> {"^" <power>} */
+    te_expr* ret = power(s);
+
+    while(s->type == TOK_INFIX && (s->function == pow)) {
+        te_fun2 t = s->function;
+        next_token(s);
+        ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s));
+        ret->function = t;
+    }
+
+    return ret;
+}
+#endif
+
+static te_expr* term(state* s) {
+    /* <term>      =    <factor> {("*" | "/" | "%") <factor>} */
+    te_expr* ret = factor(s);
+
+    while(s->type == TOK_INFIX &&
+          (s->function == mul || s->function == divide || s->function == fmod)) {
+        te_fun2 t = s->function;
+        next_token(s);
+        ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, factor(s));
+        ret->function = t;
+    }
+
+    return ret;
+}
+
+static te_expr* expr(state* s) {
+    /* <expr>      =    <term> {("+" | "-") <term>} */
+    te_expr* ret = term(s);
+
+    while(s->type == TOK_INFIX && (s->function == add || s->function == sub)) {
+        te_fun2 t = s->function;
+        next_token(s);
+        ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, term(s));
+        ret->function = t;
+    }
+
+    return ret;
+}
+
+static te_expr* list(state* s) {
+    /* <list>      =    <expr> {"," <expr>} */
+    te_expr* ret = expr(s);
+
+    while(s->type == TOK_SEP) {
+        next_token(s);
+        ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, expr(s));
+        ret->function = comma;
+    }
+
+    return ret;
+}
+
+#define TE_FUN(...) ((double (*)(__VA_ARGS__))n->function)
+#define M(e) te_eval(n->parameters[e])
+
+double te_eval(const te_expr* n) {
+    if(!n) return NAN;
+
+    switch(TYPE_MASK(n->type)) {
+    case TE_CONSTANT:
+        return n->value;
+    case TE_VARIABLE:
+        return *n->bound;
+
+    case TE_FUNCTION0:
+    case TE_FUNCTION1:
+    case TE_FUNCTION2:
+    case TE_FUNCTION3:
+    case TE_FUNCTION4:
+    case TE_FUNCTION5:
+    case TE_FUNCTION6:
+    case TE_FUNCTION7:
+        switch(ARITY(n->type)) {
+        case 0:
+            return TE_FUN(void)();
+        case 1:
+            return TE_FUN(double)(M(0));
+        case 2:
+            return TE_FUN(double, double)(M(0), M(1));
+        case 3:
+            return TE_FUN(double, double, double)(M(0), M(1), M(2));
+        case 4:
+            return TE_FUN(double, double, double, double)(M(0), M(1), M(2), M(3));
+        case 5:
+            return TE_FUN(double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4));
+        case 6:
+            return TE_FUN(double, double, double, double, double, double)(
+                M(0), M(1), M(2), M(3), M(4), M(5));
+        case 7:
+            return TE_FUN(double, double, double, double, double, double, double)(
+                M(0), M(1), M(2), M(3), M(4), M(5), M(6));
+        default:
+            return NAN;
+        }
+
+    case TE_CLOSURE0:
+    case TE_CLOSURE1:
+    case TE_CLOSURE2:
+    case TE_CLOSURE3:
+    case TE_CLOSURE4:
+    case TE_CLOSURE5:
+    case TE_CLOSURE6:
+    case TE_CLOSURE7:
+        switch(ARITY(n->type)) {
+        case 0:
+            return TE_FUN(void*)(n->parameters[0]);
+        case 1:
+            return TE_FUN(void*, double)(n->parameters[1], M(0));
+        case 2:
+            return TE_FUN(void*, double, double)(n->parameters[2], M(0), M(1));
+        case 3:
+            return TE_FUN(void*, double, double, double)(n->parameters[3], M(0), M(1), M(2));
+        case 4:
+            return TE_FUN(void*, double, double, double, double)(
+                n->parameters[4], M(0), M(1), M(2), M(3));
+        case 5:
+            return TE_FUN(void*, double, double, double, double, double)(
+                n->parameters[5], M(0), M(1), M(2), M(3), M(4));
+        case 6:
+            return TE_FUN(void*, double, double, double, double, double, double)(
+                n->parameters[6], M(0), M(1), M(2), M(3), M(4), M(5));
+        case 7:
+            return TE_FUN(void*, double, double, double, double, double, double, double)(
+                n->parameters[7], M(0), M(1), M(2), M(3), M(4), M(5), M(6));
+        default:
+            return NAN;
+        }
+
+    default:
+        return NAN;
+    }
+}
+
+#undef TE_FUN
+#undef M
+
+static void optimize(te_expr* n) {
+    /* Evaluates as much as possible. */
+    if(n->type == TE_CONSTANT) return;
+    if(n->type == TE_VARIABLE) return;
+
+    /* Only optimize out functions flagged as pure. */
+    if(IS_PURE(n->type)) {
+        const int arity = ARITY(n->type);
+        int known = 1;
+        int i;
+        for(i = 0; i < arity; ++i) {
+            optimize(n->parameters[i]);
+            if(((te_expr*)(n->parameters[i]))->type != TE_CONSTANT) {
+                known = 0;
+            }
+        }
+        if(known) {
+            const double value = te_eval(n);
+            te_free_parameters(n);
+            n->type = TE_CONSTANT;
+            n->value = value;
+        }
+    }
+}
+
+te_expr*
+    te_compile(const char* expression, const te_variable* variables, int var_count, int* error) {
+    state s;
+    s.start = s.next = expression;
+    s.lookup = variables;
+    s.lookup_len = var_count;
+
+    next_token(&s);
+    te_expr* root = list(&s);
+
+    if(s.type != TOK_END) {
+        te_free(root);
+        if(error) {
+            *error = (s.next - s.start);
+            if(*error == 0) *error = 1;
+        }
+        return 0;
+    } else {
+        optimize(root);
+        if(error) *error = 0;
+        return root;
+    }
+}
+
+double te_interp(const char* expression, int* error) {
+    te_expr* n = te_compile(expression, 0, 0, error);
+    double ret;
+    if(n) {
+        ret = te_eval(n);
+        te_free(n);
+    } else {
+        ret = NAN;
+    }
+    return ret;
+}
+
+static void pn(const te_expr* n, int depth) {
+    int i, arity;
+    printf("%*s", depth, "");
+
+    switch(TYPE_MASK(n->type)) {
+    case TE_CONSTANT:
+        printf("%f\n", n->value);
+        break;
+    case TE_VARIABLE:
+        printf("bound %p\n", n->bound);
+        break;
+
+    case TE_FUNCTION0:
+    case TE_FUNCTION1:
+    case TE_FUNCTION2:
+    case TE_FUNCTION3:
+    case TE_FUNCTION4:
+    case TE_FUNCTION5:
+    case TE_FUNCTION6:
+    case TE_FUNCTION7:
+    case TE_CLOSURE0:
+    case TE_CLOSURE1:
+    case TE_CLOSURE2:
+    case TE_CLOSURE3:
+    case TE_CLOSURE4:
+    case TE_CLOSURE5:
+    case TE_CLOSURE6:
+    case TE_CLOSURE7:
+        arity = ARITY(n->type);
+        printf("f%d", arity);
+        for(i = 0; i < arity; i++) {
+            printf(" %p", n->parameters[i]);
+        }
+        printf("\n");
+        for(i = 0; i < arity; i++) {
+            pn(n->parameters[i], depth + 1);
+        }
+        break;
+    }
+}
+
+void te_print(const te_expr* n) {
+    pn(n, 0);
+}

+ 97 - 0
calculator/tinyexpr.h

@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: Zlib
+/*
+ * TINYEXPR - Tiny recursive descent parser and evaluation engine in C
+ *
+ * Copyright (c) 2015-2020 Lewis Van Winkle
+ *
+ * http://CodePlea.com
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgement in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef TINYEXPR_H
+#define TINYEXPR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct te_expr {
+    int type;
+    union {
+        double value;
+        const double* bound;
+        const void* function;
+    };
+    void* parameters[1];
+} te_expr;
+
+enum {
+    TE_VARIABLE = 0,
+
+    TE_FUNCTION0 = 8,
+    TE_FUNCTION1,
+    TE_FUNCTION2,
+    TE_FUNCTION3,
+    TE_FUNCTION4,
+    TE_FUNCTION5,
+    TE_FUNCTION6,
+    TE_FUNCTION7,
+
+    TE_CLOSURE0 = 16,
+    TE_CLOSURE1,
+    TE_CLOSURE2,
+    TE_CLOSURE3,
+    TE_CLOSURE4,
+    TE_CLOSURE5,
+    TE_CLOSURE6,
+    TE_CLOSURE7,
+
+    TE_FLAG_PURE = 32
+};
+
+typedef struct te_variable {
+    const char* name;
+    const void* address;
+    int type;
+    void* context;
+} te_variable;
+
+/* Parses the input expression, evaluates it, and frees it. */
+/* Returns NaN on error. */
+double te_interp(const char* expression, int* error);
+
+/* Parses the input expression and binds variables. */
+/* Returns NULL on error. */
+te_expr*
+    te_compile(const char* expression, const te_variable* variables, int var_count, int* error);
+
+/* Evaluates the expression. */
+double te_eval(const te_expr* n);
+
+/* Prints debugging information on the syntax tree. */
+void te_print(const te_expr* n);
+
+/* Frees the expression. */
+/* This is safe to call on NULL pointers. */
+void te_free(te_expr* n);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*TINYEXPR_H*/