summaryrefslogtreecommitdiff
path: root/src/displayapp/screens/Calculator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/displayapp/screens/Calculator.cpp')
-rw-r--r--src/displayapp/screens/Calculator.cpp379
1 files changed, 184 insertions, 195 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;
}