Summary Files Commits Branches @commit @diff @stats
path: root / Year 1 / Object-oriented Programming / Project 2 / src
diff options
context:
space:
mode:
Commitc5431e4d321dbf0152e6826841c1ef5c1291ba5b (patch)
Author
 Syndamia <kamen@syndamia.com>
2022-06-14 15:38:50 +0300
Committer
 Syndamia <kamen@syndamia.com>
2022-06-14 15:38:50 +0300
Filesbe7dbd4b6188924bb3a1a9398169526f236b09b9 /Year 1/Object-oriented Programming/Project 2/src
uni-projects-c5431e4d321dbf0152e6826841c1ef5c1291ba5b.tar
uni-projects-c5431e4d321dbf0152e6826841c1ef5c1291ba5b.tar.gz
uni-projects-c5431e4d321dbf0152e6826841c1ef5c1291ba5b.zip
Parentccedf6f5d89f375580ff6af40296aab953f9cd09 (diff)
Added files from project 2 (mirror)
24 files changed, 1854 insertions, 0 deletions
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Command.cpp24
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Command.h27
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Menu.cpp72
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Menu.h33
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Toolbox.cpp88
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/ConsoleInterface/Toolbox.hpp60
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/List/List.hpp372
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/Pair/Pair.hpp28
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/String/String.cpp191
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Generic/String/String.h66
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/Cell.h27
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/CellDouble.cpp39
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/CellDouble.h28
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/CellFormula.cpp190
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/CellFormula.h37
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/CellInt.cpp33
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/CellInt.h28
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/CellString.cpp28
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/CellString.h25
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/Table.cpp218
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/Models/Table.h37
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/UserInterface/UserInterface.cpp148
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/UserInterface/UserInterface.h45
-rw-r--r--Year 1/Object-oriented Programming/Project 2/src/main.cpp10
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();
+}