diff --git a/CHANGELOG.md b/CHANGELOG.md index 06be61d6..28bc4138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ ### Added - new debugger commands: `stack ` and `locals ` to print the values on the stack and in the current locals scope +- custom format specifiers for lists: + - `:n` to remove surrounding brackets, + - `:c` / `:nc` to use `, ` as a separator instead of ` `, + - `:l` / `:nl` to use `\n` as a separator, + - `:?s` to format as an escaped quoted string, + - `:s` to format as a quoted string +- `format` can use format specifiers for integers: `b`, `#b`, `B`, `#B`, `c`, `d`, `o`, `x`, `#x`, `X`, and `#X` if the argument is an integer ### Changed diff --git a/src/arkreactor/Builtins/String.cpp b/src/arkreactor/Builtins/String.cpp index e74edbf8..16a880bd 100644 --- a/src/arkreactor/Builtins/String.cpp +++ b/src/arkreactor/Builtins/String.cpp @@ -1,20 +1,190 @@ #include #include +#include #include #include +#include +#include #include +#include #include #include #include +struct value_wrapper +{ + const Ark::Value& value; + Ark::VM* vm_ptr; + bool nested = false; +}; + +template <> +struct fmt::formatter +{ +private: + fmt::basic_string_view opening_bracket_ = fmt::detail::string_literal {}; + fmt::basic_string_view closing_bracket_ = fmt::detail::string_literal {}; + fmt::basic_string_view separator_ = fmt::detail::string_literal {}; + bool is_debug = false; + bool is_literal_str = false; + fmt::formatter underlying_; + +public: + void set_brackets(const fmt::basic_string_view open, const fmt::basic_string_view close) + { + opening_bracket_ = open; + closing_bracket_ = close; + } + + void set_separator(const fmt::basic_string_view sep) + { + separator_ = sep; + } + + format_parse_context::iterator parse(fmt::format_parse_context& ctx) + { + auto it = ctx.begin(); + const auto end = ctx.end(); + if (it == end) + return underlying_.parse(ctx); + + switch (detail::to_ascii(*it)) + { + case 'n': + set_brackets({}, {}); + ++it; + if (it == end) + report_error("invalid format specifier"); + if (*it == '}') + return it; + if (*it != 'c' && *it != 'l') + report_error("invalid format specifier. Expected either :nc or :nl"); + [[fallthrough]]; + + case 'c': + if (*it == 'c') + { + set_separator(fmt::detail::string_literal {}); + ++it; + return it; + } + [[fallthrough]]; + + case 'l': + set_separator(fmt::detail::string_literal {}); + ++it; + return it; + + case '?': + is_debug = true; + set_brackets({}, {}); + ++it; + if (it == end || *it != 's') + report_error("invalid format specifier. Expected :?s, not :?"); + [[fallthrough]]; + + case 's': + if (!is_debug) + { + set_brackets(fmt::detail::string_literal {}, + fmt::detail::string_literal {}); + set_separator({}); + is_literal_str = true; + } + ++it; + return it; + + default: + break; + } + + if (it != end && *it != '}') + { + if (*it != ':') + report_error("invalid format specifier"); + ++it; + } + + ctx.advance_to(it); + return underlying_.parse(ctx); + } + + template + auto write_debug_string(Output& out, It it, Sentinel end, Ark::VM* vm_ptr) const -> Output + { + auto buf = fmt::basic_memory_buffer(); + for (; it != end; ++it) + { + auto formatted = it->toString(*vm_ptr); + buf.append(formatted); + } + auto specs = fmt::format_specs(); + specs.set_type(fmt::presentation_type::debug); + return fmt::detail::write( + out, + fmt::basic_string_view(buf.data(), buf.size()), + specs); + } + + fmt::format_context::iterator format(const value_wrapper& value, fmt::format_context& ctx) const + { + auto out = ctx.out(); + auto it = fmt::detail::range_begin(value.value.constList()); + const auto end = fmt::detail::range_end(value.value.constList()); + if (is_debug) + return write_debug_string(out, it, end, value.vm_ptr); + + if ((is_literal_str && !value.nested) || !is_literal_str) + out = fmt::detail::copy(opening_bracket_, out); + + for (int i = 0; it != end; ++it) + { + if (i > 0) + out = fmt::detail::copy(separator_, out); + ctx.advance_to(out); + + auto&& item = *it; + if (item.valueType() == Ark::ValueType::List) + { + // if :s, do not put surrounding "" here + format({ item, value.vm_ptr, /* nested= */ true }, ctx); + } + else + { + std::string formatted = item.toString(*value.vm_ptr); + out = underlying_.format(formatted, ctx); + } + + ++i; + } + + if ((is_literal_str && !value.nested) || !is_literal_str) + out = detail::copy(closing_bracket_, out); + + return out; + } +}; + namespace Ark::internal::Builtins::String { /** * @name format * @brief Format a String given replacements - * @details https://fmt.dev/12.0/syntax/ + * @details See [fmt.dev](https://fmt.dev/12.0/syntax/) for syntax. + * =details-begin + * In the case of lists, we have custom specifiers: + * - `n` removes surrounding brackets, uses ' ' as a separator + * - `?s` debug format. The list is formatted as an escaped string + * - `s` string format. The list is formatted as a string + * - `c` changes the separator to ', ' + * - `l` changes the separator to '\n' + * + * `n` can be combined with either `c` and `l` (which are mutually exclusive): `nc`, `nl`. + * + * The underlying formatter is the one of strings, so you can write `::<10` to align all elements left in a 10 char wide block each. + * =details-end * @param format the String to format * @param values as any argument as you need, of any valid ArkScript type * =begin @@ -42,13 +212,30 @@ namespace Ark::internal::Builtins::String if (it->valueType() == ValueType::String) store.push_back(it->stringRef()); else if (it->valueType() == ValueType::Number) - store.push_back(it->number()); + { + double int_part; + if (std::modf(it->number(), &int_part) == 0.0) + store.push_back(static_cast(it->number())); + else + store.push_back(it->number()); + } else if (it->valueType() == ValueType::Nil) store.push_back("nil"); else if (it->valueType() == ValueType::True) store.push_back("true"); else if (it->valueType() == ValueType::False) store.push_back("false"); + else if (it->valueType() == ValueType::List) + { + // std::vector r; + // std::ranges::transform( + // it->list(), + // std::back_inserter(r), + // [&vm](const Value& val) -> value_wrapper { + // return value_wrapper { val, vm }; + // }); + store.push_back(value_wrapper { *it, vm }); + } else store.push_back(it->toString(*vm)); } diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_b.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_b.ark new file mode 100644 index 00000000..19b33aef --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_b.ark @@ -0,0 +1 @@ +(print (format "{:b}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_b.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_b.expected new file mode 100644 index 00000000..d85c2781 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_b.expected @@ -0,0 +1,6 @@ +format: can not format "{:b}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_b.ark:1 + 1 | (print (format "{:b}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_c.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_c.ark new file mode 100644 index 00000000..2362133d --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_c.ark @@ -0,0 +1 @@ +(print (format "{:c}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_c.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_c.expected new file mode 100644 index 00000000..34d9a8b5 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_c.expected @@ -0,0 +1,6 @@ +format: can not format "{:c}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_c.ark:1 + 1 | (print (format "{:c}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_d.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_d.ark new file mode 100644 index 00000000..0088033c --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_d.ark @@ -0,0 +1 @@ +(print (format "{:d}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_d.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_d.expected new file mode 100644 index 00000000..0d68383f --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_d.expected @@ -0,0 +1,6 @@ +format: can not format "{:d}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_d.ark:1 + 1 | (print (format "{:d}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_b.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_b.ark new file mode 100644 index 00000000..4a724ccb --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_b.ark @@ -0,0 +1 @@ +(print (format "{:#b}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_b.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_b.expected new file mode 100644 index 00000000..0d4ebb9f --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_b.expected @@ -0,0 +1,6 @@ +format: can not format "{:#b}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_b.ark:1 + 1 | (print (format "{:#b}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_b.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_b.ark new file mode 100644 index 00000000..8f6c207a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_b.ark @@ -0,0 +1 @@ +(print (format "{:#B}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_b.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_b.expected new file mode 100644 index 00000000..7c65ac03 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_b.expected @@ -0,0 +1,6 @@ +format: can not format "{:#B}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_b.ark:1 + 1 | (print (format "{:#B}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_x.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_x.ark new file mode 100644 index 00000000..91fab277 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_x.ark @@ -0,0 +1 @@ +(print (format "{:#X}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_x.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_x.expected new file mode 100644 index 00000000..2526be08 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_x.expected @@ -0,0 +1,6 @@ +format: can not format "{:#X}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_upper_x.ark:1 + 1 | (print (format "{:#X}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_x.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_x.ark new file mode 100644 index 00000000..181daa42 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_x.ark @@ -0,0 +1 @@ +(print (format "{:#x}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_x.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_x.expected new file mode 100644 index 00000000..8522f33e --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_x.expected @@ -0,0 +1,6 @@ +format: can not format "{:#x}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_hashtag_x.ark:1 + 1 | (print (format "{:#x}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_o.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_o.ark new file mode 100644 index 00000000..63bad4e9 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_o.ark @@ -0,0 +1 @@ +(print (format "{:o}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_o.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_o.expected new file mode 100644 index 00000000..7e41bc17 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_o.expected @@ -0,0 +1,6 @@ +format: can not format "{:o}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_o.ark:1 + 1 | (print (format "{:o}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_b.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_b.ark new file mode 100644 index 00000000..bfd062d1 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_b.ark @@ -0,0 +1 @@ +(print (format "{:B}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_b.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_b.expected new file mode 100644 index 00000000..c83556e9 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_b.expected @@ -0,0 +1,6 @@ +format: can not format "{:B}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_b.ark:1 + 1 | (print (format "{:B}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_x.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_x.ark new file mode 100644 index 00000000..cd8ba91b --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_x.ark @@ -0,0 +1 @@ +(print (format "{:X}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_x.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_x.expected new file mode 100644 index 00000000..f24ae27f --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_x.expected @@ -0,0 +1,6 @@ +format: can not format "{:X}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_upper_x.ark:1 + 1 | (print (format "{:X}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_x.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_x.ark new file mode 100644 index 00000000..0d491473 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_x.ark @@ -0,0 +1 @@ +(print (format "{:x}" 65.1)) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_x.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_x.expected new file mode 100644 index 00000000..135b33a6 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_double_x.expected @@ -0,0 +1,6 @@ +format: can not format "{:x}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_double_x.ark:1 + 1 | (print (format "{:x}" 65.1)) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid.ark new file mode 100644 index 00000000..845bd71b --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid.ark @@ -0,0 +1 @@ +(print (format "{:d}" [])) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid.expected new file mode 100644 index 00000000..8fefe45d --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid.expected @@ -0,0 +1,6 @@ +format: can not format "{:d}" (1 argument provided) because of invalid format specifier + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid.ark:1 + 1 | (print (format "{:d}" [])) + | ^~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_debug.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_debug.ark new file mode 100644 index 00000000..f51cc17a --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_debug.ark @@ -0,0 +1 @@ +(print (format "{:?}" [])) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_debug.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_debug.expected new file mode 100644 index 00000000..496a6919 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_debug.expected @@ -0,0 +1,6 @@ +format: can not format "{:?}" (1 argument provided) because of invalid format specifier. Expected :?s, not :? + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_debug.ark:1 + 1 | (print (format "{:?}" [])) + | ^~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_na.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_na.ark new file mode 100644 index 00000000..f86ffd2f --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_na.ark @@ -0,0 +1 @@ +(print (format "{:na}" [])) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_na.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_na.expected new file mode 100644 index 00000000..e19daa93 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_na.expected @@ -0,0 +1,6 @@ +format: can not format "{:na}" (1 argument provided) because of invalid format specifier. Expected either :nc or :nl + +In file tests/unittests/resources/DiagnosticsSuite/runtime/format_list_invalid_na.ark:1 + 1 | (print (format "{:na}" [])) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/LangSuite/string-tests.ark b/tests/unittests/resources/LangSuite/string-tests.ark index d297f247..5c87b4ae 100644 --- a/tests/unittests/resources/LangSuite/string-tests.ark +++ b/tests/unittests/resources/LangSuite/string-tests.ark @@ -58,4 +58,27 @@ (test:case "format strings" { (test:eq "nilfalsetrue" (format "{}{}{}" nil false true)) - (test:eq "CProcedure" (format "{}" print)) })}) + (test:eq "CProcedure" (format "{}" print)) + + (test:eq (format "{}" ["hel\tlo" 1 nil [true "a" 2]]) "[hel lo 1 nil [true a 2]]") + (test:eq (format "{::}" ["hel\tlo" 1 nil [true "a" 2]]) "[hel lo 1 nil [true a 2]]") + (test:eq (format "{:n}" ["hel\tlo" 1 nil [true "a" 2]]) "hel lo 1 nil true a 2") + (test:eq (format "{:?s}" ["hel\tlo" 1 nil [true "a" 2]]) "\"hel\\tlo1nil[true \\\"a\\\" 2]\"") + (test:eq (format "{:s}" ["hel\tlo" 1 nil [true "a" 2]]) "\"hel lo1niltruea2\"") + (test:eq (format "{:c}" ["hel\tlo" 1 nil [true "a" 2]]) "[hel lo, 1, nil, [true, a, 2]]") + (test:eq (format "{:l}" ["hel\tlo" 1 nil [true "a" 2]]) "[hel lo\n1\nnil\n[true\na\n2]]") + (test:eq (format "{:nc}" ["hel\tlo" 1 nil [true "a" 2]]) "hel lo, 1, nil, true, a, 2") + (test:eq (format "{:nl}" ["hel\tlo" 1 nil [true "a" 2]]) "hel lo\n1\nnil\ntrue\na\n2") + (test:eq (format "{::<5}" ["hello" 1 nil [true "a" 2]]) "[hello 1 nil [true a 2 ]]") + + (test:eq (format "{:b}" 65) "1000001") + (test:eq (format "{:#b}" 65) "0b1000001") + (test:eq (format "{:B}" 65) "1000001") + (test:eq (format "{:#B}" 65) "0B1000001") + (test:eq (format "{:c}" 65) "A") + (test:eq (format "{:d}" 65) "65") + (test:eq (format "{:o}" 65) "101") + (test:eq (format "{:x}" 63) "3f") + (test:eq (format "{:#x}" 63) "0x3f") + (test:eq (format "{:X}" 63) "3F") + (test:eq (format "{:#X}" 63) "0X3F") })})