/** * Copyright (C) 2016 Charybdis Development Team * Copyright (C) 2016 Jason Volk * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice is present in all copies. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include BOOST_FUSION_ADAPT_STRUCT ( ircd::fmt::spec, ( char, sign ) ( int, width ) ( std::string, name ) ) namespace ircd { namespace fmt { namespace qi = boost::spirit::qi; namespace karma = boost::spirit::karma; const char SPECIFIER { '%' }; const char SPECIFIER_TERMINATOR { '$' }; std::map _specifiers; template bool generate_string(char *&out, const generator &, const arg &); template bool generate_integer(char *&out, const size_t &max, const spec &, const integer &i); struct param_specifier :specifier { bool operator()(char *&out, const size_t &max, const spec &, const arg &) const override; using specifier::specifier; } const param_specifier { "param"s }; struct parv_specifier :specifier { bool operator()(char *&out, const size_t &max, const spec &, const arg &) const override; using specifier::specifier; } const parv_specifier { "parv"s }; struct nick_specifier :specifier { bool operator()(char *&out, const size_t &max, const spec &, const arg &) const override; using specifier::specifier; } const nick_specifier { "nick"s }; struct user_specifier :specifier { bool operator()(char *&out, const size_t &max, const spec &, const arg &) const override; using specifier::specifier; } const user_specifier { "user"s }; struct host_specifier :specifier { bool operator()(char *&out, const size_t &max, const spec &, const arg &) const override; using specifier::specifier; } const host_specifier { "host"s }; struct prefix_specifier :specifier { bool operator()(char *&out, const size_t &max, const spec &, const arg &) const override; using specifier::specifier; } const prefix_specifier { "prefix"s }; struct string_specifier :specifier { bool operator()(char *&out, const size_t &max, const spec &, const arg &val) const override; using specifier::specifier; } const string_specifier { "s"s }; struct int_specifier :specifier { bool operator()(char *&out, const size_t &max, const spec &, const arg &val) const override; using specifier::specifier; } const int_specifier { { "d"s, "ld"s, "u"s, "lu"s, "zd"s, "zu"s } }; struct char_specifier :specifier { bool operator()(char *&out, const size_t &max, const spec &, const arg &val) const override; using specifier::specifier; } const char_specifier { "c"s }; bool is_specifier(const std::string &name); void handle_specifier(char *&out, const size_t &max, const uint &idx, const spec &, const arg &); namespace parse { using qi::lit; using qi::char_; using qi::int_; using qi::eps; using qi::repeat; using qi::omit; template struct grammar :qi::grammar { qi::rule specsym; qi::rule specterm; qi::rule name; qi::rule spec; grammar(qi::rule &top_rule); }; } } // namespace fmt } // namespace ircd using namespace ircd; template fmt::parse::grammar::grammar(qi::rule &top_rule) :grammar::base_type { top_rule } ,specsym { lit(SPECIFIER) } ,specterm { char_(SPECIFIER_TERMINATOR) } ,name { repeat(1,14)[char_("A-Za-z")] >> omit[-specterm] } { spec %= specsym >> -char_("+-") >> -int_ >> name[([] (auto &str, auto &ctx, auto &valid) { valid = is_specifier(str); })]; } fmt::snprintf::snprintf(internal_t, char *const &out, const size_t &max, const char *const &fstr, const ptrs &p, const types &t) try :fstart{strchr(fstr, SPECIFIER)} ,fstop{fstr} ,fend{fstr + strlen(fstr)} ,obeg{out} ,oend{out + max} ,out{out} ,idx{0} { if(unlikely(!max)) { fstart = nullptr; return; } if(!fstart) { append(fstr, fend); return; } append(fstr, fstart); for(size_t i(0); i < p.size(); ++i) argument(arg{p.at(i), t.at(i)}); } catch(const std::out_of_range &e) { throw invalid_format("Format string requires more than %zu arguments.", p.size()); } void fmt::snprintf::argument(const arg &val) { struct parser :parse::grammar { parser(): grammar{grammar::spec} {} } static const parser; if(finished()) return; fmt::spec spec; if(qi::parse(fstart, fend, parser, spec)) handle_specifier(out, remaining(), idx++, spec, val); fstop = fstart++; if(fstop < fend) { fstart = strchr(fstart, SPECIFIER); append(fstop, fstart?: fend); } else *out = '\0'; } void fmt::snprintf::append(const char *const &begin, const char *const &end) { const size_t len(std::distance(begin, end)); const size_t &cpsz(std::min(len, size_t(remaining()))); memcpy(out, begin, cpsz); out += cpsz; *out = '\0'; } const decltype(fmt::_specifiers) & fmt::specifiers() { return _specifiers; } fmt::specifier::specifier(const std::string &name) :specifier{{name}} { } fmt::specifier::specifier(const std::initializer_list &names) :names{names} { for(const auto &name : this->names) if(is_specifier(name)) throw error("Specifier '%c%s' already registered\n", SPECIFIER, name.c_str()); for(const auto &name : this->names) _specifiers.emplace(name, this); } fmt::specifier::~specifier() noexcept { for(const auto &name : names) _specifiers.erase(name); } fmt::spec::spec() :sign('+') ,width(0) { name.reserve(14); } bool fmt::is_specifier(const std::string &name) { return specifiers().count(name); } void fmt::handle_specifier(char *&out, const size_t &max, const uint &idx, const spec &spec, const arg &val) try { const auto &type(get<1>(val)); const auto &handler(*specifiers().at(spec.name)); if(!handler(out, max, spec, val)) throw invalid_type("`%s' for format specifier '%c%s' for argument #%u", type.name(), SPECIFIER, spec.name.c_str(), idx); } catch(const std::out_of_range &e) { throw invalid_format("Unhandled specifier `%c%s' for argument #%u in format string", SPECIFIER, spec.name.c_str(), idx); } catch(const illegal &e) { throw illegal("Specifier `%c%s' for argument #%u: %s", SPECIFIER, spec.name.c_str(), idx, e.what()); } bool fmt::char_specifier::operator()(char *&out, const size_t &max, const spec &, const arg &val) const { using karma::char_; using karma::eps; using karma::maxwidth; static const auto throw_illegal([] { throw illegal("Not a printable character"); }); struct generator :rfc1459::gen::grammar { karma::rule printable { char_(rfc1459::character::charset(rfc1459::character::PRINT)) }; generator(): grammar{printable} {} } static const generator; const auto &ptr(get<0>(val)); const auto &type(get<1>(val)); if(type == typeid(const char)) { const auto &c(*reinterpret_cast(ptr)); karma::generate(out, maxwidth(max)[generator] | eps[throw_illegal], c); return true; } else return false; } bool fmt::int_specifier::operator()(char *&out, const size_t &max, const spec &s, const arg &val) const { const auto &ptr(get<0>(val)); const auto &type(get<1>(val)); if(type == typeid(const char)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const unsigned char)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const short)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const unsigned short)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const int)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const unsigned int)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const long)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const unsigned long)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const long long)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const unsigned long long)) return generate_integer(out, max, s, *reinterpret_cast(ptr)); if(type == typeid(const char[])) { size_t test; const auto &i(reinterpret_cast(ptr)); const auto len(std::min(max, strlen(i))); if(!boost::conversion::try_lexical_convert(i, test)) throw illegal("The string literal value for integer specifier is not a valid integer"); memcpy(out, i, len); out += len; return true; } if(type == typeid(const char *)) { size_t test; const auto &i(*reinterpret_cast(ptr)); const auto len(std::min(max, strlen(i))); if(!boost::conversion::try_lexical_convert(i, test)) throw illegal("The character buffer for integer specifier is not a valid integer"); memcpy(out, i, len); out += len; return true; } if(type == typeid(const std::string)) { size_t test; const auto &i(*reinterpret_cast(ptr)); const auto len(std::min(max, i.size())); if(!boost::conversion::try_lexical_convert(i, test)) throw illegal("The string argument for integer specifier is not a valid integer"); memcpy(out, i.data(), len); out += len; return true; } return false; } bool fmt::string_specifier::operator()(char *&out, const size_t &max, const spec &, const arg &val) const { using karma::char_; using karma::eps; using karma::maxwidth; static const auto throw_illegal([] { throw illegal("Not a printable string"); }); struct generator :rfc1459::gen::grammar { karma::rule printable { *char_(rfc1459::character::charset(rfc1459::character::PRINT)) }; generator(): grammar{printable} {} } static const generator; return generate_string(out, maxwidth(max)[generator] | eps[throw_illegal], val); } bool fmt::prefix_specifier::operator()(char *&out, const size_t &max, const spec &spec, const arg &val) const { using karma::eps; using karma::maxwidth; static const auto throw_illegal([] { throw illegal("Argument is not a valid IRC prefix"); }); const auto &ptr(get<0>(val)); const auto &type(get<1>(val)); if(type == typeid(rfc1459::pfx)) { struct generator :rfc1459::gen::grammar { generator(): grammar{grammar::prefix} {} } static const generator; const auto &pfx(*reinterpret_cast(ptr)); return karma::generate(out, maxwidth(max)[generator] | eps[throw_illegal], pfx); } else return false; } bool fmt::host_specifier::operator()(char *&out, const size_t &max, const spec &spec, const arg &val) const { using karma::eps; using karma::maxwidth; static const auto throw_illegal([] { throw illegal("Argument is not a valid host string"); }); struct generator :rfc1459::gen::grammar { generator(): grammar{grammar::hostname} {} } static const generator; return generate_string(out, maxwidth(max)[generator] | eps[throw_illegal], val); } bool fmt::user_specifier::operator()(char *&out, const size_t &max, const spec &spec, const arg &val) const { using karma::eps; using karma::maxwidth; static const auto throw_illegal([] { throw illegal("Argument is not a valid user string"); }); struct generator :rfc1459::gen::grammar { generator(): grammar{grammar::user} {} } static const generator; return generate_string(out, maxwidth(max)[generator] | eps[throw_illegal], val); } bool fmt::nick_specifier::operator()(char *&out, const size_t &max, const spec &spec, const arg &val) const { using karma::eps; using karma::maxwidth; static const auto throw_illegal([] { throw illegal("Argument is not a valid nick string"); }); struct generator :rfc1459::gen::grammar { generator(): grammar{grammar::nick} {} } static const generator; return generate_string(out, maxwidth(max)[generator] | eps[throw_illegal], val); } bool fmt::parv_specifier::operator()(char *&out, const size_t &max, const spec &spec, const arg &val) const { using karma::eps; using karma::delimit; using karma::maxwidth; static const auto throw_illegal([] { throw illegal("Argument is not a valid IRC parameter vector"); }); const auto &ptr(get<0>(val)); const auto &type(get<1>(val)); if(type == typeid(const rfc1459::parv)) { /* struct generator :rfc1459::gen::grammar { generator(): grammar{grammar::params} {} } static const generator; const auto &parv(*reinterpret_cast(ptr)); return karma::generate(out, maxwidth(max)[generator] | eps[throw_illegal], parv); */ struct generate_middle :rfc1459::gen::grammar { generate_middle(): grammar{grammar::middle} {} } static const generate_middle; struct generate_trailing :rfc1459::gen::grammar { generate_trailing(): grammar{grammar::trailing} {} } static const generate_trailing; const auto &parv(*reinterpret_cast(ptr)); const char *const start(out); const auto remain([&start, &out, &max] { return max - (out - start); }); ssize_t i(0); for(; i < ssize_t(parv.size()) - 1; ++i) if(!karma::generate(out, maxwidth(remain())[delimit[generate_middle]], parv.at(i))) throw illegal("Invalid middle parameter"); if(!parv.empty()) if(!karma::generate(out, maxwidth(remain())[generate_trailing], parv.at(parv.size() - 1))) throw illegal("Invalid trailing parameter"); return true; } else return false; } bool fmt::param_specifier::operator()(char *&out, const size_t &max, const spec &spec, const arg &val) const { using karma::eps; using karma::maxwidth; static const auto throw_illegal([] { throw illegal("Argument is not a valid IRC 'middle' parameter"); }); struct generator :rfc1459::gen::grammar { generator(): grammar{grammar::middle} {} } static const generator; return generate_string(out, maxwidth(max)[generator] | eps[throw_illegal], val); } template bool fmt::generate_integer(char *&out, const size_t &max, const spec &s, const integer &i) { using karma::int_; using karma::maxwidth; struct generator :rfc1459::gen::grammar { karma::rule rule { int_ }; generator(): rfc1459::gen::grammar{rule} {} } static const generator; return karma::generate(out, maxwidth(max)[generator], i); } template bool fmt::generate_string(char *&out, const generator &gen, const arg &val) { using karma::eps; const auto &ptr(get<0>(val)); const auto &type(get<1>(val)); if(type == typeid(std::string)) { const auto &str(*reinterpret_cast(ptr)); karma::generate(out, gen, str); return true; } else if(type == typeid(const char *)) { const auto &str(*reinterpret_cast(ptr)); karma::generate(out, gen, str); return true; } // This for string literals which have unique array types depending on their size. // There is no reasonable way to match them. The best that can be hoped for is the // grammar will fail gracefully (most of the time) or not print something bogus when // it happens to be legal. const auto &str(reinterpret_cast(ptr)); return karma::generate(out, gen, str); }