From eae8852832876c3fa8f606b9c6876f2b15423386 Mon Sep 17 00:00:00 2001 From: IlushaShurupov Date: Fri, 30 Jun 2023 23:05:38 +0300 Subject: [PATCH] Strings Initial --- Allocators/CMakeLists.txt | 2 +- CMakeLists.txt | 3 +- Strings/CMakeLists.txt | 22 ++ Strings/private/Logging.cpp | 69 ++++ Strings/private/Strings.cpp | 24 ++ Strings/private/TextEditor.cpp | 554 +++++++++++++++++++++++++++++++++ Strings/public/Logging.hpp | 59 ++++ Strings/public/StringLogic.hpp | 174 +++++++++++ Strings/public/Strings.hpp | 248 +++++++++++++++ Strings/public/TextEditor.hpp | 382 +++++++++++++++++++++++ Strings/tests/tests.cpp | 66 ++++ TODO | 2 +- 12 files changed, 1602 insertions(+), 3 deletions(-) create mode 100644 Strings/CMakeLists.txt create mode 100644 Strings/private/Logging.cpp create mode 100644 Strings/private/Strings.cpp create mode 100644 Strings/private/TextEditor.cpp create mode 100644 Strings/public/Logging.hpp create mode 100644 Strings/public/StringLogic.hpp create mode 100644 Strings/public/Strings.hpp create mode 100644 Strings/public/TextEditor.hpp create mode 100644 Strings/tests/tests.cpp diff --git a/Allocators/CMakeLists.txt b/Allocators/CMakeLists.txt index fae102e..99ec141 100644 --- a/Allocators/CMakeLists.txt +++ b/Allocators/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.2) set(CMAKE_CXX_STANDARD 23) -project(Allocator) +project(Allocators) ### ---------------------- Static Library --------------------- ### file(GLOB SOURCES "./private/*.cpp") diff --git a/CMakeLists.txt b/CMakeLists.txt index d23e6cf..489a229 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,4 +14,5 @@ add_subdirectory(Modules) add_subdirectory(Utils) add_subdirectory(Containers) add_subdirectory(Math) -add_subdirectory(Allocators) \ No newline at end of file +add_subdirectory(Allocators) +add_subdirectory(Strings) \ No newline at end of file diff --git a/Strings/CMakeLists.txt b/Strings/CMakeLists.txt new file mode 100644 index 0000000..e50501e --- /dev/null +++ b/Strings/CMakeLists.txt @@ -0,0 +1,22 @@ + +cmake_minimum_required(VERSION 3.2) + +set(CMAKE_CXX_STANDARD 23) + +project(Strings) + +### ---------------------- Static Library --------------------- ### +file(GLOB SOURCES "./private/*.cpp") +file(GLOB HEADERS "./public/*.hpp") +add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) +target_include_directories(${PROJECT_NAME} PUBLIC ./public/) +target_link_libraries(${PROJECT_NAME} PUBLIC Allocators Containers) + +### -------------------------- Tests -------------------------- ### +enable_testing() +file(GLOB TEST_SOURCES "./tests/*.cpp") +add_executable(${PROJECT_NAME}Tests ${TEST_SOURCES}) +target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME} Utils) +add_test(NAME ${PROJECT_NAME}Tests COMMAND ${PROJECT_NAME}Tests) + +install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib) \ No newline at end of file diff --git a/Strings/private/Logging.cpp b/Strings/private/Logging.cpp new file mode 100644 index 0000000..443de68 --- /dev/null +++ b/Strings/private/Logging.cpp @@ -0,0 +1,69 @@ + +#include "Logging.hpp" +#include "Allocators.hpp" +#include "TextEditor.hpp" + +#include + +namespace tp { + + Logger* gLogger = nullptr; + + ualni Logger::Report::getLineCount() const { + return (ualni)(mLineOffsets.size() - 1); + } + + String Logger::Report::getString() const { + return mData; + } + + void Logger::Report::calcLineCount() { + Input input = { mData.raw(), (ualni) mData.size() }; + input.getLineOffsets(mLineOffsets); + } + + Logger::Report::Report() { + mData = " - "; + } + + Logger::Report::Report(const String& text) : mData(text) { + calcLineCount(); + } + + Logger::Report::Report(const String& text, Type type) : mType(type), mData(text) { + calcLineCount(); + } + + void Logger::write(const String& in, bool post, Report::Type type) { + StringTemplate copy = in; + copy.capture(); + mBuff.pushBack(Report(copy, type)); + + mLineCount += mBuff.last()->data.getLineCount(); + + if (!mCursor) { + mCursor = mBuff.last(); + } + + if (post) { + printf("%s", in.raw()); + } + } + + String Logger::read() { + if (!mCursor) return {}; + const Report& out = mCursor->data; + mCursor = mCursor->next; + return out.getString(); + } + + void Logger::initializeGlobal() { + DEBUG_ASSERT(!gLogger) + gLogger = new Logger(); + } + + void Logger::deinitializeGlobal() { + DEBUG_ASSERT(gLogger) + delete gLogger; + } +} \ No newline at end of file diff --git a/Strings/private/Strings.cpp b/Strings/private/Strings.cpp new file mode 100644 index 0000000..2d59e4d --- /dev/null +++ b/Strings/private/Strings.cpp @@ -0,0 +1,24 @@ + +#include "Strings.hpp" +#include "Allocators.hpp" +#include "Logging.hpp" + +using namespace tp; + +bool initialize(const ModuleManifest*) { + Logger::initializeGlobal(); + return true; +} + +void deinitialize(const ModuleManifest*) { + Logger::deinitializeGlobal(); +} + +static tp::ModuleManifest* sModuleDependencies[] = { + &tp::gModuleContainers, + &tp::gModuleAllocators, + &tp::gModuleUtils, + nullptr +}; + +ModuleManifest tp::gModuleStrings = ModuleManifest("Strings", initialize, deinitialize, sModuleDependencies); \ No newline at end of file diff --git a/Strings/private/TextEditor.cpp b/Strings/private/TextEditor.cpp new file mode 100644 index 0000000..1493f76 --- /dev/null +++ b/Strings/private/TextEditor.cpp @@ -0,0 +1,554 @@ + +#include "TextEditor.hpp" + +using namespace tp; +using namespace str; + +bool tp::str::PiecesDS::Node::mInsertionOperation = false; + +bool tp::str::isNewLineChar(Char character) { + return (character == '\n') || (character == '\r'); +} + +void Input::getLineOffsets(Buffer& out) { + halni lines = 0; + for (auto idx : Range(mLen)) { + if (isNewLineChar(mInput[idx])) { + lines++; + } + } + out.reserve(lines + 2); // plust start and end offsets + lines = 0; + for (auto idx : Range(mLen)) { + if (isNewLineChar(mInput[idx])) { + out[lines + 1] = Index(idx + 1); + lines++; + } + } + out[0] = 0; + out[lines + 1] = mLen; +} + + // ---------------------- Pointer ---------------------- // + +Pointer::Pointer() { + mCol = 0; mLine = 0; +} + +Pointer::Pointer(LineId aLine, ColumnId aCol) { + mLine = aLine; + mCol = aCol; +} + +Pointer::Pointer(const Input& aInput) { + halni lines = 0; + halni col = 0; + for (auto idx : Range(aInput.mLen)) { + if (isNewLineChar(aInput.mInput[idx])) { + lines++; + col = 0; + } + else { + col++; + } + } + mLine = lines; + mCol = col; +} + +void Pointer::operator+=(Char achar) { + if (isNewLineChar(achar)) { + mLine += 1; + mCol = 0; + } + else { + mCol += 1; + } +} + +void Pointer::operator+=(const Pointer& ap) { + *this = operator+(ap); +} + +Pointer Pointer::operator-(const Pointer in) const { + if (mLine == in.mLine) { + return { 0, mCol - in.mCol }; + } + return { mLine - in.mLine, mCol }; +} + +Pointer Pointer::operator+(const Pointer& ap) const { + if (ap.mLine == 0) { + return { mLine, mCol + ap.mCol }; + } + return { mLine + ap.mLine, ap.mCol }; +} + +bool Pointer::operator==(const Pointer& in) const { + return in.mCol == mCol && in.mLine == mLine; +} + +bool Pointer::operator>(const Pointer& in) const { + if (mLine > in.mLine) { + return true; + } + else if (mLine < in.mLine) { + return false; + } + + return mCol > in.mCol; +} + +bool Pointer::operator>=(const Pointer& in) const { + return operator>(in) || in == *this; +} + +Pointer Piece::diffPtr() const { + return mEnd - mStart; +} + +LineId Piece::nLines() const { + return { mEnd.mLine - mStart.mLine }; +} + +// ---------------------- Add Buff ---------------------- // + +AddBuff::AddBuff() { + Input input = { "", 0 }; + Buffer lineoffsets; + input.getLineOffsets(lineoffsets); + mLineOffsets.append(lineoffsets.mBuff(), (Index)lineoffsets.length()); +} + +void AddBuff::append(Input aInput) { + + // TODO : errors here + + Buffer lineoffsets; + aInput.getLineOffsets(lineoffsets); + + if (lineoffsets.length() == 2) { + mLineOffsets[mLineOffsets.len() - 1] += lineoffsets[1] - lineoffsets[0]; + } + else { + + auto last_line_offset = mLineOffsets[mLineOffsets.len() - 1]; + + // offset last line ptr + mLineOffsets[mLineOffsets.len() - 1] += lineoffsets[1]; + + // offset all lines by mLineOffsets.mLast ptr + for (auto offset : lineoffsets) { + offset.data() += last_line_offset; + } + + mLineOffsets.append(&lineoffsets[2], (Index)(lineoffsets.length() - 2)); + } + + mBuff.append(aInput.mInput, aInput.mLen); +} + +Pointer AddBuff::endPtr() { + return { mLineOffsets.len() - 2, mLineOffsets[mLineOffsets.len() - 1] - mLineOffsets[mLineOffsets.len() - 2] }; +} + +// ---------------------- Orig Buff ---------------------- // + +OrigBuff::OrigBuff(Input aOriginal) { + mOriginal = aOriginal; + mOriginal.getLineOffsets(mLineOffsets); + + mPtrStart = { 0, 0 }; + mPtrEnd = mOriginal; +} + +void OrigBuff::makeOwnOriginalInput() { + if (mOwnsInput) { + return; + } + + auto input_copy = new Char[mOriginal.mLen + 1]; + DEBUG_ASSERT(sizeof(Char) == sizeof(uint1)); + memcp(input_copy, mOriginal.mInput, mOriginal.mLen); + input_copy[mOriginal.mLen] = '\0'; + mOriginal.mInput = input_copy; + mOwnsInput = true; +} + +OrigBuff::~OrigBuff() { + if (mOwnsInput) { + delete[] mOriginal.mInput; + } +} + +// ---------------------- Editor ---------------------- // + +PieceCrs TextEditor::findPiece(Pointer ptr) const { + PieceCrs out; + if (!mPieces.nPieces()) { + return out; + } + + if (mPieces.ptrSum() == ptr) { + out.piece = mPieces.maxNode(mPieces.head()); + } else { + out.piece = mPieces.find(ptr); + } + + DBG_BREAK(!out.piece); + out.piece_ptr = out.piece->getFindKey(); + out.ptr = ptr; + out.offset = out.ptr - out.piece_ptr; + return out; +} + +void TextEditor::clampPtr(Pointer& ptr) const { + if (!mPieces.nPieces()) { + ptr = { 0, 0 }; + return; + } + + CLAMP(ptr.mLine, 0, mNLines()); + + auto crs = findPiece(Pointer{ ptr.mLine, 0 }); + Index line_len = getPieceLineLen(crs.piece->mPiece, crs.offset.mLine); + + auto next_piece_ptr = nextPiecePtr(crs.piece, crs.piece_ptr); + auto piece = crs.piece; + while (piece->mNext && ptr.mLine == next_piece_ptr.mLine) { + piece = piece->mNext; + next_piece_ptr = nextPiecePtr(piece, next_piece_ptr); + line_len += getPieceLineLen(piece->mPiece, 0); + } + + if (ptr.mLine != mNLines()) { + line_len -= 1; + } + + CLAMP(ptr.mCol, 0, line_len); +} + +void TextEditor::setCursor(Pointer ptr) { + clampPtr(ptr); + mCursor = ptr; +} + +void TextEditor::setCursorEnd(Pointer ptr) { + clampPtr(ptr); + mCursorEnd = ptr; + mCursor = mCursorEnd; +} + +const Char* TextEditor::getPieceLine(const Piece* piece, LineId offset) const { + const Char* buff; + if (piece->mAddedBuff) { + if (offset == 0) { + buff = &mAddBuff.mBuff[mAddBuff.mLineOffsets[piece->mStart.mLine]] + piece->mStart.mCol; + } + else if (offset > piece->nLines()) { + buff = &mAddBuff.mBuff[mAddBuff.mLineOffsets[piece->mEnd.mLine]] + piece->mEnd.mCol; + } + else { + buff = &mAddBuff.mBuff[mAddBuff.mLineOffsets[piece->mStart.mLine + offset]]; + } + } + else { + if (offset == 0) { + buff = &mOrigBuff.mOriginal.mInput[mOrigBuff.mLineOffsets[piece->mStart.mLine]] + piece->mStart.mCol; + } + else if (offset > piece->nLines()) { + buff = &mOrigBuff.mOriginal.mInput[mOrigBuff.mLineOffsets[piece->mEnd.mLine]] + piece->mEnd.mCol; + } + else { + buff = &mOrigBuff.mOriginal.mInput[mOrigBuff.mLineOffsets[piece->mStart.mLine + offset]]; + } + } + return buff; +} + +const Char* TextEditor::getBuffLine(const Piece* piece, LineId offset) const { + const Char* buff; + if (piece->mAddedBuff) { + buff = &mAddBuff.mBuff[mAddBuff.mLineOffsets[piece->mStart.mLine + offset]]; + } + else { + buff = &mOrigBuff.mOriginal.mInput[mOrigBuff.mLineOffsets[piece->mStart.mLine + offset]]; + } + return buff; +} + +Pointer TextEditor::prevPiecePtr(PiecesDS::Node* piece_node, Pointer aPtr) const { + if (!piece_node->mPrev) { + return { 0, 0 }; + } + + return piece_node->mPrev->getFindKey(); +} + +Pointer TextEditor::nextPiecePtr(PiecesDS::Node* aPiece, Pointer aPtr) const { + if (!aPiece) return { -1, -1 }; + + if (aPiece->mNext) { + return aPiece->mNext->getFindKey(); + } + return aPiece->getFindKey() + aPiece->mDiff; +} + +LineId TextEditor::getPieceLineLen(Piece* aPiece, LineId line) const { + return LineId(getPieceLine(aPiece, line + 1) - getPieceLine(aPiece, line)); +} + +TextEditor::TextEditor(Input original) : mOrigBuff(original) { + DEBUG_ASSERT(original.mLen); + auto new_node = mPieces.insertAfter(0, { false, mOrigBuff.mPtrStart, mOrigBuff.mPtrEnd }); + mPieces.pushUndoPoint(); +} + +TextEditor::TextEditor() : mOrigBuff({ 0, 0 }) { +} + +Index TextEditor::mNLines() const { + return mPieces.ptrSum().mLine; +} + +Pointer TextEditor::getCursor() const { + return mCursor; +} + +Pointer TextEditor::getCursorEnd() const { + return mCursorEnd; +} + +void TextEditor::insert(Input aInput) { + // handle selectiom + remove(true); + + // save input to addbuff + auto start_ref = mAddBuff.endPtr(); + mAddBuff.append(aInput); + auto end_ref = mAddBuff.endPtr(); + auto input_diff = end_ref - start_ref; + + PiecesDS::Node* prev_node = nullptr; + PiecesDS::Node* new_node = nullptr; + + auto crs = findPiece(mCursor); + + if (crs.piece && crs.piece->mPrev && crs.offset == Pointer{ 0, 0 }) { + crs.piece_ptr = prevPiecePtr(crs.piece, crs.piece_ptr); + crs.offset = crs.ptr - crs.piece_ptr; + crs.piece = crs.piece->mPrev; + } + + if (crs.piece) { + if (crs.ptr == crs.piece_ptr) { // start + prev_node = crs.piece->mPrev; + } + else if (crs.offset == crs.piece->mPiece->diffPtr()) { // end + if (mLastInsertionCrs == crs.ptr && crs.piece->mPiece->mAddedBuff && crs.piece->mPiece->mEnd == start_ref) { + crs.piece->mPiece->mEnd += input_diff; + mPieces.update_cache(crs.piece); + goto END; + } + else { + prev_node = crs.piece; + } + } + else { // middle + prev_node = splitPiece(crs.piece, crs.offset); + } + } + + // create the new node + new_node = mPieces.insertAfter(prev_node, { true, start_ref, end_ref }); + + END: + mCursor = mCursor + input_diff; + mCursorEnd = mCursor; + mLastInsertionCrs = mCursor; + mPieces.pushUndoPoint(); +} + +PiecesDS::Node* TextEditor::splitPiece(PiecesDS::Node* piece, Pointer split_ptr) { + auto span = piece->mPiece->diffPtr(); + + if (span == split_ptr || split_ptr == Pointer{ 0, 0 }) { + return nullptr; + } + + // save temp data + auto end_ptr = piece->mPiece->mEnd; + auto left_piece = Piece{ piece->mPiece->mAddedBuff, piece->mPiece->mStart, piece->mPiece->mStart + split_ptr }; + auto left_node = mPieces.insertAfter(piece->mPrev, left_piece); + + // create right half + auto right_node = mPieces.insertAfter(left_node, { left_node->mPiece->mAddedBuff, left_node->mPiece->mEnd, end_ptr }); + + auto line_len = halni(getBuffLine(right_node->mPiece, 1) - getBuffLine(right_node->mPiece, 0)); + if (line_len == right_node->mPiece->mStart.mCol) { + DBG_BREAK(1); + right_node->mPiece->mStart = { right_node->mPiece->mStart.mLine + 1, 0 }; + } + + // remove current node + mPieces.remove(right_node->mNext); + + return left_node; +} + +void TextEditor::remove(bool only_selection, Dir dir, DirUnit unit) { + if (!mPieces.nPieces()) { + return; + } + + if (mCursor == mCursorEnd) { + if (only_selection) { + return; + } + + if (dir == LEFT) { + if (mCursor == Pointer(0, 0)) { + return; + } + + PieceCrs crs; + if (mCursor.mCol == nullptr) { + auto new_ptr = Pointer{ mCursor.mLine - 1, ENV_HALNI_MAX }; + clampPtr(new_ptr); + crs = findPiece(new_ptr); + //crs = findPiece({ crs.ptr.mLine, crs.ptr.mCol - 1 }); + } + else { + crs = findPiece({ mCursor.mLine, mCursor.mCol - 1 }); + } + + mCursorEnd = mCursor; + mCursor = crs.ptr; + } + else if (dir == RIGHT) { + auto crs = findPiece(Pointer{ mCursor.mLine, mCursor.mCol + 1 }); + mCursorEnd = mCursor; + mCursor = crs.ptr; + + if (mCursor == mCursorEnd) { + return; + } + } + else { + return; + } + } + + if (mCursor > mCursorEnd) { + swap(mCursor, mCursorEnd); + } + + auto crs = findPiece(mCursor); + + // first and last pieces to be deleted + PiecesDS::Node* del_start = nullptr, *del_end = nullptr; + + // find left + if (crs.offset == Pointer{ 0, 0 }) { + del_start = crs.piece; + } + else if (crs.piece->mPiece->diffPtr() == crs.offset) { + del_start = crs.piece->mNext; + } + else { + del_start = splitPiece(crs.piece, crs.offset)->mNext; + } + + auto crs_end = findPiece(mCursorEnd); + + // find right + if (crs_end.offset == Pointer{ 0, 0 }) { + del_end = crs_end.piece->mPrev; + } + else if (crs_end.piece->mPiece->diffPtr() == crs_end.offset) { + del_end = crs_end.piece; + } + else { + bool intersect = crs_end.piece == del_start; + del_end = splitPiece(crs_end.piece, crs_end.offset); + if (intersect) { + del_start = del_end; + } + } + + DEBUG_ASSERT(del_start && del_end); + auto end_piece = del_end->mNext ? del_end->mNext->mPiece : nullptr; + + while (del_start && del_start->mPiece != end_piece) { + auto del = del_start; + + // del_start will be reused by the avl-tree after deletion otherwise + // works as we go from left to right + if (!del_start->mNext || del_start->mHeight <= del_start->mNext->mHeight) { + del_start = del_start->mNext; + } + + mPieces.remove(del); + } + mCursorEnd = mCursor; + + mPieces.pushUndoPoint(); +} + +void TextEditor::undo() { + mPieces.undo(); +} + +void TextEditor::redo() { + mPieces.redo(); +} + +TextEditor::~TextEditor() { +} + +// ------------------------------ ITERATOR ------------------------------ // + +Iterator TextEditor::begin() { return Iterator(this); } +alni TextEditor::end() { return nullptr; } + +Iterator::Iterator(const TextEditor* aSelf) { + mSelf = aSelf; + mNode = mSelf->mPieces.first(); + mVal = mSelf->getPieceLine(mNode->mPiece, 0)[0]; +} + +bool Iterator::inputLeft() { + return mNode; +} + +Char Iterator::character() { + return mVal; +} + +void Iterator::operator++() { + auto nl = isNewLineChar(mVal); + auto diff = Pointer{ nl, !nl }; + mPtr += diff; + mOffset += diff; + + if (mOffset == mNode->mPiece->diffPtr()) { + mNode = mNode->mNext; + mOffset = { 0, 0 }; + if (!mNode) { + return; + } + } + + mVal = mSelf->getPieceLine(mNode->mPiece, mOffset.mLine)[mOffset.mCol]; +} + +bool Iterator::operator!=(alni const& iter) { + return inputLeft(); +} + +bool Iterator::operator==(alni const& iter) { + return !inputLeft(); +} + +Char Iterator::operator->() { return mVal; } +const Iterator& Iterator::operator*() { return *this; } diff --git a/Strings/public/Logging.hpp b/Strings/public/Logging.hpp new file mode 100644 index 0000000..6616e72 --- /dev/null +++ b/Strings/public/Logging.hpp @@ -0,0 +1,59 @@ + +#pragma once + +#include "Buffer.hpp" +#include "List.hpp" +#include "Strings.hpp" + +namespace tp { + + class Logger { + public: + + class Report { + String mData; + Buffer mLineOffsets; + + public: + enum Type { INFO, ERR, WARN, SUC } mType = INFO; + + public: + Report(); + explicit Report(const String& text); + Report(const String& text, Type type); + + public: + [[nodiscard]] ualni getLineCount() const; + [[nodiscard]] String getString() const; + + private: + void calcLineCount(); + }; + + private: + List::Node* mCursor = nullptr; + List mBuff; + ualni mLineCount = 0; + + public: + void write(const String& in, bool post = true, Report::Type type = Report::Type::INFO); + + [[nodiscard]] String read(); + [[nodiscard]] const List& getBuff() const { return mBuff; } + [[nodiscard]] ualni getLineCount() const { return mLineCount; } + + public: + static void initializeGlobal(); + static void deinitializeGlobal(); + }; + + extern Logger* gLogger; +} + +#define LOG(val) tp::str::gLogger->write((val), 1) + +#ifdef ENV_BUILD_DEBUG +#define DEBUG_LOG(val) tp::str::gLogger->write((val), 1) +#else +#define DEBUG_LOG(val) (0) +#endif \ No newline at end of file diff --git a/Strings/public/StringLogic.hpp b/Strings/public/StringLogic.hpp new file mode 100644 index 0000000..488349a --- /dev/null +++ b/Strings/public/StringLogic.hpp @@ -0,0 +1,174 @@ +#pragma once + +#include "Utils.hpp" + +namespace tp { + + template + class StringLogic { + public: + + typedef halni Index; + typedef Index LineId; + typedef Index ColumnId; + + static inline bool isNewLineChar(tChar in) { return (in == '\n') || (in == '\r'); } + static inline bool isEndChar(tChar in) { return in == '\0'; } + static constexpr tChar getEndChar() { return '\0'; } + + static ualni calcLength(const tChar* in) { + const tChar* iter = in; + while (*iter != '\0') iter++; + return iter - in; + } + + static tChar* insertLogic(const tChar* cur, const tChar* tar, ualni at, ualni len) { + ualni curLen = calcLength(cur); + ualni allLen = curLen + len; + auto* out = new tChar[allLen + 1]; + + DEBUG_ASSERT(curLen >= 0) + DEBUG_ASSERT(len >= 0) + DEBUG_ASSERT(at < curLen + 1 && at >= 0) + + for (ualni idx = 0; idx < at; idx++) { + out[idx] = cur[idx]; + } + for (ualni idx = 0; idx < len; idx++) { + out[idx + at] = tar[idx]; + } + for (ualni idx = at + len; idx < allLen; idx++) { + out[idx] = cur[idx - len]; + } + out[allLen] = '\0'; + return out; + } + + static tChar* removeLogic(const tChar* cur, ualni start, ualni end) { + ualni curLen = calcLength(cur); + ualni rangeLen = end - start; + ualni newLen = curLen - rangeLen; + auto* out = new tChar[newLen + 1]; + out[newLen] = '\0'; + for (ualni idx = 0; idx < start; idx++) { + out[idx] = cur[idx]; + } + for (ualni idx = end; idx < curLen; idx++) { + out[idx - rangeLen] = cur[idx]; + } + return out; + } + + static bool isEqualLogic(const tChar* left, const tChar* right) { + ualni idx = 0; + LOOP: + if (left[idx] == '\0' || right[idx] == '\0') { + if (left[idx] == '\0' && right[idx] == '\0') { + return true; + } + return false; + } + if (left[idx] != right[idx]) { + return false; + } + idx++; + goto LOOP; + } + + static ualni fromValueLength(alni val, ualni base) { + ualni iter = val; + ualni len = 0; + while (iter /= base) { + len++; + } + bool neg = val < 0; + len += 1 + ualni(neg); + return len; + } + + static tChar* stringFromValue(alni val, tChar* ownBuff, ualni base) { + tChar* out; + bool neg = val < 0; + ualni len = fromValueLength(val, base); + out = ownBuff ? ownBuff : new tChar[len + 1]; + out[len] = '\0'; + val = abs(val); + for (ualni i = len - 1; i >= int(neg); i--) { + out[i] = (tChar) (val % base + 48); + val /= (alni) base; + } + if (neg) { + out[0] = '-'; + } + return out; + } + + static ualni fromValueLength(alnf val, ualni base) { + auto rounded = (alni) val; + ualni mantissa = ( (alni) val - rounded) * 100000; + alni rounded_len = 0; + alni mantissa_len = 0; + while (rounded /= (alni) base) { + rounded_len++; + } + if (!rounded_len) { + rounded_len++; + } + while (mantissa /= base) { + mantissa_len++; + } + bool neg = val < 0; + bool dot = mantissa_len & 1; + alni tot_len = mantissa_len + rounded_len + dot + neg; + return tot_len; + } + + static tChar* fromValue(alnf, ualni, tChar*) { + DEBUG_BREAK(false) + return nullptr; + } + + static ualni fromValueLength(bool val) { + return val ? 4 : 5; + } + + static tChar* fromValue(bool val, tChar* ownBuff) { + alni len = val ? 4 : 5; + tChar* out = ownBuff ? ownBuff : new tChar[len + 1]; + out[len] = '\0'; + memCopy(out, val ? "True" : "False", len); + return out; + } + + static tChar* stringFromValue(int val, ualni base) { + return fromValue((alni) val, nullptr, base); + } + + static tChar* fromValue(tChar val) { + auto* out = new tChar[2]; + out[1] = '\0'; + out[0] = val; + return out; + } + + static bool toValue(const tChar*, alni&, ualni) { + DEBUG_BREAK(false) + return false; + } + + static bool toValue(const tChar*, alnf&, ualni) { + DEBUG_BREAK(false) + return false; + } + + static bool toValue(const tChar* in, bool& val) { + if (!calcLength(in)) return false; + if (isEqual(in, "False") || isEqual(in, "false") || isEqual(in, "0")) { + val = false; + } else { + val = true; + } + return true; + } + }; +} \ No newline at end of file diff --git a/Strings/public/Strings.hpp b/Strings/public/Strings.hpp new file mode 100644 index 0000000..3fdb5a4 --- /dev/null +++ b/Strings/public/Strings.hpp @@ -0,0 +1,248 @@ +#pragma once + +#include "PoolAllocator.hpp" +#include "StringLogic.hpp" +#include "TextEditor.hpp" + +namespace tp { + + // Static data + extern ModuleManifest gModuleStrings; + + template + class StringTemplate; + using String = StringTemplate; + + template + class StringTemplate { + typedef StringLogic StringLogic; + + // Hidden slave class + class StringData { + + // allows to edit each string with text editor inplace + class TextEditors { + enum { MAX_EDITED_STRINGS = 65536 }; // 2^16 - do not change mindlessly + typedef uint2 Address; + ChunkAlloc mEditorsPool; + + public: + TextEditors() : mEditorsPool() {} + + TextEditor* get(Address address) { return mEditorsPool.getBuff() + address; } + + Address createTextEditor(const tChar* originalBuffer) { + auto entry = (TextEditor**) mEditorsPool.allocate(0); + *entry = new TextEditor( originalBuffer ); + return Address(entry - mEditorsPool.getBuff()); + } + + void destroyTextEditor(Address address) { + auto entry = mEditorsPool.getBuff() + address; + mEditorsPool.deallocate(entry); + delete entry; + } + }; + + static TextEditors sTextEditors; + + public: + uint2 mIsConst; // source is non-modifiable + uint2 mEditedIdx; // editor id + uint4 mReferenceCount; // number of users of this + tChar* mBuff; // actual string data + + public: + StringData() { + MODULE_SANITY_CHECK(gModuleStrings) + mBuff = (tChar*) "*"; + mReferenceCount = 0; + mIsConst = true; + mEditedIdx = 0; + } + + StringData(const tChar* aBuff, bool aIsConst) { + MODULE_SANITY_CHECK(gModuleStrings) + DEBUG_ASSERT(aBuff) + mBuff = (tChar*) aBuff; + mReferenceCount = 0; + mIsConst = aIsConst; + mEditedIdx = 0; + } + + explicit StringData(const tChar* aBuff) { + MODULE_SANITY_CHECK(gModuleStrings) + DEBUG_ASSERT(aBuff) + auto const len = StringLogic::calcLength(aBuff); + resize(len); + memCopy(mBuff, aBuff, len); + mReferenceCount = 0; + mIsConst = false; + mEditedIdx = 0; + } + + [[nodiscard]] tChar* getBuffer() { + DEBUG_ASSERT(!isEditorMode()) + if (isEditorMode()) return nullptr; + return mBuff; + } + + ~StringData() { + if (!mIsConst) delete[] mBuff; + if (isEditorMode()) sTextEditors.destroyTextEditor(mEditedIdx); + } + + public: + void resize(ualni size) { + DEBUG_ASSERT(!isEditorMode() && mReferenceCount == 1 && !mIsConst) + delete[] mBuff; + mBuff = new tChar[size + 1]; + mBuff[size] = StringLogic::getEndChar(); + } + + public: + [[nodiscard]] bool isEditorMode() const { return mEditedIdx; } + + void createEditor() { + DEBUG_ASSERT(!isEditorMode()) + if (isEditorMode()) mEditedIdx = sTextEditors.createTextEditor(mBuff); + } + + [[nodiscard]] TextEditor* getEditor() const { + DEBUG_ASSERT(isEditorMode()) + if (!isEditorMode()) return nullptr; + return sTextEditors.get(mEditedIdx); + } + + void applyEditor() { + DEBUG_ASSERT(isEditorMode() && mReferenceCount == 1 && !mIsConst) + if (!isEditorMode()) return; + auto editor = sTextEditors.get(mEditedIdx); + resize(editor.textSize()); + for ( auto character : *editor) { + mBuff[character.idx] = character; + } + } + + void destroyEditor() { + DEBUG_ASSERT(isEditorMode()) + if (!isEditorMode()) return; + sTextEditors.destroyTextEditor(mEditedIdx); + } + }; + + private: + class StringData* mStringData; + + private: + enum { STRINGS_POOL_SIZE = 1024, }; + static PoolAlloc* sStringPool; + static StringData sNullString; + + // --------------------------------- Main Interface ---------------------------------// + public: + // Creates null string reference + StringTemplate() { + mStringData = &sNullString; + incReference(mStringData); + } + + // References existing string data + StringTemplate(const StringTemplate& in) { + if (in.isEditorMode()) { + // Copies raw data and claims ownership over it + mStringData = new (sStringPool->allocate(0)) StringData(in.mStringData->mBuff); + incReference(mStringData); + } else { + mStringData = in.mStringData; + incReference(mStringData); + } + } + + // Creates new string data from raw data pointer. + // Does not own string buffer - string buffer will not be freed. Initializes as const memory. + explicit StringTemplate(const tChar* raw) { + mStringData = new (sStringPool->allocate(0)) StringData(raw, true); + incReference(mStringData); + } + + // Copies raw data and claims ownership over it + explicit StringTemplate(tChar* raw) { + mStringData = new (sStringPool->allocate(0)) StringData(raw); + incReference(mStringData); + } + + ~StringTemplate() { + decReference(mStringData); + } + + private: + + [[nodiscard]] bool isEditorMode() const { + return mStringData->isEditorMode(); + } + + static void incReference(StringData* dp) { + dp->mReferenceCount++; + } + + void decReference(StringData* dp) { + dp->mReferenceCount--; + if (!dp->mReferenceCount) { + sStringPool->deallocate(mStringData); + mStringData->~StringData(); + } + } + + public: // Access + // used to read data + [[nodiscard]] const tChar* read() const { + return mStringData->mBuff; + } + + // used to write data + // output will be null if in TextEditing mode + [[nodiscard]] tChar* write() { + makeModifiable(); + return mStringData->getBuffer(); + } + + // output will be null if in TextEditing mode + [[nodiscard]] tChar* resize() { + if (!mStringData->getEditor()) mStringData->resize(); + return mStringData->getBuffer(); + } + + public: // TextEditor Mode + // Enters TextEditor Mode + void createEditor() { mStringData->createEdited(); } + + // Access to TextEditor Context & Interface + [[nodiscard]] TextEditor* getEditor() { return mStringData->getEdited(); } + [[nodiscard]] const TextEditor* getEditor() const { return mStringData->getEdited(); } + + // Applies TextEditor Mode To Current buffer + void applyEditor() { mStringData->applyEditor(); } + + // Leaves TextEditor Mode + void destroyEditor() { mStringData->destroyEditor(); } + + private: + void makeModifiable() { + // We have no rights to modify if someone references that memory too + // So we need to create new copy of StringData + if (mStringData->mReferenceCount > 1) { + mStringData = new (sStringPool->allocate(0)) StringData(mStringData->mBuff); + incReference(mStringData); + return; + } + + // Also if initial raw data was marked as const - create copy of raw data + // Note - raw data will be lost at this point if no other record of such is stored + if (mStringData->mIsConst) { + mStringData = new (sStringPool->allocate(0)) StringData(mStringData->mBuff); + incReference(mStringData); + } + } + }; +} \ No newline at end of file diff --git a/Strings/public/TextEditor.hpp b/Strings/public/TextEditor.hpp new file mode 100644 index 0000000..e1b1dc1 --- /dev/null +++ b/Strings/public/TextEditor.hpp @@ -0,0 +1,382 @@ + +#pragma once + +#include "Strings.hpp" +#include "Buffer.hpp" +#include "Tree.hpp" + +namespace tp { + + class PiecesDS { + + struct Input { + const Char* mInput = nullptr; + Index mLen = 0; + void getLineOffsets(Buffer& out); + }; + + struct Pointer { + LineId mLine; + ColumnId mCol; + + Pointer(); + Pointer(LineId aLine, ColumnId aCol); + Pointer(const Input& aInput); + void operator+=(Char achar); + void operator+=(const Pointer& ap); + Pointer operator-(const Pointer in) const; + Pointer operator+(const Pointer& ap) const; + bool operator==(const Pointer& in) const; + bool operator>(const Pointer& in) const; + bool operator>=(const Pointer& in) const; + }; + + struct Piece { + bool mAddedBuff = false; + Pointer mStart; + Pointer mEnd; + + Pointer diffPtr() const; + LineId nLines() const; + }; + + public: + + // Tree Node : Based on AvlNodeBlueprint + struct Node { + + Node* mLeft = nullptr; + Node* mRight = nullptr; + Node* mParent = nullptr; + halni mHeight = 0; + + Pointer mPtrLeft; + Pointer mPtrRight; + Pointer mDiff; + + Node* mNext = nullptr; + Node* mPrev = nullptr; + Piece* mPiece = nullptr; + + static bool mInsertionOperation; + + Node() {} + Node(Pointer aKey) { + //mVal = aKey; + } + + bool disentRight(Pointer aKey) const { + return aKey > mPtrLeft; + } + + bool disentLeft(Pointer aKey) const { + return mPtrLeft > aKey; + } + + bool exactNode(Pointer aKey) const { + if (mInsertionOperation) { + return aKey > mPtrLeft && mPtrLeft + mDiff > aKey; + } + return aKey >= mPtrLeft && mPtrLeft + mDiff > aKey; + } + + Pointer getFindKey(Node* node = nullptr) const { + // node must be ancestor of this node + + Pointer out = mPtrLeft; + + if (node == this) { + return out; + } + + auto iter = this; + while (iter->mParent != node) { + if (iter == iter->mParent->mRight) { + auto left_sub_tree = iter->mParent->mPtrLeft + iter->mParent->mDiff; + out = left_sub_tree + out; + } + iter = iter->mParent; + } + + if (node) { + if (iter == iter->mParent->mRight) { + auto left_sub_tree = iter->mParent->mPtrLeft + iter->mParent->mDiff; + out = left_sub_tree + out; + } + } + return out; + } + + void copyData(const Node& in) { + mPiece = in.mPiece; + mNext = in.mNext; + mPrev = in.mPrev; + + if (mPrev) { + mPrev->mNext = this; + } + + if (mNext) { + mNext->mPrev = this; + } + } + + Pointer keyInRightSubtree(Pointer key) const { return key - (mPtrLeft + mDiff); } + Pointer keyInLeftSubtree(Pointer key) const { return key; } + + void updateTreeCahceCallBack() { + DEBUG_ASSERT(mPiece); + mDiff = mPiece->diffPtr(); + if (mLeft) { + mPtrLeft = mLeft->sum(); + } + else { + mPtrLeft = { 0, 0 }; + } + + if (mRight) { + mPtrRight = mRight->sum(); + } + else { + mPtrRight = { 0, 0 }; + } + } + + Pointer sum() const { + return (mPtrLeft + mDiff) + mPtrRight; + } + }; + + private: + + AvlTree mTree; + + public: + + PiecesDS() { + } + + Node* find(Pointer ptr) const { + return mTree.find(ptr); + } + + Node* minNode(Node* node) const { + return mTree.minNode(node); + } + + Node* maxNode(Node* node) const { + return mTree.maxNode(node); + } + + Node* head() const { + return mTree.head(); + } + + void insert(const Node& node, Pointer ptr) { + Node::mInsertionOperation = true; + mTree.insert(ptr, node); + Node::mInsertionOperation = false; + } + + Node* insertAfter(Node* node, Piece piece) { + auto key = node ? (node->getFindKey() + node->mDiff) : Pointer{ 0, 0 }; + auto new_node = Node(); + new_node.mPiece = new Piece(piece); + + insert(new_node, key); + +#ifdef _DEBUG + auto err_node = mTree.isInvalid(mTree.head()); + DBG_BREAK(err_node); +#endif + + auto out = mTree.find(key); + + Node* next = out->mRight; + Node* prev = out->mLeft; + + if (!(next || prev) && out->mParent) { + if (out->mParent->mLeft == out) { + next = out->mParent; + } + else { + prev = out->mParent; + } + } + + if (next) { + out->mNext = next; + out->mPrev = next->mPrev; + next->mPrev = out; + if (out->mPrev) out->mPrev->mNext = out; + } + else if (prev) { + out->mPrev = prev; + out->mNext = prev->mNext; + prev->mNext = out; + if (out->mNext) out->mNext->mPrev = out; + } + + return out; + } + + void remove(Node* aNode) { + auto key = aNode->getFindKey(); + + if (aNode->mPrev) { + aNode->mPrev->mNext = aNode->mNext; + } + + if (aNode->mNext) { + aNode->mNext->mPrev = aNode->mPrev; + } + + auto del = aNode->mPiece; + mTree.remove(key); + delete del; + +#ifdef _DEBUG + auto err_node = mTree.isInvalid(mTree.head()); + DBG_BREAK(err_node); +#endif + } + + Node* first() const { + return mTree.minNode(mTree.head()); + } + + Pointer ptrSum() const { + if (mTree.head()) { + return mTree.head()->sum(); + } + return { 0, 0 }; + } + + halni nPieces() const { + return mTree.size(); + } + + void update_cache(Node* node) { + if (node) { + node->updateTreeCahceCallBack(); + update_cache(node->mParent); + } + } + + void undo() {} + void redo() {} + void pushUndoPoint() {} + }; + + struct AddBuff { + Buffer mBuff; + Buffer mLineOffsets; + Index mColumn = 0; + + AddBuff(); + void append(Input aInput); + Pointer endPtr(); + }; + + struct OrigBuff { + bool mOwnsInput = false; + Input mOriginal; + Buffer mLineOffsets; + + Pointer mPtrStart; + Pointer mPtrEnd; + + // does not owns the original input by default + OrigBuff(Input aOriginal); + + void makeOwnOriginalInput(); + + ~OrigBuff(); + }; + + struct PieceCrs { + Pointer piece_ptr; + Pointer offset; + Pointer ptr; + PiecesDS::Node* piece = nullptr; + }; + + class TextEditor { + friend class Iterator; + + class Iterator { + friend TextEditor; + + const TextEditor* mSelf; + PiecesDS::Node* mNode = nullptr; + Pointer mOffset = { 0, 0 }; + Pointer mPtr = { 0, 0 }; + Char mVal = 0; + + Iterator(const TextEditor* mSelf); + bool inputLeft(); + + public: + + Char character(); + void operator++(); + bool operator!=(alni const& iter); + bool operator==(alni const& iter); + Char operator->(); + const Iterator& operator*(); + }; + + public: + + // pieces of strings that construct the text + PiecesDS mPieces; + + // string buffers to wich pieces can reffer + OrigBuff mOrigBuff; + AddBuff mAddBuff; + + // active position on the text + Pointer mCursor; + Pointer mCursorEnd; + + Pointer mLastInsertionCrs; + //private: + + [[nodiscard]] PieceCrs findPiece(Pointer ptr) const; + [[nodiscard]] Pointer prevPiecePtr(PiecesDS::Node* piece_node, Pointer piece_pointer) const; + [[nodiscard]] Pointer nextPiecePtr(PiecesDS::Node* piece_node, Pointer piece_pointer) const; + + [[nodiscard]] const Char* getBuffLine(const Piece*, LineId) const; + [[nodiscard]] const Char* getPieceLine(const Piece*, LineId) const; + [[nodiscard]] PiecesDS::Node* splitPiece(PiecesDS::Node* piece, Pointer split_ptr); + [[nodiscard]] LineId getPieceLineLen(Piece* aPiece, LineId line) const; + + public: + enum Dir : uint1 { LEFT, RIGHT, UP, DOWN }; + enum DirUnit : uint1 { CHAR, WORD, TOK, LINE_END, LINE_START }; + + public: + + TextEditor(); + explicit TextEditor(Input original); + + [[nodiscard]] Index mNLines() const; + void clampPtr(Pointer& ptr) const; + [[nodiscard]] Pointer getCursor() const; + [[nodiscard]] Pointer getCursorEnd() const; + void setCursor(Pointer ptr); + void setCursorEnd(Pointer ptr); + + void insert(Input aInput); + + void remove(bool only_selection = false, Dir dir = LEFT, DirUnit unit = CHAR); + + void undo(); + void redo(); + + Iterator begin(); + alni end(); + + ~TextEditor(); + }; +} \ No newline at end of file diff --git a/Strings/tests/tests.cpp b/Strings/tests/tests.cpp new file mode 100644 index 0000000..2e9f769 --- /dev/null +++ b/Strings/tests/tests.cpp @@ -0,0 +1,66 @@ + + +#include "common.h" + +#include "allocators.h" + +#include "strings.h" + +#include + +#include +#include +#include + +#include "TextEditor.hpp" + +#include "Logging.hpp" + +using namespace tp; + +void test_conversions() { + + for (auto i : tp::Range(1000)) { + + auto sign = bool(std::rand() % 2) ? 1 : -1; + + auto floating = std::rand() / (tp::alnf)std::rand() * sign; + auto tmp = std::to_string(floating); + auto left = tp::string(tmp.c_str()); + auto right = tp::string(floating); + DBG_BREAK(left != right); + auto back = (tp::alnf)right; + //DBG_BREAK(back != floating); + + auto integer = std::rand() * sign; + tmp = std::to_string(integer); + left = tp::string(tmp.c_str()); + right = tp::string(integer); + DBG_BREAK(left != right); + back = (tp::alni)right; + DBG_BREAK(back != integer); + } +} + +void test_editor() { + tp::string str = " initial "; + str.createEdited(); + auto edited = str.getEdited(); + edited->setCursor({0, 4}); + edited->insert({"aaa", 3}); + str.saveEdited(); + str.clearEdited(); + + GLog->write(str, true); +} + +int main() { + tp::alloc_init(); + string::Initialize(); + Logger::init(); + //test_conversions(); + test_editor(); + Logger::deinit(); + string::UnInitialize(); + tp::alloc_uninit(); +} \ No newline at end of file diff --git a/TODO b/TODO index 7811a43..7ea9460 100644 --- a/TODO +++ b/TODO @@ -7,7 +7,7 @@ Containers: Buffer 2d ! Add check copy times AVL dont copy data just swap - More tests on buff and avl + More tests on mBuff and avl Strings: Implement