diff --git a/build/libaegisub/libaegisub.vcxproj.filters b/build/libaegisub/libaegisub.vcxproj.filters index 474ac2553..8244730c2 100644 --- a/build/libaegisub/libaegisub.vcxproj.filters +++ b/build/libaegisub/libaegisub.vcxproj.filters @@ -295,6 +295,9 @@ Source Files\Common + + Source Files\Common + Source Files\Common diff --git a/libaegisub/Makefile b/libaegisub/Makefile index 2008256ce..29882968b 100644 --- a/libaegisub/Makefile +++ b/libaegisub/Makefile @@ -26,6 +26,7 @@ SRC += \ common/color.cpp \ common/dispatch.cpp \ common/file_mapping.cpp \ + common/format.cpp \ common/fs.cpp \ common/hotkey.cpp \ common/io.cpp \ diff --git a/libaegisub/common/format.cpp b/libaegisub/common/format.cpp new file mode 100644 index 000000000..d05e7208e --- /dev/null +++ b/libaegisub/common/format.cpp @@ -0,0 +1,233 @@ +// Copyright (c) 2014, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include + +#include + +#include +#include + +namespace { +template +int actual_len(int max_len, const Char *value) { + int len = 0; + while (value[len] && (max_len <= 0 || len < max_len)) ++len; + return len; +} + +template +int actual_len(int max_len, std::basic_string value) { + if (max_len > 0 && static_cast(max_len) < value.size()) + return max_len; + return value.size(); +} + +template +void do_write_str(std::basic_ostream& out, const Char *str, int len) { + out.write(str, len); +} + +void do_write_str(std::ostream& out, const wchar_t *str, int len) { + std::wstring_convert> utf8_conv; + auto u8 = utf8_conv.to_bytes(str, str + len); + out.write(u8.data(), u8.size()); +} + +void do_write_str(std::wostream& out, const char *str, int len) { + std::wstring_convert> utf8_conv; + auto wc = utf8_conv.from_bytes(str, str + len); + out.write(wc.data(), wc.size()); +} + +template +struct format_parser { + agi::format_detail::formatter_state &s; + + void read_and_append_up_to_next_specifier() { + for (std::streamsize len = 0; ; ++len) { + // Ran out of format specifiers; not an error due to that + // translated strings may not need them all + if (!s.fmt[len]) { + s.out.write(s.fmt, len); + s.fmt += len; + return; + } + + if (s.fmt[len] == '%') { + if (s.fmt[len + 1] == '%') { + s.out.write(s.fmt, len); + s.fmt += len + 1; + len = 0; + continue; + } + + s.out.write(s.fmt, len); + s.fmt += len; + break; + } + } + } + + int read_int() { + int i = 0; + for (; *s.fmt_cur >= '0' && *s.fmt_cur <= '9'; ++s.fmt_cur) + i = 10 * i + (*s.fmt_cur - '0'); + return i; + } + + void parse_flags() { + for (; ; ++s.fmt_cur) { + switch (*s.fmt_cur) { + // Not supported: ' ' (add a space before positive numers to align with negative) + case '#': + s.out.setf(std::ios::showpoint | std::ios::showbase); + continue; + case '0': + // overridden by left alignment ('-' flag) + if (!(s.out.flags() & std::ios::left)) { + // Use internal padding so that numeric values are + // formatted correctly, eg -00010 rather than 000-10 + s.out.fill('0'); + s.out.setf(std::ios::internal, std::ios::adjustfield); + } + continue; + case '-': + s.out.fill(' '); + s.out.setf(std::ios::left, std::ios::adjustfield); + continue; + case '+': + s.out.setf(std::ios::showpos); + continue; + } + break; + } + } + + void parse_width() { + if (*s.fmt_cur >= '0' && *s.fmt_cur <= '9') + s.width = read_int(); + else if (*s.fmt_cur == '*') { + s.read_width = true; + s.pending = true; + ++s.fmt_cur; + } + } + + void parse_precision() { + if (*s.fmt_cur != '.') return; + ++s.fmt_cur; + + // Ignoring negative precision because it's dumb and pointless + if (*s.fmt_cur >= '0' && *s.fmt_cur <= '9') + s.precision = read_int(); + else if (*s.fmt_cur == '*') { + s.read_precision = true; + s.pending = true; + ++s.fmt_cur; + } + else + s.precision = 0; + } + + void parse_length_modifiers() { + // Where "parse" means "skip" since we don't need them + for (Char c = *s.fmt_cur; + c == 'l' || c == 'h' || c == 'L' || c == 'j' || c == 'z' || c == 't'; + c = *++s.fmt_cur); + } + + void parse_format_specifier() { + s.width = 0; + s.precision = -1; + s.out.fill(' '); + s.out.unsetf( + std::ios::adjustfield | + std::ios::basefield | + std::ios::boolalpha | + std::ios::floatfield | + std::ios::showbase | + std::ios::showpoint | + std::ios::showpos | + std::ios::uppercase); + + // Don't touch fmt until the specifier is fully applied so that if we + // have insufficient arguments it'll get passed through to the output + s.fmt_cur = s.fmt + 1; + + parse_flags(); + parse_width(); + parse_precision(); + parse_length_modifiers(); + } +}; +} + +namespace agi { + +template +void writer::write(std::basic_ostream& out, int max_len, const Char *value) { + do_write_str(out, value, actual_len(max_len, value)); +} + +template +void writer>::write(std::basic_ostream& out, int max_len, std::basic_string const& value) { + do_write_str(out, value.data(), actual_len(max_len, value)); +} + +template struct writer; +template struct writer; +template struct writer; +template struct writer; +template struct writer; +template struct writer; +template struct writer; +template struct writer; + +namespace format_detail { + +template +bool formatter::parse_next() { + format_parser parser{*static_cast *>(this)}; + parser.read_and_append_up_to_next_specifier(); + if (!*this->fmt) return false; + parser.parse_format_specifier(); + return true; +} + +template +formatter::~formatter() { + // Write remaining formatting string + for (std::streamsize len = 0; ; ++len) { + if (!this->fmt[len]) { + this->out.write(this->fmt, len); + return; + } + + if (this->fmt[len] == '%' && this->fmt[len + 1] == '%') { + this->out.write(this->fmt, len); + this->fmt += len + 1; + len = 0; + continue; + } + } +} + +template class formatter; +template class formatter; + +} } + diff --git a/libaegisub/include/libaegisub/format.h b/libaegisub/include/libaegisub/format.h index 17781ae4c..6cdd8766c 100644 --- a/libaegisub/include/libaegisub/format.h +++ b/libaegisub/include/libaegisub/format.h @@ -18,7 +18,6 @@ #include #include -#include #include class wxString; @@ -43,38 +42,6 @@ template Out runtime_cast(In const& value) { return runtime_cast_helper::cast(value); } - -// Check length for string types -template -int actual_len(int max_len, const Char *value) { - int len = 0; - while (value[len] && (max_len <= 0 || len < max_len)) ++len; - return len; -} - -template -int actual_len(int max_len, std::basic_string value) { - if (max_len > 0 && static_cast(max_len) < value.size()) - return max_len; - return value.size(); -} - -template -inline void do_write_str(std::basic_ostream& out, const Char *str, int len) { - out.write(str, len); -} - -inline void do_write_str(std::ostream& out, const wchar_t *str, int len) { - std::wstring_convert> utf8_conv; - auto u8 = utf8_conv.to_bytes(str, str + len); - out.write(u8.data(), u8.size()); -} - -inline void do_write_str(std::wostream& out, const char *str, int len) { - std::wstring_convert> utf8_conv; - auto wc = utf8_conv.from_bytes(str, str + len); - out.write(wc.data(), wc.size()); -} } template @@ -84,33 +51,27 @@ struct writer { } }; +template +struct writer { + static void write(std::basic_ostream& out, int max_len, const Char *value); +}; + +template +struct writer> { + static void write(std::basic_ostream& out, int max_len, std::basic_string const& value); +}; + // Ensure things with specializations don't get implicitly initialized template<> struct writer; template<> struct writer; template<> struct writer; template<> struct writer; -template -struct writer { - static void write(std::basic_ostream& out, int max_len, const Char *value) { - format_detail::do_write_str(out, value, format_detail::actual_len(max_len, value)); - } -}; - -template -struct writer> { - static void write(std::basic_ostream& out, int max_len, std::basic_string const& value) { - format_detail::do_write_str(out, value.data(), format_detail::actual_len(max_len, value)); - } -}; - namespace format_detail { template -class formatter { - formatter(const formatter&) = delete; - formatter& operator=(const formatter&) = delete; - +struct formatter_state { std::basic_ostream& out; + const Char *fmt; const Char *fmt_cur = nullptr; @@ -121,225 +82,101 @@ class formatter { int width = 0; int precision = 0; + formatter_state(std::basic_ostream&out , const Char *fmt) + : out(out), fmt(fmt) { } +}; + +template +class formatter : formatter_state { + formatter(const formatter&) = delete; + formatter& operator=(const formatter&) = delete; + boost::io::basic_ios_all_saver saver; - void read_and_append_up_to_next_specifier() { - for (std::streamsize len = 0; ; ++len) { - // Ran out of format specifiers; not an error due to that - // translated strings may not need them all - if (!fmt[len]) { - out.write(fmt, len); - fmt += len; - return; - } - - if (fmt[len] == '%') { - if (fmt[len + 1] == '%') { - out.write(fmt, len); - fmt += len + 1; - len = 0; - continue; - } - - out.write(fmt, len); - fmt += len; - break; - } - } - } - - int read_int() { - int i = 0; - for (; *fmt_cur >= '0' && *fmt_cur <= '9'; ++fmt_cur) - i = 10 * i + (*fmt_cur - '0'); - return i; - } - - void parse_flags() { - for (; ; ++fmt_cur) { - switch (*fmt_cur) { - // Not supported: ' ' (add a space before positive numers to align with negative) - case '#': - out.setf(std::ios::showpoint | std::ios::showbase); - continue; - case '0': - // overridden by left alignment ('-' flag) - if (!(out.flags() & std::ios::left)) { - // Use internal padding so that numeric values are - // formatted correctly, eg -00010 rather than 000-10 - out.fill('0'); - out.setf(std::ios::internal, std::ios::adjustfield); - } - continue; - case '-': - out.fill(' '); - out.setf(std::ios::left, std::ios::adjustfield); - continue; - case '+': - out.setf(std::ios::showpos); - continue; - } - break; - } - } - - void parse_width() { - if (*fmt_cur >= '0' && *fmt_cur <= '9') - width = read_int(); - else if (*fmt_cur == '*') { - read_width = true; - pending = true; - ++fmt_cur; - } - } - - void parse_precision() { - if (*fmt_cur != '.') return; - ++fmt_cur; - - // Ignoring negative precision because it's dumb and pointless - if (*fmt_cur >= '0' && *fmt_cur <= '9') - precision = read_int(); - else if (*fmt_cur == '*') { - read_precision = true; - pending = true; - ++fmt_cur; - } - else - precision = 0; - } - - void parse_length_modifiers() { - // Where "parse" means "skip" since we don't need them - for (Char c = *fmt_cur; - c == 'l' || c == 'h' || c == 'L' || c == 'j' || c == 'z' || c == 't'; - c = *++fmt_cur); - } - - void parse_format_specifier() { - width = 0; - precision = -1; - out.fill(' '); - out.unsetf( - std::ios::adjustfield | - std::ios::basefield | - std::ios::boolalpha | - std::ios::floatfield | - std::ios::showbase | - std::ios::showpoint | - std::ios::showpos | - std::ios::uppercase); - - // Don't touch fmt until the specifier is fully applied so that if we - // have insufficient arguments it'll get passed through to the output - fmt_cur = fmt + 1; - - parse_flags(); - parse_width(); - parse_precision(); - parse_length_modifiers(); - } + bool parse_next(); public: - formatter(std::basic_ostream& out, const Char *fmt) : out(out), fmt(fmt), saver(out) { } - ~formatter() { - // Write remaining formatting string - for (std::streamsize len = 0; ; ++len) { - if (!fmt[len]) { - out.write(fmt, len); - return; - } - - if (fmt[len] == '%' && fmt[len + 1] == '%') { - out.write(fmt, len); - fmt += len + 1; - len = 0; - continue; - } - } - } + formatter(std::basic_ostream& out, const Char *fmt) + : formatter_state(out, fmt), saver(out) { } + ~formatter(); template void operator()(T&& value) { - if (!pending) { - read_and_append_up_to_next_specifier(); - if (!*fmt) return; - parse_format_specifier(); - } + if (!this->pending && !parse_next()) return; - if (read_width) { - width = runtime_cast(value); - read_width = false; + if (this->read_width) { + this->width = runtime_cast(value); + this->read_width = false; return; } - if (read_precision) { - precision = runtime_cast(value); - read_precision = false; + if (this->read_precision) { + this->precision = runtime_cast(value); + this->read_precision = false; return; } - pending = false; + this->pending = false; - if (width < 0) { - out.fill(' '); - out.setf(std::ios::left, std::ios::adjustfield); - width = -width; + if (this->width < 0) { + this->out.fill(' '); + this->out.setf(std::ios::left, std::ios::adjustfield); + this->width = -this->width; } - out.width(width); - out.precision(precision < 0 ? 6 : precision); + this->out.width(this->width); + this->out.precision(this->precision < 0 ? 6 : this->precision); - Char c = *fmt_cur ? fmt_cur[0] : 's'; + Char c = *this->fmt_cur ? this->fmt_cur[0] : 's'; if (c >= 'A' && c <= 'Z') { - out.setf(std::ios::uppercase); + this->out.setf(std::ios::uppercase); c += 'a' - 'A'; } switch (c) { case 'c': - out.setf(std::ios::dec, std::ios::basefield); - out << runtime_cast(value); + this->out.setf(std::ios::dec, std::ios::basefield); + this->out << runtime_cast(value); break; case 'd': case 'i': - out.setf(std::ios::dec, std::ios::basefield); - out << runtime_cast(value); + this->out.setf(std::ios::dec, std::ios::basefield); + this->out << runtime_cast(value); break; case 'o': - out.setf(std::ios::oct, std::ios::basefield); - out << runtime_cast(value); + this->out.setf(std::ios::oct, std::ios::basefield); + this->out << runtime_cast(value); break; case 'x': - out.setf(std::ios::hex, std::ios::basefield); - out << runtime_cast(value); + this->out.setf(std::ios::hex, std::ios::basefield); + this->out << runtime_cast(value); break; case 'u': - out.setf(std::ios::dec, std::ios::basefield); - out << runtime_cast(value); + this->out.setf(std::ios::dec, std::ios::basefield); + this->out << runtime_cast(value); break; case 'e': - out.setf(std::ios::scientific, std::ios::floatfield); - out.setf(std::ios::dec, std::ios::basefield); - out << runtime_cast(value); + this->out.setf(std::ios::scientific, std::ios::floatfield); + this->out.setf(std::ios::dec, std::ios::basefield); + this->out << runtime_cast(value); break; case 'f': - out.setf(std::ios::fixed, std::ios::floatfield); - out << runtime_cast(value); + this->out.setf(std::ios::fixed, std::ios::floatfield); + this->out << runtime_cast(value); break; case 'g': - out.setf(std::ios::dec, std::ios::basefield); - out.flags(out.flags() & ~std::ios::floatfield); - out << runtime_cast(value); + this->out.setf(std::ios::dec, std::ios::basefield); + this->out.flags(this->out.flags() & ~std::ios::floatfield); + this->out << runtime_cast(value); break; case 'p': - out.setf(std::ios::hex, std::ios::basefield); - out << runtime_cast(value); + this->out.setf(std::ios::hex, std::ios::basefield); + this->out << runtime_cast(value); break; default: // s and other - out.setf(std::ios::boolalpha); - writer::type>::write(out, precision, value); + this->out.setf(std::ios::boolalpha); + writer::type>::write(this->out, this->precision, value); break; } - fmt = *fmt_cur ? fmt_cur + 1 : fmt_cur; + this->fmt = *this->fmt_cur ? this->fmt_cur + 1 : this->fmt_cur; } }; diff --git a/src/agi_pre.h b/src/agi_pre.h index b31970552..7ac6dd3c8 100644 --- a/src/agi_pre.h +++ b/src/agi_pre.h @@ -72,7 +72,6 @@ // Common C++ #include #include -#include #include #include #include diff --git a/src/format.h b/src/format.h index 381fb900c..c46ef10d4 100644 --- a/src/format.h +++ b/src/format.h @@ -23,16 +23,14 @@ namespace agi { template<> struct writer { static void write(std::basic_ostream& out, int max_len, wxString const& value) { - format_detail::do_write_str(out, value.wx_str(), - format_detail::actual_len(max_len, value.wx_str())); + writer::write(out, max_len, value.wx_str()); } }; template<> struct writer { static void write(std::basic_ostream& out, int max_len, wxString const& value) { - format_detail::do_write_str(out, value.wx_str(), - format_detail::actual_len(max_len, value.wx_str())); + writer::write(out, max_len, value.wx_str()); } };