diff --git a/include/ctp/ctp.hpp b/include/ctp/ctp.hpp index 13e34a6..af23391 100644 --- a/include/ctp/ctp.hpp +++ b/include/ctp/ctp.hpp @@ -67,12 +67,19 @@ template, file_descriptor>>* = nullptr> constexpr auto print(FileDescriptor&& fd, Args&&... args); +namespace detail { + +template +struct is_string_like; + +} + /** * Formats and prints all arguments in the desired format. * @param args - the arguments to format and print */ -template -constexpr auto printf(std::string_view format = "", Args&&... args); +template::value>* = nullptr> +constexpr auto printf(Format&& format, Args&&... args); /** * Formats and prints all arguments in the desired format to a specific file descriptor. @@ -80,9 +87,11 @@ constexpr auto printf(std::string_view format = "", Args&&... args); * @param args - the arguments to format and print */ template, file_descriptor>>* = nullptr> -constexpr auto printf(FileDescriptor&& fd, std::string_view format = "", Args&&... args); + std::enable_if_t, file_descriptor> && + detail::is_string_like::value>* = nullptr> +constexpr auto printf(FileDescriptor&& fd, Format&& format, Args&&... args); /** * For user-defined types, the format function of the specialized formatter struct template is used. @@ -139,6 +148,73 @@ template inline constexpr auto forward = detail::forward(std::forward(ExprFunc), std::forward(Args)...); +// Implementation detail follow. + +template +class view { +public: + template + struct has_size_and_data : std::false_type {}; + + template + struct has_size_and_data< + U, + std::void_t())), decltype(std::data(std::declval()))>> : std::true_type {}; + + constexpr view() = default; + + template + explicit constexpr view(const std::array& arr) : data_{arr.data()}, size_{arr.size()} {} + + template + explicit constexpr view(const T (&arr)[N]) : data_{arr}, size_{N} {} + + template::value>* = nullptr> + explicit constexpr view(const U& arr) : data_(arr.data()), size_{arr.size()} {} + + template>* = nullptr> + constexpr view(const T* first, U&& size) : data_{first}, size_{static_cast(size)} {} + + constexpr view(const T* first, const T* last) : data_{first}, size_{static_cast(last - first)} {} + + constexpr auto begin() const { + return data_; + } + + constexpr auto end() const { + return data_ + size_; + } + +private: + const T* data_{}; + size_t size_{}; +}; + +template +view(std::array) -> view; + +template +view(T (&)[N]) -> view; + +template +view(T) -> view()))>>; + +template +view(T*, T*) -> view; + +template +view(T*, U) -> view; + +template +struct type { + constexpr type() = default; + + constexpr type(Ts&&... /*unused*/) {} +}; + +template +type(Ts&&...) -> type; + namespace detail { inline constexpr auto protocol_version = 1; @@ -163,10 +239,11 @@ enum class Indicator : uint32_t { ArrayEnd = 139, StringBegin = 140, StringEnd = 141, - TupleBegin = 142, - TupleEnd = 143, - CustomFormatBegin = 144, - CustomFormatEnd = 145, + UnicodeStringEnd = 142, + TupleBegin = 143, + TupleEnd = 144, + CustomFormatBegin = 145, + CustomFormatEnd = 146, }; template>* = nullptr> @@ -232,11 +309,23 @@ constexpr void print_value(int& one, T value, Args&&... /*unused*/) { CTP_INTERNAL_PRINT(to_abs_int(fraction), Indicator::FractionFloat); } +template +struct is_string_like : + std::bool_constant || +#ifdef __cpp_char8_t + std::is_constructible_v || +#endif + std::is_constructible_v || + std::is_constructible_v> { +}; + +template +inline constexpr bool is_string_like_v = is_string_like::value; + /// Print contiguous sequences of not char-like objects. -template< - typename T, - typename... Args, - std::enable_if_t && sizeof(decltype(view(std::declval())))>* = nullptr> +template && sizeof(decltype(view(std::declval())))>* = nullptr> constexpr void print_value(int& one, T&& value, Args&&... args) { CTP_INTERNAL_PRINT(one, Indicator::ArrayBegin); for (auto v : view(value)) { @@ -246,13 +335,26 @@ constexpr void print_value(int& one, T&& value, Args&&... args) { } /// Print contiguous sequence of char-like objects. -template>* = nullptr> +template>* = nullptr> constexpr void print_value(int& one, T value, Args&&... args) { CTP_INTERNAL_PRINT(one, Indicator::StringBegin); - for (auto v : std::string_view{value}) { - print_value(one, v, std::forward(args)..., v, value); + if constexpr (std::is_constructible_v) { + print_value(one, view{std::string_view{value}}, std::forward(args)..., value); + CTP_INTERNAL_PRINT(one, Indicator::StringEnd); + } +#ifdef __cpp_char8_t + else if constexpr (std::is_constructible_v) { + print_value(one, view{std::u8string_view{value}}, std::forward(args)..., value); + CTP_INTERNAL_PRINT(one, Indicator::StringEnd); + } +#endif + else if constexpr (std::is_constructible_v) { + print_value(one, view{std::u16string_view{value}}, std::forward(args)..., value); + CTP_INTERNAL_PRINT(one, Indicator::UnicodeStringEnd); + } else if constexpr (std::is_constructible_v) { + print_value(one, view{std::u32string_view{value}}, std::forward(args)..., value); + CTP_INTERNAL_PRINT(one, Indicator::UnicodeStringEnd); } - CTP_INTERNAL_PRINT(one, Indicator::StringEnd); } /// Print tuple like. @@ -408,83 +510,20 @@ constexpr auto print(FileDescriptor&& stream, Args&&... args) { return detail::print(std::forward(stream), std::forward(args)...); } +template::value>*> +constexpr auto printf(Format&& format, Args&&... args) { + return detail::print(stdout, format, std::forward(args)...); +} + template, file_descriptor>>*> -constexpr auto printf(FileDescriptor&& stream, std::string_view format, Args&&... args) { + std::enable_if_t, file_descriptor> && + detail::is_string_like::value>*> +constexpr auto printf(FileDescriptor&& stream, Format&& format, Args&&... args) { return detail::print(std::forward(stream), format, std::forward(args)...); } -template -constexpr auto printf(std::string_view format, Args&&... args) { - return detail::print(stdout, format, std::forward(args)...); -} - -template -class view { -public: - template - struct has_size_and_data : std::false_type {}; - - template - struct has_size_and_data< - U, - std::void_t())), decltype(std::data(std::declval()))>> : std::true_type {}; - - constexpr view() = default; - - template - explicit constexpr view(const std::array& arr) : data_{arr.data()}, size_{arr.size()} {} - - template - explicit constexpr view(const T (&arr)[N]) : data_{arr}, size_{N} {} - - template::value>* = nullptr> - explicit constexpr view(const U& arr) : data_(arr.data()), size_{arr.size()} {} - - template>* = nullptr> - constexpr view(const T* first, U&& size) : data_{first}, size_{static_cast(size)} {} - - constexpr view(const T* first, const T* last) : data_{first}, size_{static_cast(last - first)} {} - - constexpr auto begin() const { - return data_; - } - - constexpr auto end() const { - return data_ + size_; - } - -private: - const T* data_{}; - size_t size_{}; -}; - -template -view(std::array) -> view; - -template -view(T (&)[N]) -> view; - -template -view(T) -> view()))>>; - -template -view(T*, T*) -> view; - -template -view(T*, U) -> view; - -template -struct type { - constexpr type() = default; - - constexpr type(Ts&&... /*unused*/) {} -}; - -template -type(Ts&&...) -> type; - } // namespace ctp #endif // COMPILE_TIME_PRINTER_HPP_INCLUDE diff --git a/src/compile_time_printer/ctp.py b/src/compile_time_printer/ctp.py index b71281f..974b478 100644 --- a/src/compile_time_printer/ctp.py +++ b/src/compile_time_printer/ctp.py @@ -58,10 +58,11 @@ class Indicator(IntEnum): ArrayEnd = 139 StringBegin = 140 StringEnd = 141 - TupleBegin = 142 - TupleEnd = 143 - CustomFormatBegin = 144 - CustomFormatEnd = 145 + UnicodeStringEnd = 142 + TupleBegin = 143 + TupleEnd = 144 + CustomFormatBegin = 145 + CustomFormatEnd = 146 class TypePrettifier: @@ -289,14 +290,17 @@ def parse_value(type_of_value: str, number: int, indicator: Indicator): num += factor * float(number) / math.pow(10, 18) stack[-1].append(num) elif indicator == Indicator.PositiveInteger: - if type_of_value == 'char': + if type_of_value in ['char', 'char8_t', 'char16_t', 'char32_t']: stack[-1].append(chr(number)) elif type_of_value == 'bool': stack[-1].append(bool(number)) else: stack[-1].append(number) elif indicator == Indicator.NegativeInteger: - stack[-1].append(-number) + if type_of_value == 'char': + stack[-1].append(chr(256 - number)) + else: + stack[-1].append(-number) elif indicator == Indicator.Type: stack[-1].append(self._type_prettifier.prettify(type_of_value)) elif indicator in [Indicator.ArrayBegin, Indicator.StringBegin, indicator.TupleBegin]: @@ -305,7 +309,12 @@ def parse_value(type_of_value: str, number: int, indicator: Indicator): array = stack.pop() stack[-1].append(array) elif indicator == Indicator.StringEnd: - array = stack.pop() + array = stack.pop()[0] + # Transform characters to bytes and decode. + array = bytes([ord(x) for x in array]) + stack[-1].append(array.decode('utf8')) + elif indicator == Indicator.UnicodeStringEnd: + array = stack.pop()[0] stack[-1].append(''.join(array)) elif indicator == Indicator.TupleEnd: array = stack.pop() diff --git a/tests/data/example.cpp b/tests/data/example.cpp index 15290ee..caf8e03 100644 --- a/tests/data/example.cpp +++ b/tests/data/example.cpp @@ -45,8 +45,8 @@ constexpr auto test() { FooBar foobar{3, -1.25F}; ctp::print(ctp::type{}, foobar); - ctp::printf(ctp::stderr, "\n\tFatal "); - ctp::print(ctp::stderr, "success! :)"); + ctp::printf(ctp::stderr, u"\n\tFatal "); + ctp::print(ctp::stderr, U"success! :)"); [[maybe_unused]] constexpr auto i = ctp::print("Print examples:\n"); diff --git a/tests/test_parser.py b/tests/test_parser.py index 1f9b7cb..8fdd670 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -22,8 +22,16 @@ """ -def compile_file(input_file): - command = ['g++', '-Iinclude', '-std=c++17', '-fpermissive', '-fsyntax-only', '-xc++', '-'] +def std_20_support(): + command = ['g++', '-std=c++20', '-fsyntax-only', '-xc++', '-'] + prog = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + prog.stdin.write('int main() {}'.encode('utf8')) + prog.stdin.close() + return prog.wait() == 0 + + +def compile_file(input_file, std): + command = ['g++', '-Iinclude', '-std={}'.format(std), '-fpermissive', '-fsyntax-only', '-xc++', '-'] prog = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) prog.stdin.write(input_file.encode('utf8')) prog.stdin.close() @@ -33,13 +41,13 @@ def compile_file(input_file): prog.stderr.close() -def compile_print_call(args, format=False, func_scope='', global_scope='', pre_include=''): +def compile_print_call(args, format=False, func_scope='', global_scope='', pre_include='', std='c++17'): if format: call = 'printf' else: call = 'print' args = [str(x) for x in args] - return compile_file(cpp_file.format(pre_include, global_scope, func_scope, call, ', '.join(args))) + return compile_file(cpp_file.format(pre_include, global_scope, func_scope, call, ', '.join(args)), std=std) def assert_printers(log, expected, prettifier=None): @@ -68,9 +76,6 @@ def test_empty(): log = compile_print_call([]) assert_printers(log, [(False, sys.stdout, [])]) - log = compile_print_call([], format=True) - assert_printers(log, [(True, sys.stdout, [''])]) - log = compile_print_call(['false'], pre_include='#define CTP_QUIET') assert_printers(log, [None]) @@ -97,6 +102,33 @@ def test_char(): assert_printers(log, [(False, sys.stdout, [' '])]) +def test_unicode(): + if std_20_support(): + log = compile_print_call(['char8_t(94)'], std='c++20') + assert_printers(log, [(False, sys.stdout, ['^'])]) + + log = compile_print_call(['char16_t(9822)']) + assert_printers(log, [(False, sys.stdout, ['♞'])]) + + log = compile_print_call(['char32_t(9822)']) + assert_printers(log, [(False, sys.stdout, ['♞'])]) + + log = compile_print_call(['"♞"']) + assert_printers(log, [(False, sys.stdout, ['♞'])]) + + log = compile_print_call(['u"♞"']) + assert_printers(log, [(False, sys.stdout, ['♞'])]) + + log = compile_print_call(['u8"♞"']) + assert_printers(log, [(False, sys.stdout, ['♞'])]) + + log = compile_print_call(['U"♞"']) + assert_printers(log, [(False, sys.stdout, ['♞'])]) + + log = compile_print_call(['"┌{0:─^{2}}┐\\n│{1: ^{2}}│\\n└{0:─^{2}}┘\\n"', '""', '"Hello, world!"', 20], format=True) + assert_printers(log, [(True, sys.stdout, ['┌{0:─^{2}}┐\n│{1: ^{2}}│\n└{0:─^{2}}┘\n', '', 'Hello, world!', 20])]) + + def test_integer(): log = compile_print_call([1]) assert_printers(log, [(False, sys.stdout, [1])])