diff options
Added files from project 2 (mirror)
24 files changed, 1854 insertions, 0 deletions
diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Command.cpp b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Command.cpp new file mode 100644 index 0000000..d05dc8d --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Command.cpp @@ -0,0 +1,24 @@ +#include "Command.h" +#include <cstring> + +Command::Command() : Command("", nullptr) {} + +/*! \param name C-style string, class stores at most 255 characters (last character is always terminating zero) + * \param exec Function pointer that will be executed when run() is called. Can be nullptr. + */ +Command::Command(const char* name, void (*exec)(const char* params)) { + strncpy(this->name, name, 255); + this->name[255] = '\0'; + this->exec = exec; +} + +/*! Executes the stored function pointer, when it's not nullptr. Otherwise does nothing. + */ +void Command::run(const char* params) const { + if (exec != nullptr) + exec(params); +} + +const char* Command::get_name() const { + return name; +} diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Command.h b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Command.h new file mode 100644 index 0000000..393d705 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Command.h @@ -0,0 +1,27 @@ +#ifndef HEADER_CONSOLEINTERFACE_COMMAND +#define HEADER_CONSOLEINTERFACE_COMMAND + +/*! \file Command.h + * \brief Stores the declaration of class Command + */ + +/*! \brief Stores a 256 character name and a function pointer to be executed when calling run() + * + * Menu uses this class for a more generic implementation of it's navigate function. + */ +class Command { + char name[256]; + void (*exec)(const char* params); + +public: + //! Leaves name empty and exec function pointer to nullptr + Command(); + //! Copies contents of nameInMenu and stores exec + Command(const char* name, void (*exec)(const char* params)); + //! Executes the stored function pointer + void run(const char* params) const; + + const char* get_name() const; +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Menu.cpp b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Menu.cpp new file mode 100644 index 0000000..9c2fca5 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Menu.cpp @@ -0,0 +1,72 @@ +#include "Menu.h" +#include "Toolbox.hpp" +#include <cstring> + +Menu::Menu() : menuOptions() {} + +unsigned indexOfCommandEnd(char* str) { + unsigned ind = 0; + std::cout << str[ind] << std::endl; + + return ind; +} + +void Menu::addCommand(const Command& command) { + menuOptions.add(command); +} + +/*! + * Prints the title, then an Error/Warning/Sucess message (if set), + * prints all command names as an ordered list (starting from 1), + * waits for user input to select one of those commands (by list number) + * and finally executes the appropriate command's run() function. + * + * After the run() function exists, everything is reprinted. + * The 0 list index is always "Go Back"/"Exit" and it always stops the reprinting loop. + * + * If there are no commands, prints "Menu is empty!". + * If user input doesn't correspond to any command, registers an error message and reprints. + */ +void Menu::navigate() const { + if (menuOptions.get_count() == 0) { + printLine("Menu is empty!"); + return; + } + + char buffer[MAX_LINE_WIDTH]; + + unsigned startIndex, commandEnd, paramsStart, index; + bool hasThrown = false; + while (!hasThrown) { + startIndex = 0; + inputLineBox(buffer, MAX_LINE_WIDTH, false); + + while (buffer[startIndex] == ' ') startIndex++; + + commandEnd = startIndex; + while (buffer[commandEnd] != ' ' && buffer[commandEnd] != '\n' && buffer[commandEnd] != '\0') + commandEnd++; + + index = menuOptions.get_count(); + for (unsigned i = 0; i < menuOptions.get_count() && index == menuOptions.get_count(); i++) { + if (strncmp(buffer + startIndex, menuOptions[i].get_name(), commandEnd - startIndex) == 0) + index = i; + } + + paramsStart = startIndex + commandEnd; + while (buffer[paramsStart] == ' ') paramsStart++; + + if (index == menuOptions.get_count()) { + printLine("Invalid menu option!"); + continue; + } + + try { + menuOptions[index].run(buffer + paramsStart); + } + catch (std::logic_error err) { + hasThrown = true; + printLine(err.what()); + } + } +} diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Menu.h b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Menu.h new file mode 100644 index 0000000..faadba9 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Menu.h @@ -0,0 +1,33 @@ +#ifndef HEADER_CONSOLEINTERFACE_MENU +#define HEADER_CONSOLEINTERFACE_MENU + +/*! \file Menu.h + * \brief Stores the declaration of class Menu + */ + +#include "Command.h" +#include "../List/List.hpp" + +/*! \brief Handles navigation between multiple commands + * + * Shows the title, then below it an Error/Warning/Success message if set, + * lists all command options as an enumerated list, takes command index + * and calls the chosen command's run() function. + */ +class Menu { + List<Command> menuOptions; + +public: + //! Sets name as "Menu", leaves all flags to false and creates an empty Command list + Menu(); + //! Copies commands + Menu(const Command* commands, unsigned commandCount); + + //! Adds a command to the internal Command list + void addCommand(const Command& command); + + //! Lists commands and after user input executes an appropriate command run() function + void navigate() const; +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Toolbox.cpp b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Toolbox.cpp new file mode 100644 index 0000000..ca739f0 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Toolbox.cpp @@ -0,0 +1,88 @@ +#include "Toolbox.hpp" + +/* Input line boxes */ + +void __inputLineBoxGetInput(char* output, unsigned maxWidth, bool ignore) { + if (ignore) + std::cin.ignore(); + std::cin.getline(output, maxWidth); +} + +/*! \param label C-style string, there are no size check so it could wrap + * \param output Pointer to a char array + * \param maxWidth Maximum count of characters to read from user input. output MUST be able to hold that many characters! + * \param ignore Whether or not to ignore the first new-line delimiter. + * true by default, should be set to false only when an inputLineBox/SubBox has been issued directly prior or when it's the very first issued box command. + * + * Prints "(+)" before the label. + */ +void inputLineBox(char* output, unsigned maxWidth, bool ignore) { + print("> "); + __inputLineBoxGetInput(output, maxWidth, ignore); +} + +/* Table */ + +void __printTableHeaderNumber(unsigned number) { + std::cout << ":" << ((number < 100) ? " " : "") << number << ((number < 10) ? " " : ""); +} + +void __printTableColumnHeader(unsigned count, unsigned start = 1) { + std::cout << ": "; + for (unsigned i = 0; i < count; i++) + __printTableHeaderNumber(start++); + std::cout << ":" << std::endl; + + for (unsigned i = 0; i <= count; i++) + std::cout << "===="; + std::cout << "=" << std::endl; +} + +void __printTableLine(unsigned columns) { + std::cout << ":==="; + for (unsigned i = 0; i < columns; i++) + std::cout << "+---"; + std::cout << ":" << std::endl; +} + +void __printPadded(const String& str, unsigned width) { + while (width - str.get_length() > 0) { + width--; + print(" "); + } + print(str.get_cstr()); +} + +/*! \param startNumber The number by which column and row enumeration begins + * \param columns How many columns the table should have + * \param items C-style string, each cell is a single character from the string + * + * Prints a string as a grid/table of characters from top to bottom, left to right. + * The first character is on the top left, the last one is on the bottom right. + * + * Table is printed until a terminating zero is encountered. + * The rows are "calculated" from the columns count and the items length. + */ +void printTable(const List<String>& items, unsigned columns) { + unsigned *colWidths = new unsigned[columns]; + for (unsigned i = 0; i < columns; i++) + colWidths[i] = 0; + + for (unsigned i = 0; i < items.get_count(); i++) { + if (colWidths[i % columns] < items[i].get_length()) + colWidths[i % columns] = items[i].get_length(); + } + + // __printTableColumnHeader(columns); + + for (unsigned i = 0; i < items.get_count(); i++) { + __printPadded(items[i], colWidths[i % columns]); + if (i % columns == columns - 1) + printLine(""); + else + print("|"); + } + + delete[] colWidths; +} + diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Toolbox.hpp b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Toolbox.hpp new file mode 100644 index 0000000..4b59626 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Toolbox.hpp @@ -0,0 +1,60 @@ +#ifndef HEADER_CONSOLEINTERFACE_TOOLBOX +#define HEADER_CONSOLEINTERFACE_TOOLBOX + +#include <iostream> +#include "../List/List.hpp" +#include "../String/String.h" + +/*! \file Toolbox.hpp + * \brief Stores a wide range of functions for simpler/more automated printing. + * + * Adds a lot of functions for printing. + * + * \remark iostream is included by necessity (templated functions), you should only use the provided functions, if you can. + */ + +#define MAX_LINE_WIDTH 1024 + +//! Prints label, gets a whole line of input and stores it to output +void inputLineBox(char* output, unsigned maxWidth, bool ignore = true); + +//! Prints a string list as a table +void printTable(const List<String>& items, unsigned columns); + +//! Reads user input and stores it +template <typename T> +void read(T* storage) { + std::cin >> storage; +} + +//! Reads user input and stores it +template <typename T> +void read(T& storage) { + std::cin >> storage; +} + +//! Prints given item +template <typename T> +void print(const T* item) { + std::cout << item; +} + +//! Prints given item +template <typename T> +void print(const T& item) { + std::cout << item; +} + +//! Prints given item and an endline character +template <typename T> +void printLine(const T* item) { + std::cout << item << std::endl; +} + +//! Prints given item and an endline character +template <typename T> +void printLine(const T& item) { + std::cout << item << std::endl; +} + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/List/List.hpp b/Year 1/Object-oriented Programming/Project 2/src/Generic/List/List.hpp new file mode 100644 index 0000000..e0ae8e3 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/List/List.hpp @@ -0,0 +1,372 @@ +#ifndef HEADER_LIST +#define HEADER_LIST + +/*! \file List.hpp + * \brief Stores declaration and definition of templated class List + */ + +#include <istream> +#include <ostream> + +/*! \brief Templated class that stores an array of elements in dynamic memory + * + * \warning findIndex(), contain(), read(), write(), operator<<() and operator>>() require the type to have defined a couple of functions. + * \sa findIndex() + * \sa contain() + * \sa read() + * \sa write() + * \sa operator<<() + * \sa operator>>() + */ +template <typename T> +class List { +protected: + T* elements; + unsigned length; + unsigned count; + + void resize(); + void free(); + void copyFrom(const List& other); + +public: + //! Copies all elements in given array + List(const T* elements, unsigned elementsCount); + //! Allocates memory for the given length + List(unsigned length); + //! Adds an element + void add(const T& element); + //! Inserts element at given index + void insertAt(const T& element, unsigned index); + //! Removes element at index + bool removeAt(unsigned index); + //! Finds the index of element + unsigned findIndex(const T& element) const; + bool contain(const T& element) const; + //! Returns reference to element at index + T& operator[](unsigned index); + //! Returns constant reference to element at index + const T& operator[](unsigned index) const; + //! Returns raw array of data + const T* raw_data() const; + //! Removes all data + void clear(); + + //! Appends elements from other list + List<T>& operator+=(const List<T> other); + + //! Reads from stream + std::istream& read(std::istream& istr); + //! Writes to stream + std::ostream& write(std::ostream& ostr) const; + + //! Returns the length + unsigned get_length() const; + //! Returns the count + unsigned get_count() const; + + List(); + List& operator=(const List& other); + List(const List& other); + ~List(); + + List(List&& other); + List& operator=(List&& other); +}; + +//! Reads List from stream with >> operator +template <typename T> +std::istream& operator>>(std::istream& istr, List<T>& obj); +//! Writes List to stream with << operator +template <typename T> +std::ostream& operator<<(std::ostream& ostr, const List<T>& obj); + +/* Private */ + +template <typename T> +void List<T>::resize() { + length = (length == 0) ? 8 : length << 1; + T* temp = new T[length]; + for (int i = 0; i < count; i++) + temp[i] = elements[i]; + delete[] elements; + elements = temp; +} + +template <typename T> +void List<T>::free() { + delete[] elements; +} + +template <typename T> +void List<T>::copyFrom(const List& other) { + elements = new T[other.length]; + for (int i = 0; i < other.count; i++) + elements[i] = other.elements[i]; + length = other.length; + count = other.count; +} + +/* Public */ + +template <typename T> +List<T>::List(const T* elements, unsigned elementsCount) { + length = 8; + count = elementsCount; + this->elements = new T[length]; + for (unsigned i = 0; i < count; i++) + this->elements[i] = elements[i]; +} + +template <typename T> +List<T>::List(unsigned length) { + this->length = count = length; + this->elements = new T[length]; + for (unsigned i = 0; i < count; i++) + this->elements[i] = elements[i]; +} + +/*! + * Resizes internal array if there is no space for additional elements. + */ +template <typename T> +void List<T>::add(const T& element) { + if (length == count) resize(); + + elements[count++] = element; +} + +/*! + * If index is after the last element, the element is just added. + * Otherwise all elements after the index are shifted right and element is put in place. + * + * Resizes internal array if there is no space for additional elements. + */ +template <typename T> +void List<T>::insertAt(const T& element, unsigned index) { + if (index >= count) { + add(element); + return; + } + if (length == count) resize(); + + for (unsigned i = count; i > index; i--) + elements[i] = elements[i - 1]; + + elements[index] = element; + count++; +} + +/*! \returns Wether element could be removed + * + * If index is after that of the last element, nothing is done and false is returned. + * Otherwise elements after index are shifted right and count is reduced. + */ +template <typename T> +bool List<T>::removeAt(unsigned index) { + if (index >= count) return false; + + for (int i = index; i < count; i++) + elements[i] = elements[i + 1]; + count--; + return true; +} + +/*! \returns Index of element. If element isn't found, returns the count of element. + * + * \warning The function depends on the type having a function "compare" defined, which takes two elements and returns a number <0 if elem1 < elem2, >0 if elem1 > elem2, 0 if elem1 == elem2 + * \note Searching is done linearly + */ +template <typename T> +unsigned List<T>::findIndex(const T& element) const { + unsigned ind = 0; + while (ind < count && elements[ind].compare(element) != 0) + ind++; + return ind; +} + +/*! \returns Whether the element is contained in the current List + * + * \warning Function depends on findIndex(), which means the same "compare" function must be defined in the type + * \sa findIndex() + */ +template <typename T> +bool List<T>::contain(const T& element) const { + return findIndex(element) < count; +} + +template <typename T> +T& List<T>::operator[](unsigned index) { + return elements[index]; +} + +template <typename T> +const T& List<T>::operator[](unsigned index) const { + return elements[index]; +} + +template <typename T> +const T* List<T>::raw_data() const { + return elements; +} + +template <typename T> +void List<T>::clear() { + free(); + length = 8; + count = 0; + this->elements = new T[length]; +} + +template <typename T> +List<T>& List<T>::operator+=(const List<T> other) { + for (unsigned i = 0; i < other.length; i++) + add(other[i]); + return *this; +} + +/*! \param istr An input stream + * + * Directly reads bytes from stream (calls read() function). + * Any stored values are deleted and replaced with those from the stream. + * + * \warning The function depends on the type having a function "read" defined, which takes an std::istream& and writes it's data to it. Return type doesn't matter. + * \remark Doesn't alter the stream in any other way. + * \note Best used with binary ifstream + */ +template <typename T> +std::istream& List<T>::read(std::istream& istr) { + istr.read((char*)&length, sizeof(length)); + istr.read((char*)&count, sizeof(count)); + + delete[] elements; + elements = new T[length]; + + for (int i = 0; i < count; i++) + elements[i].read(istr); + + return istr; +} + +/*! \param ostr An output stream + * + * Directly writes bytes to stream (calls write() function). + * + * \warning The function depends on the type having a function "write" defined, which takes an std::ostream& and writes it's data to it. Return type doesn't matter. + * \remark Doesn't alter the stream in any other way. + * \note Best used with binary ofstream + */ +template <typename T> +std::ostream& List<T>::write(std::ostream& ostr) const { + ostr.write((const char*)&length, sizeof(length)); + ostr.write((const char*)&count, sizeof(count)); + + for (int i = 0; i < count; i++) + elements[i].write(ostr); + + return ostr; +} + +/*! + * Length is the size of the underlying array (allocated memory). + */ +template <typename T> +unsigned List<T>::get_length() const { + return length; +} + +/*! + * Count is the amount of elements that are stored. + */ +template <typename T> +unsigned List<T>::get_count() const { + return count; +} + +// Rule of 4 + +template <typename T> +List<T>::List() : List(nullptr, 0) {} + +template <typename T> +List<T>& List<T>::operator=(const List& other) { + if (this != &other) { + free(); + copyFrom(other); + } + return *this; +} + +template <typename T> +List<T>::List(const List& other) { + copyFrom(other); +} + +template <typename T> +List<T>::~List() { + free(); +} + +// Move semantics + +template <typename T> +List<T>::List(List&& other) { + length = other.length; + count = other.count; + elements = other.elements; + other.elements = nullptr; +} + +template <typename T> +List<T>& List<T>::operator=(List&& other) { + if (this != &other) { + free(); + length = other.length; + count = other.count; + elements = other.elements; + other.elements = nullptr; + } + return *this; +} + +/* Outside of class */ + +/*! + * Uses the stream's >> operator to read and parse the elements. + * The first item in the stream should be the count. + * + * \warning The function depends on the type having the operator >> defined, which takes an std::istream& and writes it's data to it. Return type doesn't matter. + * \note Best used with std::cin or text std::ifstream + */ +template <typename T> +std::istream& operator>>(std::istream& istr, List<T>& obj) { + List<T> newObj; + unsigned count; + istr >> count; + + T temp; + for (int i = 0; i < count; i++) { + istr >> temp; + obj.add(temp); + } + + return istr; +} + +/*! + * Uses the stream's << operator to write the count and then all objects. + * + * \warning The function depends on the type having the operator << defined, which takes an std::ostream& and writes it's data to it. Return type doesn't matter. + * \note Best used with std::cout or text std::ofstream + */ +template <typename T> +std::ostream& operator<<(std::ostream& ostr, const List<T>& obj) { + ostr << obj.get_count() << std::endl; + for (int i = 0; i < obj.get_count(); i++) + ostr << obj[i]; + + return ostr; +} + + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/Pair/Pair.hpp b/Year 1/Object-oriented Programming/Project 2/src/Generic/Pair/Pair.hpp new file mode 100644 index 0000000..64614ca --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/Pair/Pair.hpp @@ -0,0 +1,28 @@ +#ifndef PAIR +#define PAIR + +/*! \file Pair.hpp + * \brief Stores the declaration of struct Pair + */ + +/*! \brief Stores two values of any types + * + * Values are public, so no explicit getters or setters are made. + */ +template <typename T, typename U> +struct Pair { + T left; + U right; + + Pair(); + Pair(const T& left, const U& right); +}; + +template <typename T, typename U> +Pair<T, U>::Pair() : left(), right() {} + + +template <typename T, typename U> +Pair<T, U>::Pair(const T& left, const U& right) : left(left), right(right) {} + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/String/String.cpp b/Year 1/Object-oriented Programming/Project 2/src/Generic/String/String.cpp new file mode 100644 index 0000000..57e5d58 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/String/String.cpp @@ -0,0 +1,191 @@ +#include "String.h" +#include <cstring> + +/* Private */ + +void String::free() { + delete[] str; +} + +void String::copyFrom(const String& other) { + str = new char[other.length + 1]; + strncpy(str, other.str, other.length + 1); + length = other.length; +} + +/* Public */ + +/*! + * Allocated with length + 1 size, so there is a terminating zero at the end. + */ +String::String(unsigned length) { + this->length = length; + str = new char[length + 1]; + str[length] = '\0'; +} + +/*! \params str C-style string + */ +String::String(const char* str) { + length = strlen(str); + this->str = new char[length + 1]; // capture terminating zero + strncpy(this->str, str, length + 1); +} + +const char* String::get_cstr() const { + return str; +} + +unsigned String::get_length() const { + return length; +} + +char& String::operator[](unsigned index) { + return str[index]; +} + +/*! \params str C-style string + */ +String& String::operator+=(const char* str) { + unsigned strLen = strlen(str); + char* newStr = new char[length + strLen + 1]; + strncpy(newStr, this->str, length + 1); + strncat(newStr, str, strLen + 1); + + delete[] this->str; + this->str = newStr; + length += strLen; + + return *this; +} + +/*! + * Converts the number to a C-style string and then uses += to append it. + */ +String& String::operator+=(int number) { + if (number < 0) { + *this += "-"; + number *= -1; + } + return *this += (unsigned)number; +} + +/*! + * Converts the number to a C-style string and then uses += to append it. + */ +String& String::operator+=(unsigned number) { + unsigned tempLen = 0; + for (unsigned tmp = number; tmp > 0 || tempLen == 0; tmp /= 10) + tempLen++; + + char* tmp = new char[tempLen + 1]; + tmp[tempLen] = '\0'; + for (unsigned i = tempLen - 1; i < tempLen; i--, number /= 10) + tmp[i] = number % 10 + '0'; + + *this += tmp; + delete[] tmp; + return *this; +} + +/*! + * Converts the number to a C-style string and then uses += to append it. + */ +String& String::operator+=(double number) { + if (number < 0) { + *this += "-"; + number *= -1; + } + number += 1.0 / (DOUBLE_PRECISION * 10); // bump up the number, in cases it becomes something like 8.59999999999... + return ((*this += (unsigned)number) += ".") + += (unsigned)((number - (unsigned)number) * DOUBLE_PRECISION); +} + +String::String() : String("") {} + +String::String(const String& other) { + copyFrom(other); +} + +String& String::operator=(const String& other) { + if (this != &other) { + free(); + copyFrom(other); + } + return *this; +} + +String::~String() { + free(); +} + +String::String(String&& other) { + str = other.str; + other.str = nullptr; + length = other.length; +} + +String& String::operator=(String&& other) { + if (this != &other) { + delete[] str; + str = other.str; + other.str = nullptr; + length = other.length; + } + return *this; +} + +/*! \param istr An input stream + * + * Directly reads bytes from stream (calls read() function). + * First reads the string length, then the underlying C-style string (including terminating zero). + * + * \remark Doesn't alter the stream in any other way. + * \note Best used with binary ifstream + */ +void String::read(std::istream& istr) { + istr.read((char*)&length, sizeof(length)); + istr.read(str, sizeof(char) * (length + 1)); +} + +/*! \param ostr An output stream + * + * Directly writes bytes to stream (calls write() function). + * First writes the string length, then the underlying C-style string (including terminating zero). + * + * \remark Doesn't alter the stream in any other way. + * \note Best used with binary ofstream + */ +void String::write(std::ostream& ostr) const { + ostr.write((char*)&length, sizeof(length)); + ostr.write(str, sizeof(char) * (length + 1)); +} + +/*! \params other C-style string + * \returns strcmp between underlying C-style string and "other" + */ +int String::compare(const String& other) const { + return strcmp(str, other.str); +} + +/*! + * Uses the stream's getline function to read the data. + * + * \warning It takes at most 1024 characters from the stream! + * \note Best used with std::cin or text std::ifstream + */ +std::istream& operator>>(std::istream& istr, String& str) { + istr.getline(str.str, 1024); + str.length = strlen(str.str); + + return istr; +} + +/*! + * Uses the stream's << operator to write the underlying C-style string + * + * \note Best used with std::cout or text std::ofstream + */ +std::ostream& operator<<(std::ostream& ostr, const String& str) { + return ostr << str.str; +} diff --git a/Year 1/Object-oriented Programming/Project 2/src/Generic/String/String.h b/Year 1/Object-oriented Programming/Project 2/src/Generic/String/String.h new file mode 100644 index 0000000..93534fe --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Generic/String/String.h @@ -0,0 +1,66 @@ +#ifndef HEADER_STRING +#define HEADER_STRING + +/*! \file String.h + * \brief Stores declaration of class String + */ + +#include <istream> +#include <ostream> + +#define DOUBLE_PRECISION 100 + +/*! \brief A dynamically-allocated C-style string with extra features + * + * \remark There is no buffer allocation, meaning each String takes only the amount of memory it needs and every append operation juggles around memory. + */ +class String { + char* str; + unsigned length; + + void free(); + void copyFrom(const String& other); + +public: + //! The underlying array is allocated with length (+ 1) size + String(unsigned length); + //! Copies str + String(const char* str); + //! Returns internal C-style string + const char* get_cstr() const; + //! Returns number of characters in string + unsigned get_length() const; + //! Returns a character reference (from the underlying C-style string) at the given index + char& operator[](unsigned index); + + //! Appends a C-style string + String& operator+=(const char* str); + //! Appends a number + String& operator+=(int number); + //! Appends a number + String& operator+=(unsigned number); + //! Appends a number + String& operator+=(double number); + + String(); + String(const String& other); + String& operator=(const String& other); + ~String(); + + String(String&& other); + String& operator=(String&& other); + + //! Reads String from stream + void read(std::istream& istr); + //! Writes String to stream + void write(std::ostream& ostr) const; + //! Compares two strings + int compare(const String& other) const; + + //! Reads String from stream with >> operator + friend std::istream& operator>>(std::istream& istr, String& str); + //! Writes String to stream with << operator + friend std::ostream& operator<<(std::ostream& ostr, const String& str); +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/Cell.h b/Year 1/Object-oriented Programming/Project 2/src/Models/Cell.h new file mode 100644 index 0000000..9abc639 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/Cell.h @@ -0,0 +1,27 @@ +#ifndef HEADER_CELL +#define HEADER_CELL + +/*! \file Cell.h + * \brief Stores the declaration of class Cell + */ + +#include <fstream> +#include "../Generic/String/String.h" + +/*! \brief Fully abstract class that represents a cell in the table + */ +class Cell { +public: + //! Returns the value of the cell as a double-precision floating point number + virtual double getNumeralValue() const = 0; + //! Returns the value of the cell as a String + virtual String getValueForPrint() const = 0; + //! Parses given C-style string and stores value + virtual void parseAndSetValue(const char* str) = 0; + //! Writes value to a text file + virtual void writeToFile(std::ofstream& file) = 0; + + virtual ~Cell() = default; +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/CellDouble.cpp b/Year 1/Object-oriented Programming/Project 2/src/Models/CellDouble.cpp new file mode 100644 index 0000000..660fc88 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/CellDouble.cpp @@ -0,0 +1,39 @@ +#include "CellDouble.h" +#include <cstring> + +CellDouble::CellDouble(double value) { + this->value = value; +} + +/*! + * Accepts a C-style string in the form "<+/-><digits>.<digits>" + */ +CellDouble::CellDouble(const char* str) { + value = 0; + bool negative = *str == '-'; + if (*str == '-' || *str == '+') str++; + + while (*str != '.') + value = value * 10 + (*(str++) - '0'); + str++; + for (unsigned div = 10; *str != '\0'; div *= 10) + value += (double)(*(str++) - '0') / div; + + if (negative) value *= -1; +} + +double CellDouble::getNumeralValue() const { + return value; +} + +String CellDouble::getValueForPrint() const { + return String() += value; +} + +void CellDouble::parseAndSetValue(const char* str) { + value = atof(str); +} + +void CellDouble::writeToFile(std::ofstream& file) { + file << value; +} diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/CellDouble.h b/Year 1/Object-oriented Programming/Project 2/src/Models/CellDouble.h new file mode 100644 index 0000000..bd21119 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/CellDouble.h @@ -0,0 +1,28 @@ +#ifndef HEADER_CELLDOUBLE +#define HEADER_CELLDOUBLE + +/*! \file CellDouble.h + * \brief Stores the declaration of class CellDouble + */ + +#include "Cell.h" + +/*! \brief A table cell which contains a double-precision floating point number + * + * Implements the Cell class by implementing a value of type double. + */ +class CellDouble : public Cell { + double value; + +public: + CellDouble() = default; + CellDouble(double value); + CellDouble(const char* str); + + double getNumeralValue() const override; + String getValueForPrint() const override; + void parseAndSetValue(const char* str) override; + void writeToFile(std::ofstream& file) override; +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/CellFormula.cpp b/Year 1/Object-oriented Programming/Project 2/src/Models/CellFormula.cpp new file mode 100644 index 0000000..1cdf06a --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/CellFormula.cpp @@ -0,0 +1,190 @@ +#include "CellFormula.h" +#include "CellDouble.h" +#include <cstring> +#include <iostream> +#include <stdexcept> + +const Cell* CellFormula::ptrByInd(Pair<int, int> loc) const { + if (loc.left < 0) { + if (loc.right < 0 || localCells.get_count() - 1 < loc.right) return nullptr; + return &localCells[loc.right]; + } + + if (loc.right < 0 + || (*tableCells).get_count() - 1 < loc.left || (*tableCells)[loc.left].get_count() - 1 < loc.right) + return nullptr; + return (*tableCells)[loc.left][loc.right]; +} + +double pow(double x, int y) { + if (y <= 0) return 1; + + double res = x; + for (unsigned i = 1; i < y; i++) + res *= x; + return res; +} + +double CellFormula::calculate(unsigned index) const { + const Cell* currCell = ptrByInd(formula[index].left); + double numVal = (currCell == nullptr) ? 0 : currCell->getNumeralValue(); + + // Handle prioritised operation + while (formula[index].right < 0) { + switch (-formula[index++].right) { // Prioritized operations are stored as negative characters + case '+' : numVal += ptrByInd(formula[index].left)->getNumeralValue(); break; + case '*' : numVal *= ptrByInd(formula[index].left)->getNumeralValue(); break; + case '/' : if (ptrByInd(formula[index].left)->getNumeralValue() == 0) throw std::logic_error("Error: Divion by zero!"); + numVal /= ptrByInd(formula[index].left)->getNumeralValue(); break; + case '^' : numVal = pow(numVal, ptrByInd(formula[index].left)->getNumeralValue()); break; + } + } + + double temp; + switch(formula[index].right) { + case '+' : return numVal + calculate(++index); + case '*' : return numVal * calculate(++index); + case '/' : temp = calculate(++index); + if (temp == 0) throw std::logic_error("Error: Division by zero!"); + return numVal / temp; + case '^' : return pow(numVal, calculate(++index)); + case '\0' : + default : return numVal; + } +} + +CellFormula::CellFormula(const char* str, const List<List<Cell*>>* tableCells) : localCells(), formula(), rawFormula() { + this->tableCells = tableCells; + CellFormula::parseAndSetValue(str); +} + +#include <iostream> + +/*! + * Value is recalculated every time this function is called (in case a referenced cell is changed). + * + * \return 0 \if Calculation failed + * \return Calculated value \otherwise + */ +double CellFormula::getNumeralValue() const { + try { + return calculate(); + } + catch (std::logic_error e) { + return 0; + } +} + +/*! + * Value is recalculated every time this function is called (in case a referenced cell is changed). + * + * \return "Error" \if Calculation failed + * \return Calculated value as a String \otherwise + */ +String CellFormula::getValueForPrint() const { + try { + return String() += calculate(); + } + catch (std::logic_error e) { + return "Error"; + } +} + +// Rough implementation of the grammar +// +// S -> WDWOS | WD.DWOS | WRDCDWOS | W +// O -> + | - | * | / | ^ +// D -> D0 | D1 | ... | D9 | 0 | 1 | ... | 9 +// W -> W | epsilon +// +// over the string + +void W(const char*& str) { + while (*str == ' ') str++; +} + +void D(const char*& str, List<char>& buffer) { + while (*str >= '0' && *str <= '9') + buffer.add(*(str++)); +} + +void O(const char*& str, char& buffer) { + if (*str == '+' || *str == '-' || *str == '*' || *str == '/' || *str == '^' || *str == '\0') + buffer = *(str++); + else buffer = -2; +} + +/*! + * Each math operation is represented with a Pair that holds indecies of cell reference + * and a character for the operation. + * + * Negatve value for the operation character means it's with priority and the result get's calculated + * immediately (rather than recursively, taking into account other values). + * + * \exception std::logic_error("Error: Invalid column character!") Thrown when there is something between Rx and C in cell references + * \exception std::logic_error("Error: Invalid value!") Thrown when met value isn't a cell reference or number + * \exception throw std::logic_error("Error: Could not find operand!") Thrown when there isn't an operator character between two values + */ +void CellFormula::parseAndSetValue(const char* str) { + rawFormula = String(str); + + formula.clear(); + localCells.clear(); + Pair<int, int> loc; + char currOp = -2; + + str++; // Removes = from the beginning + while (*str != '\0') { + List<char> buffer; + W(str); + if (*str == 'R') { + D(++str, buffer); + loc.left = atoi(buffer.raw_data()) - 1; + if (*str != 'C') throw std::logic_error("Error: Invalid column character!"); + str++; + buffer.clear(); + D(str, buffer); + loc.right = atoi(buffer.raw_data()) - 1; + } + else if (*str >= '0' && *str <= '9') { + D(str, buffer); + if (*str == '.') { + buffer.add(*str); + D(++str, buffer); + localCells.add(CellDouble(atof(buffer.raw_data()))); + } + else + localCells.add(CellDouble(atoi(buffer.raw_data()))); + + loc.left = -1; + loc.right = (int)(localCells.get_count() - 1); + } + else + throw std::logic_error("Error: Invalid value!"); + + W(str); + O(str, currOp); + if (currOp == -2) throw std::logic_error("Error: Could not find operand!"); + + // Operator priority magic + if (currOp == '^') currOp *= -1; + else if ((currOp == '+' || currOp == '-') && formula.get_count() != 0) { + for (unsigned i = formula.get_count() - 1; i < formula.get_count() && (formula[i].right == '*' || formula[i].right == '/'); i--) + if (formula[i].right > 0) formula[i].right *= -1; + } + + if (currOp == '-') { // a - b turns into a + -1 * b + formula.add({loc, '+'}); + localCells.add(CellDouble(-1.0)); + loc.left = -1; + loc.right = (int)(localCells.get_count() - 1); + currOp = '*' * -1; + } + + formula.add(Pair<Pair<int, int>, char>(loc, currOp)); + } +} + +void CellFormula::writeToFile(std::ofstream& file) { + file << '"' << rawFormula << '"'; +} diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/CellFormula.h b/Year 1/Object-oriented Programming/Project 2/src/Models/CellFormula.h new file mode 100644 index 0000000..411de84 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/CellFormula.h @@ -0,0 +1,37 @@ +#ifndef HEADER_CELLFORMULA +#define HEADER_CELLFORMULA + +/*! \file CellFormula.h + * \brief Stores the declaration of class CellFormula + */ + +#include "Cell.h" +#include "CellDouble.h" +#include "../Generic/List/List.hpp" +#include "../Generic/Pair/Pair.hpp" + +/*! \brief A table cell which contains a formula + * + * Implements the Cell class by storing a formula as a String and in a dynamic List + */ +class CellFormula : public Cell { + const List<List<Cell*>>* tableCells; + List<CellDouble> localCells; + + List<Pair<Pair<int, int>, char>> formula; + String rawFormula; + + const Cell* ptrByInd(Pair<int, int> loc) const; + double calculate(unsigned index = 0) const; + +public: + CellFormula() = default; + CellFormula(const char* str, const List<List<Cell*>>* tableCells); + + double getNumeralValue() const override; + String getValueForPrint() const override; + void parseAndSetValue(const char* str) override; + void writeToFile(std::ofstream& file) override; +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/CellInt.cpp b/Year 1/Object-oriented Programming/Project 2/src/Models/CellInt.cpp new file mode 100644 index 0000000..cc09bd0 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/CellInt.cpp @@ -0,0 +1,33 @@ +#include "CellInt.h" +#include <cstring> + +CellInt::CellInt(int value) { + this->value = value; +} + +CellInt::CellInt(const char* str) { + value = 0; + bool negative = *str == '-'; + if (*str == '-' || *str == '+') str++; + + while (*str != '\0') + value = value * 10 + (*(str++) - '0'); + + if (negative) value *= -1; +} + +double CellInt::getNumeralValue() const { + return value; +} + +String CellInt::getValueForPrint() const { + return String() += value; +} + +void CellInt::parseAndSetValue(const char* str) { + value = atoi(str); +} + +void CellInt::writeToFile(std::ofstream& file) { + file << value; +} diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/CellInt.h b/Year 1/Object-oriented Programming/Project 2/src/Models/CellInt.h new file mode 100644 index 0000000..c24ef85 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/CellInt.h @@ -0,0 +1,28 @@ +#ifndef HEADER_CELLINT +#define HEADER_CELLINT + +/*! \file CellInt.h + * \brief Stores the declaration of class Cell + */ + +#include "Cell.h" + +/*! \brief A table cell which contains an integer + * + * Impelements the Cell class by implementing a value of type int + */ +class CellInt : public Cell { + int value; + +public: + CellInt() = default; + CellInt(int value); + CellInt(const char* str); + + double getNumeralValue() const override; + String getValueForPrint() const override; + void parseAndSetValue(const char* str) override; + void writeToFile(std::ofstream& file) override; +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/CellString.cpp b/Year 1/Object-oriented Programming/Project 2/src/Models/CellString.cpp new file mode 100644 index 0000000..2a105ef --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/CellString.cpp @@ -0,0 +1,28 @@ +#include "CellString.h" +#include <cstring> + +CellString::CellString(const char* str) { + value = String(str); +} + +double CellString::getNumeralValue() const { + return atof(value.get_cstr()); +} + +String CellString::getValueForPrint() const { + return value; +} + +void CellString::parseAndSetValue(const char* str) { + value = String(str); +} + +void CellString::writeToFile(std::ofstream& file) { + file << '"'; + const char* strCpy = value.get_cstr(); + while (*strCpy != '\0') { + if (*strCpy == '\\' || *strCpy == '"') file << '\\'; + file << *(strCpy++); + } + file << '"'; +} diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/CellString.h b/Year 1/Object-oriented Programming/Project 2/src/Models/CellString.h new file mode 100644 index 0000000..788aa4c --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/CellString.h @@ -0,0 +1,25 @@ +#ifndef HEADER_CELLSTRING +#define HEADER_CELLSTRING + +/*! \file CellString.h + * \brief Stores the declaration of class CellString + */ + +#include "Cell.h" + +/*! \brief A table cell which contains a String value + */ +class CellString : public Cell { + String value; + +public: + CellString() = default; + CellString(const char* str); + + double getNumeralValue() const override; + String getValueForPrint() const override; + void parseAndSetValue(const char* str) override; + void writeToFile(std::ofstream& file) override; +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/Table.cpp b/Year 1/Object-oriented Programming/Project 2/src/Models/Table.cpp new file mode 100644 index 0000000..0231dea --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/Table.cpp @@ -0,0 +1,218 @@ +#include "Table.h" +#include "CellDouble.h" +#include "CellInt.h" +#include "CellString.h" +#include "CellFormula.h" +#include <cstring> +#include <fstream> +#include <exception> +#include <stdexcept> + +#include <iostream> // TODO: remove + +// Rough implementation of the grammar +// +// S -> W"=A",S | W"A",S | WPD,S | WPD.D,S | ,S | epsilon +// A -> Aa | Ab | ... | AZ | A0 | A1 | ... | A* | ... | a | b | ... | epsilon +// D -> D0 | D1 | ... | D9 | 0 | 1 | ... | 9 +// P -> + | - | epsilon +// W -> W | epsilon +// +// over the input file stream + +void W(std::ifstream& in) { + while (in.peek() == ' ') in.get(); +} + +void P(std::ifstream& in, List<char>& buffer) { + if (in.peek() == '-' || in.peek() == '+') + buffer.add(in.get()); +} + +void D(std::ifstream& in, List<char>& buffer) { + while (in.peek() >= '0' && in.peek() <= '9') + buffer.add(in.get()); +} + +void A(std::ifstream& in, List<char>& buffer) { + while ((in.peek() >= ' ' && in.peek() <= '~') && in.peek() != '"') { + if (in.peek() == '\\') in.get(); + buffer.add(in.get()); + } +} + +void throwException(std::ifstream& inFile, unsigned row) { + row++; + throw std::logic_error(( + (((((String("Error: Invalid character '") += (char)inFile.peek()) + += "' at row ") += row) += " and column ") += ((int)inFile.tellg() / row + 1)) + ).get_cstr()); +} + +void Table::readFromFile(std::ifstream& inFile) { + unsigned row = longestRow = 0; + + while (inFile.peek() != EOF) { + if (cells.get_count() < row + 1) + cells.add(List<Cell*>()); + + if (inFile.peek() == '\n') { + row++; + inFile.get(); + continue; + } + + List<char> buffer; + + W(inFile); + if (inFile.peek() == '"') { // W"=A",S | W"A",S + inFile.get(); + A(inFile, buffer); + buffer.add('\0'); + if (inFile.peek() != '"') throwException(inFile, row); + inFile.get(); + W(inFile); + + if (inFile.peek() == ',') inFile.get(); + else if (inFile.peek() != '\n' && inFile.peek() != EOF) + throwException(inFile, row); + + cells[row].add((buffer[0] == '=') + ? (Cell*)new CellFormula(buffer.raw_data(), &cells) + : (Cell*)new CellString(buffer.raw_data())); + } + else if (inFile.peek() == ',') { // ,S + inFile.get(); + cells[row].add(nullptr); + } + else { // WPD,S | WPD.D,S + P(inFile, buffer); + D(inFile, buffer); + if (inFile.peek() == '.') { + buffer.add(inFile.get()); + D(inFile, buffer); + buffer.add('\0'); + W(inFile); + + if (inFile.peek() == ',') inFile.get(); + else if (inFile.peek() != '\n' && inFile.peek() != EOF) + throwException(inFile, row); + + cells[row].add((Cell*)new CellDouble(buffer.raw_data())); + } + else { + buffer.add('\0'); + W(inFile); + if (inFile.peek() == ',') inFile.get(); + else if (inFile.peek() != '\n' && inFile.peek() != EOF) + throwException(inFile, row); + + cells[row].add((Cell*)new CellInt(buffer.raw_data())); + } + } + + if (cells[row].get_count() > longestRow) + longestRow = cells[row].get_count(); + } +} + +#include <iostream> +Table::Table(const char* filePath) { + longestRow = 0; + std::ifstream inFile(filePath); + if (!inFile.is_open()) + throw std::logic_error("Could not open file!"); + cells = List<List<Cell*>>(); + readFromFile(inFile); + inFile.close(); +} + +Table::~Table() { + for (unsigned i = 0; i < cells.get_count(); i++) { + for (unsigned j = 0; j < cells[i].get_count(); j++) + delete cells[i][j]; + } +} + +unsigned Table::get_rows() const { + return cells.get_count(); +} + +unsigned Table::get_cols() const { + return longestRow; +} + +#define abs(a) ((a < 0) ? -a : a) +#define FLOATING_POINT_PRECISION 0.0001f + +void Table::putCell(unsigned row, unsigned col, const char* rawValue) { + Cell* newCell; + + while (*rawValue == ' ') rawValue++; + + if (*rawValue == '"') { + List<char> buffer; + rawValue++; + while (*rawValue != '"' && *rawValue != '\0') { + if (*rawValue == '\\') rawValue++; + buffer.add(*(rawValue++)); + } + if (*rawValue == '"' && *(rawValue + 1) != '\0') + throw std::logic_error("Error: Text after closing (inescaped) brace!"); + else if (*rawValue != '"') + throw std::logic_error("Error: No closing brace found!"); + + newCell = (buffer[0] == '=') + ? (Cell*)new CellFormula(buffer.raw_data(), &cells) + : (Cell*)new CellString(buffer.raw_data()); + } + else if ((*rawValue == '+' || *rawValue == '-') || (*rawValue >= '0' && *rawValue <= '9')) { + double doubleParse = atof(rawValue); + unsigned intPart = abs(doubleParse); + + newCell = (abs(doubleParse) - intPart > FLOATING_POINT_PRECISION) + ? (Cell*)new CellDouble(doubleParse) + : (Cell*)new CellInt(intPart); + } + else + throw std::logic_error("Error: Could not determine data type."); + + if (cells[row].get_count() - 1 < col) { + while (cells[row].get_count() < col) + cells[row].add((Cell*)new CellString()); + cells[row].add(newCell); + } + else { + delete cells[row][col]; + cells[row][col] = newCell; + } +} + +List<String> Table::getAllCells() const { + List<String> toRet; + for (unsigned i = 0; i < cells.get_count(); i++) { + for (unsigned j = 0; j < get_cols(); j++) { + if (cells[i].get_count() > j && cells[i][j] != nullptr) + toRet.add(cells[i][j]->getValueForPrint()); + else + toRet.add(""); + } + } + return toRet; +} + +void Table::saveToFile(const char* filePath) const { + std::ofstream outFile(filePath); + if (!outFile.is_open()) + throw std::logic_error("Could not open file!"); + + for (unsigned i = 0; i < cells.get_count(); i++) { + for (unsigned j = 0; j < cells[i].get_count(); j++) { + if (cells[i][j] != nullptr) + cells[i][j]->writeToFile(outFile); + outFile << ','; + } + outFile << '\n'; + } + outFile.close(); +} diff --git a/Year 1/Object-oriented Programming/Project 2/src/Models/Table.h b/Year 1/Object-oriented Programming/Project 2/src/Models/Table.h new file mode 100644 index 0000000..6dc07a3 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/Models/Table.h @@ -0,0 +1,37 @@ +#ifndef MODELS_TABLE +#define MODELS_TABLE + +/*! \file Table.h + * \brief Stores the declaration of class Table + */ + +#include "Cell.h" +#include "../Generic/List/List.hpp" +#include <fstream> + +/*! \brief A 2D collection of Cell instances + */ +class Table { + List<List<Cell*>> cells; + unsigned longestRow; + void readFromFile(std::ifstream& inFile); + +public: + Table(const char* filePath); + ~Table(); + + //! Returns count of rows + unsigned get_rows() const; + //! Returns largest count of columns in all rows + unsigned get_cols() const; + + //! Parses rawValue and replaces the value at row and col with it + void putCell(unsigned row, unsigned col, const char* rawValue); + + //! Returns a List of String with String representation of every cell in order + List<String> getAllCells() const; + //! Saves all cells to a text file + void saveToFile(const char* filePath) const; +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/UserInterface/UserInterface.cpp b/Year 1/Object-oriented Programming/Project 2/src/UserInterface/UserInterface.cpp new file mode 100644 index 0000000..0fc9e96 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/UserInterface/UserInterface.cpp @@ -0,0 +1,148 @@ +#include "UserInterface.h" +#include "../Generic/List/List.hpp" +#include "../Generic/String/String.h" +#include "../Generic/ConsoleInterface/Toolbox.hpp" +#include <stdexcept> + +/* Private (static) */ + +Table* UserInterface::table = nullptr; +String UserInterface::fileName = String(); +bool UserInterface::saved = false; + + +void UserInterface::com_open(const char* params) { + if (table != nullptr) { + printLine("A file has already been opened!"); + return; + } + + try { + table = new Table(params); + fileName = params; + saved = true; + printLine("Successfully opened file!"); + } + catch (std::logic_error err) { + printLine(err.what()); + } +} +void UserInterface::com_close(const char* params) { + if (table == nullptr) { + printLine("No file has been opened!"); + return; + } + + delete table; +} +void UserInterface::com_save(const char* params) { + com_saveas(fileName.get_cstr()); +} +void UserInterface::com_saveas(const char* params) { + if (table == nullptr) { + printLine("No file has been opened!"); + return; + } + + try { + table->saveToFile(params); + saved = true; + printLine("Successfully saved to file!"); + } + catch (std::logic_error err) { + printLine(err.what()); + } +} + +void UserInterface::com_help(const char* params) { + std::cout << "The following commands are supported:" << std::endl + << "print prints the whole table" << std::endl + << "edit RxCy <data> Replaces the value on row x and column y with the given data" << std::endl + << "open <file> opens <file>" << std::endl + << "close closes currently opened file" << std::endl + << "save saves the currently open file" << std::endl + << "saveas <file> saves the currently open file in <file>" << std::endl + << "help prints this information" << std::endl + << "exit exists the program" << std::endl; +} + +void UserInterface::com_exit(const char* params) { + if (!saved && table != nullptr) { + printLine("You have an open file with unsaved changes, please select close or save first."); + return; + } + + throw std::logic_error("Exiting program..."); // stupid way of making the program stop +} + +void UserInterface::com_print(const char* params) { + if (table == nullptr) { + printLine("You need to open a file!"); + return; + } + + List<String> cells = table->getAllCells(); + printTable(cells, table->get_cols()); +} + +#define isDigit(a) (a >= '0' && a <= '9') + +void UserInterface::com_edit(const char* params) { + if (*params != 'R') { + printLine("Error: Invalid cell position format! Expected R<number>C<number>"); + return; + } + + unsigned row = atoi(++params) - 1; + while (*params != 'C') { + if (*params == '\0') { + printLine("Error: End of line reached. Expected row number."); + return; + } + if (!isDigit(*params)) { + printLine("Error: Found a non-digit character. Exptected row number."); + return; + } + + params++; + } + unsigned col = atoi(++params) - 1; + while (*params != ' ') { + if (*params == '\0') { + printLine("Error: End of line reached. Expected columns number."); + return; + } + if (!isDigit(*params)) { + printLine("Error: Found a non-digit character. Exptected column number."); + return; + } + + params++; + } + + try { + table->putCell(row, col, params); + saved = false; + printLine("Successfully edited cell!"); + } + catch (std::logic_error err) { + printLine(err.what()); + } +} + +/* Public */ + +UserInterface::UserInterface() : menu() { + menu.addCommand(Command("open", com_open)); + menu.addCommand(Command("close", com_close)); + menu.addCommand(Command("save", com_save)); + menu.addCommand(Command("saveas", com_saveas)); + menu.addCommand(Command("help", com_help)); + menu.addCommand(Command("exit", com_exit)); + menu.addCommand(Command("print", com_print)); + menu.addCommand(Command("edit", com_edit)); +} + +void UserInterface::run() { + menu.navigate(); +} diff --git a/Year 1/Object-oriented Programming/Project 2/src/UserInterface/UserInterface.h b/Year 1/Object-oriented Programming/Project 2/src/UserInterface/UserInterface.h new file mode 100644 index 0000000..3557da8 --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/UserInterface/UserInterface.h @@ -0,0 +1,45 @@ +#ifndef HEADER_USERINTERFACE +#define HEADER_USERINTERFACE + +/*! \file UserInterface.h + * \brief Stores the declaration of class UserInterface + */ + +#include "../Generic/ConsoleInterface/Menu.h" +#include "../Generic/String/String.h" +#include "../Models/Table.h" + +/*! \brief The class that contains and via which the user controls the application's user interface + */ +class UserInterface { + Menu menu; + + static Table* table; + static String fileName; + static bool saved; + + //! Opens a file + static void com_open(const char* params); + //! Closes a file + static void com_close(const char* params); + //! Saves a file to the location it was opened from + static void com_save(const char* params); + //! Saves a file to a given location + static void com_saveas(const char* params); + //! Prints the help message + static void com_help(const char* params); + //! Exists the appliction + static void com_exit(const char* params); + + //! Prints the currently opened table + static void com_print(const char* params); + //! Edits a cell of the currently opened table + static void com_edit(const char* params); + +public: + UserInterface(); + //! Starts the user interface + void run(); +}; + +#endif diff --git a/Year 1/Object-oriented Programming/Project 2/src/main.cpp b/Year 1/Object-oriented Programming/Project 2/src/main.cpp new file mode 100644 index 0000000..d7f6f5d --- /dev/null +++ b/Year 1/Object-oriented Programming/Project 2/src/main.cpp @@ -0,0 +1,10 @@ +#include "UserInterface/UserInterface.h" + +/*! \file main.cpp + * \brief Creates a defualt instance of UserInterface and runs it + */ + +int main() { + UserInterface ui; + ui.run(); +} |
