#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; }