Кодирование независимого синтаксического анализа с помощью c ++2b
Иногда мне приходится разбирать текстовые файлы с различными кодировками, мне интересно, появятся ли в будущем стандарте какие-то инструменты для этого, потому что я не очень доволен своим текущим решением. Я даже не уверен, что это правильный подход, однако я определяю шаблон функтора для извлечения символа из потока:
#include <string>
#include <istream> // 'std::istream'
/////////////////////////////////////////////////////////////////////////////
// Generic implementation (couldn't resist to put one)
template<bool LE,typename T> class ReadChar
{
public:
std::istream& operator()(T& c, std::istream& in)
{
in.read(buf,bufsiz);
//const std::streamsize n_read = in ? bufsiz : in.gcount();
if(!in)
{// Could not real all bytes
c = std::char_traits<T>::eof();
}
else if constexpr (LE)
{// Little endian
c = buf[0];
for(int i=1; i<bufsiz; ++i) c |= buf[i] << (8*i);
}
else
{// Big endian
const std::size_t imax = bufsiz-1;
for(std::size_t i=0; i<imax; ++i) c |= buf[i] << (8*(imax-i));
c |= buf[imax];
}
return in;
}
private:
static constexpr std::size_t bufsiz = sizeof(T);
unsigned char buf[bufsiz];
};
/////////////////////////////////////////////////////////////////////////////
// Partial specialization for 32bit chars
template<bool LE> class ReadChar<LE,char32_t>
{
public:
std::istream& operator()(char32_t& c, std::istream& in)
{
in.read(buf,4);
if constexpr (LE) c = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); // Little endian
else c = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; // Big endian
return in;
}
private:
char buf[4];
};
/////////////////////////////////////////////////////////////////////////////
// Partial specialization for 16bit chars
template<bool LE> class ReadChar<LE,char16_t>
{
public:
std::istream& operator()(char16_t& c, std::istream& in)
{
in.read(buf,2);
if constexpr (LE) c = buf[0] | (buf[1] << 8); // Little endian
else c = (buf[0] << 8) | buf[1]; // Big endian
return in;
}
private:
char buf[2];
};
/////////////////////////////////////////////////////////////////////////////
// Specialization for 8bit chars
template<> class ReadChar<false,char>
{
public:
std::istream& operator()(char& c, std::istream& in)
{
return in.get(c);
}
};
я использую
ReadChar
для реализации функции синтаксического анализа:
template<typename T,bool LE> void parse(std::istream& fin)
{
ReadChar<LE,T> get;
T c;
while( get(c,fin) )
{
if(c==static_cast<T>('a')) {/* ... */} // Ugly comparison of T with a char literal
}
}
Уродливая часть
static_cast
когда мне нужно сравнить с буквальным символом.
Затем я использую этот уродливый шаблонный код:
#include <fstream> // 'std::ifstream'
std::ifstream fin("/path/to/file", std::ios::binary);
auto bom = check_bom(fin); // 'check_bom' function is quite trivial
if( bom.is_empty() ) parse<char>(fin);
else if( bom.is_utf8() ) parse<char>(fin); // In my case there's no need to handle multi-byte chars
else if( bom.is_utf16le() ) parse<char16_t,true>(fin);
else if( bom.is_utf16be() ) parse<char16_t,false>(fin);
else if( bom.is_utf32le() ) parse<char32_t,true>(fin);
else if( bom.is_utf32be() ) parse<char32_t,false>(fin);
else throw std::runtime_error("Unrecognized BOM");
Теперь у этого решения есть некоторые особенности (нельзя напрямую использовать строковые литералы в
parse
) мой вопрос в том, есть ли альтернативные подходы к этой проблеме, возможно, с использованием существующих или будущих стандартных средств, которые я игнорирую.
1 ответ
В c ++17C ++17 мы получили типобезопасные союзы. Их можно использовать для сопоставления состояния выполнения и времени компиляции вместе с.
template<auto x>
using constant_t = std::integral_constant<std::decay_t<decltype(x)>, x>;
template<auto x>
constexpr constant_t<x> constant = {};
template<auto...Xs>
using variant_enum_t = std::variant< constant_t<Xs>... >;
enum class EBom {
None,
utf8,
utf16le,
utf16be,
utf32le,
utf32be,
count,
};
using VEBom = variant_enum< EBom::None, EBom::utf8, EBom::utf16le, EBom::utf16be, EBom::utf32le, EBom::utf32be >;
template<std::size_t...Is>
constexpr VEBom make_ve_bom( EBom bom, std::index_sequence<Is...> ) {
static constexpr VEBom retvals[] = {
constant_t<static_cast<EBom>(Is)>...
};
return retvals[ static_cast<std::size_t>(bom) ];
}
constexpr VEBom make_ve_bom( EBom bom ) {
return make_ve_bom( bom, std::make_index_sequence< static_cast<std::size_t>(EBom::count) >{} );
}
А теперь, имея значение времени выполнения, мы можем создать файл.
Благодаря этому мы можем получить тип во время компиляции. Предположим, у вас есть такие черты характера, как:
template<EBom>
constexpr boom bom_is_bigendian_v = ???;
template<EBom>
using bom_chartype_t = ???;
теперь вы можете написать такой код:
std::visit( vebom, [&](auto bom) {
bom_chartype_t<bom> next = ???;
if constexpr (bom_is_bigendian_v<bom>) {
// swizzle
}
} );
и т.п.
Ваш не СУХИЙ код
template<bool LE, class char_t> class ReadChar {
public:
std::istream& operator()(char_t& c, std::istream& in)
{
in.read(buf,sizeof(char_t));
c = buf[0] | (buf[1] << 8);
if constexpr(!LE)
reverse_bytes(&c);
return in;
}
private:
char buf[sizeof(char_t)];
};
становится СУХИМ при простой перезаписи.
Ваш шаблон становится:
std::ifstream fin("/path/to/file", std::ios::binary);
auto bom = check_bom(fin); // 'check_bom' function is quite trivial
if (bom.invalid())
throw std::runtime_error("Unrecognized BOM");
auto vebom = make_ve_bom( bom.enum() );
std:visit( vebom, [&]( auto ebom ) {
parse<bom_char_t<ebom>, !bom_is_bigendian_v<ebom>>( fin );
});
и магия творится в другом месте.
Магия здесь в том, что он держит кучу
integral_constants
, каждый из которых не имеет состояния и знает (по своему типу), каково его значение.
Таким образом, единственное состояние в элементе - это то, какое из значений перечисления без сохранения состояния он содержит.
переходит к вызову переданной лямбды с любым лицом без гражданства, которое находится в. Внутри этой лямбды мы можем использовать ее значение как константу времени компиляции , как и с любыми другими
std::integral_constant
.
Состояние выполнения
std::variant
на самом деле является значением из-за того, как мы его настроили, поэтому преобразование
EBom
к
VEBom
буквально копирует число (так что бесплатно). Магия в
std::visit
, который автоматизирует написание оператора switch и внедрение значения времени компиляции (интегральной константы) для каждой из возможностей в ваш код.
Ничего из этого не C ++23. По большей части это , возможно, я использовал и здесь функцию C ++20 .
Приведенный выше код не компилируется, он просто написан. Возможно, там есть опечатки, но техника правильная.