xbot/myirc/ircmsg.cpp

187 lines
3.5 KiB
C++
Raw Normal View History

2023-11-22 19:59:34 -08:00
#include <cstring>
#include <optional>
#include "ircmsg.hpp"
namespace {
2025-01-25 15:45:31 -08:00
class Parser
{
char *msg_;
2023-11-22 19:59:34 -08:00
inline static char empty[1];
2025-01-25 15:45:31 -08:00
inline void trim()
{
while (*msg_ == ' ')
msg_++;
2023-11-22 19:59:34 -08:00
}
public:
2025-01-25 15:45:31 -08:00
Parser(char *msg)
: msg_(msg)
{
if (msg_ == nullptr)
{
2023-11-22 19:59:34 -08:00
msg_ = empty;
2025-01-25 15:45:31 -08:00
}
else
{
2023-11-22 19:59:34 -08:00
trim();
}
}
2025-01-25 15:45:31 -08:00
char *word()
{
const auto start = msg_;
while (*msg_ != '\0' && *msg_ != ' ')
msg_++;
if (*msg_ != '\0')
{ // prepare for next token
2023-11-22 19:59:34 -08:00
*msg_++ = '\0';
trim();
}
return start;
}
2025-01-25 15:45:31 -08:00
bool match(char c)
{
if (c == *msg_)
{
2023-11-22 19:59:34 -08:00
msg_++;
return true;
}
return false;
}
2025-01-25 15:45:31 -08:00
bool isempty() const { return *msg_ == '\0'; }
2023-11-22 19:59:34 -08:00
2025-01-25 15:45:31 -08:00
const char *peek() { return msg_; }
2023-11-22 19:59:34 -08:00
};
2025-01-25 15:45:31 -08:00
std::string_view unescape_tag_value(char *const val)
2023-11-22 19:59:34 -08:00
{
// only start copying at the first escape character
// skip everything before that
auto cursor = strchr(val, '\\');
2025-01-25 15:45:31 -08:00
if (cursor == nullptr)
{
return {val};
}
2023-11-22 19:59:34 -08:00
auto write = cursor;
for (; *cursor; cursor++)
{
if (*cursor == '\\')
{
cursor++;
switch (*cursor)
{
2025-01-25 15:45:31 -08:00
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};
2023-11-22 19:59:34 -08:00
}
}
else
{
*write++ = *cursor;
}
}
return {val, write};
}
} // namespace
2025-01-25 15:45:31 -08:00
auto parse_irc_tags(char *str) -> std::vector<irctag>
2023-11-22 19:59:34 -08:00
{
std::vector<irctag> tags;
2025-01-25 15:45:31 -08:00
do
{
2023-11-22 19:59:34 -08:00
auto val = strsep(&str, ";");
auto key = strsep(&val, "=");
2025-01-25 15:45:31 -08:00
if ('\0' == *key)
{
2023-11-22 19:59:34 -08:00
throw irc_parse_error(irc_error_code::MISSING_TAG);
}
2025-01-25 15:45:31 -08:00
if (nullptr == val)
{
2023-11-22 19:59:34 -08:00
tags.emplace_back(key, "");
}
2025-01-25 15:45:31 -08:00
else
{
tags.emplace_back(std::string_view{key, val - 1}, unescape_tag_value(val));
}
}
while (nullptr != str);
2023-11-22 19:59:34 -08:00
return tags;
}
2025-01-25 15:45:31 -08:00
auto parse_irc_message(char *msg) -> IrcMsg
2023-11-22 19:59:34 -08:00
{
2025-01-25 15:45:31 -08:00
Parser p{msg};
2023-11-25 09:22:55 -08:00
IrcMsg out;
2023-11-22 19:59:34 -08:00
/* MESSAGE TAGS */
2025-01-25 15:45:31 -08:00
if (p.match('@'))
{
2023-11-22 19:59:34 -08:00
out.tags = parse_irc_tags(p.word());
}
/* MESSAGE SOURCE */
2025-01-25 15:45:31 -08:00
if (p.match(':'))
{
2023-11-22 19:59:34 -08:00
out.source = p.word();
}
/* MESSAGE COMMANDS */
out.command = p.word();
2025-01-25 15:45:31 -08:00
if (out.command.empty())
{
2023-11-22 19:59:34 -08:00
throw irc_parse_error{irc_error_code::MISSING_COMMAND};
}
/* MESSAGE ARGUMENTS */
2025-01-25 15:45:31 -08:00
while (!p.isempty())
{
if (p.match(':'))
{
2023-11-22 19:59:34 -08:00
out.args.push_back(p.peek());
break;
}
out.args.push_back(p.word());
}
return out;
}
2025-01-25 15:45:31 -08:00
auto IrcMsg::hassource() const -> bool { return source.data() != nullptr; }
2023-11-22 19:59:34 -08:00
2025-01-25 15:45:31 -08:00
auto operator<<(std::ostream &out, irc_error_code code) -> std::ostream &
2023-11-22 19:59:34 -08:00
{
2025-01-25 15:45:31 -08:00
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;
2023-11-22 19:59:34 -08:00
}
}