From dabf8a22f6e482d1fc8c4a1950ddb0915af48694 Mon Sep 17 00:00:00 2001 From: Wroclaw Date: Sun, 15 May 2022 21:47:33 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + .vscode/launch.json | 26 +++ .vscode/settings.json | 8 + .vscode/tasks.json | 27 +++ README.md | 5 + concept.txt | 26 +++ src/calculator.cpp | 417 ++++++++++++++++++++++++++++++++++++++++++ src/commands.cpp | 79 ++++++++ src/cursor.cpp | 145 +++++++++++++++ src/getch.h | 12 ++ src/main.cpp | 185 +++++++++++++++++++ src/stack.cpp | 39 ++++ src/terminal.cpp | 66 +++++++ zadanie.txt | 36 ++++ 14 files changed, 1072 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 README.md create mode 100644 concept.txt create mode 100644 src/calculator.cpp create mode 100644 src/commands.cpp create mode 100644 src/cursor.cpp create mode 100644 src/getch.h create mode 100644 src/main.cpp create mode 100644 src/stack.cpp create mode 100644 src/terminal.cpp create mode 100644 zadanie.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22e98f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +\build* diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9401465 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug c++ project", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}\\build\\build.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}\\build\\", + "environment": [], + "externalConsole": true, + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "Build c++ project" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ca6ef2c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "*.tcc": "cpp", + "string": "cpp", + "new": "cpp", + "iosfwd": "cpp" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..69a451e --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,27 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "Build c++ project", + "command": "c++", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${workspaceFolder}\\src\\main.cpp", + "-o", + "${workspaceFolder}\\build\\build.exe" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fe5d67 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# calculator +Terminal-like calculator for simple arithmetic operations. +This is project for a college, given requirements are in (zadanie.txt)[./zadanie.txt]. + +_Inspired by vi keybinds :^)_ \ No newline at end of file diff --git a/concept.txt b/concept.txt new file mode 100644 index 0000000..9d8c94b --- /dev/null +++ b/concept.txt @@ -0,0 +1,26 @@ +press esc or type :q to exit, type :h for help +> 2+5 +7 + +> 2+2*2 +6 <-- when typing it should change dynamically, it may be too ambitious though + +> (2+2)*2 +8 + +> :h +This is help, it should appear instantly after user presses :h +Type your equation to the terminal and get quick answers + +commands: +:h - help - displays this message +:a - about program +:q - exit + +> 2+2:h +It should not print equation result here + +It should not remove equation + +> 2+2 +4 \ No newline at end of file diff --git a/src/calculator.cpp b/src/calculator.cpp new file mode 100644 index 0000000..c7cda3c --- /dev/null +++ b/src/calculator.cpp @@ -0,0 +1,417 @@ +#include +#include +#include "stack.cpp" + +namespace Calculator +{ +enum SymbolType { + number, + add, + subract, + multiply, + divide, + left_bracket, + right_bracket, +}; + +/** + * @brief Represents one symbol. + */ +struct Symbol { + SymbolType type = SymbolType::number; + double value = 0; +}; + +void dbgPrint(const std::vector* _input) { + std::cout << "\033[31m"; + + for (auto it = _input->begin(); it != _input->end(); it++) { + auto type = it->type; + + std::cout << "["; + if (type == SymbolType::add) std::cout << "+"; + else if (type == SymbolType::subract) std::cout << "-"; + else if (type == SymbolType::divide) std::cout << "/"; + else if (type == SymbolType::multiply) std::cout << "*"; + else if (type == SymbolType::number) std::cout << it->value; + else if (type == SymbolType::left_bracket) std::cout << "("; + else if (type == SymbolType::right_bracket) std::cout << ")"; + + std::cout << "]"; + }; + std::cout << "\033[0m"; +} + +void dbgPrint(const std::vector* _input) { + std::cout << "\033[31m"; + for (auto it = _input->begin(); it != _input->end(); it++) { + std::cout << "[" << *it << "]"; + } + std::cout << "\033[31m"; +} + +/** + * @brief the result of parse function. + */ +struct parseResult { + std::vector* result; + short unmatchedParanthesis = 0; +}; + +parseResult* parse(const std::string& _input) { + parseResult* returnVal = new parseResult; + returnVal->result = new std::vector(); + + for (unsigned int i = 0; i < _input.size(); i++) { + switch (_input[i]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': { + int length = 0; + auto j = i; + std::string textifiedValue = ""; // I don't know how to avoid duplication :< + while ( // while character is one of 0123456789. and it's not the end of _input + ( + (_input[j] >= 0x30 && _input[j] <= 0x39) || // 0123456789 + _input[j] == '.' // . + ) && + j < _input.size() + ) { + textifiedValue += _input[j]; + j++; + } + Symbol symbol; + symbol.value = std::atof(textifiedValue.c_str()); + returnVal->result->push_back(symbol); + i = j - 1; + } break; + case '+': { + Symbol symbol; + symbol.type = SymbolType::add; + returnVal->result->push_back(symbol); + } break; + case '-': { + Symbol symbol; + symbol.type = SymbolType::subract; + returnVal->result->push_back(symbol); + } break; + case '*': { + Symbol symbol; + symbol.type = SymbolType::multiply; + returnVal->result->push_back(symbol); + } break; + case '/': { + Symbol symbol; + symbol.type = SymbolType::divide; + returnVal->result->push_back(symbol); + } break; + case '(': { + Symbol symbol; + symbol.type = SymbolType::left_bracket; + returnVal->result->push_back(symbol); + returnVal->unmatchedParanthesis++; + } break; + case ')': { + Symbol symbol; + symbol.type = SymbolType::right_bracket; + returnVal->result->push_back(symbol); + returnVal->unmatchedParanthesis--; + } break; + default: + // it should be impossible to get there + static_assert(true); + } + } + + return returnVal; +} + +/** + * @brief corrects negative signs in input + * + * @param _input what to correct + */ +void correctNegativeSign(parseResult* _input) { + auto parsed = _input->result; + + for (auto it = parsed->begin(); it != parsed->end(); it++) { + auto jt = it + 1; //next element + if (it->type == SymbolType::subract && jt != parsed->end() && jt->type == SymbolType::number) { + it = parsed->erase(it); + it->value = it->value * (-1); + continue; + } + } + parsed->shrink_to_fit(); +} + +/** + * @brief validates parsation + * + * @param _input + * @return true if valid + * @return false if invalid + */ +bool validateParsation(const std::vector* _input) { + return true; + if (_input->size() == 0) return false; + for (auto it = _input->begin(); true /* Break argument 2 lines down */; it++) { + auto jt = it + 1; + auto kt = jt + 1; + + if (jt == _input->end()) { + if (it->type == SymbolType::add) return false; + if (it->type == SymbolType::subract) return false; + if (it->type == SymbolType::multiply) return false; + if (it->type == SymbolType::divide) return false; + break; + } + if (kt == _input->end()) { + if (jt->type == SymbolType::left_bracket) { + if (it->type == SymbolType::add) return false; + if (it->type == SymbolType::subract) return false; + if (it->type == SymbolType::multiply) return false; + if (it->type == SymbolType::divide) return false; + } + } + switch (it->type) + { + case SymbolType::number: + if (jt->type == SymbolType::number) return false; + if (jt->type == SymbolType::left_bracket) return false; + break; + case SymbolType::left_bracket: // sign cannot be after left bracket, only number can be + case SymbolType::add: + case SymbolType::subract: + case SymbolType::multiply: + case SymbolType::divide: + if (jt->type == SymbolType::add) return false; + if (jt->type == SymbolType::subract) return false; + if (jt->type == SymbolType::multiply) return false; + if (jt->type == SymbolType::divide) return false; + break; + case SymbolType::right_bracket: + if (jt->type == SymbolType::number) return false; + break; + } + } + + return true; +} + +/** + * @brief finds maximum in _vector. + * + * @param _vector in which vector find max. + * @return unsigned int the position of maximum value. + */ +unsigned int findMaxPosition(const std::vector& _vector) { + int maxPosition = 0; + int maxValue = 0; + + for (int i = 0; i < _vector.size(); i++) { + if (_vector[i] > maxValue) { + maxPosition = i; + maxValue = _vector[i]; + } + } + return maxPosition; +} + +/** + * @brief Converts _expression to ONP + * + * @param _expression which expression to convert + * @return std::vector* expression in ONP + */ +std::vector* toONP(std::vector* _expression) { + std::vector* expression = new std::vector(); // will not contain brackets + std::vector* returnVal = new std::vector(); + std::vector priorities; + unsigned int bracketPriority = 0; + + unsigned int size = _expression->size(); + + expression->reserve(size); + returnVal->reserve(size); + priorities.reserve(size); + + // 1. Copy _expression to expression removing paranthesis + // NOTE: paranthesis should be matched before. + + for (unsigned int i = 0; i < _expression->size(); i++) { + switch (_expression->at(i).type) + { + case SymbolType::number: + expression->push_back(_expression->at(i)); + priorities.push_back((1<<0) + bracketPriority * (1<<3)); + break; + case SymbolType::add: + case SymbolType::subract: + expression->push_back(_expression->at(i)); + priorities.push_back((1<<1) + bracketPriority * (1<<3)); + break; + case SymbolType::multiply: + case SymbolType::divide: + expression->push_back(_expression->at(i)); + priorities.push_back((1<<2) + bracketPriority * (1<<3)); + break; + case SymbolType::left_bracket: + bracketPriority++; + break; + case SymbolType::right_bracket: + bracketPriority--; + break; + } + } + + size = expression->size(); + // 2. Actually convert to ONP + + if (size > 0) while (true) { + unsigned int maxOnPos = findMaxPosition(priorities); + + if (priorities[maxOnPos] == 0) break; + if (maxOnPos >= 1 && maxOnPos - 1 < size && priorities[maxOnPos-1] != 0) { // left + returnVal->push_back(expression->at(maxOnPos-1)); + priorities[maxOnPos-1] = 0; + } + if (maxOnPos >= 0 && maxOnPos + 1 < size && priorities[maxOnPos+1] != 0) { // right + returnVal->push_back(expression->at(maxOnPos+1)); + priorities[maxOnPos+1] = 0; + } + if (maxOnPos >= 0 && maxOnPos < size && priorities[maxOnPos] != 0) { // center (maximum element) + returnVal->push_back(expression->at(maxOnPos)); + priorities[maxOnPos] = 0; + } + } + + delete expression; + return returnVal; +} + +Symbol calculateUsingStack(std::vector& _stack) { + switch (_stack.back().type) { + case SymbolType::number: { + return Stack::take(_stack); + } + case SymbolType::add: { + Stack::take(_stack); //top symbol is add + Symbol toPut; + toPut.value = calculateUsingStack(_stack).value + calculateUsingStack(_stack).value; + Stack::put(_stack, toPut); + return toPut; + } break; + case SymbolType::subract: { + Stack::take(_stack); //top symbol... + Symbol toPut; + toPut.value = calculateUsingStack(_stack).value - calculateUsingStack(_stack).value; + Stack::put(_stack, toPut); + return toPut; + } break; + case SymbolType::multiply: { + Stack::take(_stack); //top symbol... + Symbol toPut; + toPut.value = calculateUsingStack(_stack).value * calculateUsingStack(_stack).value; + Stack::put(_stack, toPut); + return toPut; + } break; + case SymbolType::divide: { + Stack::take(_stack); //top symbol... + Symbol toPut; + auto val = calculateUsingStack(_stack).value; + toPut.value = calculateUsingStack(_stack).value / val; + Stack::put(_stack, toPut); + return toPut; + } break; + default: + throw "Impossible"; + } +} + +/** + * @brief the return value of parseAndCalculate + */ +struct parseAndCalculateResult { + int unmatchedParanthesis = 0; + bool valid = true; + double value = 0; +}; + +/** + * @brief Parses and calculates the value of math expression expressed in std::vector. + * + * @param _input mathematical expression. + * @return parseAndCalculateResult the result of solving it. + */ +parseAndCalculateResult parseAndCaluclate(const std::string& _input, bool debug = false) { + parseAndCalculateResult returnVal; + + // 0. return if _input is empty + if (_input.size() == 0) return returnVal; + + // 1. Parse input + parseResult* parsed = parse(_input); + + // 2. if unmatchedBrackes > 0, add unmatched brackets + returnVal.unmatchedParanthesis = parsed->unmatchedParanthesis; // Let's not forget that we still return it :^) + while (parsed->unmatchedParanthesis > 0) { + Symbol sym; + sym.type = SymbolType::right_bracket; + parsed->unmatchedParanthesis--; + parsed->result->push_back(sym); + } + + // 3. if unmatchedBrackets < 0, throw something or... abort + // display amount of missing opening brackets in red at the end, + // if it's more than 3, display number, not individual ones + // IMPLEMENT DISPLAYING IT SOMEWHERE ELSE!!! + + if (parsed->unmatchedParanthesis < 0) { + returnVal.unmatchedParanthesis = parsed->unmatchedParanthesis; + return returnVal; + }; + + // 4. correct minus sign + correctNegativeSign(parsed); + + // 5. Validate parsation. If invalid, set appripraite flag and return. + returnVal.valid = validateParsation(parsed->result); + + if (!returnVal.valid) { + delete parsed; + return returnVal; + } + + // 6. Convet to Reversed Polish Adnotation (ONP) + + auto* onped = toONP(parsed->result); + delete parsed; + + // 7. if onped vector is length of 0, invalidate and return. + if (onped->size() == 0) { + delete onped; + return returnVal; + } + + // 8. do a stacky wacky recursive onp magic to calculate the result + try { + returnVal.value = calculateUsingStack(*onped).value; + } + catch (char const* e) { + //everything is fine + returnVal.value = onped->end()->value; + } + delete onped; + return returnVal; +} + +}; // namespace Calculator \ No newline at end of file diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000..23026de --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,79 @@ +#include +#include +#include "cursor.cpp" +#include "terminal.cpp" + +namespace commands { + +/** + * @brief Prints keys in command like visual input in current line. + * + * @param keys the string to print + */ +void printInput(terminal::state keys) { + cursor::setX(0); + std::cout << "\033[2K:" << keys.keys; +} + +/** + * @brief prints the desired output in command-like mode + * + * @param keys keys pressed + * @param string the output + */ +void printOutput(terminal::state keys, std::wstring string) { + cursor::setX(0); + cursor::up(); + std::cout << "\033[2K\n\033[2K:" << keys.keys << "\n"; + std::wcout << L"\033[2K" << string << "\n"; +} + +/** + * @brief main loop for commands input. + * + * @return true if we should quit program. + */ +bool loop() { + std::cout << "\n\033[2K"; + + terminal::state keys; + + while(true) { + printInput(keys); + auto input = cursor::getchar(); + + if (input.code >= 'a' && input.code <= 'z' || input.code >= 'A' && input.code <= 'Z') { + terminal::key_insert(keys, input.code); + continue; + } + else if (input.code == 8) { // BACKSPACE + if (!terminal::key_backspace(keys)) return false; + } + else if (input.code == 13) { // ENTER + if (keys.keys.length() == 0) { + printOutput(keys, L"No command provided"); + break; + } + if (keys.keys[0] == 'q') { + printOutput(keys, L"Exiting..."); + return true; + } + else if (keys.keys[0] == 'a') { + printOutput(keys, L"calculator\nCopyright Rafał F.\nAll rights reserved."); + break; + } + else if (keys.keys[0] == 'h') { + printOutput(keys, L"Calculator\n:q - quit\n:a - about"); + break; + } + else { + printOutput(keys, L"Command not found\n"); + break; + } + } + } + + return false; +} + +} //namespace commands diff --git a/src/cursor.cpp b/src/cursor.cpp new file mode 100644 index 0000000..f904599 --- /dev/null +++ b/src/cursor.cpp @@ -0,0 +1,145 @@ +#ifndef __CURSOR_CPP +#define __CURSOR_CPP + +#include +#include "getch.h" + +/** + * @brief Set of tools to manipulate cursor in terminal. + */ +namespace cursor { +/** + * @brief Represents cursor coordinates. + */ +struct position { + short x = 0; // column + short y = 0; // line +}; + +/** + * @brief Get current cursor position. + * + * @return position the current position. + */ +position getPosition() { + position returnVal; + std::cout << "\033[6n"; + + bool readSemicolon = false; + bool stillReading = true; + + while (stillReading) { + char read = getch(); + + // \033[{line};{column}R + switch (read) + { + case '\033': + case '[': + break; + case ';': + readSemicolon = true; + break; + case 'R': + stillReading = false; + break; + default: + // if character read is one of: 0123456789 + if (read >= 0x30 & read <= 0x39) { + read -= 0x30; + if (readSemicolon) // if reading x pos + returnVal.x = 10 * returnVal.x + read; + else + returnVal.y = 10 * returnVal.y + read; + } + else {std::cout << (int)read << ",";} + break; + } + } + return returnVal; +} + +/** + * @brief Set the position of a cursor in terminal. + * + * @param x the column to which to set. + * @param y the line to which to set. + */ +void setPosition(const short& x, const short& y) { + std::cout << "\033[" << y << ";" << x << "H"; +} + +/** + * @brief Set the position of a cursor in terminal. + * + * @param pos the position to which to set. + */ +void setPosition(const position& pos) { + std::cout << "\033[" << pos.y << ";" << pos.x << "H"; +} + +/** + * @brief Sets the column of a cursor. + * + * @param _x The column to which to set. + */ +void setX(const short& _x) { std::cout << "\033[" << _x << "G"; } + +/** + * @brief Set the row of a cursor. + * + * @param _y The row to which to set. + */ +void setY(const short& _y) { setPosition(getPosition().x, _y); } + +/** + * @brief Requests terminal to save position. + */ +void savePosition() { std::cout << "\0337"; } + +/** + * @brief Requests termianl to restore saved position. + */ +void restorePosition() { std::cout << "\0338"; } + +/** + * @brief moves cursor 1 line up + */ +void up() { std::cout << "\033[1A"; } + +/** + * @brief represents pressed key. + * + */ +struct key { + unsigned char code = 0; + unsigned char special = 0; +}; + +/** + * @brief gets keyboard characker. + * + * @return key the pressed key. + */ +key getchar() { + key returnval; + unsigned char input = getch(); + switch (input) { + case 0: // special + returnval.special = 1; + returnval.code = getch(); + break; + case 224: // non numpad navigation keys + returnval.special = 2; + returnval.code = getch(); + break; + default: + returnval.code = input; + break; + } + return returnval; +} + +}; // namespace Cursor + +#endif diff --git a/src/getch.h b/src/getch.h new file mode 100644 index 0000000..ca5849a --- /dev/null +++ b/src/getch.h @@ -0,0 +1,12 @@ +#ifndef __GETCH_H +#define __GETCH_H + +#ifdef _WIN32 +// FIXME: assuming that windows getch behaves the same as linux getch, this should be checked! +#include +#include // used only for virtual terminal configuring for windows default terminal +#else +#include +#endif + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..19be4e4 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,185 @@ +#include +#include +#include "calculator.cpp" +#include "commands.cpp" +#include "cursor.cpp" +#include "getch.h" +#include "terminal.cpp" + +/** + * @brief Prints value of pressed key in top left corner. + * Additionally it can get offet to see more presses. + * + * @param _flags keypress flags. + * @param _key char that we got. + * @param offset how much column do you want to spare? + */ +void debugKey(cursor::key _key, short& offset) { + cursor::savePosition(); + cursor::setPosition(1, 1); + // std::cout << " "; // clear debug corner :^) + cursor::setPosition(offset++ * 4 + 1, 1); + std::cout << "\033[31;1m"; + // if control flag set + if (_key.special != 0) + std::cout << "^"; + std::cout << int(_key.code); + std::cout << "\033[0m"; + cursor::restorePosition(); +} + +/** + * @brief Prints entered keys to the console. + * + * @param keys currently typed keys. + */ +void writeInput(terminal::state keys) { + // FIXME: Do not duplicate lines when line overflows + // possible mitigation: only write one line + + // FIXME: flickering + cursor::setX(1); + std::cout << "\033[0K" << "> "; + for(auto it = keys.keys.begin(); it != keys.keys.end(); it++) { + std::cout << *it; + } + + cursor::setX(3+keys.cursorPos); +} + +/** + * @brief Writes result line with the result while in typing mode + * + * @param string the result + * @param paranthesis missing paranthesis count, negative if misses left paranthesis + * @param calcuationValid is calculation valid + * @param inputSize the current input size (used to where display missing paranthesis) + */ +void writeResultLine( + const std::string string, + const int paranthesis = 0, + const bool calcuationValid = true, + const unsigned int inputSize = 20 +) { + auto savedCursorPosition = cursor::getPosition().x; + + if (paranthesis != 0) { + cursor::setX(inputSize+5); + std::cout << "\033[31m"; + if (paranthesis > 4) { + std::cout << ") * " << paranthesis; + } + else if (paranthesis > 0) { + for(unsigned short i = 0; i < paranthesis; i++) std::cout << ")"; + } + else if (paranthesis < 4) { + std::cout << "( * " << -paranthesis; + } + else { //paranthesis is one of -1,-2,-3,-4 + for(unsigned short i = 0; i < -paranthesis; i++) std::cout << ")"; + } + std::cout << "\033[0m"; + } + + std::cout << "\n\033[0K\033[90m"; + // FIXME: line overflow handling + if (calcuationValid) std::cout << string; + else std::cout << "-"; + cursor::up(); + cursor::setX(savedCursorPosition); + std::cout << "\033[0m"; +} + +/** + * @brief Writes result line with the result while in typing mode + * + * @param result the calculation result + * @param inputSize the current input size (used to where display missing paranthesis) + */ +void writeResultLine(Calculator::parseAndCalculateResult result, const unsigned int inputSize) { + std::stringstream result_string; + result_string << result.value; + writeResultLine(result_string.str(), result.unmatchedParanthesis, result.valid, inputSize); +} + +int main() { + #ifdef _WIN32 + // https://stackoverflow.com/a/47158348 + { // enable virtual terminal for legacy Windows terminals + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD consoleMode; + GetConsoleMode(console, &consoleMode); + consoleMode |= 0x0004; + SetConsoleMode(console, consoleMode); + } + #endif + std::setlocale(LC_ALL, ""); + std::cout << "type :q to exit, type :h for help\n"; + + short debugOffset = 0; //debug + bool exit = false; //should we exit? + terminal::state keys; + while (!exit) { + writeInput(keys); + // writeResultLine(""); + // background calculation + // new std::thread([keys]{ + writeResultLine(Calculator::parseAndCaluclate(keys.keys), keys.keys.size()); + // }); + auto input = cursor::getchar(); + + // if speciality char + if (input.special != 0) { + switch (input.code) + { + case 75: // ARROW_LEFT + terminal::key_left(keys); + break; + case 77: // ARROW_RIGHT + terminal::key_right(keys); + break; + default: + debugKey(input, debugOffset); + break; + } + continue; + } + else switch (input.code) { + case 8: // BACKSPACE + terminal::key_backspace(keys); + break; + case 13: // ENTER + std::cout << "\n\033[2K" << Calculator::parseAndCaluclate(keys.keys, true).value << "\n"; + keys.cursorPos = 0; + keys.keys.clear(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case '+': + case '-': + case '*': + case '/': + case '(': + case ')': + terminal::key_insert(keys, input.code); + break; + case ':': + exit |= commands::loop(); + break; + default: + debugKey(input, debugOffset); + break; + }; + } + std::cout << "\033[0m"; + return 0; +} diff --git a/src/stack.cpp b/src/stack.cpp new file mode 100644 index 0000000..23ef443 --- /dev/null +++ b/src/stack.cpp @@ -0,0 +1,39 @@ +#include + +/** + * @brief Set of tools to manipulate vector stacks. + */ +namespace Stack +{ +template +bool isEmpty(const std::vector& stack) { + return stack.empty(); +}; + +// template +// bool isFull(std::vector& stack) { +// return !stack->empty(); +// }; + +template +void put(std::vector& stack, const type& item) { + stack.push_back(item); +} + +template +type take(std::vector& stack) { + if (stack.size() == 0) throw "a"; + type toReturn = stack.back(); + stack.pop_back(); + return toReturn; +} + +namespace oper { + template + void add(std::vector& stack) { + take(stack); + // put(take(stack)+) + } +} + +}; // namespace stack diff --git a/src/terminal.cpp b/src/terminal.cpp new file mode 100644 index 0000000..ccc5e9d --- /dev/null +++ b/src/terminal.cpp @@ -0,0 +1,66 @@ +#ifndef __TERM_CPP +#define __TERM_CPP + +#include + +namespace terminal { + +/** + * @brief represents state of input form + */ +struct state { + std::string keys; + unsigned int cursorPos = 0; +}; + +/** + * @brief removes current character from state. + * + * @param state + * @return true if character was removed + */ +bool key_backspace(state& state) { + if (state.cursorPos <= 0) return false; + state.keys.erase(state.cursorPos - 1, 1); + state.cursorPos--; + return true; +} + +/** + * @brief moves cursor one characer to the left + * + * @param state + * @return true if moved + */ +bool key_left(state& state) { + if (state.cursorPos <= 0) return false; + state.cursorPos--; + return true; +} + +/** + * @brief moves cursor one character to the right + * + * @param state + * @return true if moved + */ +bool key_right(state& state) { + if (state.cursorPos >= state.keys.length()) return false; + state.cursorPos++; + return true; +} + +/** + * @brief inserts character to current state + * + * @param state form state + * @param key character to insert + */ +void key_insert(state& state, unsigned char key) { + state.keys.insert(state.cursorPos, std::string(1, key)); + state.cursorPos++; +} + +} //namespace terminal + +#endif \ No newline at end of file diff --git a/zadanie.txt b/zadanie.txt new file mode 100644 index 0000000..15b585a --- /dev/null +++ b/zadanie.txt @@ -0,0 +1,36 @@ +Aplikacja rozwiązująca równania matematyczne +Zastosowany algorytm: ONP +Zastosowana struktura: STOS/LIFO +Na wejściu: równanie matematyczne +Na wyjściu: wynik obliczeń, liczba +Akceptowane znaki: wszystkie matematyczne: +, -, * ,/ +Akceptowalne inne znaki: TAK +Inne znaki akceptowalne: ( )[]. +Język programowania: C/C++ +Możliwość zastosowania gotowych bibliotek: Brak możliwości stosowania wbudowanych funkcji, tylko IOSTREAM +Sposób programowania: STRUKTURALNY +Wymagane komentarze: TAK +Wymagane formatowanie: TAK - prawidłowe formatowanie kodu +Wymagany opis funkcji: TAK +Hermetyczność funkcji: TAK +Zasięg zmiennych: LOKALNY +Obiekty wskaźnikowe: DOZWOLONE +Nadmiarowość zakresu zmiennych: NIEDOZWOLONA +Nadmiarowość kodu: NIEDOZWOLONA +Interfejs użytkownika: TAK +Forma interfejsu: TEKSTOWA +Możliwość wyboru opcji: TAK +Konieczność zatwierdzania enterem w UI: NIE +Wbudowane okno informacji o autorze i wersji programu: TAK +Dokumentacja: TAK +Forma dokumentacji: PEŁNA +Poziom dokumentacji: 3 stopniowa (wymagania, instrukcja z zrzutami ekranu, kod programu) +Format instrukcji: A4 +Format zapisu instrukcji: PDF +Format pliku do upload'u: ZIP (bez hasła) +Czcionka kodu programu: KONSOLOWA +Wymagana wersja źródłowa kodu: TAK +Wymagana wersja wykonywalna kodu: TAK +Karta projektu: TAK +Format karty: A4 +Format zapisu karty: PDF \ No newline at end of file