diff options
11 files changed, 690 insertions, 0 deletions
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 |
