From b629e93b124fcad6635a508e47c7776bb0891d1b Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 20 Jan 2019 05:02:13 +0100 Subject: Start with custom emoji (binds) and tests --- include/ChatMessage.hpp | 3 +- include/MessageComposer.hpp | 32 +++++++++ src/MessageComposer.cpp | 159 ++++++++++++++++++++++++++++++++++++++++++++ tests/main.cpp | 111 +++++++++++++++++++++++++++++++ 4 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 include/MessageComposer.hpp create mode 100644 src/MessageComposer.cpp create mode 100644 tests/main.cpp diff --git a/include/ChatMessage.hpp b/include/ChatMessage.hpp index 5259a9c..a50a799 100644 --- a/include/ChatMessage.hpp +++ b/include/ChatMessage.hpp @@ -7,7 +7,6 @@ namespace dchat { - class User; class ChatMessage : public Gtk::Grid { public: @@ -18,4 +17,4 @@ namespace dchat Gtk::Label text; uint32_t timestampSeconds; }; -} \ No newline at end of file +} diff --git a/include/MessageComposer.hpp b/include/MessageComposer.hpp new file mode 100644 index 0000000..5af065d --- /dev/null +++ b/include/MessageComposer.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +namespace dchat +{ + struct Range + { + int start; + int end; + + int length() const + { + return end - start; + } + }; + + struct MessagePart + { + enum class Type + { + TEXT, + EMOJI + }; + + Type type; + Range textRange; + }; + + void compose(const Glib::ustring &text, std::function callbackFunc); +} \ No newline at end of file diff --git a/src/MessageComposer.cpp b/src/MessageComposer.cpp new file mode 100644 index 0000000..56ff925 --- /dev/null +++ b/src/MessageComposer.cpp @@ -0,0 +1,159 @@ +#include "../include/MessageComposer.hpp" +#include +#include + +namespace dchat +{ + enum class Token + { + NONE, + END_OF_FILE, + + TEXT, + TYPE + }; + + struct Tokenizer + { + Tokenizer(const Glib::ustring *_text) + { + assert(_text); + text = _text; + index = 0; + length = text->size(); + + identifierRange = { 0, 0 }; + } + + enum class EnclosedType + { + TEXT, + DATA + }; + + EnclosedType parseEnclosedData(char endSymbol) + { + ++index; + bool foundEndOfType = false; + + while(index < length) + { + char c = getChar(); + ++index; + if(c == endSymbol) + { + foundEndOfType = true; + break; + } + } + + if(!foundEndOfType || index == length) + return EnclosedType::TEXT; + + return EnclosedType::DATA; + } + + Token next() + { + if(index >= length) + return Token::END_OF_FILE; + + char c = getChar(); + if(c == '[') + { + usize start = index; + if(parseEnclosedData(']') == EnclosedType::TEXT) + { + identifierRange.start = start; + identifierRange.end = index; + return Token::TEXT; + } + + c = getChar(); + if(c != '(') + { + identifierRange.start = start; + identifierRange.end = index; + return Token::TEXT; + } + + typeRange.start = start + 1; + typeRange.end = index - 1; + typeDataRange.start = index + 1; + + switch(parseEnclosedData(')')) + { + case EnclosedType::TEXT: + { + identifierRange.start = start; + identifierRange.end = index; + return Token::TEXT; + } + case EnclosedType::DATA: + { + typeDataRange.end = index - 1; + return Token::TYPE; + } + } + } + else + { + identifierRange.start = index; + ++index; + while(index < length) + { + c = getChar(); + if(c == '[') + break; + ++index; + } + identifierRange.end = index; + return Token::TEXT; + } + + assert(false); + return Token::NONE; + } + + char getChar() const + { + assert(index < length); + return (*text)[index]; + } + + const Glib::ustring *text; + usize index; + usize length; + + Range identifierRange; + Range typeRange; + Range typeDataRange; + }; + + void compose(const Glib::ustring &text, std::function callbackFunc) + { + Tokenizer tokenizer(&text); + Token token = tokenizer.next(); + while(token != Token::END_OF_FILE) + { + if(token == Token::TEXT) + { + callbackFunc(MessagePart { MessagePart::Type::TEXT, tokenizer.identifierRange }); + token = tokenizer.next(); + } + else if(token == Token::TYPE) + { + if(text.compare(tokenizer.typeRange.start, tokenizer.typeRange.length(), "emoji") != 0) + { + Range typeToTextRange = { tokenizer.typeRange.start - 1, tokenizer.typeDataRange.end + 1 }; + callbackFunc(MessagePart{ MessagePart::Type::TEXT, typeToTextRange }); + } + else + { + callbackFunc(MessagePart { MessagePart::Type::EMOJI, tokenizer.typeDataRange }); + } + token = tokenizer.next(); + } + } + } +} \ No newline at end of file diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..719a9ea --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,111 @@ +#include "../include/MessageComposer.hpp" +#include +#include +#include + +#define REQUIRE(expr) do { if(!(expr)) { fprintf(stderr, "%s:%d: Assert failed: %s\n", __FILE__, __LINE__, #expr); exit(EXIT_FAILURE); } } while(0) +static void requireEqualValues(int a, int b, const char *file, int line) +{ + if(a != b) + { + fprintf(stderr, "%s:%d: Assert failed: %d == %d\n", file, line, a, b); + exit(EXIT_FAILURE); + } +} +#define REQUIRE_EQUAL(a, b) do { requireEqualValues((a), (b), __FILE__, __LINE__); } while(0) + +int main(int argc, char **argv) +{ + std::vector expect = + { + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::MessagePart { dchat::MessagePart::Type::EMOJI, dchat::Range{ 12, 16 } }, + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 17, 21 } } + }; + + std::vector expectText = + { + "abc ", + "cool", + " def" + }; + + int index = 0; + const char *str = "abc [emoji](cool) def"; + + auto compareFunc = [&str, &index, &expect, &expectText](dchat::MessagePart messagePart) + { + REQUIRE(index < expect.size()); + REQUIRE_EQUAL((int)messagePart.type, (int)expect[index].type); + REQUIRE_EQUAL(messagePart.textRange.start, expect[index].textRange.start); + REQUIRE_EQUAL(messagePart.textRange.end, expect[index].textRange.end); + int length = messagePart.textRange.end - messagePart.textRange.start; + if(strncmp(str + messagePart.textRange.start, expectText[index], length) != 0) + { + fprintf(stderr, "Assert failed: |%.*s| == |%.*s|\n", length, str + messagePart.textRange.start, length, expectText[index]); + exit(EXIT_FAILURE); + } + ++index; + }; + + dchat::compose(str, compareFunc); + + { + expect = + { + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 4, 20 } } + }; + + expectText = + { + "abc ", + "[emoji](cool def" + }; + + index = 0; + str = "abc [emoji](cool def"; + dchat::compose(str, compareFunc); + } + + { + expect = + { + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 4, 11 } }, + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 11, 22 } } + }; + + expectText = + { + "abc ", + "[emoji]", + " (cool def)" + }; + + index = 0; + str = "abc [emoji] (cool def)"; + dchat::compose(str, compareFunc); + } + + { + expect = + { + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 4 } }, + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 4, 20 } }, + dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 20, 24 } } + }; + + expectText = + { + "abc ", + "[notemoji](cool)", + " def" + }; + + index = 0; + str = "abc [notemoji](cool) def"; + dchat::compose(str, compareFunc); + } + return 0; +} -- cgit v1.2.3