mirror of
https://github.com/matrix-construct/construct
synced 2024-11-30 02:32:43 +01:00
760 lines
18 KiB
C++
760 lines
18 KiB
C++
/**
|
|
* Copyright (C) 2016 Charybdis Development Team
|
|
* Copyright (C) 2016 Jason Volk <jason@zemos.net>
|
|
*
|
|
* 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 <ircd/rfc1459_parse.h>
|
|
#include <ircd/rfc1459_gen.h>
|
|
#include <ircd/lex_cast.h>
|
|
#include <ircd/fmt.h>
|
|
|
|
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<std::string, specifier *> _specifiers;
|
|
|
|
template<class generator> bool generate_string(char *&out, const generator &, const arg &);
|
|
template<class integer> 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<class it,
|
|
class top>
|
|
struct grammar
|
|
:qi::grammar<it, top>
|
|
{
|
|
qi::rule<it> specsym;
|
|
qi::rule<it> specterm;
|
|
qi::rule<it, std::string()> name;
|
|
qi::rule<it, fmt::spec> spec;
|
|
|
|
grammar(qi::rule<it, top> &top_rule);
|
|
};
|
|
}
|
|
|
|
} // namespace fmt
|
|
} // namespace ircd
|
|
|
|
using namespace ircd;
|
|
|
|
template<class it,
|
|
class top>
|
|
fmt::parse::grammar<it, top>::grammar(qi::rule<it, top> &top_rule)
|
|
:grammar<it, top>::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<const char *, fmt::spec>
|
|
{
|
|
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<std::string> &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<char *, char()>
|
|
{
|
|
karma::rule<char *, char()> 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<const char *>(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<const char *>(ptr));
|
|
|
|
if(type == typeid(const unsigned char))
|
|
return generate_integer(out, max, s, *reinterpret_cast<const unsigned char *>(ptr));
|
|
|
|
if(type == typeid(const short))
|
|
return generate_integer(out, max, s, *reinterpret_cast<const short *>(ptr));
|
|
|
|
if(type == typeid(const unsigned short))
|
|
return generate_integer(out, max, s, *reinterpret_cast<const unsigned short *>(ptr));
|
|
|
|
if(type == typeid(const int))
|
|
return generate_integer(out, max, s, *reinterpret_cast<const int *>(ptr));
|
|
|
|
if(type == typeid(const unsigned int))
|
|
return generate_integer(out, max, s, *reinterpret_cast<const unsigned int *>(ptr));
|
|
|
|
if(type == typeid(const long))
|
|
return generate_integer(out, max, s, *reinterpret_cast<const long *>(ptr));
|
|
|
|
if(type == typeid(const unsigned long))
|
|
return generate_integer(out, max, s, *reinterpret_cast<const unsigned long *>(ptr));
|
|
|
|
if(type == typeid(const long long))
|
|
return generate_integer(out, max, s, *reinterpret_cast<const long long *>(ptr));
|
|
|
|
if(type == typeid(const unsigned long long))
|
|
return generate_integer(out, max, s, *reinterpret_cast<const unsigned long long *>(ptr));
|
|
|
|
if(type == typeid(const char[]))
|
|
{
|
|
size_t test;
|
|
const auto &i(reinterpret_cast<const char *>(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<const char *const *>(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<const std::string *>(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<char *, std::string()>
|
|
{
|
|
karma::rule<char *, std::string()> 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<char *, rfc1459::pfx>
|
|
{
|
|
generator(): grammar{grammar::prefix} {}
|
|
}
|
|
static const generator;
|
|
const auto &pfx(*reinterpret_cast<const rfc1459::pfx *>(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<char *, std::string()>
|
|
{
|
|
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<char *, std::string()>
|
|
{
|
|
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<char *, std::string()>
|
|
{
|
|
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<char *, rfc1459::parv>
|
|
{
|
|
generator(): grammar{grammar::params} {}
|
|
}
|
|
static const generator;
|
|
const auto &parv(*reinterpret_cast<const rfc1459::parv *>(ptr));
|
|
return karma::generate(out, maxwidth(max)[generator] | eps[throw_illegal], parv);
|
|
*/
|
|
|
|
struct generate_middle
|
|
:rfc1459::gen::grammar<char *, std::string()>
|
|
{
|
|
generate_middle(): grammar{grammar::middle} {}
|
|
}
|
|
static const generate_middle;
|
|
|
|
struct generate_trailing
|
|
:rfc1459::gen::grammar<char *, std::string()>
|
|
{
|
|
generate_trailing(): grammar{grammar::trailing} {}
|
|
}
|
|
static const generate_trailing;
|
|
const auto &parv(*reinterpret_cast<const rfc1459::parv *>(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<char *, std::string()>
|
|
{
|
|
generator(): grammar{grammar::middle} {}
|
|
}
|
|
static const generator;
|
|
return generate_string(out, maxwidth(max)[generator] | eps[throw_illegal], val);
|
|
}
|
|
|
|
template<class integer>
|
|
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<char *, integer()>
|
|
{
|
|
karma::rule<char *, integer()> rule
|
|
{
|
|
int_
|
|
};
|
|
|
|
generator(): rfc1459::gen::grammar<char *, integer()>{rule} {}
|
|
}
|
|
static const generator;
|
|
return karma::generate(out, maxwidth(max)[generator], i);
|
|
}
|
|
|
|
template<class generator>
|
|
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<const std::string *>(ptr));
|
|
karma::generate(out, gen, str);
|
|
return true;
|
|
}
|
|
else if(type == typeid(const char *))
|
|
{
|
|
const auto &str(*reinterpret_cast<const char *const *>(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<const char *>(ptr));
|
|
return karma::generate(out, gen, str);
|
|
}
|