xbot/myirc/ircmsg.cpp

187 lines
3.5 KiB
C++

#include <cstring>
#include <optional>
#include "ircmsg.hpp"
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
auto parse_irc_tags(char *str) -> std::vector<irctag>
{
std::vector<irctag> 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;
}
}