diff options
| -rw-r--r-- | src/displayapp/screens/Calculator.cpp | 379 | ||||
| -rw-r--r-- | src/displayapp/screens/Calculator.h | 3 |
2 files changed, 186 insertions, 196 deletions
diff --git a/src/displayapp/screens/Calculator.cpp b/src/displayapp/screens/Calculator.cpp index b7a3dfe..c0a947d 100644 --- a/src/displayapp/screens/Calculator.cpp +++ b/src/displayapp/screens/Calculator.cpp @@ -1,89 +1,98 @@ #include "Calculator.h" #include <string> -#include <stack> #include <cfloat> #include <cmath> #include <map> #include <memory> +#include <string> using namespace Pinetime::Applications::Screens; // Anonymous Namespace for all the structs namespace { - struct CalcTreeNode { - virtual double calculate() = 0; + struct Node { + char op; + double val; }; - - struct NumNode : CalcTreeNode { - double value; - - double calculate() override { - return value; - }; + template <typename X, uint8_t max_stack_len> struct StaticallyAllocatedStack { + X data[max_stack_len]; + uint8_t stack_len = 0; + inline auto size() { return stack_len; } + inline bool empty() { return size() == 0; } + inline bool full() { return size() == max_stack_len; } + inline X& top() { return data[stack_len-1]; } + inline X& pop() { return data[--stack_len]; } + inline X& push() { return data[stack_len++]; } + inline void push(X x) { X& datum{ push() }; datum = x; } }; - - struct BinOp : CalcTreeNode { - std::shared_ptr<CalcTreeNode> left; - std::shared_ptr<CalcTreeNode> right; - - char op; - - double calculate() override { - // make sure we have actual numbers - if (!right || !left) { - errno = EINVAL; - return 0.0; - } - - double rightVal = right->calculate(); - double leftVal = left->calculate(); - switch (op) { - case '^': - // detect overflow - if (log2(leftVal) + rightVal > 31) { - errno = ERANGE; - return 0.0; - } - return pow(leftVal, rightVal); - case 'x': - // detect over/underflowflow - if ((DBL_MAX / abs(rightVal)) < abs(leftVal)) { - errno = ERANGE; - return 0.0; - } - return leftVal * rightVal; - case '/': - // detect under/overflow - if ((DBL_MAX * abs(rightVal)) < abs(leftVal)) { - errno = ERANGE; - return 0.0; - } - // detect divison by zero - if (rightVal == 0.0) { - errno = EDOM; - return 0.0; - } - return leftVal / rightVal; - case '+': - // detect overflow - if ((DBL_MAX - rightVal) < leftVal) { - errno = ERANGE; - return 0.0; - } - return leftVal + rightVal; - case '-': - // detect underflow - if ((DBL_MIN + rightVal) > leftVal) { - errno = ERANGE; - return 0.0; + template <uint8_t max_stack_len> struct CalcStack : public StaticallyAllocatedStack<Node, max_stack_len> { + typedef StaticallyAllocatedStack<Node, max_stack_len> Super; + inline Node& push() { return Super::push(); } + inline void pushValue(double value) { + Super::data[Super::stack_len].op = 0; + Super::data[Super::stack_len].val = value; + Super::stack_len++; + } + void pushOperator(char op) { + Node* node0 = Super::data + Super::stack_len; + node0->op = op; + if (Super::stack_len > 1) { + Node* node1 = node0 - 1; + if (node1->op == 0) { + Node* node2 = node1 - 1; + auto val2 = node2->val; + auto val1 = node1->val; + if (node2->op == 0) { + switch (op) { + case '^': + val2 = pow(val2, val1); + break; + case 'x': + val2 *= val1; + break; + case '/': + val2 /= val1; + break; + case '+': + val2 += val1; + break; + case '-': + val2 -= val1; + break; + default: + goto done; + } } - return leftVal - rightVal; + node2->val = val2; + Super::stack_len--; + return; + } } - errno = EINVAL; - return 0.0; - }; + done: + Super::stack_len++; + } }; + template <typename I, typename F, typename S> bool parseFloat(I& i, F& f, S& s) { + f = 0; + int8_t dot_position = -1; + while (!i.empty()) { + auto& c = i.top(); + if ('0' <= c && c <= '9') { + if (dot_position >= 0) dot_position++; + f *= 10; + f += c - '0'; + } else if ('.' == c) { + if (dot_position >= 0) return false; + dot_position = 0; + } else break; + i.pop(); + } + while (dot_position-- > 0) f /= 10; + if (s < 0) { f = -f; } + return true; + } + uint8_t getPrecedence(char op) { switch (op) { case '^': @@ -110,8 +119,7 @@ namespace { } return false; } - -} +}; static void eventHandler(lv_obj_t* obj, lv_event_t event) { auto calc = static_cast<Calculator*>(obj->user_data); @@ -139,7 +147,7 @@ static const char* buttonMap2[] = { Calculator::Calculator(DisplayApp* app, Controllers::MotorController& motorController) : Screen(app), motorController {motorController} { result = lv_label_create(lv_scr_act(), nullptr); lv_label_set_long_mode(result, LV_LABEL_LONG_BREAK); - lv_label_set_text(result, "0"); + lv_label_set_text_static(result, "0"); lv_obj_set_size(result, 180, 60); lv_obj_set_pos(result, 0, 0); @@ -148,7 +156,7 @@ Calculator::Calculator(DisplayApp* app, Controllers::MotorController& motorContr lv_obj_set_pos(returnButton, 186, 0); lv_obj_t* returnLabel; returnLabel = lv_label_create(returnButton, nullptr); - lv_label_set_text(returnLabel, "<="); + lv_label_set_text_static(returnLabel, "<="); lv_obj_align(returnLabel, nullptr, LV_ALIGN_CENTER, 0, 0); returnButton->user_data = this; lv_obj_set_event_cb(returnButton, eventHandler); @@ -162,65 +170,45 @@ Calculator::Calculator(DisplayApp* app, Controllers::MotorController& motorContr lv_obj_set_event_cb(buttonMatrix, eventHandler); } -void Calculator::eval() { - std::stack<char> input {}; +void Calculator::Eval() { + StaticallyAllocatedStack<char, 32> input; for (int8_t i = position - 1; i >= 0; i--) { input.push(text[i]); } - std::stack<std::shared_ptr<CalcTreeNode>> output {}; - std::stack<char> operators {}; + CalcStack<16> output; + StaticallyAllocatedStack<char, 32> operators; bool expectingNumber = true; int8_t sign = +1; + double resultFloat; + uint32_t lower, upper; while (!input.empty()) { if (input.top() == '.') { input.push('0'); } + if (isdigit(input.top())) { - char numberStr[31]; - uint8_t strln = 0; - uint8_t pointpos = 0; - while (!input.empty() && (isdigit(input.top()) || input.top() == '.')) { - if (input.top() == '.') { - if (pointpos != 0) { - motorController.RunForDuration(10); - return; - } - pointpos = strln; - } else { - numberStr[strln] = input.top(); - strln++; - } - input.pop(); - } - // replacement for strtod() since using that increased .txt by 76858 bzt - if (pointpos == 0) { - pointpos = strln; - } - double num = 0; - for (uint8_t i = 0; i < pointpos; i++) { - num += (numberStr[i] - '0') * pow(10, pointpos - i - 1); - } - for (uint8_t i = 0; i < strln - pointpos; i++) { - num += (numberStr[i + pointpos] - '0') / pow(10, i + 1); + if (!parseFloat(input, output.push().val, sign)) { + goto eval_error; } - - auto number = std::make_shared<NumNode>(); - number->value = sign * num; - output.push(number); - + output.top().op = 0; sign = +1; expectingNumber = false; continue; } - if (expectingNumber && input.top() == '+') { - input.pop(); - continue; - } - if (expectingNumber && input.top() == '-') { - sign *= -1; - input.pop(); + if (expectingNumber) { + switch (input.top()) { + case '-': + sign *= -1; + [[fallthrough]]; + case '+': + input.pop(); + break; + default: + goto not_sign; + } continue; + not_sign: ; } char next = input.top(); @@ -242,25 +230,26 @@ void Calculator::eval() { || (getPrecedence(operators.top()) == getPrecedence(next) && leftAssociative(next)))) { // need two elements on the output stack to add a binary operator if (output.size() < 2) { - motorController.RunForDuration(10); - return; + goto eval_error; } - auto node = std::make_shared<BinOp>(); - node->right = output.top(); - output.pop(); - node->left = output.top(); - output.pop(); - node->op = operators.top(); + output.pushOperator(operators.top()); operators.pop(); - output.push(node); } operators.push(next); expectingNumber = true; break; case '(': - // we expect there to be a binary operator here but found a left parenthesis. this occurs in terms like this: a+b(c). This should be - // interpreted as a+b*(c) - if (!expectingNumber) { + if (expectingNumber) { + if (sign < 0) { + // Handle correctly expressions like: '-(5+11)' or '2*-(5+11)' + sign = 1; + output.pushValue(0); + operators.push('-'); + } + } else { + // We expect there to be a binary operator here but found a left parenthesis. + // This occurs in terms like this: a+b(c). + // This should be interpreted as a+b*(c) operators.push('x'); } operators.push(next); @@ -270,20 +259,12 @@ void Calculator::eval() { while (operators.top() != '(') { // need two elements on the output stack to add a binary operator if (output.size() < 2) { - motorController.RunForDuration(10); - return; + goto eval_error; } - auto node = std::make_shared<BinOp>(); - node->right = output.top(); - output.pop(); - node->left = output.top(); - output.pop(); - node->op = operators.top(); + output.pushOperator(operators.top()); operators.pop(); - output.push(node); if (operators.empty()) { - motorController.RunForDuration(10); - return; + goto eval_error; } } // discard the left parentheses @@ -291,59 +272,69 @@ void Calculator::eval() { } } while (!operators.empty()) { - char op = operators.top(); - if (op == ')' || op == '(') { - motorController.RunForDuration(10); - return; - } - // need two elements on the output stack to add a binary operator - if (output.size() < 2) { - motorController.RunForDuration(10); - return; + char op = operators.pop(); + if ((op == ')' || op == '(') + || + // need two elements on the output stack to add a binary operator + (output.size() < 2)) + { + goto eval_error; } - auto node = std::make_shared<BinOp>(); - node->right = output.top(); - output.pop(); - node->left = output.top(); - output.pop(); - node->op = op; - operators.pop(); - output.push(node); + output.pushOperator(op); } // perform the calculation - errno = 0; - double resultFloat = output.top()->calculate(); - if (errno != 0) { - motorController.RunForDuration(10); - return; + resultFloat = output.top().val; +#if 0 + // This only seems to work in the simulator + if (!std::isfinite(resultFloat)) { + goto eval_error; + } + position = 0; + for (char s : std::to_string(resultFloat)) { + text[position++] = s; } - // make sure the result fits in a 32 bit int - if (INT32_MAX < resultFloat || INT32_MIN > resultFloat) { - motorController.RunForDuration(10); - return; + text[position] = 0; +#else + // position = snprintf(text, 20, "%.9g", resultFloat); + // make sure that the absolute value of the integral part of result fits in a 32 bit unsigned integer + sign = (resultFloat < 0); + if (sign) { + resultFloat = -resultFloat; } - // weird workaround because sprintf crashes when trying to use a float - int32_t upper = resultFloat; - int32_t lower = round(std::abs(resultFloat - upper) * 10000); - // round up to the next int value - if (lower >= 10000) { + if (!(resultFloat <= UINT32_MAX)) { + goto eval_error; // if too large or NaN + } + if (sign) { + text[0] = '-'; + position = 1; + } else { + position = 0; + } + // workaround: provided sprintf doesn't support floats + upper = resultFloat; + lower = round((resultFloat - upper) * 1000000); + if (lower >= 1000000) { lower = 0; upper++; } - // see if decimal places have to be printed + position += sprintf(text + position, "%lu", upper); if (lower != 0) { - if (upper == 0 && resultFloat < 0) { - position = sprintf(text, "-%ld.%ld", upper, lower); - } else { - position = sprintf(text, "%ld.%ld", upper, lower); - } + // see if decimal places have to be printed + position += sprintf(text + position, ".%06lu", lower); // remove extra zeros while (text[position - 1] == '0') { position--; } - } else { - position = sprintf(text, "%ld", upper); } +#endif + return; + eval_error: + motorController.RunForDuration(10); +} + +void Calculator::Reset() { + position = 0; + lv_label_set_text_static(result, "0"); } void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { @@ -351,7 +342,7 @@ void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { if (obj == buttonMatrix) { const char* buttonstr = lv_btnmatrix_get_active_btn_text(obj); if (*buttonstr == '=') { - eval(); + Eval(); } else { if (position >= 30) { motorController.RunForDuration(10); @@ -362,33 +353,31 @@ void Calculator::OnButtonEvent(lv_obj_t* obj, lv_event_t event) { } } else if (obj == returnButton) { if (position > 1) { - position--; } else { - position = 0; - lv_label_set_text(result, "0"); + Reset(); return; } } text[position] = '\0'; - lv_label_set_text(result, text); + lv_label_set_text_static(result, text); } } bool Calculator::OnTouchEvent(Pinetime::Applications::TouchEvents event) { - if (event == Pinetime::Applications::TouchEvents::LongTap) { - position = 0; - lv_label_set_text(result, "0"); - return true; - } - if (event == Pinetime::Applications::TouchEvents::SwipeLeft) { - lv_btnmatrix_set_map(buttonMatrix, buttonMap2); - return true; - } - if (event == Pinetime::Applications::TouchEvents::SwipeRight) { - lv_btnmatrix_set_map(buttonMatrix, buttonMap1); - return true; + switch (event) { + case Pinetime::Applications::TouchEvents::LongTap: + Reset(); + break; + case Pinetime::Applications::TouchEvents::SwipeLeft: + lv_btnmatrix_set_map(buttonMatrix, buttonMap2); + break; + case Pinetime::Applications::TouchEvents::SwipeRight: + lv_btnmatrix_set_map(buttonMatrix, buttonMap1); + break; + default: + return false; } - return false; + return true; } diff --git a/src/displayapp/screens/Calculator.h b/src/displayapp/screens/Calculator.h index 25c4456..7b1e49b 100644 --- a/src/displayapp/screens/Calculator.h +++ b/src/displayapp/screens/Calculator.h @@ -27,7 +27,8 @@ namespace Pinetime { char text[31]; uint8_t position = 0; - void eval(); + void Eval(); + void Reset(); Controllers::MotorController& motorController; }; |
