Initial commit
This commit is contained in:
commit
dabf8a22f6
14 changed files with 1072 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
\build*
|
26
.vscode/launch.json
vendored
Normal file
26
.vscode/launch.json
vendored
Normal file
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"*.tcc": "cpp",
|
||||
"string": "cpp",
|
||||
"new": "cpp",
|
||||
"iosfwd": "cpp"
|
||||
}
|
||||
}
|
27
.vscode/tasks.json
vendored
Normal file
27
.vscode/tasks.json
vendored
Normal file
|
@ -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"
|
||||
}
|
5
README.md
Normal file
5
README.md
Normal file
|
@ -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 :^)_
|
26
concept.txt
Normal file
26
concept.txt
Normal file
|
@ -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
|
||||
<display help message here>
|
||||
It should not remove equation
|
||||
|
||||
> 2+2
|
||||
4
|
417
src/calculator.cpp
Normal file
417
src/calculator.cpp
Normal file
|
@ -0,0 +1,417 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
#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<Symbol>* _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<unsigned int>* _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<Symbol>* result;
|
||||
short unmatchedParanthesis = 0;
|
||||
};
|
||||
|
||||
parseResult* parse(const std::string& _input) {
|
||||
parseResult* returnVal = new parseResult;
|
||||
returnVal->result = new std::vector<Symbol>();
|
||||
|
||||
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<Symbol>* _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<unsigned int>& _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<Symbol>* expression in ONP
|
||||
*/
|
||||
std::vector<Symbol>* toONP(std::vector<Symbol>* _expression) {
|
||||
std::vector<Symbol>* expression = new std::vector<Symbol>(); // will not contain brackets
|
||||
std::vector<Symbol>* returnVal = new std::vector<Symbol>();
|
||||
std::vector<unsigned int> 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<Symbol>& _stack) {
|
||||
switch (_stack.back().type) {
|
||||
case SymbolType::number: {
|
||||
return Stack::take(_stack);
|
||||
}
|
||||
case SymbolType::add: {
|
||||
Stack::take<Symbol>(_stack); //top symbol is add
|
||||
Symbol toPut;
|
||||
toPut.value = calculateUsingStack(_stack).value + calculateUsingStack(_stack).value;
|
||||
Stack::put<Symbol>(_stack, toPut);
|
||||
return toPut;
|
||||
} break;
|
||||
case SymbolType::subract: {
|
||||
Stack::take<Symbol>(_stack); //top symbol...
|
||||
Symbol toPut;
|
||||
toPut.value = calculateUsingStack(_stack).value - calculateUsingStack(_stack).value;
|
||||
Stack::put<Symbol>(_stack, toPut);
|
||||
return toPut;
|
||||
} break;
|
||||
case SymbolType::multiply: {
|
||||
Stack::take<Symbol>(_stack); //top symbol...
|
||||
Symbol toPut;
|
||||
toPut.value = calculateUsingStack(_stack).value * calculateUsingStack(_stack).value;
|
||||
Stack::put<Symbol>(_stack, toPut);
|
||||
return toPut;
|
||||
} break;
|
||||
case SymbolType::divide: {
|
||||
Stack::take<Symbol>(_stack); //top symbol...
|
||||
Symbol toPut;
|
||||
auto val = calculateUsingStack(_stack).value;
|
||||
toPut.value = calculateUsingStack(_stack).value / val;
|
||||
Stack::put<Symbol>(_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<char>.
|
||||
*
|
||||
* @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
|
79
src/commands.cpp
Normal file
79
src/commands.cpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
#include <iostream>
|
||||
#include <string>
|
||||
#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
|
145
src/cursor.cpp
Normal file
145
src/cursor.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
#ifndef __CURSOR_CPP
|
||||
#define __CURSOR_CPP
|
||||
|
||||
#include <iostream>
|
||||
#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
|
12
src/getch.h
Normal file
12
src/getch.h
Normal file
|
@ -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 <conio.h>
|
||||
#include <windows.h> // used only for virtual terminal configuring for windows default terminal
|
||||
#else
|
||||
#include <curses.h>
|
||||
#endif
|
||||
|
||||
#endif
|
185
src/main.cpp
Normal file
185
src/main.cpp
Normal file
|
@ -0,0 +1,185 @@
|
|||
#include <iostream>
|
||||
#include <sstream>
|
||||
#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;
|
||||
}
|
39
src/stack.cpp
Normal file
39
src/stack.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief Set of tools to manipulate vector stacks.
|
||||
*/
|
||||
namespace Stack
|
||||
{
|
||||
template <class type>
|
||||
bool isEmpty(const std::vector<type>& stack) {
|
||||
return stack.empty();
|
||||
};
|
||||
|
||||
// template <class type>
|
||||
// bool isFull(std::vector<type>& stack) {
|
||||
// return !stack->empty();
|
||||
// };
|
||||
|
||||
template <class type>
|
||||
void put(std::vector<type>& stack, const type& item) {
|
||||
stack.push_back(item);
|
||||
}
|
||||
|
||||
template <class type>
|
||||
type take(std::vector<type>& stack) {
|
||||
if (stack.size() == 0) throw "a";
|
||||
type toReturn = stack.back();
|
||||
stack.pop_back();
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
namespace oper {
|
||||
template <class type>
|
||||
void add(std::vector<type>& stack) {
|
||||
take(stack);
|
||||
// put(take(stack)+)
|
||||
}
|
||||
}
|
||||
|
||||
}; // namespace stack
|
66
src/terminal.cpp
Normal file
66
src/terminal.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef __TERM_CPP
|
||||
#define __TERM_CPP
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
36
zadanie.txt
Normal file
36
zadanie.txt
Normal file
|
@ -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
|
Reference in a new issue