#include "myirc/ircmsg.hpp" #include #include namespace { class Parser { char *msg_; inline static char empty[1]; inline void trim() { while (*msg_ == ' ') msg_++; } public: Parser(char *msg) : msg_(msg) { if (msg_ == nullptr) { msg_ = empty; } else { trim(); } } char *word() { const auto start = msg_; while (*msg_ != '\0' && *msg_ != ' ') msg_++; if (*msg_ != '\0') { // prepare for next token *msg_++ = '\0'; trim(); } return start; } bool match(char c) { if (c == *msg_) { msg_++; return true; } return false; } bool isempty() const { return *msg_ == '\0'; } const char *peek() { return msg_; } }; std::string_view unescape_tag_value(char *const val) { // only start copying at the first escape character // skip everything before that auto cursor = strchr(val, '\\'); if (cursor == nullptr) { return {val}; } auto write = cursor; for (; *cursor; cursor++) { if (*cursor == '\\') { cursor++; switch (*cursor) { default: *write++ = *cursor; break; case ':': *write++ = ';'; break; case 's': *write++ = ' '; break; case 'r': *write++ = '\r'; break; case 'n': *write++ = '\n'; break; case '\0': return {val, write}; } } else { *write++ = *cursor; } } return {val, write}; } } // namespace namespace myirc { auto parse_irc_tags(char *str) -> std::vector { std::vector tags; do { auto val = strsep(&str, ";"); auto key = strsep(&val, "="); if ('\0' == *key) { throw irc_parse_error(irc_error_code::MISSING_TAG); } if (nullptr == val) { tags.emplace_back(key, ""); } else { tags.emplace_back(std::string_view{key, val - 1}, unescape_tag_value(val)); } } while (nullptr != str); return tags; } auto parse_irc_message(char *msg) -> IrcMsg { Parser p{msg}; IrcMsg out; /* MESSAGE TAGS */ if (p.match('@')) { out.tags = parse_irc_tags(p.word()); } /* MESSAGE SOURCE */ if (p.match(':')) { out.source = p.word(); } /* MESSAGE COMMANDS */ out.command = p.word(); if (out.command.empty()) { throw irc_parse_error{irc_error_code::MISSING_COMMAND}; } /* MESSAGE ARGUMENTS */ while (!p.isempty()) { if (p.match(':')) { out.args.push_back(p.peek()); break; } out.args.push_back(p.word()); } return out; } auto IrcMsg::hassource() const -> bool { return source.data() != nullptr; } auto operator<<(std::ostream &out, irc_error_code code) -> std::ostream & { switch (code) { case irc_error_code::MISSING_COMMAND: out << "MISSING COMMAND"; return out; case irc_error_code::MISSING_TAG: out << "MISSING TAG"; return out; default: return out; } } } // namespace myirc