|
|
@@ -0,0 +1,460 @@
|
|
|
+#include "doogle999.h"
|
|
|
+
|
|
|
+static unsigned char inputLocation = 0; // Current index in text input
|
|
|
+
|
|
|
+static double calc(const char input[CALC_BUFFER_SIZE +1]) // Finds value of input char array, relatively small and fast I think
|
|
|
+{
|
|
|
+ char inputToken[CALC_BUFFER_SIZE + 1]; // Input buffer, used when a single token (generally a number) takes up more
|
|
|
+ unsigned char inputTokenLocation = 0, inputLocation = 0; // Keep track of indices
|
|
|
+
|
|
|
+ struct Token tokens[CALC_BUFFER_SIZE + 1]; // Input, converted to tokens, one extra large to accomodate for possible negative sign then open parenthesis as first character
|
|
|
+ unsigned char tokenCount = 0; // Keep track of index
|
|
|
+
|
|
|
+ bool dashAsMinus = false; // Kind of a hacky solution to determining whether to treat a dash as a minus sign or a negative sign
|
|
|
+
|
|
|
+ while(inputLocation < CALC_BUFFER_SIZE + 1)
|
|
|
+ {
|
|
|
+ char digit = input[inputLocation];
|
|
|
+
|
|
|
+ if(inputLocation == 0 && input[inputLocation] == CALC_CHAR_SUB && input[inputLocation + 1] == CALC_CHAR_BEG)
|
|
|
+ {
|
|
|
+ tokens[tokenCount].raw.num = 0;
|
|
|
+ tokens[tokenCount].isNum = true;
|
|
|
+
|
|
|
+ tokenCount++;
|
|
|
+ dashAsMinus = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((digit >= '0' && digit <= '9') || /* valid digit */
|
|
|
+ (inputTokenLocation != 0 && input[inputLocation] == CALC_CHAR_DEC) || /* valid floating point */
|
|
|
+ (!dashAsMinus && inputTokenLocation == 0 && input[inputLocation] == CALC_CHAR_SUB)) /* - is negative sign */
|
|
|
+ {
|
|
|
+ inputToken[inputTokenLocation] = input[inputLocation];
|
|
|
+ inputTokenLocation++;
|
|
|
+ inputLocation++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(inputTokenLocation != 0)
|
|
|
+ {
|
|
|
+ // sscanf(inputToken, "%lf", &tokens[tokenCount].raw.num); // I would like to use sscanf here, but the small version of stdio.h on the chip doesn't allow sscanf or its sister functions to be used to process floats
|
|
|
+ tokens[tokenCount].raw.num = atof(inputToken);
|
|
|
+ tokens[tokenCount].isNum = true;
|
|
|
+ for(unsigned char i = 0; i < inputTokenLocation + 1; i++)
|
|
|
+ {
|
|
|
+ inputToken[i] = '\0';
|
|
|
+ }
|
|
|
+ inputTokenLocation = 0;
|
|
|
+ tokenCount++;
|
|
|
+ dashAsMinus = true;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* inputTokenLocation == 0 */
|
|
|
+ tokens[tokenCount].isNum = false;
|
|
|
+ tokens[tokenCount].raw.op.c = input[inputLocation];
|
|
|
+ tokens[tokenCount].raw.op.priority = 0;
|
|
|
+ tokens[tokenCount].raw.op.ltr = true;
|
|
|
+ dashAsMinus = false;
|
|
|
+
|
|
|
+ switch(input[inputLocation])
|
|
|
+ {
|
|
|
+ case CALC_CHAR_BEG:
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_END:
|
|
|
+ dashAsMinus = true;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_ADD:
|
|
|
+ tokens[tokenCount].raw.op.priority = CALC_PRIO_ADD;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_SUB:
|
|
|
+ tokens[tokenCount].raw.op.priority = CALC_PRIO_SUB;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_MUL:
|
|
|
+ tokens[tokenCount].raw.op.priority = CALC_PRIO_MUL;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_DIV:
|
|
|
+ tokens[tokenCount].raw.op.priority = CALC_PRIO_DIV;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_EXP:
|
|
|
+ tokens[tokenCount].raw.op.priority = CALC_PRIO_EXP;
|
|
|
+ tokens[tokenCount].raw.op.ltr = false;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_SIN:
|
|
|
+ case CALC_CHAR_COS:
|
|
|
+ case CALC_CHAR_TAN:
|
|
|
+ case CALC_CHAR_ASN:
|
|
|
+ case CALC_CHAR_ACS:
|
|
|
+ case CALC_CHAR_ATN:
|
|
|
+ case CALC_CHAR_LGE:
|
|
|
+ case CALC_CHAR_LOG:
|
|
|
+ case CALC_CHAR_SQT:
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_EUL:
|
|
|
+ tokens[tokenCount].isNum = true;
|
|
|
+ tokens[tokenCount].raw.num = CALC_VALU_EUL;
|
|
|
+ dashAsMinus = true;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_PI:
|
|
|
+ tokens[tokenCount].isNum = true;
|
|
|
+ tokens[tokenCount].raw.num = CALC_VALU_PI;
|
|
|
+ dashAsMinus = true;
|
|
|
+ break;
|
|
|
+ case '\0':
|
|
|
+ tokenCount--;
|
|
|
+ inputLocation = CALC_BUFFER_SIZE;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ tokenCount--;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ tokenCount++;
|
|
|
+ inputLocation++;
|
|
|
+ }
|
|
|
+
|
|
|
+ struct Token output[CALC_BUFFER_SIZE + 1]; // Final output tokens before evaluation
|
|
|
+ struct Token opstack[CALC_BUFFER_SIZE + 1]; // Stack of operators
|
|
|
+ unsigned char outputLocation = 0, opstackLocation = 0; // Keep track of indices
|
|
|
+
|
|
|
+ unsigned char numBrackets = 0; // The number of parenthesis
|
|
|
+
|
|
|
+ for(unsigned char i = 0; i < tokenCount; i++)
|
|
|
+ {
|
|
|
+ if(tokens[i].isNum)
|
|
|
+ {
|
|
|
+ output[outputLocation] = tokens[i];
|
|
|
+ outputLocation++;
|
|
|
+ }
|
|
|
+ else if(tokens[i].raw.op.c == CALC_CHAR_BEG)
|
|
|
+ {
|
|
|
+ opstack[opstackLocation] = tokens[i];
|
|
|
+ opstackLocation++;
|
|
|
+ }
|
|
|
+ else if(tokens[i].raw.op.c == CALC_CHAR_END)
|
|
|
+ {
|
|
|
+ while(opstack[opstackLocation - 1].raw.op.c != CALC_CHAR_BEG)
|
|
|
+ {
|
|
|
+ output[outputLocation] = opstack[opstackLocation - 1];
|
|
|
+ outputLocation++;
|
|
|
+ opstackLocation--;
|
|
|
+ }
|
|
|
+ opstackLocation--;
|
|
|
+
|
|
|
+ numBrackets += 2;
|
|
|
+ }
|
|
|
+ else if(tokens[i].raw.op.priority == 0)
|
|
|
+ {
|
|
|
+ opstack[opstackLocation] = tokens[i];
|
|
|
+ opstackLocation++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ while(opstackLocation != 0
|
|
|
+ && (opstack[opstackLocation - 1].raw.op.priority == 0
|
|
|
+ || tokens[i].raw.op.priority < opstack[opstackLocation - 1].raw.op.priority
|
|
|
+ || (tokens[i].raw.op.priority == opstack[opstackLocation - 1].raw.op.priority && opstack[opstackLocation - 1].raw.op.ltr))
|
|
|
+ && opstack[opstackLocation - 1].raw.op.c != CALC_CHAR_BEG)
|
|
|
+ {
|
|
|
+ output[outputLocation] = opstack[opstackLocation - 1];
|
|
|
+ outputLocation++;
|
|
|
+ opstackLocation--;
|
|
|
+ }
|
|
|
+ opstack[opstackLocation] = tokens[i];
|
|
|
+ opstackLocation++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ tokenCount -= numBrackets;
|
|
|
+
|
|
|
+ for(signed char i = opstackLocation - 1; i >= 0; i--)
|
|
|
+ {
|
|
|
+ output[outputLocation] = opstack[i];
|
|
|
+ outputLocation++;
|
|
|
+ opstackLocation--;
|
|
|
+ }
|
|
|
+
|
|
|
+ double answer[CALC_BUFFER_SIZE];
|
|
|
+ unsigned char answerLocation = 0;
|
|
|
+
|
|
|
+ for(unsigned char i = 0; i < tokenCount; i++)
|
|
|
+ {
|
|
|
+ if(output[i].isNum)
|
|
|
+ {
|
|
|
+ answer[answerLocation] = output[i].raw.num;
|
|
|
+ answerLocation++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(output[i].raw.op.priority == 0)
|
|
|
+ {
|
|
|
+ if (answerLocation < 1) { /* not handled here -- ERROR? */ } else
|
|
|
+ if(answerLocation >= 1)
|
|
|
+ {
|
|
|
+ double (*op)(double);
|
|
|
+ switch(output[i].raw.op.c)
|
|
|
+ {
|
|
|
+ case CALC_CHAR_SIN:
|
|
|
+ op = sin;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_COS:
|
|
|
+ op = cos;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_TAN:
|
|
|
+ op = tan;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_ASN:
|
|
|
+ op = asin;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_ACS:
|
|
|
+ op = acos;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_ATN:
|
|
|
+ op = atan;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_LGE:
|
|
|
+ op = log;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_LOG:
|
|
|
+ op = log10;
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_SQT:
|
|
|
+ op = sqrt;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ continue; /* invalid input */
|
|
|
+ }
|
|
|
+ answer[answerLocation - 1] = op(answer[answerLocation - 1]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /* priority != 0 */
|
|
|
+ else if(answerLocation >= 2)
|
|
|
+ {
|
|
|
+ switch(output[i].raw.op.c)
|
|
|
+ {
|
|
|
+ case CALC_CHAR_ADD:
|
|
|
+ answer[answerLocation - 2] += answer[answerLocation - 1];
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_SUB:
|
|
|
+ answer[answerLocation - 2] -= answer[answerLocation - 1];
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_MUL:
|
|
|
+ answer[answerLocation - 2] *= answer[answerLocation - 1];
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_DIV:
|
|
|
+ answer[answerLocation - 2] /= answer[answerLocation - 1];
|
|
|
+ break;
|
|
|
+ case CALC_CHAR_EXP:
|
|
|
+ answer[answerLocation - 2] = pow(answer[answerLocation - 2], answer[answerLocation - 1]);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ answerLocation--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return answer[0];
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * @returns 0 when nothing should happen and QMK should work as usual
|
|
|
+ * @returns -1 when invalid input was given, QMK should ignore it
|
|
|
+ * @returns -2 when BSP should be done
|
|
|
+ * @returns -3 when CALC should be done
|
|
|
+ * @returns -4 when ENDCALC should be done
|
|
|
+ * @returns positive value of CALC_* when normal input was processed
|
|
|
+ */
|
|
|
+static int process_input(const uint16_t keycode, const uint8_t mods, const keyevent_t event)
|
|
|
+{
|
|
|
+ /* handle even when no key was pressed */
|
|
|
+ if(!event.pressed)
|
|
|
+ {
|
|
|
+ switch(keycode)
|
|
|
+ {
|
|
|
+ /* QMK should handle those */
|
|
|
+ case KC_RSFT:
|
|
|
+ case KC_LSFT:
|
|
|
+ return 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ /* ??? ignore */
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* when shift key is pressed handle characters differently */
|
|
|
+ char characterPressed;
|
|
|
+ if((get_mods() & MODS_SHIFT_MASK))
|
|
|
+ {
|
|
|
+ switch(keycode)
|
|
|
+ {
|
|
|
+ case KC_9:
|
|
|
+ characterPressed = CALC_CHAR_BEG;
|
|
|
+ break;
|
|
|
+ case KC_0:
|
|
|
+ characterPressed = CALC_CHAR_END;
|
|
|
+ break;
|
|
|
+ case KC_EQUAL:
|
|
|
+ characterPressed = CALC_CHAR_ADD;
|
|
|
+ break;
|
|
|
+ case KC_KP_PLUS:
|
|
|
+ characterPressed = CALC_CHAR_ADD;
|
|
|
+ break;
|
|
|
+ case KC_6:
|
|
|
+ characterPressed = CALC_CHAR_EXP;
|
|
|
+ break;
|
|
|
+ case KC_8:
|
|
|
+ characterPressed = CALC_CHAR_MUL;
|
|
|
+ break;
|
|
|
+ case KC_KP_ASTERISK:
|
|
|
+ characterPressed = CALC_CHAR_MUL;
|
|
|
+ break;
|
|
|
+ case KC_S:
|
|
|
+ characterPressed = CALC_CHAR_ASN;
|
|
|
+ break;
|
|
|
+ case KC_C:
|
|
|
+ characterPressed = CALC_CHAR_ACS;
|
|
|
+ break;
|
|
|
+ case KC_T:
|
|
|
+ characterPressed = CALC_CHAR_ATN;
|
|
|
+ break;
|
|
|
+ case KC_L:
|
|
|
+ characterPressed = CALC_CHAR_LOG;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return -1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return characterPressed;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* normal key handling: shift not pressed */
|
|
|
+
|
|
|
+ /* digits */
|
|
|
+ if (keycode == KC_KP_0 || keycode == KC_0) {
|
|
|
+ return '0';
|
|
|
+ } else if (keycode >= KC_KP_1 && keycode <= KC_KP_9) {
|
|
|
+ return keycode - KC_KP_1 +1 + '0';
|
|
|
+ } else if (keycode >= KC_1 && keycode <= KC_9) {
|
|
|
+ return keycode - KC_1 +1 + '0';
|
|
|
+ }
|
|
|
+
|
|
|
+ /* other tokens */
|
|
|
+ switch (keycode) {
|
|
|
+ case KC_MINUS:
|
|
|
+ case KC_KP_MINUS:
|
|
|
+ return characterPressed = CALC_CHAR_SUB;
|
|
|
+ case KC_SLASH:
|
|
|
+ case KC_KP_SLASH:
|
|
|
+ return characterPressed = CALC_CHAR_DIV;
|
|
|
+ case KC_S:
|
|
|
+ return characterPressed = CALC_CHAR_SIN;
|
|
|
+ case KC_C:
|
|
|
+ return characterPressed = CALC_CHAR_COS;
|
|
|
+ case KC_T:
|
|
|
+ return characterPressed = CALC_CHAR_TAN;
|
|
|
+ case KC_Q:
|
|
|
+ return characterPressed = CALC_CHAR_SQT;
|
|
|
+ case KC_L:
|
|
|
+ return characterPressed = CALC_CHAR_LGE;
|
|
|
+ case KC_DOT:
|
|
|
+ case KC_KP_DOT:
|
|
|
+ return characterPressed = CALC_CHAR_DEC;
|
|
|
+ case KC_P:
|
|
|
+ return characterPressed = CALC_CHAR_PI;
|
|
|
+ case KC_E:
|
|
|
+ return characterPressed = CALC_CHAR_EUL;
|
|
|
+ case KC_BSPC:
|
|
|
+ return -2;
|
|
|
+ case KC_RSFT:
|
|
|
+ return 0;
|
|
|
+ case KC_LSFT:
|
|
|
+ return 0;
|
|
|
+ case CALC:
|
|
|
+ return -3;
|
|
|
+ case ENDCALC:
|
|
|
+ return -4;
|
|
|
+ default:
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+bool process_record_user(uint16_t keycode, keyrecord_t* record)
|
|
|
+{
|
|
|
+ static char text[CALC_BUFFER_SIZE + 1]; // Used to store input and then output when ready to print
|
|
|
+ static char backspaceText[CALC_BUFFER_SIZE + 1]; // Pretty dumb waste of memory because only backspace characters, used with send_string to backspace and remove input
|
|
|
+
|
|
|
+ if((biton32(layer_state) == CALC_LAYER && CALC_FORCE_NUM_LOCK_INSIDE_CALC) || (biton32(layer_state) != CALC_LAYER && CALC_FORCE_NUM_LOCK_OUTSIDE_CALC))
|
|
|
+ {
|
|
|
+ bool numpadKeyPressed = record->event.pressed &&
|
|
|
+ !(get_mods() & MODS_SHIFT_MASK) &&
|
|
|
+ /* KC_KP_1, KC_KP_2, ..., KC_KP_0, KC_KP_DOT */
|
|
|
+ (keycode >= KC_KP_1 && keycode <= KC_KP_DOT);
|
|
|
+
|
|
|
+ if(numpadKeyPressed && !(host_keyboard_leds() & (1 << USB_LED_NUM_LOCK)))
|
|
|
+ {
|
|
|
+ add_key(KC_NLCK);
|
|
|
+ send_keyboard_report();
|
|
|
+ del_key(KC_NLCK);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(biton32(layer_state) != CALC_LAYER) { return true; }
|
|
|
+
|
|
|
+ int action = process_input(keycode, get_mods(), record->event);
|
|
|
+ switch(action)
|
|
|
+ {
|
|
|
+ case 0:
|
|
|
+ return true;
|
|
|
+ case -1:
|
|
|
+ return false;
|
|
|
+ case -2:
|
|
|
+ if(inputLocation > 0)
|
|
|
+ {
|
|
|
+ inputLocation--;
|
|
|
+ text[inputLocation] = '\0';
|
|
|
+ backspaceText[0] = (char)8;
|
|
|
+ backspaceText[1] = '\0';
|
|
|
+ send_string(backspaceText);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ case -3:
|
|
|
+ for(int i = 0; i < inputLocation; i++)
|
|
|
+ {
|
|
|
+ backspaceText[i] = (char)8;
|
|
|
+ }
|
|
|
+ send_string(backspaceText);
|
|
|
+ dtostrf(calc(text), CALC_PRINT_SIZE, CALC_PRINT_SIZE, text);
|
|
|
+ send_string(text);
|
|
|
+ for(unsigned char i = 0; i < CALC_BUFFER_SIZE; i++)
|
|
|
+ {
|
|
|
+ text[i] = '\0';
|
|
|
+ backspaceText[i] = '\0';
|
|
|
+ }
|
|
|
+ inputLocation = 0;
|
|
|
+ return false;
|
|
|
+ case -4:
|
|
|
+ for(unsigned char i = 0; i < CALC_BUFFER_SIZE; i++)
|
|
|
+ {
|
|
|
+ text[i] = '\0';
|
|
|
+ backspaceText[i] = '\0';
|
|
|
+ }
|
|
|
+ inputLocation = 0;
|
|
|
+ layer_off(CALC_LAYER);
|
|
|
+ return false;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ char characterPressed = (char)action;
|
|
|
+
|
|
|
+ if(inputLocation < CALC_BUFFER_SIZE)
|
|
|
+ {
|
|
|
+ text[inputLocation] = characterPressed;
|
|
|
+ inputLocation++;
|
|
|
+
|
|
|
+ char characterToSend[2];
|
|
|
+ characterToSend[0] = characterPressed;
|
|
|
+ characterToSend[1] = '\0';
|
|
|
+
|
|
|
+ send_string(characterToSend);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|