There's a TODO here about checking for invalid UTF-8, which is done by the next patch.
I don't know if the Windows code actually works. I tried to test it with mingw and Wine, but I got garbled text. But I'm not sure if that's my code here, or the conversion to UTF-16, or how I'm testing, or just that Wine in a Linux terminal doesn't properly emulat the Windows console, or something else. This needs tests, so I need to write them before pushing, but I still plan to get that done for GCC 14. -- >8 -- libstdc++-v3/ChangeLog: PR libstdc++/107760 * config/abi/pre/gnu.ver: Export new symbols. * include/Makefile.am: Add new header. * include/Makefile.in: Regenerate. * include/bits/version.def (__cpp_lib_print): Define. * include/bits/version.h: Regenerate. * include/std/ostream (vprintf_nonunicode, vprintf_unicode) (print, println): New functions. * include/std/print: New file. * src/c++20/Makefile.am: Add new source file. * src/c++20/Makefile.in: Regenerate. * src/c++98/globals_io.cc [_WIN32] (__fd_for_console): New function. * src/c++20/print.cc: New file. --- libstdc++-v3/config/abi/pre/gnu.ver | 4 + libstdc++-v3/include/Makefile.am | 1 + libstdc++-v3/include/Makefile.in | 1 + libstdc++-v3/include/bits/version.def | 9 ++ libstdc++-v3/include/bits/version.h | 29 ++++-- libstdc++-v3/include/std/ostream | 102 ++++++++++++++++++++ libstdc++-v3/include/std/print | 128 ++++++++++++++++++++++++++ libstdc++-v3/src/c++20/Makefile.am | 2 +- libstdc++-v3/src/c++20/Makefile.in | 4 +- libstdc++-v3/src/c++20/print.cc | 35 +++++++ libstdc++-v3/src/c++98/globals_io.cc | 23 +++++ 11 files changed, 326 insertions(+), 12 deletions(-) create mode 100644 libstdc++-v3/include/std/print create mode 100644 libstdc++-v3/src/c++20/print.cc diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver index 15b50d51251..c7200929e34 100644 --- a/libstdc++-v3/config/abi/pre/gnu.ver +++ b/libstdc++-v3/config/abi/pre/gnu.ver @@ -2514,6 +2514,10 @@ GLIBCXX_3.4.31 { _ZNKSt12__shared_ptrINSt10filesystem28recursive_directory_iterator10_Dir_stackELN9__gnu_cxx12_Lock_policyE[012]EEcvbEv; _ZNKSt12__shared_ptrINSt10filesystem7__cxx1128recursive_directory_iterator10_Dir_stackELN9__gnu_cxx12_Lock_policyE[012]EEcvbEv; + # These are only defined for *-*-mingw* + _ZSt16__fd_for_consolePSt15basic_streambufIcSt11char_traitsIcEE; + _ZSt24__write_utf16_to_consoleiNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE; + } GLIBCXX_3.4.30; GLIBCXX_3.4.32 { diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index 17d9d9cec31..368b92eafbc 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -85,6 +85,7 @@ std_headers = \ ${std_srcdir}/memory_resource \ ${std_srcdir}/mutex \ ${std_srcdir}/ostream \ + ${std_srcdir}/print \ ${std_srcdir}/queue \ ${std_srcdir}/random \ ${std_srcdir}/regex \ diff --git a/libstdc++-v3/include/Makefile.in b/libstdc++-v3/include/Makefile.in index f038af709cc..a31588c0100 100644 --- a/libstdc++-v3/include/Makefile.in +++ b/libstdc++-v3/include/Makefile.in @@ -441,6 +441,7 @@ std_freestanding = \ @GLIBCXX_HOSTED_TRUE@ ${std_srcdir}/memory_resource \ @GLIBCXX_HOSTED_TRUE@ ${std_srcdir}/mutex \ @GLIBCXX_HOSTED_TRUE@ ${std_srcdir}/ostream \ +@GLIBCXX_HOSTED_TRUE@ ${std_srcdir}/print \ @GLIBCXX_HOSTED_TRUE@ ${std_srcdir}/queue \ @GLIBCXX_HOSTED_TRUE@ ${std_srcdir}/random \ @GLIBCXX_HOSTED_TRUE@ ${std_srcdir}/regex \ diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 15bd502f52c..8b5cace3775 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1578,6 +1578,15 @@ ftms = { }; }; +ftms = { + name = print; + values = { + v = 202211; + cxxmin = 23; + hosted = yes; + }; +}; + ftms = { name = spanstream; values = { diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index 9563b6cd2f7..f197408e60f 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -1923,6 +1923,17 @@ #undef __glibcxx_want_out_ptr // from version.def line 1582 +#if !defined(__cpp_lib_print) +# if (__cplusplus >= 202100L) && _GLIBCXX_HOSTED +# define __glibcxx_print 202211L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_print) +# define __cpp_lib_print 202211L +# endif +# endif +#endif /* !defined(__cpp_lib_print) && defined(__glibcxx_want_print) */ +#undef __glibcxx_want_print + +// from version.def line 1591 #if !defined(__cpp_lib_spanstream) # if (__cplusplus >= 202100L) && _GLIBCXX_HOSTED && (__glibcxx_span) # define __glibcxx_spanstream 202106L diff --git a/libstdc++-v3/include/std/ostream b/libstdc++-v3/include/std/ostream index 1de1c1bd359..e81c39a7c80 100644 --- a/libstdc++-v3/include/std/ostream +++ b/libstdc++-v3/include/std/ostream @@ -39,6 +39,11 @@ #include <ios> #include <bits/ostream_insert.h> +#if __cplusplus > 202002L +# include <format> +#endif + +# define __glibcxx_want_print #include <bits/version.h> // __glibcxx_syncbuf namespace std _GLIBCXX_VISIBILITY(default) @@ -872,6 +877,103 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } #endif // __glibcxx_syncbuf +#if __cpp_lib_print // C++ >= 23 + + inline void + vprint_nonunicode(ostream& __os, string_view __fmt, format_args __args) + { + ostream::sentry __cerb(__os); + if (__cerb) + { + string __out = std::vformat(__fmt, __args); + __try + { + const streamsize __w = __os.width(); + const streamsize __n = __out.size(); + if (__w > __n) + { + const bool __left + = (__os.flags() & ios_base::adjustfield) == ios_base::left; + if (!__left) + std::__ostream_fill(__os, __w - __n); + if (__os.good()) + std::__ostream_write(__os, __out.data(), __n); + if (__left && __os.good()) + std::__ostream_fill(__os, __w - __n); + } + else + std::__ostream_write(__os, __out.data(), __n); + } + __catch(const __cxxabiv1::__forced_unwind&) + { + __os._M_setstate(ios_base::badbit); + __throw_exception_again; + } + __catch(...) + { __os._M_setstate(ios_base::badbit); } + } + } + + inline void + vprint_unicode(ostream& __os, string_view __fmt, format_args __args) + { + // TODO: diagnose invalid UTF-8 code units +#ifdef _WIN32 + int __fd_for_console(std::streambuf*); + void __write_utf16_to_console(int, string); + + // If stream refers to a terminal convert to UTF-16 and use WriteConsoleW. + if (int __fd = __fd_for_console(__os.rdbuf()); __fd >= 0) + { + ostream::sentry __cerb(__os); + if (__cerb) + { + string __out = std::vformat(__fmt, __args); + ios_base::iostate __err = ios_base::goodbit; + __try + { + if (__os.rdbuf()->pubsync() == -1) + __err = ios::badbit; + else if (__write_utf16_to_console(__fd, __out)) + __err = ios::badbit; + } + __catch(const __cxxabiv1::__forced_unwind&) + { + __os._M_setstate(ios_base::badbit); + __throw_exception_again; + } + __catch(...) + { __os._M_setstate(ios_base::badbit); } + + if (__err) + __os.setstate(__err); + } + } +#endif + std::vprint_nonunicode(__os, __fmt, __args); + } + + + template<typename... _Args> + inline void + print(ostream& __os, format_string<_Args...> __fmt, _Args&&... __args) + { + auto __fmtargs = std::make_format_args(std::forward<_Args>(__args)...); + if constexpr (string_view(__GNUC_EXECUTION_CHARSET_NAME) == "UTF-8") + std::vprint_unicode(__os, __fmt.get(), __fmtargs); + else + std::vprint_nonunicode(__os, __fmt.get(), __fmtargs); + } + + template<typename... _Args> + inline void + println(ostream& __os, format_string<_Args...> __fmt, _Args&&... __args) + { + std::print(__os, "{}\n", + std::format(__fmt, std::forward<_Args>(__args)...)); + } +#endif // __cpp_lib_print + #endif // C++11 _GLIBCXX_END_NAMESPACE_VERSION diff --git a/libstdc++-v3/include/std/print b/libstdc++-v3/include/std/print new file mode 100644 index 00000000000..75e78841247 --- /dev/null +++ b/libstdc++-v3/include/std/print @@ -0,0 +1,128 @@ +// <print> Print functions -*- C++ -*- + +// Copyright The GNU Toolchain Authors. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// <http://www.gnu.org/licenses/>. + +/** @file include/print + * This is a Standard C++ Library header. + */ + +#ifndef _GLIBCXX_PRINT +#define _GLIBCXX_PRINT 1 + +#pragma GCC system_header + +#include <bits/requires_hosted.h> // for std::format + +#define __glibcxx_want_print +#include <bits/version.h> + +#ifdef __cpp_lib_print // C++ >= 23 + +#include <format> +#include <cstdio> +#include <cerrno> +#include <bits/functexcept.h> + +#ifdef _WIN32 +# include <system_error> +#endif + +namespace std _GLIBCXX_VISIBILITY(default) +{ +_GLIBCXX_BEGIN_NAMESPACE_VERSION + + inline void + vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args) + { + string __out = std::vformat(__fmt, __args); + if (std::fwrite(__out.data(), 1, __out.size(), __stream) != __out.size()) + __throw_system_error(EIO); + } + + inline void + vprint_unicode(FILE* __stream, string_view __fmt, format_args __args) + { + // TODO: diagnose invalid UTF-8 code units +#ifdef _WIN32 + int __fd_for_console(FILE*); + void __write_utf16_to_console(int, string); + + // If stream refers to a terminal convert to UTF-16 and use WriteConsoleW. + if (int __fd = __fd_for_console(__stream); __fd >= 0) + { + string __out = std::vformat(__fmt, __args); + error_code __e; + if (!std::fflush(__stream)) + { + if (!(__e = __write_utf16_to_console(__fd, __out))) + return; + } + else + __e = error_code(errno, generic_category()); + _GLIBCXX_THROW_OR_ABORT(system_error(__e, "std::vprint_unicode")); + } +#endif + std::vprint_nonunicode(__stream, __fmt, __args); + } + + template<typename... _Args> + inline void + print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) + { + auto __fmtargs = std::make_format_args(std::forward<_Args>(__args)...); + if constexpr (string_view(__GNUC_EXECUTION_CHARSET_NAME) == "UTF-8") + std::vprint_unicode(__stream, __fmt.get(), __fmtargs); + else + std::vprint_nonunicode(__stream, __fmt.get(), __fmtargs); + } + + template<typename... _Args> + inline void + print(format_string<_Args...> __fmt, _Args&&... __args) + { std::print(stdout, __fmt, std::forward<_Args>(__args)...); } + + template<typename... _Args> + inline void + println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) + { + std::print(__stream, "{}\n", + std::format(__fmt, std::forward<_Args>(__args)...)); + } + + template<typename... _Args> + inline void + println(format_string<_Args...> __fmt, _Args&&... __args) + { std::println(stdout, __fmt, std::forward<_Args>(__args)...); } + + inline void + vprint_unicode(string_view __fmt, format_args __args) + { std::vprint_unicode(stdout, __fmt, __args); } + + inline void + vprint_nonunicode(string_view __fmt, format_args __args) + { std::vprint_nonunicode(stdout, __fmt, __args); } + +_GLIBCXX_END_NAMESPACE_VERSION +} // namespace std +#endif // __cpp_lib_print +#endif // _GLIBCXX_PRINT diff --git a/libstdc++-v3/src/c++20/Makefile.am b/libstdc++-v3/src/c++20/Makefile.am index e947855e6ae..3cdc6521bb4 100644 --- a/libstdc++-v3/src/c++20/Makefile.am +++ b/libstdc++-v3/src/c++20/Makefile.am @@ -36,7 +36,7 @@ else inst_sources = endif -sources = tzdb.cc +sources = tzdb.cc print.cc vpath % $(top_srcdir)/src/c++20 diff --git a/libstdc++-v3/src/c++20/Makefile.in b/libstdc++-v3/src/c++20/Makefile.in index 3ec8c5ce804..b732e6fc005 100644 --- a/libstdc++-v3/src/c++20/Makefile.in +++ b/libstdc++-v3/src/c++20/Makefile.in @@ -121,7 +121,7 @@ CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = LTLIBRARIES = $(noinst_LTLIBRARIES) libc__20convenience_la_LIBADD = -am__objects_1 = tzdb.lo +am__objects_1 = tzdb.lo print.lo @ENABLE_EXTERN_TEMPLATE_TRUE@am__objects_2 = sstream-inst.lo @GLIBCXX_HOSTED_TRUE@am_libc__20convenience_la_OBJECTS = \ @GLIBCXX_HOSTED_TRUE@ $(am__objects_1) $(am__objects_2) @@ -432,7 +432,7 @@ headers = @ENABLE_EXTERN_TEMPLATE_TRUE@inst_sources = \ @ENABLE_EXTERN_TEMPLATE_TRUE@ sstream-inst.cc -sources = tzdb.cc +sources = tzdb.cc print.cc @GLIBCXX_HOSTED_FALSE@libc__20convenience_la_SOURCES = @GLIBCXX_HOSTED_TRUE@libc__20convenience_la_SOURCES = $(sources) $(inst_sources) diff --git a/libstdc++-v3/src/c++20/print.cc b/libstdc++-v3/src/c++20/print.cc new file mode 100644 index 00000000000..d97a0c71dfe --- /dev/null +++ b/libstdc++-v3/src/c++20/print.cc @@ -0,0 +1,35 @@ +#ifdef _WIN32 +#include <system_error> +#include <codecvt> +#include <bits/locale_conv.h> +#include <stdio.h> // _fileno +#include <io.h> // _get_osfhandle, _isatty +#include <windows.h> // GetLastError, WriteConsoleW + +namespace std _GLIBCXX_VISIBILITY(default) +{ + int + __fd_for_console(FILE* f) + { + if (int fd = _fileno(f); fd >= 0 && _isatty(fd)) + return fd; + return -1; + } + + error_code + __write_utf16_to_console(int fd, string str) + { + std::wstring wstr; + std::codecvt_utf8_utf16<wchar_t> wcvt; + const auto p = str.data(); + unsigned long nchars = 0; + if (!std::__str_codecvt_in_all(p, p + str.size(), wstr, wcvt)) + return std::make_error_code(errc::illegal_byte_sequence); + WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), + wstr.data(), wstr.size(), &nchars, nullptr); + if (nchars != wstr.size()) + return {(int)GetLastError(), system_category()}; + return {}; + } +} +#endif // _WIN32 diff --git a/libstdc++-v3/src/c++98/globals_io.cc b/libstdc++-v3/src/c++98/globals_io.cc index 0c4f270977d..538efd01b53 100644 --- a/libstdc++-v3/src/c++98/globals_io.cc +++ b/libstdc++-v3/src/c++98/globals_io.cc @@ -107,3 +107,26 @@ namespace __gnu_internal _GLIBCXX_VISIBILITY(hidden) fake_wfilebuf buf_wcerr; #endif } // namespace __gnu_internal + +#ifdef _WIN32 +namespace std _GLIBCXX_VISIBILITY(default) +{ + int __fd_for_console(FILE*); + + int __fd_for_console(std::streambuf* sb) + { + using namespace __gnu_internal; + using namespace __gnu_cxx; + + FILE* f = NULL; + void* p = sb; + if (p == buf_cout_sync || p == buf_cin_sync || p == buf_cerr_sync) + f = dynamic_cast<stdio_sync_filebuf<char>*>(sb)->file(); + else if (p == buf_cout || p == buf_cin || p == buf_cerr) + f = dynamic_cast<stdio_filebuf<char>*>(sb)->file(); + else + return -1; + return __fd_for_console(f); + } +} +#endif -- 2.41.0