aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2019-01-24 23:10:59 +0100
committerdec05eba <dec05eba@protonmail.com>2020-08-18 23:38:23 +0200
commit2446736bc775b22cf5aaae88c2c69c9504e5eb17 (patch)
tree54f1b892dbd3c38b05dcd81c33ffe7ae9c84d6e7
parent8fb1062b263c90c22b2cd6a22e9031eef71240ef (diff)
Move message composer from dchat_gtk to dchat_core
-rw-r--r--include/dchat/MessageComposer.hpp32
-rw-r--r--src/MessageComposer.cpp160
-rw-r--r--tests/main.cpp132
3 files changed, 322 insertions, 2 deletions
diff --git a/include/dchat/MessageComposer.hpp b/include/dchat/MessageComposer.hpp
new file mode 100644
index 0000000..b4b55c2
--- /dev/null
+++ b/include/dchat/MessageComposer.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "types.hpp"
+#include <functional>
+
+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 char *text, usize size, std::function<void(MessagePart)> callbackFunc);
+} \ No newline at end of file
diff --git a/src/MessageComposer.cpp b/src/MessageComposer.cpp
new file mode 100644
index 0000000..71fa62e
--- /dev/null
+++ b/src/MessageComposer.cpp
@@ -0,0 +1,160 @@
+#include "../include/dchat/MessageComposer.hpp"
+#include <assert.h>
+#include <string.h>
+
+namespace dchat
+{
+ enum class Token
+ {
+ NONE,
+ END_OF_FILE,
+
+ TEXT,
+ TYPE
+ };
+
+ struct Tokenizer
+ {
+ Tokenizer(const char *_text, usize _size)
+ {
+ assert(_text);
+ text = _text;
+ size = _size;
+ index = 0;
+ identifierRange = { 0, 0 };
+ typeRange = { 0, 0 };
+ typeDataRange = { 0, 0 };
+ }
+
+ enum class EnclosedType
+ {
+ TEXT,
+ DATA
+ };
+
+ EnclosedType parseEnclosedData(char endSymbol)
+ {
+ bool foundEndOfType = false;
+
+ while(index < size)
+ {
+ char c = getChar();
+ ++index;
+ if(c == endSymbol)
+ {
+ foundEndOfType = true;
+ break;
+ }
+ }
+
+ if(!foundEndOfType)
+ return EnclosedType::TEXT;
+
+ return EnclosedType::DATA;
+ }
+
+ Token next()
+ {
+ if(index >= size)
+ return Token::END_OF_FILE;
+
+ char c = getChar();
+ if(c == '[')
+ {
+ usize start = index;
+ ++index;
+ if(parseEnclosedData(']') == EnclosedType::TEXT)
+ {
+ identifierRange.start = start;
+ identifierRange.end = index;
+ return Token::TEXT;
+ }
+
+ if(index == size || getChar() != '(')
+ {
+ identifierRange.start = start;
+ identifierRange.end = index;
+ return Token::TEXT;
+ }
+
+ typeRange.start = start + 1;
+ typeRange.end = index - 1;
+ typeDataRange.start = index + 1;
+
+ ++index;
+ 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 < size)
+ {
+ c = getChar();
+ if(c == '[')
+ break;
+ ++index;
+ }
+ identifierRange.end = index;
+ return Token::TEXT;
+ }
+
+ assert(false);
+ return Token::NONE;
+ }
+
+ char getChar() const
+ {
+ assert(index < size);
+ return text[index];
+ }
+
+ const char *text;
+ usize size;
+ usize index;
+
+ Range identifierRange;
+ Range typeRange;
+ Range typeDataRange;
+ };
+
+ void compose(const char *text, usize size, std::function<void(MessagePart)> callbackFunc)
+ {
+ Tokenizer tokenizer(text, size);
+ 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(tokenizer.typeRange.length() == 5 && memcmp(text + tokenizer.typeRange.start, "emoji", 5) == 0)
+ {
+ callbackFunc(MessagePart { MessagePart::Type::EMOJI, tokenizer.typeDataRange });
+ }
+ else
+ {
+ Range typeToTextRange = { tokenizer.typeRange.start - 1, tokenizer.typeDataRange.end + 1 };
+ callbackFunc(MessagePart{ MessagePart::Type::TEXT, typeToTextRange });
+ }
+ token = tokenizer.next();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/main.cpp b/tests/main.cpp
index 9ad80a6..9f33a05 100644
--- a/tests/main.cpp
+++ b/tests/main.cpp
@@ -1,7 +1,135 @@
+#include <dchat/MessageComposer.hpp>
+#include <vector>
#include <stdio.h>
+#include <string.h>
+
+#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)
{
- printf("hello, world!\n");
+ std::vector<dchat::MessagePart> 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<const char*> expectText =
+ {
+ "abc ",
+ "cool",
+ " def"
+ };
+
+ int index = 0;
+ const char *str = "abc [emoji](cool) def";
+ dchat::usize strSize = strlen(str);
+
+ 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, strSize, 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";
+ strSize = strlen(str);
+ dchat::compose(str, strSize, 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)";
+ strSize = strlen(str);
+ dchat::compose(str, strSize, 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";
+ strSize = strlen(str);
+ dchat::compose(str, strSize, compareFunc);
+ }
+
+ {
+ expect =
+ {
+ dchat::MessagePart { dchat::MessagePart::Type::TEXT, dchat::Range{ 0, 12 } },
+ dchat::MessagePart { dchat::MessagePart::Type::EMOJI, dchat::Range{ 20, 71 } }
+ };
+
+ expectText =
+ {
+ "Hello world ",
+ "https://discordemoji.com/assets/emoji/PeepoHide.png"
+ };
+
+ index = 0;
+ str = "Hello world [emoji](https://discordemoji.com/assets/emoji/PeepoHide.png)";
+ strSize = strlen(str);
+ dchat::compose(str, strSize, compareFunc);
+ }
+
return 0;
-}
+} \ No newline at end of file