format_opcode_argument.cpp (5193B)
1 #include "libshit/translate/format_opcode_argument.hpp" 2 3 #include "libshit/doctest.hpp" 4 #include "libshit/except.hpp" 5 #include "libshit/maybe_owning_vector.hpp" 6 #include "libshit/number_format.hpp" 7 #include "libshit/translate/context.hpp" 8 #include "libshit/translate/format_test_helper.hpp" 9 #include "libshit/translate/plural.hpp" 10 #include "libshit/utils.hpp" 11 12 #include <boost/container/small_vector.hpp> // IWYU pragma: keep 13 14 #include <cstdint> 15 #include <sstream> 16 #include <variant> 17 #include <vector> 18 19 namespace Libshit::Translate 20 { 21 TEST_SUITE_BEGIN("Libshit::Translate::Format"); 22 using namespace std::string_literals; 23 namespace { using ArgOC = FormatOpcodes<FormatOpcode::ARGUMENT>; } 24 25 void ArgOC::UpdateTypes(Types& types) 26 { 27 auto code = data.code.GetSpan(); 28 auto index = (code[pc] >> 8) & 0xff; 29 auto type = (code[pc] >> 16) & 0xff; 30 SetType(types, index, static_cast<Type>(type)); 31 pc += 2; 32 } 33 34 void ArgOC::Format( 35 std::string& out, const Context& ctx, Span<const ArgumentType> args) 36 { 37 pc += 2; 38 if (pc > data.code.size()) 39 LIBSHIT_THROW(InvalidFormat, "Invalid code size", 40 "PC", pc, "Size", data.code.size()); 41 42 auto code = data.code.GetSpan(); 43 auto index = (code[pc-2] >> 8) & 0xff; 44 if (index >= args.size()) 45 { 46 out += "???"; 47 return; 48 } 49 50 FormatParams fp{code[pc-1]}; 51 Overloaded ol{ 52 [&](auto x) 53 { 54 static_assert(std::is_integral_v<decltype(x)>); 55 auto info = ctx.GetInfo(); 56 FormatInt(out, x, fp.GetDigits(), fp.GetShowPos(), info.digit_sep, 57 fp.GetGrouping() ? info.grouping : 0); 58 }, 59 [&](double d) 60 { 61 auto info = ctx.GetInfo(); 62 FormatDouble( 63 out, d, fp.GetDoubleFormat(), fp.GetDigits(), fp.GetPrecision(), 64 fp.GetShowPos(), info.digit_sep, info.decimal_sep, 65 fp.GetGrouping() ? info.grouping : 0); 66 }, 67 [&](StringView v) { out += v; }, 68 [&](const TranslationPtr& ptr) { out += ptr->Get({fp.GetCaseId()}).str; }, 69 [&](const NotNullSmartPtr<Printer>& p) { p->Print(out); }, 70 }; 71 std::visit(Move(ol), args[index].ToBase()); 72 } 73 74 unsigned ArgOC::RegisterCommon(RegisterArgs&& r, Type type) 75 { 76 r.parser.FinishVerb(); 77 r.parser.code.push_back( 78 FormatOpcode::ARGUMENT | (r.index << 8) | (unsigned(type) << 16)); 79 r.parser.code.push_back(r.fp.GetRaw()); 80 return r.index + 1; 81 } 82 83 namespace 84 { 85 struct X {}; 86 LIBSHIT_TEST_FUN std::ostream& operator<<(std::ostream& os, X) 87 { return os << "X::op<<"; } 88 } 89 90 TEST_CASE("Argument format") 91 { 92 // ctxp, index, fp 93 #define REG(ctxp, ...) \ 94 FormatData data; \ 95 Context ctx = ctxp; \ 96 FormatParser parser{{}, ctx, data}; \ 97 ArgOC::Register<Type::GENERIC>({parser, __VA_ARGS__, {}}); \ 98 parser.Finish() 99 100 auto fmt = Test::Format<FormatOpcode::ARGUMENT>; 101 #define CHK(exp, argv, ctxp, ...) \ 102 do \ 103 { \ 104 REG(ctxp, 0, __VA_ARGS__); \ 105 FAST_CHECK_EQ(fmt(parser, {argv}), exp##_ns); \ 106 } \ 107 while (0) 108 109 CHK("3", 3, {}, {}); 110 CHK("3140000", 3.14, {}, {}); // decimal dot is an empty string! 111 CHK("foo", "foo", {}, {}); 112 CHK("foo", "foo"s, {}, {}); 113 CHK("foo", "foo"_ns, {}, {}); 114 CHK("X::op<<", X{}, {}, {}); 115 116 using DF = DoubleFormat; 117 LanguageInfo a{"", "", "", ".", "'", 3, {}, {}, {}, 0, {}}; 118 CHK("003", 3, {}, {{}, {}, {}, 0, 3, 0}); 119 CHK("3.140", 3.14, a, {{}, {}, {}, 0, 0, 3}); 120 CHK("012.346", 12.34567, a, {{}, {}, {}, 0, 6, 3}); 121 CHK("+01'234", 1234, a, {true, true, {}, 0, 5, 6}); 122 CHK("+0'000'012.340", 12.34, a, {true, true, DF::FIXED, 0, 10, 3}); 123 CHK("+0'001.234e+01", 12.34, a, {true, true, DF::SCIENTIFIC, 0, 7, 3}); 124 CHK("foo", "foo", a, {true, true, DF::SCIENTIFIC, 0, 7, 6}); 125 126 LanguageInfo b{"", "", "", "::", "()", 2, {}, {}, {}, 0, {}}; 127 CHK("0()12()34", 1234, b, {false, true, {}, 0, 5, 6}); 128 CHK("1()23::456", 123.456, b, {false, true, DF::GENERIC, 0, 0, 6}); 129 130 ArgumentType case_ptr = MakeSmart<Test::Case>(); 131 CHK("aa", case_ptr, a, {{}, {}, {}, 0, 0, 6}); 132 CHK("bb", case_ptr, a, {{}, {}, {}, 1, 0, 6}); 133 CHK("cc", case_ptr, a, {{}, {}, {}, 2, 0, 6}); 134 #undef CHK 135 136 // non first arg 137 ArgumentType args[] = { 0, "1str", 2.03 }; 138 { 139 REG({}, 1, {}); 140 // Span(args): workaround gcc bug https://godbolt.org/z/Ev3n7eo3G 141 CHECK(fmt(parser, Span(args)) == "1str"_ns); 142 } 143 144 { 145 REG({}, 2, {}); 146 CHECK(fmt(parser, Span(args)) == "2030000"_ns); 147 } 148 149 // not enough args 150 { 151 REG({}, 0, {}); 152 CHECK(fmt(parser, {}) == "???"_ns); 153 } 154 155 // FormatParams missing 156 { 157 REG({}, 0, {}); 158 data.code = MaybeOwningVector<const std::uint32_t>::Copy( 159 data.code.GetSpan().subspan(0, 1)); 160 CHECK_THROWS(fmt(parser, {1})); 161 } 162 } 163 #undef REG 164 165 TEST_SUITE_END(); 166 }