Compare commits

...

434 Commits

Author SHA1 Message Date
9588b602a9 system: add to_sstring()
Some checks failed
Doxygen Action / build (push) Has been cancelled
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-07-08 11:17:40 +02:00
b99b2fc19e Common: Enforce explicit handle validation
Some invalid handle values are -1 or INVALID_HANDLE_VALUE, hence our
handle template has a specific invalid handle value parameter we should
always test against. Time and again, I find my code simply comparing
handle against zero.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-07-08 11:17:16 +02:00
a9d87b4cfd stream: set file sharing on POSIX
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-30 20:25:33 +02:00
faeddedd46 stream: modify read_reminder() to allow custom allocator
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-27 15:13:02 +02:00
214e307486 mbedtls: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-17 16:45:29 +02:00
7aa64cb625 exception, curl: switch to a reusable numbered errors
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-17 16:45:09 +02:00
e0cbc4a90a locale: add missing #include
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-17 16:38:06 +02:00
19a3ee3af2 exception: add missing #include
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-17 16:37:40 +02:00
5f2b243942 system: basic_sys_object::get → basic_sys_object::operator T
The handle validation is done with valid() method now, so using operator
T to auto-retrieve object handle should not provide ambiguities any
more. As long as we remember to use valid() for testing!

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-12 14:20:13 +02:00
db91035b42 unicode: fix and cleanup Windows support
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-11 09:57:30 +02:00
b5cc4c01a9 math: fix align_up declaration
Using add() which is non-constexpr, the align_up() itself may not be
constexpr.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-11 09:55:41 +02:00
47d10e2d78 Require explicit handle validation
Using operator bool() hid ambiguity when handle was polymorfic with
bool.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-09 22:37:08 +02:00
f1e894aefd stream: read huge data in blocks on POSIX
read() blocks when there is no more data to read. However, we do need
to loop when read is divided into blocks.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-05 12:29:47 +02:00
fc852c77c7 parser: address GCC warnings
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-05 12:29:43 +02:00
341073b587 html: ignore MSVC pragmas in GCC builds
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-05 12:29:35 +02:00
2bfe310d47 unicode: inject invalid character on encode failure
...rather than throw. This mimics Windows behaviour.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-05 12:29:10 +02:00
f454ae9cab unicode: add ASCII charset
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-05 12:29:03 +02:00
862c073ca8 stream: read from system stream once on POSIX
Looping ReadFile() system call is Windows-specific. POSIX systems don't
need this. In fact: with pipes using buffered streams, this blocks the
read until one full buffer of data is available.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-06-02 14:22:44 +02:00
7bb614a34f stream: assert buffer is not nullptr before use
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-05-27 15:22:42 +02:00
191f3bb2f9 stream: check for address overflows in memory_stream
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-05-27 11:55:07 +02:00
406d14746f math: add align_up/down
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-05-27 11:52:14 +02:00
496dc9cb34 Revise ternary operators
Where test expression is already or almost boolean type, using ternery
operator is excessive and possibly more difficult to read.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-05-27 10:23:25 +02:00
bed22e7fa5 compat: add _Acquires_lock_
It is not exactly needed by stdex, but we have such a nice collection of
the MSVC SAL here already.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-05-26 16:00:27 +02:00
be273d4263 exception: Add exception_msg helper
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-05-13 09:36:22 +02:00
4c499f2905 json: add escape
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-04-22 18:43:34 +02:00
1b8053f95c Revise lambda declarations
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-04-16 10:34:11 +02:00
487f6fa922 Clear warning suppression
Code analysis got quite good and we'd better keep warnings on our radar.

Either:
1. Research and resolve the reason for the warning.
2. Remove silencing and let code analysis complain.
3. Comment why some warning silencing is there.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-04-03 16:15:27 +02:00
91c9a71a5d Resolve code analysis warnings
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-03-28 17:28:19 +01:00
5fc35751a3 UnitTest: Switch to Assert::IsTrue for primitive datatypes
Visual Studio 2019 has issues compiling Assert::AreEqual<stdex::langid>.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-03-28 15:45:24 +01:00
51d472d8b2 UnitTest: ShortProjectName is not available in VS2019
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-03-28 14:26:54 +01:00
50578dabaa unicode: Remove deprecated MB_PRECOMPOSED
Online documentation suggests it should be used as default (for the most
of code pages), SDK source code says it's deprecated. Let's remove it
and see how things work.

Reference: https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-03-28 14:17:29 +01:00
832daf89d6 UnitTests: Fix source code charset
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-03-28 14:17:29 +01:00
aae79719cb AppVeyor: Initial support
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-03-28 13:31:14 +01:00
2694f59d79 Sync with Xcode
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-03-12 11:14:19 +01:00
bfd8aaff65 Stop throwing in destructors
When exception processing is unwinding the stack, any exception thrown
in destructors end up std::terminate()-in our process.

Since all workarounds to report errors from destructors seems an
overkill, and the only important place where we should notify the user
about a failure is the stdex::stream::cache::~cache() (data loss occurs
when failure happens here), and if we'd throw in stdex::stream::cache::
~cache(), our process would get terminated anyway so the data loss is
inevitable, let's just silence this for now and come up with a better
solution later if we get smarter anytime in the future.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-03-06 22:15:45 +01:00
69f2c639cd string: improve strndup documentation
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-02-20 09:11:00 +01:00
442001f66b curl: use CURL_GLOBAL_DEFAULT by default
This makes the client code a bit simpler for typical use-cases.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-02-13 12:51:49 +01:00
faa776a060 curl: fix missing #include
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-02-13 12:44:37 +01:00
d13646952d progress: unify clock usage
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-01-14 12:13:48 +01:00
32d656ceed html: default string lengths in document::assign/append
We run a strnlen before processing anyway.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-01-14 12:13:48 +01:00
2674bb0e32 string: add variants for char16_t/char32_t strings
char16_t is not exactly the wchar_t on Windows. char32_t is not exactly
the wchar_t on POSIX. Rather than selecting the appropriate variant,
polymorphism picked the template implementation of strncmp, strcpy and
strncpy. The one that does not convert UTF16 surrogate pairs against
their UTF32 representation.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-01-13 11:01:01 +01:00
a7543cf9ab string: add snprintf
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-01-13 11:01:01 +01:00
db413bb5ce UnitTests: fix to compile on Windows
sys_info.hpp requires an extra #define and linker library.

Signed-off-by: Simon Rozman <simon@rozman.si>
2025-01-13 11:01:01 +01:00
8fd9fbf191 Bump Copyright year
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-01-08 12:36:35 +01:00
fc5a9aacf8 Trim trailing whitespace
Signed-off-by: Simon Rozman <simon@rozman.si>
2025-01-07 15:01:45 +01:00
246f2baa1e string: silence false-positive Code Analysis warning
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-11-19 10:18:03 +01:00
f45c92da23 Use == 0 for C strcmp
! is too easy to overlook.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-11-15 10:39:45 +01:00
09123326c1 stream: add missing UTF-8 BOM
We want to ensure non-UTF8 configured Windows machines interpret our
source code correctly.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-11-11 12:24:32 +01:00
0ae7fb6c7c stream: use indexing operator for pointer arithmetics 2024-10-22 17:25:54 +02:00
4477706f06 stream: trun off false-positive Xcode analysis warning 2024-10-22 17:24:56 +02:00
74718b9ef8 uuid: provide uuid_str_max constant for client code
Using cryptic constants like 39 makes code awkward to understand.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-10-21 12:50:52 +02:00
e442f4f502 chrono: add to_system() variants for Windows
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-10-21 12:49:08 +02:00
16e2384419 system: add safearray_accessor_with_size
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-10-18 23:47:10 +02:00
bfda0e962b html: fix url_escape
When used in URLs to be passed to a browser, strings MUST NOT contain +.
We must escape + too, otherwise browsers converts it to a space or %20,
which does not represent the original value anymore.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-10-09 17:09:50 +02:00
6379065f15 curl: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-10-03 17:26:25 +02:00
582438ac61 sys_info: fix to compile for _WIN32
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-10-01 16:37:35 +02:00
8844c4059d sys_info: add username
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-09-30 17:18:12 +02:00
9fb899849d stream: weasel around false-positive GCC warning
As _countof is manually implemented on non-Windows platforms, GCC
knows it returns size_t, but fails to realize it will never be more than
0x400 in our case making truncation harmless.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-09-30 13:30:13 +02:00
4fa30d5630 memory: add get_ptr
This was migrated from WinStd being platform independent.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-09-30 09:40:19 +02:00
5675f8b139 string: add recoding from UTF-16 to UTF-32
I know there are system functions for this (and libiconv), but this is
so trivial and quick in our implementation.
2024-09-27 14:11:11 +02:00
da11495282 string: fix high&low UTF-16 surrogate detection
U+D800 is a perfectly valid high surrogate. (Thou, not normal.)
While U+DC00 is a perfectly valid low surrogate.
2024-09-27 14:09:05 +02:00
f87191d113 memory: silence unknown #pragma warning 2024-09-27 14:05:46 +02:00
bdae4913c0 compat: add _In_reads_bytes_ 2024-09-27 12:24:17 +02:00
8075686eee memory: add sanitizing_allocator and sanitizing_blob
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-26 15:50:33 +02:00
d2be6a7c08 langid: address MSVC warnings
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-22 17:59:21 +02:00
c4e57f15df langid: extend and fix conversions
LANGID 9 should convert to "en" and 1033 to "en-US". 36 to "sl" and
1060 to "sl-SI". Likewise backwards. Otherwise, we are loosing
disambiguation between generic and country-specific language variants.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-22 17:02:44 +02:00
e65eb35cc2 Cleanup precompiler directives
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-21 13:32:58 +02:00
d9eb8292a9 langid: finish
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-19 16:29:22 +02:00
8bd8143c6b langid: sync Windows build
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-19 13:01:29 +02:00
e24bd3d1fa socket: sync Windows build
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-19 13:01:13 +02:00
7b21a3983c stdex: add langid
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-19 11:20:42 +02:00
98e8756808 string: add ispunct
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-19 09:25:23 +02:00
a95409c9ed socket: add addrinfo, waddrinfo and saddrinfo
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-17 21:42:20 +02:00
d118313485 compat: add _Out_writes_all_opt_
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-08-17 21:40:38 +02:00
b1a19c01e8 uuid: fix non-Windows platforms 2024-08-14 13:39:03 +02:00
633d0bd119 uuid: add strtouuid
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-06-18 16:58:22 +02:00
5503a0c339 string: add 8 and 16-bit string to binary conversions
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-06-18 16:31:03 +02:00
351fd6babc assert: silence compiler warning for NDEBUG
_Analysis_assume_(expression) is removed by precompiler and statements
like: if (test) stdex_assert(expr); resolved to: if (test) ;

Remains to be seen if Xcode is happy with this change.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-06-10 11:30:03 +02:00
ef829b02e9 assert: fix to compile Release versions
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-28 11:54:12 +02:00
8c9b07fd2c assert: finish Windows part
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-28 11:31:04 +02:00
5c5197d80d string: remove dead code
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-28 10:35:47 +02:00
ebd1414721 compat: fix
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-28 10:34:26 +02:00
f643951425 system: move _T to compat
...where it belongs.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-28 10:25:20 +02:00
5436dda7f6 assert: initial version
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-28 10:22:24 +02:00
146c9c948d sgml: address signed/unsigned warnings
The sgml_unicode.hpp stores UTF-32 strings in
stdex::sgml_unicode_pair::unicode.  Hence stdex::utf32_t should be used as
its datatype.  Unfortunately, that doesn't work with U string literal: U
requires hundreds of typecasts to make it fit stdex::utf32_t, since char32_t
is signed, stdex::utf32_t is unsigned.  OTOH, L string literal cannot be
used on Windows (produces UTF-16, not UTF-32).

Reported-by: Xcode
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-27 15:05:23 +02:00
ea2f129577 string: stop abusing logical | to load operands and test for zero
Looks short and compact, but requires static_cast<unsigned int> to cover T1
and T2 of different signess. Which doesn't look all that nice anymore.

However, typecasting would only hide the warning.  We want the warning to
occur, to encourage us to sync types on template invocation instead.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-27 14:37:25 +02:00
ac6bd1315c minisign: add trailing newline
Reported-by: Xcode
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-27 13:52:36 +02:00
10c9362b4f mapping: disambiguate local variable el
Reported-by: Xcode
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-27 13:51:37 +02:00
a0626f802a system: fix to compile with Xcode
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-05-27 12:17:38 +02:00
c1616b032e mapping: add invert()
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-04-25 15:16:12 +02:00
c6c7498562 mapping: add dst2src and src2dst
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-04-25 12:41:08 +02:00
a97b037a78 minisign: fix SAL
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-04-23 11:12:54 +02:00
369dc7ce39 base64: fix code-analysis warning
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-04-23 11:12:39 +02:00
24c35279a0 chrono: fix aosn_timestamp::period
AOsn is measuring time in **milli**seconds. Not **micro**seconds! All
conversions among clocks were off by a factor of 1000.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-04-23 11:12:07 +02:00
3d3b7565bf socket: add missing namespace
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-04-22 13:56:10 +02:00
a70db017b0 stream: fix spelling
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-04-22 08:57:19 +02:00
7e4dd3e49d socket: add sys_object alike wrapper for sockets
Unlike Posix, Windows handles HANDLE (file, pipe) and SOCKET descriptors
differently. It uses different close(), different errno... So sys_object
was not usable for socket without templating operations to a traits
class.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-04-22 08:43:00 +02:00
1f242823d0 system: make sys_object::duplicate public
It's useful to clone std handles when creating streams to write/read to
standard streams. As the stream we give handle to assumes the handle
ownership, it'd close the std handles in stream destructor.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-04-17 13:58:59 +02:00
525645b43b Configure branch for Git submodules
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-03-15 19:55:53 +01:00
9809765b33 minisign: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-03-15 14:45:27 +01:00
5e86663557 locale: allow setting of default locale
References: f689011a65bd14e8ce59b5402db9e2f4232b8346
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-03-04 14:21:50 +01:00
48e35c1ebd sgml: std::basic_string_view → std::basic_string
MSVC fails to deduce character type from std::string/wstring when
converting to std::basic_string_view.

Reference: f5bce32d06cdcf677ce8080027d3317c6387e715
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-03-04 13:46:00 +01:00
7e07d14de7 scoped_executor: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-03-01 09:28:48 +01:00
e0e1663c58 sys_info: fix missing #includes
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-27 13:57:03 +01:00
e558c9f244 sgml: Silence unreferenced parameter warning
This parameter is needed to provide compatibility between utf16_t and
utf32_t, so we may call this function from a single template for both
types.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-23 16:48:04 +01:00
d738c9f8fd Xcode: fix to compile
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-21 17:51:02 +01:00
e01040430f debug: fix to compile on non-Windows 2024-02-21 17:38:29 +01:00
c20d9dbc09 sgml: upgrade to support UTF-16 and 32
Windows and Apple (NSString) are using UTF-16 wchar_t. All others use
UTF-32. This eliminates need for additional conversion should we need
UTF-32 on Windows&Apple or UTF-16 on others. Mind, UTF-16↔32 is not a
trivial conversion as it may change number of code units.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-21 17:33:00 +01:00
c6f5a72e70 zlib: fix length comparison in unit test
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-21 15:51:21 +01:00
31a53e9e43 UnitTests: Specify intermediate and output folders
Microsoft changed defaults in recent Visual Studio update. To worse
IMHO.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-21 15:50:30 +01:00
ae8ce7f9f0 string: cleanup
Templates are inline by default.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-21 15:21:45 +01:00
f689011a65 locale: use nullptr as default locale on Windows too
Since stdex::locale_default is inline, it is initialized by the first
module referencing it. Not necessarily compiled with the same _CONSOLE
definition as other modules. This makes the default locale fluid.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-19 14:32:57 +01:00
87d678f524 sgml: dismiss MSVC warnings
Brain-dead _Printf_format_string_ SAL doesn't understand utf32_t is good
enough for unsigned int expected by %x placeholder.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-19 09:16:06 +01:00
10f999c7bf unicode: Cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-15 09:13:51 +01:00
0a719e3081 Xcode: turn on and resolve all warnings 2024-02-14 15:05:41 +01:00
0560c7bce9 Add missing UTF-8 BOM
This is required on Windows with default charset set to Windows-1252 or
another non-UTF-8 charset.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-14 12:33:29 +01:00
8ac0efa85d Xcode: resolve -Wsign-compare
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-14 12:28:39 +01:00
681a6955d8 Xcode: resolve -Wunknown-pragmas
This is a multi-platform project and while trying to reduce amount of

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-14 12:26:52 +01:00
2232b0d23e UnitTests: remove Xcode settings set as default already 2024-02-14 08:49:36 +01:00
82a0ea69f9 unicode: don't specify MB_PRECOMPOSED for UTF-7&8
MultiByteToWideChar returns ERROR_INVALID_FLAGS on pre Windows 10.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-12 13:02:06 +01:00
fb6ac904af sys_info: fix Windows x86 compilation
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-09 09:02:12 +01:00
b0c682edd6 Fix to compile for Windows
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-08 15:26:20 +01:00
9acd185d44 UnitTest: redesing to avoid #include ".cpp" in Xcode
Each unit test .cpp file is not a separate compilation unit in Xcode project
like it is in Visual Studio. Hopefully, Visual Studio test tool still likes
this arrangement.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-08 15:18:42 +01:00
1e627e1c6b UnitTests: add zlib testing
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-08 14:38:34 +01:00
6e121395f3 UnitTest: switch to C++17
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-08 14:17:15 +01:00
5b80f24830 string: fix vsnprintf on macOS
vsnprintf_l and vswprintf_l mutate va_list parameter on some versions of macOS (11.7 Big Sur).
This makes it impossible to call printf multiple times with a growing buffer.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-08 14:16:36 +01:00
b5e35922f1 string: revert emojis in unit test
Actually, using ill-created locale was the cause sprintf returned EILSEQ on %ls inserts containing emoji.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-08 14:04:15 +01:00
a4ca4df926 locale: use nullptr as default locale on non-Windows systems
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-08 14:02:20 +01:00
f5f4cbf308 sys_info: make platform_id enum
This allows same platform management across different operating systems.
Like with charset_id, the Windows numbering was adopted.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-08 13:08:29 +01:00
08a18d1519 macOS support
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-02-08 12:09:33 +01:00
b6be4f040e #if defined(_DEBUG) → #if !defined(NDEBUG)
Microsoft declares all identifiers starting with underscore _ as
internal to MSVC. Thus, we should either use defined(DEBUG) or
!defined(NDEBUG) to distinguish Debug and Release builds.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-25 10:35:09 +01:00
ce1688964a progress: add timeout_progress
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-25 09:52:43 +01:00
10830d5583 html: cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-24 18:19:01 +01:00
4a8ba9eb60 mapping: add + operator
This allows cleaner arithmetic when adjusting mapping by an offset.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-24 12:26:54 +01:00
18c41ebfdf interval: add += and -= operators
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-24 12:25:47 +01:00
cf73f8a48d zlib: Update
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-23 11:01:12 +01:00
016577dbec endian: mark one-byte "swap" functions as constexpr
While being here only for templates to work with various data types, the
one-byte "swap" functions are actually noop.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-22 16:57:25 +01:00
bf029efd07 parser: switch to own endian conversion
htonl and htons require Ws2_32.lib on Windows, while we already have
endian swapping functions in stdex.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-22 16:55:40 +01:00
bdb266f039 parser: fix detected IPv4 and IPv6 byte order
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-22 11:40:51 +01:00
848979950c progress: add aggregate_progress
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-19 15:00:53 +01:00
f3e117afcc watchdog: upgrade to rearm rather than die on initial timeout
This makes watchdog reusable.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-19 14:48:23 +01:00
528ea5fffe pool: add clear
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-19 14:44:37 +01:00
8839c80fec debug: add benchmark
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-18 12:19:44 +01:00
ab94d7014f stream: cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-16 08:24:37 +01:00
59a53e10f4 string: update documentation
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-11 09:09:46 +01:00
9f3d65d910 zlib: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-09 20:07:06 +01:00
790c26789a sys_info: add is_screen_reader
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-09 20:06:02 +01:00
c5feba2ed0 string: add unit test for sprintf
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-09 19:31:07 +01:00
38c6b40b21 Bump Copyright year
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-06 10:55:09 +01:00
b42aa969dc unicode: Write directly into std::string buffer
This removes extra memory allocation.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-05 22:55:15 +01:00
114609274f string: address Code Analysis warnings
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-05 22:45:41 +01:00
293da738b9 string: upgrade sprintf to write directly into std::string buffer
This removes extra memory allocation.

Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-05 20:32:00 +01:00
1685cd3283 string: fix sprintf
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-05 17:01:43 +01:00
0eebde6d81 parser::http_media_type: fix stack overflow
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-03 15:49:01 +01:00
b96d909cfe html: fix to build
Signed-off-by: Simon Rozman <simon@rozman.si>
2024-01-03 13:16:35 +01:00
d981225ba8 sgml: add sgmlerr
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-23 08:19:43 +01:00
fdf16a65b6 string: add is7bit
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-23 08:19:26 +01:00
e1f53f31ad string: add glyphrlen
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 21:36:21 +01:00
9c9fd9d05a math: add muldiv
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 21:35:56 +01:00
024f3f7172 string: add strncpy for fixed-size buffers
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:10 +01:00
61bf25ac6a string: switch from intptr_t to ptrdiff_t at strtoi()
strtoi() is complementary to strtoui() returning size_t. But, intptr_t
is complementary to uintptr_t, and ptrdiff_t is complementary to size_t.

I know it doesn't matter on flat-memory platforms, but nevertheless, we
try to keep things organized in a portable way.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:10 +01:00
ba38dad49d string: add strrcmp and strrncmp
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:10 +01:00
09b59e698d Refactor
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:09 +01:00
c212e68bb5 debug: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:09 +01:00
088dd86d63 string: add strtod
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:09 +01:00
441a2ae9d5 compat: add _Verify_
This eases porting of legacy code.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:09 +01:00
2020fa2eec locale: add UTF8 and ACP/OCP locales
Those are frequently used:

- UTF8: for exception message formatting
- ACP(OCP): for generic (console) output

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:09 +01:00
a5193c9738 string: remove excessive std::string_view helpers
The standard C++ approach is preferred over legacy C-style string
operations. Some helpers have clean standard C++ implementation on
std::string(_view) classes. All were just a simple .data() and .size()
wrappers for legacy C functions.

The main concern is the amount of helpers that require maintenance.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:09 +01:00
37891e8a2a Simplify and unify template parameter naming
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:08 +01:00
f5bce32d06 Use std::string_view where possible
Unfortunately, MSVC cannot deduce template parameters properly where
`const std::basic_string_view<...>` is the parameter. It requires
explicit type cast or explicit template type specification. Which kind
of voids the whole purpose of using std::basic_string_view to make the
client code simpler.

Example: https://gist.github.com/rozmansi/493911be70bdac08dc6826c976c5bbe4
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:08 +01:00
da9dcc9c1a parser: move virtual match() to do_match() and provide frontend
This allows to call match() method equally by using char T* or
std::basic_string_view<T>.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:08 +01:00
bce104d97b locale: redesign locale to behave like locale_t on demand
This avoids painful `.get()` in every `stdex::sprintf()` call. We could
use C++ polymorphism to add other sprintf variants, but the argument
combinations would explode.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-22 14:17:08 +01:00
2610547137 Cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-21 09:21:32 +01:00
2e65d0351c Trim excessive inline
Methods and templates are implicitly marked as inline already.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-11 11:33:27 +01:00
7eba65813e stream: fix cache::read() to return state_t::eof correctly
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-11 09:55:20 +01:00
91ffe3f633 wav: cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-08 18:53:08 +01:00
bdd76b302d wav: fix cue loading
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-08 18:53:07 +01:00
6fc378ff61 idrec: fix reading from limited streams
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-08 18:53:04 +01:00
0ec86f6161 idrec: cleanup 2023-12-08 18:47:05 +01:00
d5bf60447b Cleanup 2023-12-08 16:22:09 +01:00
bb9988933e unicode: add unit test for normalize
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-08 14:21:52 +01:00
6d1d9bd1b8 sgml: fix unit tests 2023-12-08 14:21:20 +01:00
5d7e44a3cd parser: address Code Analysis warning
Nice catch: when calling with basic_bol::match(nullptr, 5, 5), the
method addresses null[4].

Turned this condition into debug assert, as (nullptr, 5, 5) is invalid
input parameter combination.
2023-12-08 14:20:56 +01:00
8ba5a06379 parser: update SAL
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-08 14:03:26 +01:00
5215d5e6dc unicode: add normalize
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-07 18:43:14 +01:00
475f05b6d2 string: add strrchr and stricmp variants 2023-12-07 18:42:52 +01:00
2440e5baca Cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-07 18:41:55 +01:00
72bab6d6b2 string: add strlwr variants 2023-12-07 12:27:38 +01:00
0d3785850b html: add (un)escaping function variants 2023-12-07 12:26:41 +01:00
b15ab697c2 string: add strnlen variant for known fixed-size buffers 2023-12-07 12:25:35 +01:00
8fc9a7e56b stream: allow invalid handle detection in cached_file
Otherwise one cannot distinguish between file open but in failed state
and file not open.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-06 15:25:48 +01:00
9967b41eae stream: make memory_file properly copyable and movable
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-06 11:02:12 +01:00
5a4194c9ba wav: add 2023-12-01 19:06:20 +01:00
c69b79c0f2 idrec: cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-01 19:06:10 +01:00
976662415b stream: unify size method and make it const
diag_file::size() was setting the m_state and returning 0 on error,
which violated original size() convention not to change stream state and
to return fsize_max on error.

Changing m_state prevented declaring size() method as const before.

cache::seek() was missing size() return value check.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-01 14:11:02 +01:00
8fbbf58a1b system: update documentation
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-12-01 14:05:54 +01:00
571463b22c html: fix parser to handle empty documents
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-29 12:04:17 +01:00
14e6bec509 parser: fix basic_eol
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-29 11:32:28 +01:00
e17fa1d8c2 parser: detect spaces, characters and newline faster where appropriate
No need to use locale-specific character type detection when ASCII.
Locale-specific implementation on Windows is not that very fast.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-28 13:48:54 +01:00
7685818bf7 parser: HTTP spaces are always ASCII
No need to use "C" locale, which implementation on Windows is not that
very fast.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-28 13:38:20 +01:00
d9e04170ac unicode: extend charset_from_name
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-28 13:36:42 +01:00
97014df244 html: fix localizable attribute mapping
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-28 12:14:37 +01:00
b824c204a6 parser: fix IPv6 detection
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-19 12:11:54 +01:00
d44aed2cff string: add "C" locale function variants
"C"-locale is basically ASCII while (Microsoft) implementation is not
quite performant to our likes. We can do faster.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-18 09:57:38 +01:00
dff646a4f8 README: add link to auto-generated documentation
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-17 15:18:06 +01:00
87c41c0947 html: add simple tokenizer and parser
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-17 15:14:54 +01:00
424f297c7b sgml: sgml2wstr→sgml2str, wstr2sgml→str2sgml 🧨
This is analogous to string.hpp's strlen, strcpy, strcat, which use C++
polymorphism rather than function name decorations for char/wchar_t
flavors.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-17 15:14:53 +01:00
06da717405 string: upgrade vappendf and appendf to return number of chars appended
This comes handy when concatenating strings and calculating mappings.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-17 15:14:53 +01:00
10791467d8 string: add isblank
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-17 15:14:53 +01:00
3dfcaf3f10 string: add std_locale_C
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-17 15:14:53 +01:00
6c8d6f182c unicode: add charset_from_name
This makes name->charset_t conversion available to others.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-17 15:14:53 +01:00
52d9956891 Cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-17 15:14:53 +01:00
bea4b5b408 spinlock, pool: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-10 12:38:13 +01:00
cc8a6eeee7 Make C++17 minimum requirement
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-10 08:36:33 +01:00
57ccb713ad Make inline variables optional before C++17
This allows gradual projects migration to C++17 and later.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-09 09:25:42 +01:00
3dfd62ec2f socket: add own cross-platform declaration
Like with stdex::locale_t, this frees us from using Windowsizms on other
platforms. Sockets came from Unix to Windows and not the other way
around in the first place.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-09 09:25:42 +01:00
2c45749f78 string, locale: make stdex::locale(_t) independent .hpp file
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-09 08:12:31 +01:00
9d50a878dc Inline variables to have compiler reuse them
Unfortunately, this requires at least C++17, but the inline is exactly
what we need to avoid redundant copies of static const data.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-08 18:48:31 +01:00
4fb684ce75 Cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-08 18:35:59 +01:00
ee8f54ee5f Fix to compile for Linux
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-08 13:48:41 +01:00
9ff15a27a1 string: move uuidtostr to a separate .hpp
It requires libuuid-devel on Linux and since this function is optional, lets
not impose libuuid-devel dependency on anything that #include
<stdex/string.hpp>.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-08 13:41:50 +01:00
fedeef0bea string: fix strncat SAL
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-06 11:57:47 +01:00
dec5ad54d1 interval: add invalidate method
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-11-06 11:57:08 +01:00
89a5c6e5e6 sys_info: move to a separate .hpp
The stdex::sys_info is instantiated for each compilation unit. To reduce
amount of copies, move it to a separate .hpp as system.hpp is almost
always #included.

Breaking-change: Add #include <stdex/sys_info.hpp>
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-19 10:16:53 +02:00
16a86cf350 Add #include wrapper to fix min/max <Windows.h> mess
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-18 12:51:49 +02:00
856be3a0d8 Revise #include to make each .hpp individually compilable
Mind that min/max Windows.h mess is Microsoft's problem, not ours.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-18 09:12:06 +02:00
11e56f927f string: add missing #include to support uuid_t on Windows
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-18 08:14:28 +02:00
983891ec41 string: add crlf2nl
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:09 +02:00
913cbc104a string: add strcmp
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:09 +02:00
8a6462a40c string: add uuidtostr
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:08 +02:00
a469860382 stream: revise cache init and cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:08 +02:00
00b05092af stream: reuse converter R/W methods
Where we need to read from/write to m_source and immediately apply its
state to our converter, we already have methods for this.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:08 +02:00
47e63b1f32 Use lambda as trampoline to simplify thread functions
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:08 +02:00
8732b1df5b system: add admin and elevated bools to sys_info
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:08 +02:00
3a39f2438a chrono: resolve time constant confusion
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:08 +02:00
7cd4d099ff stream: cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:08 +02:00
ab8d37ee75 Turn assert() into _Analysis_assume_ on Release builds
While runtime asserts also served as MSVC Code Analysis hints, the lack
of asserts in Release builds provides no hints to Code Analysis which
rises a lot of warnings then.

Maybe I should learn how to use SAL to annotate <ptr, len> parameter
pairs to allow ptr==nullptr when and only when len==0? 😇

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:07 +02:00
9ba4b21cef Un-static global data
Otherwise, compiler generates instances for each compilation unit.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:07 +02:00
36012a107b stream: cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:07 +02:00
b791621a2a stream: make std:thread member rather than derive from it
When deriving, C++ might believe the derived class is still movable. But
it's not and rather than deleting move constructor and operator, this
approach made simpler code.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:07 +02:00
9365f0252c stream: add socket
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:07 +02:00
e61720598f stream: revise string reading and writing
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:07 +02:00
86e7ed3690 stream: add support for collection reading and writing
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:06 +02:00
838dfabbda string: add trim
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:06 +02:00
82a16ef579 string: add strftime
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:06 +02:00
a888731c39 string: extend strupr
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:06 +02:00
02c531e2a8 string: fix strncpy and strncat buffer limit check
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:06 +02:00
d7a44c2929 string: add strncmp
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:06 +02:00
7ea1870552 interval: allow use with scoped enums
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:06 +02:00
010ee71d93 interval: make operators a class friend
This makes them better discoverable by MSVC from various non-root
namespaces.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:05 +02:00
3183b58e0d idrec: cleanup and make operators a class friend
This makes them better discoverable by MSVC from various non-root
namespaces.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:43:05 +02:00
6f19e5250d parser: weasel winsock2.h support
This is a royal PITA to get compiled under various combinations of
WIN32_LEAN_AND_MEAN and _WINSOCKAPI_ combinations.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:42:04 +02:00
c16579984d system: add sys_info
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:42:04 +02:00
9f21015209 system: add sregex
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:42:03 +02:00
1a9b63279d unicode: add UTF-7 and improve system charset detection
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:42:03 +02:00
970917f164 unicode: silence MSVC code analysis warnings
Those lines of code were tested.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:42:03 +02:00
67d328a550 watchdog: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-10 16:42:03 +02:00
06a896ccf6 hash: make operators a class friend
This makes them better discoverable by MSVC from various non-root
namespaces.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-03 14:56:15 +02:00
dfdc4369b8 base64: add base64_reader and base64_writer
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-03 14:56:14 +02:00
49b741c94f Replace errno_error with std::system_error
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-10-01 23:09:50 +02:00
b8fae2d0dd pch.h -> pch.hpp
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-25 15:05:36 +02:00
4e25c13d08 hash: macOS support
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-25 15:04:19 +02:00
c4e94150d1 hash: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-25 14:57:14 +02:00
772dbb3524 Add README.md
A very vague one, but still. 😇

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-25 14:55:27 +02:00
4a5970595f math: document add and mul
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-25 14:54:46 +02:00
41d764eeef parser: fix compilation for macOS
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-23 17:58:40 +02:00
80eecac31a unicode: add charset_id::utf32 for Windows
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-22 13:36:13 +02:00
10d74988aa Add mising UTF-8 BOM's
Curse you XCode! 😔

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-22 13:07:14 +02:00
b724ce1333 stream: fix wchar_t/UTF-16 confusion and revise readln and write_array
On non-Windows, wchar_t is UTF-32.  This adds preliminary support for UTF-32
and changes readln and write_array members to use charset_encoder for better
performance on non-Windows platforms.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-22 13:04:41 +02:00
921d235459 unicode: upgrade and promote use of charset_encoder
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-22 13:02:27 +02:00
cb010bd72e UnitTests: upgrade XCode project to include all .hpp files
This simplifies source code browsing.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-20 14:02:40 +02:00
6689aa5210 stream: add file::exists and file::readonly
We are targeting C++14, while C++17 already has std::filesystem::exists. 😢

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-20 13:00:20 +02:00
dcfc4752b5 stream: add stdex::sstring method variants 2023-09-20 12:09:58 +02:00
475e6734a7 stream: extend open/create disposition flags
User could not create file only if it did not exist.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-20 11:13:16 +02:00
50fea81f83 parser: cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-20 08:07:50 +02:00
613bba9e05 parser: refine IBAN checking
Allow arbitrary spacing, minor optimizations...

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-19 21:40:36 +02:00
b5984ea8f2 Port to macOS
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-19 18:02:18 +02:00
27afd7afa5 parser: add IBAN, RF and SI support
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-19 16:53:16 +02:00
0e8e119346 unicode: Fix charset detection on macOS
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-15 16:33:20 +02:00
fea0ed7754 unicode: add system charset detection
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-15 15:32:14 +02:00
bffb48c87d string: streamline C locale management across platforms
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-15 15:31:18 +02:00
bb2578f65c UnitTests: Revert to XCode 13
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-14 16:10:13 +02:00
edd480d64b macOS fixes
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-14 15:57:55 +02:00
51d7ee3493 Fix to support pre-C++17
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-14 13:01:16 +02:00
13703b1747 unicode: extend conversion with reusable charset_encoder
Windows takes care of internal converter state in MultiByteToWideChar
and WideCharToMultiByte and keeps them thread-safe. On other platforms,
iconv requires user to setup and keep converter state for thread-safe
conversions. This sounds time consuming for every string conversion,
therefore the concept of string converter (or converter state) has been
extended to Windows too, allowing uniform client code. On Windows, using
charset_encoder has no performance benefit, where on Linux and macOS,
there should be. To be measured...

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-14 12:28:05 +02:00
66f8a6c3b7 Re-add UTF-8 BOM XCode is removing
Visual Studio IDE really needs it on non-UTF-8 PCs.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-14 09:13:04 +02:00
a43e633d2b chrono: fix aosn_date::from_system
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-14 08:38:24 +02:00
b2af5291f5 Fix issues introduced with porting to macOS and sync with Windows
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-13 23:19:46 +02:00
870961cd12 chrono: port to macOS
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-13 22:47:18 +02:00
3da2529b08 compat: add more SAL as needed by other projects
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-13 22:46:22 +02:00
f05bf56a61 Don't #define NOMINMAX when compiled with -DNOMINMAX
MSVC issues compile-time warning about redefining.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-13 14:27:14 +02:00
180f763184 exception: set default user_cancelled exception message
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-13 13:21:30 +02:00
657eac0c69 system: add PATH_SEPARATOR
I am aware of the std::filesystem::path::preferred_separator, but we
are targeting C++14 and we need this as a precompiler macro to allow
concatenation of the path strings in precompile stage.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-13 11:29:20 +02:00
bd14f7c629 Fix issues introduced with porting to macOS and sync with Windows
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-12 17:15:33 +02:00
83d7fd844d Port to macOS
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-12 16:55:16 +02:00
1e993c8c65 math: port unit tests to XCode
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-11 17:34:40 +02:00
b326f819ab Fix to compile using XCode
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-11 16:44:30 +02:00
fb66eeb852 Make inout param designations XCode-friendly 2023-09-11 16:12:22 +02:00
aa4de5aa40 Make space for and add macOS unit testing stub project
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-11 16:11:57 +02:00
a13aba304a interval: extend arithmetic
Additional operators allow moving intervals left and right.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-11 11:46:23 +02:00
5558bd6ccc string: add strchr, strstr and stristr
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-08 17:54:20 +02:00
753da36672 idrec: add support for stdex::stream
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-08 11:41:23 +02:00
b8cb07f456 stream: cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-07 14:35:03 +02:00
0848056487 string: fix strnlen parameter validation assert
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-09-04 10:10:03 +02:00
670cbb8d36 Catch by reference
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-29 16:19:53 +02:00
b76db46076 system: simplify and streamline system char and string type names
"sys_char" sticks out of "wchar_t", "char32_t", "uint32_t"...
Likewise, "sys_string" out of "string", "wstring", "u16string",
"u32string"...

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-29 10:09:35 +02:00
e1f27662a1 string: streamline locale_t experience
Not that we would like to promote the use of C locale_t over C++
std::locale, but we need former for sprintf and we need decent support
for it.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-25 04:45:32 +02:00
2c2680dfb3 Resolve _WINSOCKAPI_ and WIN32_LEAN_AND_MEAN hell. Hopefully!
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-25 03:56:27 +02:00
fb48db73c1 string: add strdup and strndup
I know this is a step backward to C, but we need this in AJT for
backward compatibility.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-24 14:07:28 +02:00
703e43d055 string: add some more helpers for locale manipulation
Microsoft has nonstandard implementation.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-24 14:04:23 +02:00
b0b0a91729 unicode: add variants for zero-terminated input
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-24 13:58:55 +02:00
be83f36082 stream: add write_array variants for zero-terminated strings
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-24 13:57:57 +02:00
73d03d81af stream: fix buffered_sys and cached_file destruction
We had to introduce out-of-order construction for those classes, but we
forgot to implement out-of-order destruction. File gets closed first
by cached_file::m_source destructor, then inherited destructor
cache::~cache() which flushes the cache is called.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-24 13:56:41 +02:00
2b1bfc235d sgml, unicode: fix 2023-08-23 15:30:55 +02:00
52d6f8daf1 sgml, unicode: split strcat and strcpy variants
Previous implementation had only strcat-class conversion using vague
function names. This commit makes function names more explicit by adding
"cat" and deprecating previous function variants.

Since many situation required strcpy variant, the move above made space
to add strcpy variants too.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-23 14:45:39 +02:00
fe47a47e4e string: add reusable C locale
This is frequently needed locale.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-22 17:16:23 +02:00
4ca3ed0718 system: add missing include
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-22 17:06:17 +02:00
1c622c3091 stream: fix ISequentialStream
MSVC 2019 didn't handle stdex::stream::ISequentialStream and
ISequentialStream disambiguation very well.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-22 17:05:49 +02:00
f9809adf0c sgml: add variants to convert into fixed-sized buffers
This is a performance requirement of PRSkupno.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-22 17:04:36 +02:00
6bb4027553 parser::date_format_t: make classic enum
With scoped enum, bitwise operations in C++ require insane amount of
type-casting.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-22 17:03:24 +02:00
f08aa73690 chrono: additional conversions
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-22 17:00:58 +02:00
e28c61e431 chrono: finish AOsn date and time-stamp implementations
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-21 16:33:03 +02:00
55d39b1566 stream: fix cache handling in open and close
Reusing same class instance for different files reused cache content
as well.

Reported-by: Peter Holozan <peter.holozan@amebis.si>
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-21 10:27:43 +02:00
e7b6ab5345 stream: using namespace in UnitTests
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-21 10:22:32 +02:00
34449d124d system: add sys_string
"std::basic_string<sys_char>" is too cumbersome and error-prone for
daily use.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-21 10:20:44 +02:00
42a38802d3 stream: fix Win32
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-19 08:31:08 +02:00
8457226168 ios, stream: replace <iostream> with leaner and faster own streaming
Our test program runs 15 minutes using our streams vs. 25 minutes using
std::iostream derived streams.

Streams were ported from Amebis AOsn project.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-18 15:05:24 +02:00
4965d1eac5 unicode: add unit tests
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-18 15:05:19 +02:00
d7950abe42 unicode: add more constants for Windows codepages
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-18 15:05:19 +02:00
057174bef9 string: fix strtoint() template
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-18 15:05:19 +02:00
43d0c4ba05 ring: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-18 15:05:19 +02:00
501183ca3e math: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-18 15:05:19 +02:00
3516c546ca interval: add contains() method
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-18 15:05:19 +02:00
72ce0a03e5 system: add
Windows is very peculiar with #include <windows.h>. Besides we need some
OS primitive wrappers that are OS-specific.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-08-18 15:05:18 +02:00
d3a12f45b1 string: Add strcpy, strcat, strncat
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-26 12:17:47 +02:00
c61ce2cbf9 unicode: Add Windows-specific UTF-8 charset ID
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-26 11:04:43 +02:00
6aa63f3bd6 ios: Combine and simplify i/odiagstream
We need this for a rare use-case, so maintaining three flavors of
diagstream is too expensive. Furthermore, the buffering was removed
for simplicity.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-25 16:38:55 +02:00
516b428d4b ios: Add diagstreambuf
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-25 16:32:46 +02:00
07f42442e0 string: Fix and extend vsprintf
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-25 16:32:45 +02:00
34f05feae4 ios: extend and update
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-24 11:46:26 +02:00
3e69770585 parser: Fix basic_scientific_numeral detection
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-24 09:46:18 +02:00
95141510b1 chrono: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-23 14:02:58 +02:00
58caa542ac Add missing UTF-8 BOM
Many many many Windows out there are still using Windows-1252 and
similar ancient encodings.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-23 14:02:58 +02:00
991f81254d ios: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-23 14:02:58 +02:00
519a8c70ca endian: add
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-21 11:53:48 +02:00
df4ab7aa79 string: upgrade sprintf to use specific locale
One can always revert to default locale by specifying NULL. Mind that
stdlib locale_t is used, as we are using its sprintf() implementation
and it is not simple to convert from std::locale to locale_t.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-21 11:53:48 +02:00
82b25cc24a parser: add missing #include
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-21 11:53:48 +02:00
c5f972971e parser: revise
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-17 17:05:31 +02:00
f55d5636aa string: add sprintf and appendf
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-17 15:11:04 +02:00
863d37546e sgml: add missing #include
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-17 14:56:01 +02:00
33859f3a2a string: silence the code analysis warning for (nullptr, 0) combinations
Functions are allowed to be called with nullptr string input as long as
the length of those strings is set to 0.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-17 13:01:56 +02:00
aedb0921f2 parser: adopt changes from string
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-17 12:57:23 +02:00
434cf6d3e2 unicode: add conversion between char* and wchar_t*
It's implemented for Windows-only for the time being.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-17 12:53:03 +02:00
6cdcb08365 sgml: rename str -> wstr
The sgml.hpp is about converting between SGML and UTF-16/Unicode
actually. The "wstr" naming aligns better with std::wstring, wchar_t
etc.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-17 12:51:41 +02:00
08de93ce2b sgml: add helpers for std::string input parameters
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-17 11:33:03 +02:00
cd61f8afe0 progress: cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-17 10:39:46 +02:00
a8c3ade263 progress: add progress_switcher
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-15 10:10:05 +02:00
ff097d4432 string: have strncpy return number of non-zero code points in dst
This information is a side product of these templates and saves an extra
strnlen when caller plans on appending dst string some more data.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-13 14:35:48 +02:00
27d10344d9 string: Add strncoll
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-12 13:57:13 +02:00
1dda8cde86 string: Revise searching
POSIX C strchr and strstr implementations return pointers and pointer
arithmetic is error prone. Switch to std C++ index search offsets.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-12 13:56:52 +02:00
a6c1c6c7ae interval: Add equality operators
MSVC is not comparing instances properly otherwise.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-04 11:08:54 +02:00
44975a016f mapping: Use member equality operators and document
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-04 11:08:54 +02:00
1c54745b3b string: Add strncpy
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-03 13:56:48 +02:00
556d5a9798 string: Revise SAL for strnlen
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-03 13:56:24 +02:00
7731a20f56 interval: Add interval_vector
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-07-03 13:55:34 +02:00
067cbddf64 Reformat á la VS2022
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-06-07 12:06:43 +02:00
5d46888dc2 Revise SAL for "start&length" string parameters
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-06-06 19:28:39 +02:00
2c8fad779c sgml: support appending SGML to supplied string
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-06-06 19:28:39 +02:00
1fb78a78f2 parser: Stabilize HTTP suite
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-16 12:35:52 +01:00
b028c8772e parser: Cleanup
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-16 12:35:08 +01:00
a59163733a parser: Add missing constructors to allow locale propagation
Classes using m_locale must allow locale configuration in their
constructor. Otherwise m_locale was always set to default by
basic_parser<> constructor.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-16 11:21:53 +01:00
38fac2837f parser: Duplicate locale
The Release testing revealed that compiler might free temporary
std::locale instances sooner than we thought, exposing UaF.

On 64-bit arch, a reference takes 8 bytes, a std::locale copy takes 16
bytes. So duplicating a locale in each parser instance is not such a big
deal to risk an UaF.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-16 11:02:23 +01:00
127704d2d8 parser: Rename "tester" to "parser"
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-16 10:58:15 +01:00
33012e1513 parser: Use ranged for loops where appropriate
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-16 09:38:53 +01:00
aa233bd5f9 Convert space to tab indentation
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-15 21:51:39 +01:00
308f63490c Rename .h to .hpp
These files are C++ only. They should either have no extension like
standard C++ headers (which is cumbersome on Windows environments), or
.hpp.

.h is used for C and hybrid C/C++ headers.

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-15 21:49:41 +01:00
261ad98812 Add string parsers
Ported from Amebis AOsn library to standard C++

Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-15 21:38:57 +01:00
d13421e4b6 Add noop deleter
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-15 09:09:23 +01:00
601cfec62d interval: Add empty() method
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-15 09:03:01 +01:00
b43b853235 Add SGML↔Unicode conversion
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-15 09:02:25 +01:00
9ce8e6bff9 Add support for character testing, strncmp, strnstr and number parsing
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-15 09:02:21 +01:00
f0d37935ec Add missing #include <assert.h>
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-09 13:54:15 +01:00
fe3580792f Update Copyright year
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-08 13:34:04 +01:00
a59971fdbd Add strnchr() and crlf2nl()
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-08 12:01:57 +01:00
dda761a692 Add progress indicator templates
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-06 16:08:16 +01:00
fbc2afb450 Add interval template
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-06 16:07:50 +01:00
be8cffc109 Add user_cancelled exception type
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-06 16:07:31 +01:00
515d92b035 Add errno_error exception type
Signed-off-by: Simon Rozman <simon@rozman.si>
2023-03-06 16:07:08 +01:00
ff8ca7f073 Use uint8_t where appropriate
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-09-15 12:22:37 +02:00
857b0b36c0 Adjust to compile with gcc
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-09-13 15:36:47 +02:00
09d0f347e8 Add .h extension to #include files
Using extension-less #include files brought more issues than it was
worth.

Reverts: dfa34420d9ff29932f1e7d06bb98f1f757373bd5
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-03-07 11:37:35 +01:00
a100acff13 Doxygen: Update project description
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-03-07 11:29:24 +01:00
8602b469db Doxygen: Instruct GitHub Actions to build documentation on push
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-03-07 11:26:20 +01:00
dfa34420d9 Doxygen: Fix skipping extension-less C++ #include files
Careful, not to include `Doxyfile` and ``LICENSE` extension-less files
from repository root.

Signed-off-by: Simon Rozman <simon@rozman.si>
2022-03-07 11:26:20 +01:00
11df03b0ad Doxygen: Add static functions and templates
Being portable, means having all function implementations in #include
files and functions marked as static. We have no global functions in
this project yet, but it took me quite some head scratching in WinStd
project why all the global functions are missing in the documentation.

Signed-off-by: Simon Rozman <simon@rozman.si>
2022-03-07 10:55:35 +01:00
1fbff95bd7 Doxyfile: Update
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-02-04 14:42:06 +01:00
13666a0d2f Move vector_queue from WinStd to stdex
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-02-04 13:49:12 +01:00
c7c3ee71d1 Switch to MIT license
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-02-03 15:38:03 +01:00
f76ece6ca5 Move Hex from WinStd to stdex
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-02-03 15:14:33 +01:00
76a90a203c Discontinue manual inline hinting
Compiler knows better than we do.

Signed-off-by: Simon Rozman <simon@rozman.si>
2022-02-03 14:20:51 +01:00
4521ea8f00 Move our Base64 implementation from WinStd
Base64 is general algorithm not provided with Win32 API.

Signed-off-by: Simon Rozman <simon@rozman.si>
2022-02-03 13:43:15 +01:00
b2e4c7dc4a Discontinue manual inline hinting
Compiler knows better than we do.

Signed-off-by: Simon Rozman <simon@rozman.si>
2022-02-03 13:34:41 +01:00
90de5d7140 Switch to standard C++ header filename convention
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-02-03 13:33:25 +01:00
364ed2cfd4 Declare as portable
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-02-03 13:17:59 +01:00
6b9972b0a6 Update Copyright year
Signed-off-by: Simon Rozman <simon@rozman.si>
2022-01-07 11:37:01 +01:00
c694b22fae Redirect output files to the output folder
Thou linker can locate the output .lib file of referenced projects
wherever .lib is just fine, this helps us to gather all .pdb files in
the output folder.

Signed-off-by: Simon Rozman <simon@rozman.si>
2021-12-01 08:46:17 +01:00
704de7808c AppVeyor: Remove Visual Studio 2017 from build matrix
As we no longer explicitly set WindowsTargetPlatformVersion property,
AppVeyor Visual Studio 2017 builders assume 8.1 SDK for ARM64 too,
resulting in an unknown platform.

Signed-off-by: Simon Rozman <simon@rozman.si>
2021-12-01 08:46:17 +01:00
b614865e2b Cleanup project files
Signed-off-by: Simon Rozman <simon@rozman.si>
2021-12-01 08:46:17 +01:00
e3ad3fe729 Switch to SPDX license notice
Signed-off-by: Simon Rozman <simon@rozman.si>
2021-11-30 11:05:28 +01:00
69d0a92288 AppVeyor: Add Visual Studio 2022 to the build matrix
Signed-off-by: Simon Rozman <simon@rozman.si>
2021-11-30 09:30:41 +01:00
b5999a4520 Retire Visual Studio 2010 support and merge 2017 and 2019 project files
Signed-off-by: Simon Rozman <simon@rozman.si>
2021-11-30 09:26:42 +01:00
3d90357905 Cleanup common.h
Signed-off-by: Simon Rozman <simon@rozman.si>
2021-03-30 11:57:13 +02:00
919b44573a Copyright: Bump year
Signed-off-by: Simon Rozman <simon@rozman.si>
2021-03-25 08:35:33 +01:00
7a7030b9e3 Add UTF-8 BOM markers
MSVC needs them to use correct charset when Language for non-Unicode
programs is set to Windows-1252 or anything different than UTF-8.

Signed-off-by: Simon Rozman <simon@rozman.si>
2020-11-19 10:53:27 +01:00
325f9d6b08 Address code analysis warnings
Signed-off-by: Simon Rozman <simon@rozman.si>
2020-02-12 20:33:41 +01:00
e6a06ee2d1 Extend copyright year
Signed-off-by: Simon Rozman <simon@rozman.si>
2020-02-11 15:12:27 +01:00
eeff73ce37 Discontinue versioning
As stdex has been made static library it really makes no sense to
continue labeling versions.

Signed-off-by: Simon Rozman <simon@rozman.si>
2020-02-11 15:12:14 +01:00
da7ca1c368 Configure precompiled headers locally
Build is using precompiled headers in pch.h. Explicitly configure it so
it does not rely on host solution configuration.

Signed-off-by: Simon Rozman <simon@rozman.si>
2020-02-10 15:15:25 +01:00
caa85e1da8 Reference solution property pages only if they exist
Signed-off-by: Simon Rozman <simon@rozman.si>
2020-02-10 15:09:59 +01:00
37335edb71 Add AppVeyor support
Signed-off-by: Simon Rozman <simon@rozman.si>
2020-02-10 15:07:44 +01:00
42cb00a22a Rename stdafx.h to pch.h
Signed-off-by: Simon Rozman <simon@rozman.si>
2020-02-10 14:08:50 +01:00
98fdf583f2 Unify platform designations
.sln uses the same as .vcxproj files

Signed-off-by: Simon Rozman <simon@rozman.si>
2020-02-10 13:50:16 +01:00
50cdbee3ae Add Visual Studio 2019 support
Signed-off-by: Simon Rozman <simon@rozman.si>
2019-06-19 11:27:43 +02:00
7d1ba8c1d1 Extend copyright year 2018-09-07 23:47:57 +02:00
5a75611807 Clean-up 2018-09-07 23:47:39 +02:00
7dfd391098 Introduce VS solution files 2018-09-07 22:55:19 +02:00
913158633a Make stdex static library 2018-09-07 22:54:58 +02:00
2f908dbeca Introduce ARM64 support 2018-09-07 22:39:30 +02:00
942c57f8c2 Remove MFC dependency 2018-09-07 19:25:34 +02:00
47ce0810c2 Merge branch 'master' of https://github.com/Amebis/stdex 2018-09-03 10:41:26 +02:00
55ea9fa324 Set version independent project name 2018-08-31 14:24:19 +02:00
a950ec9f5c Redesign the project files and split to versions for VC2010 and VC2017 2018-08-31 12:29:04 +02:00
22151a982c Merge branch 'Branch_245715b10a1997e8cfd6733f169111e7e20c0ab3' 2017-12-22 10:16:36 +01:00
abf068584a Update build year 2017-12-22 10:16:14 +01:00
81 changed files with 38032 additions and 3554 deletions

39
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,39 @@
# This is a basic workflow to help you get started with Actions
name: Doxygen Action
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Doxygen Action
uses: mattnotmitt/doxygen-action@v1.1.0
with:
# Path to Doxyfile
doxyfile-path: "./Doxyfile" # default is ./Doxyfile
# Working directory
working-directory: "." # default is .
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Default Doxyfile build documentation to html directory.
# Change the directory if changes in Doxyfile
publish_dir: ./doc/html

3
.gitignore vendored
View File

@ -1,3 +1,2 @@
/doc /doc
*.user xcuserdata
temp

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "UnitTests/zlib"]
path = UnitTests/zlib
url = https://github.com/madler/zlib.git
branch = master

914
Doxyfile

File diff suppressed because it is too large Load Diff

675
LICENSE
View File

@ -1,674 +1,9 @@
GNU GENERAL PUBLIC LICENSE The MIT License (MIT)
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Copyright © 2016-2025 Amebis
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The GNU General Public License is a free, copyleft license for The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
software and other kinds of works.
The licenses for most software and other practical works are designed THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program 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 of the License, or
(at your option) any later version.
This program 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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# stdex - Random stuff that didn't made it into std C++
An auto-generated documentation is [here](https://amebis.github.io/stdex/).
## Requirements
- MSVC 2019 or later, XCode 13 or later
- C++17 or later

3
UnitTests/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/.tmp
/.vs
/*.user

31
UnitTests/UnitTests.sln Normal file
View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35919.96 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests", "UnitTests.vcxproj", "{9AFC377D-C32D-4D42-82C2-09FC818020A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9AFC377D-C32D-4D42-82C2-09FC818020A2}.Debug|x64.ActiveCfg = Debug|x64
{9AFC377D-C32D-4D42-82C2-09FC818020A2}.Debug|x64.Build.0 = Debug|x64
{9AFC377D-C32D-4D42-82C2-09FC818020A2}.Debug|x86.ActiveCfg = Debug|Win32
{9AFC377D-C32D-4D42-82C2-09FC818020A2}.Debug|x86.Build.0 = Debug|Win32
{9AFC377D-C32D-4D42-82C2-09FC818020A2}.Release|x64.ActiveCfg = Release|x64
{9AFC377D-C32D-4D42-82C2-09FC818020A2}.Release|x64.Build.0 = Release|x64
{9AFC377D-C32D-4D42-82C2-09FC818020A2}.Release|x86.ActiveCfg = Release|Win32
{9AFC377D-C32D-4D42-82C2-09FC818020A2}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BBDB843D-98C3-46EF-BDE8-0E80FD851852}
EndGlobalSection
EndGlobal

187
UnitTests/UnitTests.vcxproj Normal file
View File

@ -0,0 +1,187 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{9AFC377D-C32D-4D42-82C2-09FC818020A2}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>UnitTests</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectSubType>NativeUnitTestProject</ProjectSubType>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<UseOfMfc>false</UseOfMfc>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(SolutionDir).tmp\$(ProjectName)\$(PlatformTarget)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir).tmp\$(ProjectName)\$(PlatformTarget)\$(Configuration)\</IntDir>
<RunCodeAnalysis>true</RunCodeAnalysis>
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<EnablePREfast>true</EnablePREfast>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\include;$(VCInstallDir)UnitTest\include;zlib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>SECURITY_WIN32;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeaderFile>pch.hpp</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>Advapi32.lib;Secur32.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Platform)'=='Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
<ItemGroup>
<ClCompile Include="hash.cpp" />
<ClCompile Include="langid.cpp" />
<ClCompile Include="math.cpp" />
<ClCompile Include="parser.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="pool.cpp" />
<ClCompile Include="ring.cpp" />
<ClCompile Include="sgml.cpp" />
<ClCompile Include="stream.cpp" />
<ClCompile Include="string.cpp" />
<ClCompile Include="unicode.cpp" />
<ClCompile Include="watchdog.cpp" />
<ClCompile Include="zlib.cpp" />
<ClCompile Include="zlib\adler32.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
<ClCompile Include="zlib\compress.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
<ClCompile Include="zlib\crc32.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
<ClCompile Include="zlib\deflate.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
<ClCompile Include="zlib\inffast.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
<ClCompile Include="zlib\inflate.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
<ClCompile Include="zlib\inftrees.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
<ClCompile Include="zlib\trees.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<DisableSpecificWarnings>6385</DisableSpecificWarnings>
</ClCompile>
<ClCompile Include="zlib\uncompr.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
<ClCompile Include="zlib\zutil.c">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="compat.hpp" />
<ClInclude Include="pch.hpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Source Files\zlib">
<UniqueIdentifier>{c793ce2d-1a0b-43b6-b0b4-60025ceafa56}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="sgml.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="parser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="math.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ring.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="stream.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="unicode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="hash.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="watchdog.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pool.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="string.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="zlib.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="zlib\adler32.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="zlib\compress.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="zlib\crc32.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="zlib\deflate.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="zlib\inffast.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="zlib\inflate.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="zlib\inftrees.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="zlib\trees.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="zlib\uncompr.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="zlib\zutil.c">
<Filter>Source Files\zlib</Filter>
</ClCompile>
<ClCompile Include="langid.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="compat.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -0,0 +1,607 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
F421D4702B750E0F004ECBB0 /* adler32.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D46F2B750E0F004ECBB0 /* adler32.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F421D4722B750E18004ECBB0 /* compress.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D4712B750E18004ECBB0 /* compress.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F421D4742B750E21004ECBB0 /* crc32.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D4732B750E21004ECBB0 /* crc32.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F421D47C2B750EAE004ECBB0 /* inflate.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D4752B750EAE004ECBB0 /* inflate.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F421D47D2B750EAE004ECBB0 /* deflate.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D4762B750EAE004ECBB0 /* deflate.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F421D47E2B750EAE004ECBB0 /* inffast.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D4772B750EAE004ECBB0 /* inffast.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F421D47F2B750EAE004ECBB0 /* zutil.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D4782B750EAE004ECBB0 /* zutil.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F421D4802B750EAE004ECBB0 /* trees.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D4792B750EAE004ECBB0 /* trees.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F421D4812B750EAE004ECBB0 /* inftrees.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D47A2B750EAE004ECBB0 /* inftrees.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F421D4822B750EAE004ECBB0 /* uncompr.c in Sources */ = {isa = PBXBuildFile; fileRef = F421D47B2B750EAE004ECBB0 /* uncompr.c */; settings = {COMPILER_FLAGS = "-w"; }; };
F44DE97F2D818A9F007E4ECC /* math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4C07F4E2AB059300044EDC0 /* math.cpp */; };
F44DE9802D818A9F007E4ECC /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4C07F542AB05B5B0044EDC0 /* main.cpp */; };
F44DE9812D818A9F007E4ECC /* ring.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4C07F592AB08E690044EDC0 /* ring.cpp */; };
F44DE9822D818A9F007E4ECC /* hash.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F437AA902AC1BB64001E2230 /* hash.cpp */; };
F44DE9832D818A9F007E4ECC /* sgml.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4C07F582AB08E690044EDC0 /* sgml.cpp */; };
F44DE9842D818A9F007E4ECC /* pool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4CCA3B72B73B940007B857B /* pool.cpp */; };
F44DE9852D818A9F007E4ECC /* watchdog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4CCA3B62B73B912007B857B /* watchdog.cpp */; };
F44DE9862D818A9F007E4ECC /* stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4C07F5A2AB08E690044EDC0 /* stream.cpp */; };
F44DE9872D818A9F007E4ECC /* langid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4481A192C73427600CED93B /* langid.cpp */; };
F44DE9882D818A9F007E4ECC /* pch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4C07F512AB059580044EDC0 /* pch.cpp */; };
F44DE9892D818A9F007E4ECC /* string.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4CCA3B82B73D2E2007B857B /* string.cpp */; };
F44DE98A2D818A9F007E4ECC /* zlib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F421D46B2B750BFD004ECBB0 /* zlib.cpp */; };
F44DE98B2D818A9F007E4ECC /* unicode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4C07F572AB08E690044EDC0 /* unicode.cpp */; };
F44DE98C2D818A9F007E4ECC /* parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F4C07F562AB08E690044EDC0 /* parser.cpp */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
F4B7FBDA2AAF49BC00C6BE9F /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
F4213D172ABB14C600F72674 /* vector_queue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = vector_queue.hpp; path = ../include/stdex/vector_queue.hpp; sourceTree = "<group>"; };
F4213D182ABB14C600F72674 /* base64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = base64.hpp; path = ../include/stdex/base64.hpp; sourceTree = "<group>"; };
F4213D1A2ABB14C600F72674 /* exception.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = exception.hpp; path = ../include/stdex/exception.hpp; sourceTree = "<group>"; };
F4213D1B2ABB14C600F72674 /* idrec.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = idrec.hpp; path = ../include/stdex/idrec.hpp; sourceTree = "<group>"; };
F4213D1C2ABB14C600F72674 /* compat.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = compat.hpp; path = ../include/stdex/compat.hpp; sourceTree = "<group>"; };
F4213D1E2ABB14C600F72674 /* progress.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = progress.hpp; path = ../include/stdex/progress.hpp; sourceTree = "<group>"; };
F4213D1F2ABB14C600F72674 /* stream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = stream.hpp; path = ../include/stdex/stream.hpp; sourceTree = "<group>"; };
F4213D202ABB14C600F72674 /* memory.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = memory.hpp; path = ../include/stdex/memory.hpp; sourceTree = "<group>"; };
F4213D212ABB14C600F72674 /* interval.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = interval.hpp; path = ../include/stdex/interval.hpp; sourceTree = "<group>"; };
F4213D222ABB14C600F72674 /* ring.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ring.hpp; path = ../include/stdex/ring.hpp; sourceTree = "<group>"; };
F4213D232ABB14C600F72674 /* unicode.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = unicode.hpp; path = ../include/stdex/unicode.hpp; sourceTree = "<group>"; };
F4213D242ABB14C600F72674 /* string.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = string.hpp; path = ../include/stdex/string.hpp; sourceTree = "<group>"; };
F4213D252ABB14C600F72674 /* mapping.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = mapping.hpp; path = ../include/stdex/mapping.hpp; sourceTree = "<group>"; };
F4213D262ABB14C600F72674 /* hex.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = hex.hpp; path = ../include/stdex/hex.hpp; sourceTree = "<group>"; };
F4213D272ABB14C600F72674 /* endian.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = endian.hpp; path = ../include/stdex/endian.hpp; sourceTree = "<group>"; };
F4213D282ABB14C600F72674 /* math.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = math.hpp; path = ../include/stdex/math.hpp; sourceTree = "<group>"; };
F4213D292ABB14C600F72674 /* parser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = parser.hpp; path = ../include/stdex/parser.hpp; sourceTree = "<group>"; };
F4213D2A2ABB14C600F72674 /* sgml_unicode.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sgml_unicode.hpp; path = ../include/stdex/sgml_unicode.hpp; sourceTree = "<group>"; };
F4213D2B2ABB14C600F72674 /* sgml.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = sgml.hpp; path = ../include/stdex/sgml.hpp; sourceTree = "<group>"; };
F4213D2C2ABB14C600F72674 /* system.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = system.hpp; path = ../include/stdex/system.hpp; sourceTree = "<group>"; };
F421D46B2B750BFD004ECBB0 /* zlib.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = zlib.cpp; sourceTree = "<group>"; };
F421D46F2B750E0F004ECBB0 /* adler32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = adler32.c; sourceTree = "<group>"; };
F421D4712B750E18004ECBB0 /* compress.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = compress.c; sourceTree = "<group>"; };
F421D4732B750E21004ECBB0 /* crc32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = crc32.c; sourceTree = "<group>"; };
F421D4752B750EAE004ECBB0 /* inflate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = inflate.c; sourceTree = "<group>"; };
F421D4762B750EAE004ECBB0 /* deflate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = deflate.c; sourceTree = "<group>"; };
F421D4772B750EAE004ECBB0 /* inffast.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = inffast.c; sourceTree = "<group>"; };
F421D4782B750EAE004ECBB0 /* zutil.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zutil.c; sourceTree = "<group>"; };
F421D4792B750EAE004ECBB0 /* trees.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = trees.c; sourceTree = "<group>"; };
F421D47A2B750EAE004ECBB0 /* inftrees.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = inftrees.c; sourceTree = "<group>"; };
F421D47B2B750EAE004ECBB0 /* uncompr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = uncompr.c; sourceTree = "<group>"; };
F437AA902AC1BB64001E2230 /* hash.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hash.cpp; sourceTree = "<group>"; };
F4481A192C73427600CED93B /* langid.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = langid.cpp; sourceTree = "<group>"; };
F44DE94A2D818A48007E4ECC /* watchdog.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = watchdog.hpp; path = ../include/stdex/watchdog.hpp; sourceTree = "<group>"; };
F44DE94C2D818A48007E4ECC /* sys_info.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = sys_info.hpp; path = ../include/stdex/sys_info.hpp; sourceTree = "<group>"; };
F44DE94D2D818A48007E4ECC /* langid.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = langid.hpp; path = ../include/stdex/langid.hpp; sourceTree = "<group>"; };
F44DE94E2D818A48007E4ECC /* base64.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = base64.hpp; path = ../include/stdex/base64.hpp; sourceTree = "<group>"; };
F44DE94F2D818A48007E4ECC /* curl.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = curl.hpp; path = ../include/stdex/curl.hpp; sourceTree = "<group>"; };
F44DE9512D818A48007E4ECC /* scoped_executor.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = scoped_executor.hpp; path = ../include/stdex/scoped_executor.hpp; sourceTree = "<group>"; };
F44DE9522D818A48007E4ECC /* windows.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = windows.h; path = ../include/stdex/windows.h; sourceTree = "<group>"; };
F44DE9532D818A48007E4ECC /* zlib.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = zlib.hpp; path = ../include/stdex/zlib.hpp; sourceTree = "<group>"; };
F44DE9542D818A48007E4ECC /* unicode.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = unicode.hpp; path = ../include/stdex/unicode.hpp; sourceTree = "<group>"; };
F44DE9552D818A48007E4ECC /* minisign.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = minisign.hpp; path = ../include/stdex/minisign.hpp; sourceTree = "<group>"; };
F44DE9572D818A48007E4ECC /* math.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = math.hpp; path = ../include/stdex/math.hpp; sourceTree = "<group>"; };
F44DE9582D818A48007E4ECC /* parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = parser.hpp; path = ../include/stdex/parser.hpp; sourceTree = "<group>"; };
F44DE9592D818A48007E4ECC /* ring.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = ring.hpp; path = ../include/stdex/ring.hpp; sourceTree = "<group>"; };
F44DE95A2D818A48007E4ECC /* spinlock.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = spinlock.hpp; path = ../include/stdex/spinlock.hpp; sourceTree = "<group>"; };
F44DE95B2D818A49007E4ECC /* string.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = string.hpp; path = ../include/stdex/string.hpp; sourceTree = "<group>"; };
F44DE95C2D818A49007E4ECC /* hash.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = hash.hpp; path = ../include/stdex/hash.hpp; sourceTree = "<group>"; };
F44DE95D2D818A49007E4ECC /* wav.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = wav.hpp; path = ../include/stdex/wav.hpp; sourceTree = "<group>"; };
F44DE95E2D818A49007E4ECC /* endian.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = endian.hpp; path = ../include/stdex/endian.hpp; sourceTree = "<group>"; };
F44DE95F2D818A49007E4ECC /* html.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = html.hpp; path = ../include/stdex/html.hpp; sourceTree = "<group>"; };
F44DE9602D818A49007E4ECC /* hex.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = hex.hpp; path = ../include/stdex/hex.hpp; sourceTree = "<group>"; };
F44DE9622D818A49007E4ECC /* socket.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = socket.hpp; path = ../include/stdex/socket.hpp; sourceTree = "<group>"; };
F44DE9642D818A49007E4ECC /* assert.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = assert.hpp; path = ../include/stdex/assert.hpp; sourceTree = "<group>"; };
F44DE9662D818A49007E4ECC /* exception.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = exception.hpp; path = ../include/stdex/exception.hpp; sourceTree = "<group>"; };
F44DE9672D818A49007E4ECC /* debug.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = debug.hpp; path = ../include/stdex/debug.hpp; sourceTree = "<group>"; };
F44DE9682D818A49007E4ECC /* sgml.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = sgml.hpp; path = ../include/stdex/sgml.hpp; sourceTree = "<group>"; };
F44DE9692D818A49007E4ECC /* chrono.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = chrono.hpp; path = ../include/stdex/chrono.hpp; sourceTree = "<group>"; };
F44DE96A2D818A49007E4ECC /* uuid.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = uuid.hpp; path = ../include/stdex/uuid.hpp; sourceTree = "<group>"; };
F44DE96B2D818A49007E4ECC /* locale.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = locale.hpp; path = ../include/stdex/locale.hpp; sourceTree = "<group>"; };
F44DE96C2D818A4A007E4ECC /* pool.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = pool.hpp; path = ../include/stdex/pool.hpp; sourceTree = "<group>"; };
F44DE96D2D818A4A007E4ECC /* compat.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = compat.hpp; path = ../include/stdex/compat.hpp; sourceTree = "<group>"; };
F44DE96F2D818A4A007E4ECC /* interval.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = interval.hpp; path = ../include/stdex/interval.hpp; sourceTree = "<group>"; };
F44DE9702D818A4A007E4ECC /* idrec.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = idrec.hpp; path = ../include/stdex/idrec.hpp; sourceTree = "<group>"; };
F4C07F502AB059580044EDC0 /* pch.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = pch.hpp; sourceTree = "<group>"; };
F4B7FBDC2AAF49BC00C6BE9F /* UnitTests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = UnitTests; sourceTree = BUILT_PRODUCTS_DIR; };
F4C07F4E2AB059300044EDC0 /* math.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = math.cpp; sourceTree = "<group>"; };
F4C07F512AB059580044EDC0 /* pch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pch.cpp; sourceTree = "<group>"; };
F4C07F532AB05A240044EDC0 /* compat.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = compat.hpp; sourceTree = "<group>"; };
F4C07F542AB05B5B0044EDC0 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; };
F4C07F562AB08E690044EDC0 /* parser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = parser.cpp; sourceTree = "<group>"; };
F4C07F572AB08E690044EDC0 /* unicode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unicode.cpp; sourceTree = "<group>"; };
F4C07F582AB08E690044EDC0 /* sgml.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sgml.cpp; sourceTree = "<group>"; };
F4C07F592AB08E690044EDC0 /* ring.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ring.cpp; sourceTree = "<group>"; };
F4C07F5A2AB08E690044EDC0 /* stream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = stream.cpp; sourceTree = "<group>"; };
F4CCA3B62B73B912007B857B /* watchdog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = watchdog.cpp; sourceTree = "<group>"; };
F4CCA3B72B73B940007B857B /* pool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pool.cpp; sourceTree = "<group>"; };
F4CCA3B82B73D2E2007B857B /* string.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string.cpp; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
F4B7FBD92AAF49BC00C6BE9F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
F4213D162ABB14AA00F72674 /* stdex */ = {
isa = PBXGroup;
children = (
F44DE9642D818A49007E4ECC /* assert.hpp */,
F4213D182ABB14C600F72674 /* base64.hpp */,
F4213D1C2ABB14C600F72674 /* compat.hpp */,
F44DE94F2D818A48007E4ECC /* curl.hpp */,
F44DE9672D818A49007E4ECC /* debug.hpp */,
F4213D272ABB14C600F72674 /* endian.hpp */,
F44DE94E2D818A48007E4ECC /* base64.hpp */,
F44DE9662D818A49007E4ECC /* exception.hpp */,
F44DE9692D818A49007E4ECC /* chrono.hpp */,
F44DE95C2D818A49007E4ECC /* hash.hpp */,
F44DE9602D818A49007E4ECC /* hex.hpp */,
F44DE96D2D818A4A007E4ECC /* compat.hpp */,
F44DE95F2D818A49007E4ECC /* html.hpp */,
F44DE9702D818A4A007E4ECC /* idrec.hpp */,
F44DE95E2D818A49007E4ECC /* endian.hpp */,
F44DE96F2D818A4A007E4ECC /* interval.hpp */,
F44DE94D2D818A48007E4ECC /* langid.hpp */,
F44DE96B2D818A49007E4ECC /* locale.hpp */,
F44DE9572D818A48007E4ECC /* math.hpp */,
F44DE9552D818A48007E4ECC /* minisign.hpp */,
F44DE9582D818A48007E4ECC /* parser.hpp */,
F44DE96C2D818A4A007E4ECC /* pool.hpp */,
F44DE9592D818A48007E4ECC /* ring.hpp */,
F44DE9512D818A48007E4ECC /* scoped_executor.hpp */,
F44DE9682D818A49007E4ECC /* sgml.hpp */,
F44DE9622D818A49007E4ECC /* socket.hpp */,
F44DE95A2D818A48007E4ECC /* spinlock.hpp */,
F44DE95B2D818A49007E4ECC /* string.hpp */,
F44DE94C2D818A48007E4ECC /* sys_info.hpp */,
F44DE9542D818A48007E4ECC /* unicode.hpp */,
F44DE96A2D818A49007E4ECC /* uuid.hpp */,
F44DE94A2D818A48007E4ECC /* watchdog.hpp */,
F44DE95D2D818A49007E4ECC /* wav.hpp */,
F44DE9522D818A48007E4ECC /* windows.h */,
F44DE9532D818A48007E4ECC /* zlib.hpp */,
F4213D1A2ABB14C600F72674 /* exception.hpp */,
F4213D262ABB14C600F72674 /* hex.hpp */,
F4213D1B2ABB14C600F72674 /* idrec.hpp */,
F4213D212ABB14C600F72674 /* interval.hpp */,
F4213D252ABB14C600F72674 /* mapping.hpp */,
F4213D282ABB14C600F72674 /* math.hpp */,
F4213D202ABB14C600F72674 /* memory.hpp */,
F4213D292ABB14C600F72674 /* parser.hpp */,
F4213D1E2ABB14C600F72674 /* progress.hpp */,
F4213D222ABB14C600F72674 /* ring.hpp */,
F4213D2A2ABB14C600F72674 /* sgml_unicode.hpp */,
F4213D2B2ABB14C600F72674 /* sgml.hpp */,
F4213D1F2ABB14C600F72674 /* stream.hpp */,
F4213D242ABB14C600F72674 /* string.hpp */,
F4213D2C2ABB14C600F72674 /* system.hpp */,
F4213D232ABB14C600F72674 /* unicode.hpp */,
F4213D172ABB14C600F72674 /* vector_queue.hpp */,
);
name = stdex;
sourceTree = "<group>";
};
F421D46E2B750D3A004ECBB0 /* zlib */ = {
isa = PBXGroup;
children = (
F421D46F2B750E0F004ECBB0 /* adler32.c */,
F421D4712B750E18004ECBB0 /* compress.c */,
F421D4732B750E21004ECBB0 /* crc32.c */,
F421D4762B750EAE004ECBB0 /* deflate.c */,
F421D4772B750EAE004ECBB0 /* inffast.c */,
F421D4752B750EAE004ECBB0 /* inflate.c */,
F421D47A2B750EAE004ECBB0 /* inftrees.c */,
F421D4792B750EAE004ECBB0 /* trees.c */,
F421D47B2B750EAE004ECBB0 /* uncompr.c */,
F421D4782B750EAE004ECBB0 /* zutil.c */,
);
path = zlib;
sourceTree = "<group>";
};
F4B7FBD32AAF49BC00C6BE9F = {
isa = PBXGroup;
children = (
F4C07F532AB05A240044EDC0 /* compat.hpp */,
F437AA902AC1BB64001E2230 /* hash.cpp */,
F4481A192C73427600CED93B /* langid.cpp */,
F4C07F542AB05B5B0044EDC0 /* main.cpp */,
F4C07F4E2AB059300044EDC0 /* math.cpp */,
F4C07F562AB08E690044EDC0 /* parser.cpp */,
F4C07F512AB059580044EDC0 /* pch.cpp */,
F4C07F502AB059580044EDC0 /* pch.hpp */,
F4CCA3B72B73B940007B857B /* pool.cpp */,
F4B7FBDD2AAF49BC00C6BE9F /* Products */,
F4C07F592AB08E690044EDC0 /* ring.cpp */,
F4C07F582AB08E690044EDC0 /* sgml.cpp */,
F4213D162ABB14AA00F72674 /* stdex */,
F4C07F5A2AB08E690044EDC0 /* stream.cpp */,
F4CCA3B82B73D2E2007B857B /* string.cpp */,
F4C07F572AB08E690044EDC0 /* unicode.cpp */,
F4CCA3B62B73B912007B857B /* watchdog.cpp */,
F421D46E2B750D3A004ECBB0 /* zlib */,
F421D46B2B750BFD004ECBB0 /* zlib.cpp */,
);
sourceTree = "<group>";
usesTabs = 1;
};
F4B7FBDD2AAF49BC00C6BE9F /* Products */ = {
isa = PBXGroup;
children = (
F4B7FBDC2AAF49BC00C6BE9F /* UnitTests */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
F4B7FBDB2AAF49BC00C6BE9F /* UnitTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = F4B7FBE32AAF49BC00C6BE9F /* Build configuration list for PBXNativeTarget "UnitTests" */;
buildPhases = (
F4B7FBD82AAF49BC00C6BE9F /* Sources */,
F4B7FBD92AAF49BC00C6BE9F /* Frameworks */,
F4B7FBDA2AAF49BC00C6BE9F /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = UnitTests;
productName = UnitTests;
productReference = F4B7FBDC2AAF49BC00C6BE9F /* UnitTests */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
F4B7FBD42AAF49BC00C6BE9F /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1430;
TargetAttributes = {
F4B7FBDB2AAF49BC00C6BE9F = {
CreatedOnToolsVersion = 14.3.1;
};
};
};
buildConfigurationList = F4B7FBD72AAF49BC00C6BE9F /* Build configuration list for PBXProject "UnitTests" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = F4B7FBD32AAF49BC00C6BE9F;
productRefGroup = F4B7FBDD2AAF49BC00C6BE9F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
F4B7FBDB2AAF49BC00C6BE9F /* UnitTests */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
F4B7FBD82AAF49BC00C6BE9F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F44DE98A2D818A9F007E4ECC /* zlib.cpp in Sources */,
F44DE9812D818A9F007E4ECC /* ring.cpp in Sources */,
F421D47F2B750EAE004ECBB0 /* zutil.c in Sources */,
F44DE97F2D818A9F007E4ECC /* math.cpp in Sources */,
F44DE9862D818A9F007E4ECC /* stream.cpp in Sources */,
F421D4802B750EAE004ECBB0 /* trees.c in Sources */,
F44DE9852D818A9F007E4ECC /* watchdog.cpp in Sources */,
F44DE9802D818A9F007E4ECC /* main.cpp in Sources */,
F44DE98B2D818A9F007E4ECC /* unicode.cpp in Sources */,
F421D47D2B750EAE004ECBB0 /* deflate.c in Sources */,
F44DE9832D818A9F007E4ECC /* sgml.cpp in Sources */,
F44DE9882D818A9F007E4ECC /* pch.cpp in Sources */,
F44DE98C2D818A9F007E4ECC /* parser.cpp in Sources */,
F421D4702B750E0F004ECBB0 /* adler32.c in Sources */,
F421D4822B750EAE004ECBB0 /* uncompr.c in Sources */,
F421D4742B750E21004ECBB0 /* crc32.c in Sources */,
F421D47E2B750EAE004ECBB0 /* inffast.c in Sources */,
F421D4722B750E18004ECBB0 /* compress.c in Sources */,
F421D4812B750EAE004ECBB0 /* inftrees.c in Sources */,
F44DE9842D818A9F007E4ECC /* pool.cpp in Sources */,
F44DE9872D818A9F007E4ECC /* langid.cpp in Sources */,
F44DE9822D818A9F007E4ECC /* hash.cpp in Sources */,
F44DE9892D818A9F007E4ECC /* string.cpp in Sources */,
F421D47C2B750EAE004ECBB0 /* inflate.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
F4B7FBE12AAF49BC00C6BE9F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_DEADCODE_DEADSTORES = YES;
CLANG_ANALYZER_DIVIDE_BY_ZERO = YES;
CLANG_ANALYZER_GCD = YES;
CLANG_ANALYZER_GCD_PERFORMANCE = YES;
CLANG_ANALYZER_LIBKERN_RETAIN_COUNT = YES;
CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_MEMORY_MANAGEMENT = YES;
CLANG_ANALYZER_MIG_CONVENTIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NULL_DEREFERENCE = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_ANALYZER_OBJC_COLLECTIONS = YES;
CLANG_ANALYZER_OBJC_NSCFERROR = YES;
CLANG_ANALYZER_OSOBJECT_C_STYLE_CAST = YES;
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_GETPW_GETS = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_MKSTEMP = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_UNCHECKEDRETURN = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_VFORK = YES;
CLANG_ANALYZER_SECURITY_KEYCHAIN_API = YES;
CLANG_ANALYZER_USE_AFTER_MOVE = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_STATIC_ANALYZER_MODE = deep;
CLANG_STATIC_ANALYZER_MODE_ON_ANALYZE_ACTION = deep;
CLANG_TIDY_BUGPRONE_ASSERT_SIDE_EFFECT = YES;
CLANG_TIDY_BUGPRONE_INFINITE_LOOP = YES;
CLANG_TIDY_BUGPRONE_MOVE_FORWARDING_REFERENCE = YES;
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CLANG_WARN_ASSIGN_ENUM = YES;
CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_COMPLETION_HANDLER_MISUSE = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DELETE_NON_VIRTUAL_DTOR = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_FLOAT_CONVERSION = YES;
CLANG_WARN_FRAMEWORK_INCLUDE_PRIVATE_FROM_PUBLIC = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_PRAGMA_PACK = YES;
CLANG_WARN_PRIVATE_MODULE = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN_VEXING_PARSE = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
_DEBUG,
"$(inherited)",
);
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES;
GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_CHECK_SWITCH_STATEMENTS = YES;
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_MISSING_PARENTHESES = YES;
GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ../include;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "-liconv";
RUN_CLANG_STATIC_ANALYZER = YES;
SDKROOT = macosx;
};
name = Debug;
};
F4B7FBE22AAF49BC00C6BE9F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_DEADCODE_DEADSTORES = YES;
CLANG_ANALYZER_DIVIDE_BY_ZERO = YES;
CLANG_ANALYZER_GCD = YES;
CLANG_ANALYZER_GCD_PERFORMANCE = YES;
CLANG_ANALYZER_LIBKERN_RETAIN_COUNT = YES;
CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_MEMORY_MANAGEMENT = YES;
CLANG_ANALYZER_MIG_CONVENTIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NULL_DEREFERENCE = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_ANALYZER_OBJC_COLLECTIONS = YES;
CLANG_ANALYZER_OBJC_NSCFERROR = YES;
CLANG_ANALYZER_OSOBJECT_C_STYLE_CAST = YES;
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_GETPW_GETS = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_MKSTEMP = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_UNCHECKEDRETURN = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_VFORK = YES;
CLANG_ANALYZER_SECURITY_KEYCHAIN_API = YES;
CLANG_ANALYZER_USE_AFTER_MOVE = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_STATIC_ANALYZER_MODE = deep;
CLANG_STATIC_ANALYZER_MODE_ON_ANALYZE_ACTION = deep;
CLANG_TIDY_BUGPRONE_ASSERT_SIDE_EFFECT = YES;
CLANG_TIDY_BUGPRONE_INFINITE_LOOP = YES;
CLANG_TIDY_BUGPRONE_MOVE_FORWARDING_REFERENCE = YES;
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CLANG_WARN_ASSIGN_ENUM = YES;
CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_COMPLETION_HANDLER_MISUSE = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DELETE_NON_VIRTUAL_DTOR = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_FLOAT_CONVERSION = YES;
CLANG_WARN_FRAMEWORK_INCLUDE_PRIVATE_FROM_PUBLIC = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_PRAGMA_PACK = YES;
CLANG_WARN_PRIVATE_MODULE = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN_VEXING_PARSE = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
NDEBUG,
"$(inherited)",
);
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES;
GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_CHECK_SWITCH_STATEMENTS = YES;
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_MISSING_PARENTHESES = YES;
GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = ../include;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "-liconv";
RUN_CLANG_STATIC_ANALYZER = YES;
SDKROOT = macosx;
};
name = Release;
};
F4B7FBE42AAF49BC00C6BE9F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
F4B7FBE52AAF49BC00C6BE9F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
F4B7FBD72AAF49BC00C6BE9F /* Build configuration list for PBXProject "UnitTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F4B7FBE12AAF49BC00C6BE9F /* Debug */,
F4B7FBE22AAF49BC00C6BE9F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F4B7FBE32AAF49BC00C6BE9F /* Build configuration list for PBXNativeTarget "UnitTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F4B7FBE42AAF49BC00C6BE9F /* Debug */,
F4B7FBE52AAF49BC00C6BE9F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = F4B7FBD42AAF49BC00C6BE9F /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F4B7FBDB2AAF49BC00C6BE9F"
BuildableName = "UnitTests"
BlueprintName = "UnitTests"
ReferencedContainer = "container:UnitTests.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F4B7FBDB2AAF49BC00C6BE9F"
BuildableName = "UnitTests"
BlueprintName = "UnitTests"
ReferencedContainer = "container:UnitTests.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F4B7FBDB2AAF49BC00C6BE9F"
BuildableName = "UnitTests"
BlueprintName = "UnitTests"
ReferencedContainer = "container:UnitTests.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

96
UnitTests/compat.hpp Normal file
View File

@ -0,0 +1,96 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#ifdef _WIN32
#include <CppUnitTest.h>
#else
#include <stdexcept>
#define TEST_CLASS(name) class name
#define TEST_METHOD(name) static void name()
namespace Assert
{
inline void IsTrue(bool c)
{
if (!c)
throw std::runtime_error("not true");
}
inline void IsFalse(bool c)
{
if (c)
throw std::runtime_error("not false");
}
template <class T>
void AreEqual(const T& a, const T& b)
{
if (!(a == b))
throw std::runtime_error("not equal");
}
template <class T, size_t N>
void AreEqual(const T (&a)[N], const T (&b)[N])
{
for (size_t i = 0; i < N; ++i)
if (!(a[i] == b[i]))
throw std::runtime_error("not equal");
}
inline void AreEqual(const char* a, const char* b)
{
if (strcmp(a, b) != 0)
throw std::runtime_error("not equal");
}
inline void AreEqual(const wchar_t* a, const wchar_t* b)
{
if (wcscmp(a, b) != 0)
throw std::runtime_error("not equal");
}
inline void AreEqual(const char32_t* a, const char32_t* b)
{
#ifdef _WIN32
if (stdex::strcmp(a, b) != 0)
throw std::runtime_error("not equal");
#else
if (wcscmp(reinterpret_cast<const wchar_t*>(a), reinterpret_cast<const wchar_t*>(b)) != 0)
throw std::runtime_error("not equal");
#endif
}
template <class T>
void AreNotEqual(const T& a, const T& b)
{
if (a == b)
throw std::runtime_error("equal");
}
inline void AreNotEqual(const char* a, const char* b)
{
if (strcmp(a, b) == 0)
throw std::runtime_error("equal");
}
inline void AreNotEqual(const wchar_t* a, const wchar_t* b)
{
if (wcscmp(a, b) == 0)
throw std::runtime_error("equal");
}
template <class E, typename F>
void ExpectException(F functor)
{
try { functor(); }
catch (const E&) { return; }
catch (...) { throw std::runtime_error("unexpected exception"); }
throw std::runtime_error("exception not thrown");
}
}
#endif

63
UnitTests/hash.cpp Normal file
View File

@ -0,0 +1,63 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace Microsoft {
namespace VisualStudio {
namespace CppUnitTestFramework {
static std::wstring ToString(const stdex::md5_t& q)
{
stdex::hex_enc enc;
wstring str;
enc.encode(str, &q, sizeof(q));
return str;
}
static std::wstring ToString(const stdex::sha1_t& q)
{
stdex::hex_enc enc;
wstring str;
enc.encode(str, &q, sizeof(q));
return str;
}
}
}
}
#endif
namespace UnitTests
{
void hash::crc32()
{
stdex::crc32_hash h;
static const char data[] = "This is a test.";
h.hash(data, sizeof(data) - sizeof(*data));
h.finalize();
Assert::IsTrue(h == 0xc6c3c95d);
}
void hash::md5()
{
stdex::md5_hash h;
static const char data[] = "This is a test.";
h.hash(data, sizeof(data) - sizeof(*data));
h.finalize();
Assert::AreEqual<stdex::md5_t>({{0x12,0x0e,0xa8,0xa2,0x5e,0x5d,0x48,0x7b,0xf6,0x8b,0x5f,0x70,0x96,0x44,0x00,0x19}}, h);
}
void hash::sha1()
{
stdex::sha1_hash h;
static const char data[] = "This is a test.";
h.hash(data, sizeof(data) - sizeof(*data));
h.finalize();
Assert::AreEqual<stdex::sha1_t>({{0xaf,0xa6,0xc8,0xb3,0xa2,0xfa,0xe9,0x57,0x85,0xdc,0x7d,0x96,0x85,0xa5,0x78,0x35,0xd7,0x03,0xac,0x88}}, h);
}
}

35
UnitTests/langid.cpp Normal file
View File

@ -0,0 +1,35 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2024-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
namespace UnitTests
{
void langid::from_rfc1766()
{
Assert::IsTrue(stdex::langid_from_rfc1766("en") == 9);
Assert::IsTrue(stdex::langid_from_rfc1766("en-US") == 1033);
Assert::IsTrue(stdex::langid_from_rfc1766("en_US") == 1033);
Assert::IsTrue(stdex::langid_from_rfc1766("en-GB") == 2057);
Assert::IsTrue(stdex::langid_from_rfc1766("en_GB") == 2057);
Assert::IsTrue(stdex::langid_from_rfc1766("EN") == 9);
Assert::IsTrue(stdex::langid_from_rfc1766("EN-US") == 1033);
Assert::IsTrue(stdex::langid_from_rfc1766("EN_US") == 1033);
Assert::IsTrue(stdex::langid_from_rfc1766("EN-GB") == 2057);
Assert::IsTrue(stdex::langid_from_rfc1766("EN_GB") == 2057);
Assert::IsTrue(stdex::langid_from_rfc1766("sl") == 36);
Assert::IsTrue(stdex::langid_from_rfc1766("sl-SI") == 1060);
Assert::IsTrue(stdex::langid_from_rfc1766("sl_SI") == 1060);
Assert::IsTrue(stdex::langid_from_rfc1766("SL") == 36);
Assert::IsTrue(stdex::langid_from_rfc1766("SL-SI") == 1060);
Assert::IsTrue(stdex::langid_from_rfc1766("SL_SI") == 1060);
}
}

46
UnitTests/main.cpp Normal file
View File

@ -0,0 +1,46 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
#include <iostream>
int main(int, const char *[])
{
try {
UnitTests::hash::crc32();
UnitTests::hash::md5();
UnitTests::hash::sha1();
UnitTests::langid::from_rfc1766();
UnitTests::math::add();
UnitTests::math::mul();
UnitTests::parser::http_test();
UnitTests::parser::sgml_test();
UnitTests::parser::wtest();
UnitTests::pool::test();
UnitTests::ring::test();
UnitTests::sgml::sgml2str();
UnitTests::sgml::str2sgml();
UnitTests::stream::async();
UnitTests::stream::file_stat();
UnitTests::stream::open_close();
UnitTests::stream::replicator();
UnitTests::string::strncpy();
UnitTests::string::sprintf();
UnitTests::string::snprintf();
UnitTests::unicode::charset_encoder();
UnitTests::unicode::normalize();
UnitTests::unicode::str2wstr();
UnitTests::unicode::wstr2str();
UnitTests::watchdog::test();
UnitTests::zlib::test();
std::cout << "PASS\n";
return 0;
}
catch (const std::exception& ex) {
std::cerr << stdex::exception_msg(ex) << " FAIL\n";
return 1;
}
}

40
UnitTests/math.cpp Normal file
View File

@ -0,0 +1,40 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
namespace UnitTests
{
void math::mul()
{
Assert::IsTrue(stdex::mul(2, 5) == 10);
Assert::IsTrue(stdex::mul(5, 2) == 10);
Assert::IsTrue(stdex::mul(0, 10) == 0);
Assert::IsTrue(stdex::mul(10, 0) == 0);
Assert::IsTrue(stdex::mul(SIZE_MAX, 0) == 0);
Assert::IsTrue(stdex::mul(0, SIZE_MAX) == 0);
Assert::IsTrue(stdex::mul(SIZE_MAX, 1) == SIZE_MAX);
Assert::IsTrue(stdex::mul(1, SIZE_MAX) == SIZE_MAX);
Assert::ExpectException<std::invalid_argument>([] { stdex::mul(SIZE_MAX, 2); });
Assert::ExpectException<std::invalid_argument>([] { stdex::mul(2, SIZE_MAX); });
}
void math::add()
{
Assert::IsTrue(stdex::add(2, 5) == 7);
Assert::IsTrue(stdex::add(5, 2) == 7);
Assert::IsTrue(stdex::add(0, 10) == 10);
Assert::IsTrue(stdex::add(10, 0) == 10);
Assert::IsTrue(stdex::add(SIZE_MAX, 0) == SIZE_MAX);
Assert::IsTrue(stdex::add(0, SIZE_MAX) == SIZE_MAX);
Assert::ExpectException<std::invalid_argument>([] { stdex::add(SIZE_MAX, 1); });
Assert::ExpectException<std::invalid_argument>([] { stdex::add(1, SIZE_MAX); });
}
}

505
UnitTests/parser.cpp Normal file
View File

@ -0,0 +1,505 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
using namespace stdex;
using namespace stdex::parser;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace Microsoft {
namespace VisualStudio {
namespace CppUnitTestFramework {
static std::wstring ToString(const stdex::interval<size_t>& q)
{
return stdex::sprintf(L"<%zu, %zu>", nullptr, q.start, q.end);
}
}
}
}
#endif
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wexit-time-destructors"
#endif
namespace UnitTests
{
void parser::wtest()
{
static const wchar_t text[] = L"This is a test.\nSecond line.";
{
wnoop p;
Assert::IsTrue(p.match(text));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 0);
}
{
wcu p(L't');
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 0, _countof(text), match_case_insensitive));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 1);
}
{
wspace_cu p;
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 4));
Assert::IsTrue(p.interval.start == 4);
Assert::IsTrue(p.interval.end == 5);
}
{
wpunct_cu p;
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 14));
Assert::IsTrue(p.interval.start == 14);
Assert::IsTrue(p.interval.end == 15);
}
{
wspace_or_punct_cu p;
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 4));
Assert::IsTrue(p.interval.start == 4);
Assert::IsTrue(p.interval.end == 5);
Assert::IsTrue(p.match(text, 14));
Assert::IsTrue(p.interval.start == 14);
Assert::IsTrue(p.interval.end == 15);
}
{
wbol p;
Assert::IsTrue(p.match(text));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 0);
Assert::IsFalse(p.match(text, 1));
Assert::IsFalse(p.match(text, 15));
Assert::IsTrue(p.match(text, 16));
Assert::IsTrue(p.interval.start == 16);
Assert::IsTrue(p.interval.end == 16);
}
{
weol p;
Assert::IsFalse(p.match(text));
Assert::IsFalse(p.match(text, 1));
Assert::IsTrue(p.match(text, 15));
Assert::IsTrue(p.interval.start == 15);
Assert::IsTrue(p.interval.end == 15);
Assert::IsFalse(p.match(text, 16));
}
{
wcu_set p(L"abcD");
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 8));
Assert::IsTrue(p.interval.start == 8);
Assert::IsTrue(p.interval.end == 9);
Assert::IsTrue(p.hit_offset == 0);
Assert::IsFalse(p.match(text, 21));
Assert::IsTrue(p.match(text, 21, _countof(text), match_case_insensitive));
Assert::IsTrue(p.interval.start == 21);
Assert::IsTrue(p.interval.end == 22);
Assert::IsTrue(p.hit_offset == 3);
}
{
stdex::parser::wstring p(L"this");
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 0, sizeof(text), match_case_insensitive));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 4);
}
{
wany_cu chr;
witerations p(make_shared_no_delete(&chr), 1, 5);
Assert::IsTrue(p.match(text));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 5);
}
{
wspace_cu nospace(true);
witerations p(make_shared_no_delete(&nospace), 1);
Assert::IsTrue(p.match(text));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 4);
}
{
wcu chr_t(L't'), chr_h(L'h'), chr_i(L'i'), chr_s(L's');
wspace_cu space;
wsequence p({
make_shared_no_delete(&chr_t),
make_shared_no_delete(&chr_h),
make_shared_no_delete(&chr_i),
make_shared_no_delete(&chr_s),
make_shared_no_delete(&space) });
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 0, _countof(text), match_case_insensitive));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 5);
}
{
stdex::parser::wstring apple(L"apple"), orange(L"orange"), _this(L"this");
wspace_cu space;
wbranch p({
make_shared_no_delete(&apple),
make_shared_no_delete(&orange),
make_shared_no_delete(&_this),
make_shared_no_delete(&space) });
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 0, _countof(text), match_case_insensitive));
Assert::IsTrue(p.hit_offset == 2);
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 4);
}
{
wstring_branch p(L"apple", L"orange", L"this", nullptr);
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 0, _countof(text), match_case_insensitive));
Assert::IsTrue(p.hit_offset == 2);
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 4);
}
{
wcu chr_s(L's'), chr_h(L'h'), chr_i(L'i'), chr_t(L't');
wpermutation p({
make_shared_no_delete(&chr_s),
make_shared_no_delete(&chr_h),
make_shared_no_delete(&chr_i),
make_shared_no_delete(&chr_t) });
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 0, _countof(text), match_case_insensitive));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 4);
}
{
std::locale locale_slSI("sl_SI");
wspace_cu space(false, locale_slSI);
wiban p(make_shared_no_delete(&space), locale_slSI);
Assert::IsTrue(p.match(L"SI56023120015226972", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual(L"SI", p.country);
Assert::AreEqual(L"56", p.check_digits);
Assert::AreEqual(L"023120015226972", p.bban);
Assert::IsTrue(p.match(L"SI56 0231 2001 5226 972", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual(L"SI", p.country);
Assert::AreEqual(L"56", p.check_digits);
Assert::AreEqual(L"023120015226972", p.bban);
Assert::IsFalse(p.match(L"si56 0231 2001 5226 972", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match(L"si56 0231 2001 5226 972", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SI56 0231 2001 5226 9720", 0, SIZE_MAX));
Assert::AreEqual(stdex::interval<size_t>(0, 23), p.interval);
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"...SI56 0231 2001 5226 972...", 3, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SI56 0231 2001 5226 972", 0, SIZE_MAX)); // no-break space
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"BE71 0961 2345 6769", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"BR15 0000 0000 0000 1093 2840 814 P2", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"CR99 0000 0000 0000 8888 88", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match(L"FR76 3000 6000 0112 3456 7890 189", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"IE12 BOFI 9000 0112 3456 78", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match(L"DE91 1000 0000 0123 4567 89", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"GR96 0810 0010 0000 0123 4567 890", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"MU43 BOMM 0101 1234 5678 9101 000 MUR", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"PK70 BANK 0000 1234 5678 9000", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"PL10 1050 0099 7603 1234 5678 9123", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"RO09 BCYP 0000 0012 3456 7890", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"LC14 BOSL 1234 5678 9012 3456 7890 1234", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SA44 2000 0001 2345 6789 1234", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"ES79 2100 0813 6101 2345 6789", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"SE87 3000 0000 0101 2345 6789", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match(L"CH56 0483 5012 3456 7800 9", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"GB98 MIDL 0700 9312 3456 78", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
}
{
std::locale locale_slSI("sl_SI");
wspace_cu space(false, locale_slSI);
wcreditor_reference p(make_shared_no_delete(&space), locale_slSI);
Assert::IsTrue(p.match(L"RF18539007547034", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual(L"18", p.check_digits);
Assert::AreEqual(L"000000000539007547034", p.reference);
Assert::IsTrue(p.match(L"RF18 5390 0754 7034", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual(L"18", p.check_digits);
Assert::AreEqual(L"000000000539007547034", p.reference);
Assert::IsFalse(p.match(L"rf18 5390 0754 7034", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match(L"rf18 5390 0754 7034", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"RF18 5390 0754 70340", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match(L"...RF18 5390 0754 7034...", 3, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match(L"RF18 5390 0754 7034", 0, SIZE_MAX)); // no-break space
Assert::IsTrue(p.is_valid);
}
{
std::locale locale_slSI("sl_SI");
wspace_cu space(false, locale_slSI);
wsi_reference p(make_shared_no_delete(&space), locale_slSI);
Assert::IsTrue(p.match(L"SI121234567890120", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual(L"12", p.model);
Assert::AreEqual(stdex::interval<size_t>(4, 17), p.part1.interval);
Assert::IsTrue(p.match(L"SI12 1234567890120", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual(L"12", p.model);
Assert::AreEqual(stdex::interval<size_t>(5, 18), p.part1.interval);
Assert::IsFalse(p.match(L"si12 1234567890120", 0, SIZE_MAX));
Assert::IsTrue(p.match(L"si12 1234567890120", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.match(L"...SI12 1234567890120...", 3, SIZE_MAX));
Assert::IsTrue(p.match(L"SI12 1234567890120", 0, SIZE_MAX)); // no-break space
}
}
void parser::sgml_test()
{
std::locale locale_slSI("sl_SI");
static const char text[] = "V ko&zcaron;u&scaron;&ccaron;ku zlobnega mizarja stopiclja fant\nin kli&ccaron;e&nbsp;1234567890.";
{
sgml_noop p;
Assert::IsTrue(p.match(text));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 0);
}
{
sgml_cp p("v");
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 0, _countof(text), match_case_insensitive));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 1);
}
{
sgml_cp p("&Zcaron;", SIZE_MAX, false, locale_slSI);
Assert::IsFalse(p.match(text, 4));
Assert::IsTrue(p.match(text, 4, _countof(text), match_case_insensitive));
Assert::IsTrue(p.interval.start == 4);
Assert::IsTrue(p.interval.end == 12);
}
{
sgml_space_cp p(false, locale_slSI);
Assert::IsFalse(p.match(text));
Assert::IsTrue(p.match(text, 1));
Assert::IsTrue(p.interval.start == 1);
Assert::IsTrue(p.interval.end == 2);
Assert::IsTrue(p.match(text, 79));
Assert::IsTrue(p.interval.start == 79);
Assert::IsTrue(p.interval.end == 85);
}
{
sgml_string_branch p(locale_slSI, "apple", "orange", "Ko&Zcaron;u&Scaron;&ccaron;Ku", nullptr);
Assert::IsFalse(p.match(text, 2));
Assert::IsTrue(p.match(text, 2, _countof(text), match_case_insensitive));
Assert::IsTrue(p.hit_offset == 2);
Assert::IsTrue(p.interval.start == 2);
Assert::IsTrue(p.interval.end == 31);
}
{
sgml_space_cp space(false, locale_slSI);
sgml_iban p(make_shared_no_delete(&space), locale_slSI);
Assert::IsTrue(p.match("SI56023120015226972", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual("SI", p.country);
Assert::AreEqual("56", p.check_digits);
Assert::AreEqual("023120015226972", p.bban);
Assert::IsTrue(p.match("SI56 0231 2001 5226 972", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual("SI", p.country);
Assert::AreEqual("56", p.check_digits);
Assert::AreEqual("023120015226972", p.bban);
Assert::IsFalse(p.match("si56 0231 2001 5226 972", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match("si56 0231 2001 5226 972", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("SI56 0231 2001 5226 9720", 0, SIZE_MAX));
Assert::AreEqual(stdex::interval<size_t>(0, 23), p.interval);
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("...SI56 0231 2001 5226 972...", 3, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("SI56&nbsp;0231&nbsp;2001&nbsp;5226&nbsp;972", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
}
{
sgml_space_cp space(false, locale_slSI);
sgml_creditor_reference p(make_shared_no_delete(&space), locale_slSI);
Assert::IsTrue(p.match("RF18539007547034", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual("18", p.check_digits);
Assert::AreEqual("000000000539007547034", p.reference);
Assert::IsTrue(p.match("RF18 5390 0754 7034", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual("18", p.check_digits);
Assert::AreEqual("000000000539007547034", p.reference);
Assert::IsFalse(p.match("rf18 5390 0754 7034", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match("rf18 5390 0754 7034", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("RF18 5390 0754 70340", 0, SIZE_MAX));
Assert::IsFalse(p.is_valid);
Assert::IsTrue(p.match("...RF18 5390 0754 7034...", 3, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::IsTrue(p.match("RF18&nbsp;5390&nbsp;0754&nbsp;7034", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
}
{
sgml_space_cp space(false, locale_slSI);
sgml_si_reference p(make_shared_no_delete(&space), locale_slSI);
Assert::IsTrue(p.match("SI121234567890120", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual("12", p.model);
Assert::AreEqual(stdex::interval<size_t>(4, 17), p.part1.interval);
Assert::IsTrue(p.match("SI12 1234567890120", 0, SIZE_MAX));
Assert::IsTrue(p.is_valid);
Assert::AreEqual("12", p.model);
Assert::AreEqual(stdex::interval<size_t>(5, 18), p.part1.interval);
Assert::IsFalse(p.match("si12 1234567890120", 0, SIZE_MAX));
Assert::IsTrue(p.match("si12 1234567890120", 0, SIZE_MAX, match_case_insensitive));
Assert::IsTrue(p.match("...SI12 1234567890120...", 3, SIZE_MAX));
Assert::IsTrue(p.match("SI12&nbsp;1234567890120", 0, SIZE_MAX));
}
}
void parser::http_test()
{
static const std::locale locale("en_US.UTF-8");
static const char request[] =
"GET / HTTP/2\r\n"
"Host: stackoverflow.com\r\n"
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0\r\n"
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n"
"Accept-Language: sl,en-US;q=0.8,en;q=0.6,de-DE;q=0.4,de;q=0.2\r\n"
"Accept-Encoding: gzip, deflate, br\r\n"
"DNT: 1\r\n"
"Connection: keep-alive\r\n"
"Cookie: prov=00000000-0000-0000-0000-000000000000; acct=t=00000000000000000%2f%2f0000%2b0000%2b000&s=00000000000000000000000000000000; OptanonConsent=isGpcEnabled=0&datestamp=Fri+Feb+03+2023+11%3A11%3A08+GMT%2B0100+(Srednjeevropski+standardni+%C4%8Das)&version=6.37.0&isIABGlobal=false&hosts=&consentId=00000000-0000-0000-0000-000000000000&interactionCount=1&landingPath=NotLandingPage&groups=00000%3A0%2C00000%3A0%2C00000%3A0%2C00000%3A0; OptanonAlertBoxClosed=2023-02-03T10:11:08.683Z\r\n"
"Upgrade-Insecure-Requests: 1\r\n"
"Sec-Fetch-Dest: document\r\n"
"Sec-Fetch-Mode: navigate\r\n"
"Sec-Fetch-Site: none\r\n"
"Sec-Fetch-User: ?1\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n"
"\r\n";
{
http_request p(locale);
Assert::IsTrue(p.match(request));
Assert::IsTrue(p.interval.start == 0);
Assert::IsTrue(p.interval.end == 14);
Assert::IsTrue(p.verb.start == 0);
Assert::IsTrue(p.verb.end == 3);
Assert::IsTrue(p.url.interval.start == 4);
Assert::IsTrue(p.url.interval.end == 5);
Assert::IsTrue(p.protocol.interval.start == 6);
Assert::IsTrue(p.protocol.interval.end == 12);
Assert::IsTrue(p.protocol.version == 0x200);
}
{
list<http_header> hdrs;
size_t offset = 14;
for (;;) {
http_header h;
if (h.match(request, offset)) {
offset = h.interval.end;
hdrs.push_back(std::move(h));
}
else
break;
}
Assert::IsTrue(hdrs.size() == 15);
http_weighted_collection<http_weighted_value<http_language>> langs;
for (const auto& h : hdrs)
if (strnicmp(request + h.name.start, h.name.size(), "Accept-Language", SIZE_MAX, locale) == 0)
langs.insert(request, h.value.start, h.value.end);
Assert::IsTrue(!langs.empty());
{
const vector<std::string> control = {
"sl", "en-US", "en", "de-DE", "de"
};
auto c = control.cbegin();
auto l = langs.cbegin();
for (; c != control.cend() && l != langs.cend(); ++c, ++l)
Assert::IsTrue(strnicmp(request + l->value.interval.start, l->value.interval.size(), c->c_str(), c->size(), locale) == 0);
Assert::IsTrue(c == control.cend());
Assert::IsTrue(l == langs.cend());
}
}
//static const char response[] =
// "HTTP/2 200 OK\r\n"
// "cache-control: private\r\n"
// "content-type: text/html; charset=utf-8\r\n"
// "content-encoding: gzip\r\n"
// "strict-transport-security: max-age=15552000\r\n"
// "x-frame-options: SAMEORIGIN\r\n"
// "set-cookie: acct=t=00000000000000000%2f%2f0000%2b0000%2b000&s=00000000000000000000000000000000; expires=Sat, 16 Sep 2023 10:23:00 GMT; domain=.stackoverflow.com; path=/; secure; samesite=none; httponly\r\n"
// "set-cookie: prov_tgt=; expires=Tue, 14 Mar 2023 10:23:00 GMT; domain=.stackoverflow.com; path=/; secure; samesite=none; httponly\r\n"
// "x-request-guid: a6536a49-b473-4c6f-b313-c1e7c0d8f600\r\n"
// "feature-policy: microphone 'none'; speaker 'none'\r\n"
// "content-security-policy: upgrade-insecure-requests; frame-ancestors 'self' https://stackexchange.com\r\n"
// "accept-ranges: bytes\r\n"
// "date: Thu, 16 Mar 2023 10:23:00 GMT\r\n"
// "via: 1.1 varnish\r\n"
// "x-served-by: cache-vie6354-VIE\r\n"
// "x-cache: MISS\r\n"
// "x-cache-hits: 0\r\n"
// "x-timer: S1678962181.533907,VS0,VE144\r\n"
// "vary: Accept-Encoding,Fastly-SSL\r\n"
// "x-dns-prefetch-control: off\r\n"
// "X-Firefox-Spdy: h2\r\n"
// "\r\n";
}
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

6
UnitTests/pch.cpp Normal file
View File

@ -0,0 +1,6 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"

135
UnitTests/pch.hpp Normal file
View File

@ -0,0 +1,135 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include <stdex/assert.hpp>
#include <stdex/base64.hpp>
#include <stdex/compat.hpp>
#include <stdex/exception.hpp>
#include <stdex/hash.hpp>
#include <stdex/hex.hpp>
#include <stdex/html.hpp>
#include <stdex/idrec.hpp>
#include <stdex/interval.hpp>
#include <stdex/langid.hpp>
#include <stdex/locale.hpp>
#include <stdex/mapping.hpp>
#include <stdex/math.hpp>
#include <stdex/memory.hpp>
#include <stdex/minisign.hpp>
#include <stdex/parser.hpp>
#include <stdex/pool.hpp>
#include <stdex/progress.hpp>
#include <stdex/ring.hpp>
#include <stdex/scoped_executor.hpp>
#include <stdex/sgml.hpp>
#include <stdex/socket.hpp>
#include <stdex/spinlock.hpp>
#include <stdex/stream.hpp>
#include <stdex/string.hpp>
#include <stdex/sys_info.hpp>
#include <stdex/system.hpp>
#include <stdex/unicode.hpp>
#include <stdex/vector_queue.hpp>
#include <stdex/watchdog.hpp>
#include <stdex/wav.hpp>
#include <stdex/zlib.hpp>
#include "compat.hpp"
#include <cstdlib>
#include <filesystem>
#include <list>
#include <thread>
namespace UnitTests
{
TEST_CLASS(hash)
{
public:
TEST_METHOD(crc32);
TEST_METHOD(md5);
TEST_METHOD(sha1);
};
TEST_CLASS(langid)
{
public:
TEST_METHOD(from_rfc1766);
};
TEST_CLASS(math)
{
public:
TEST_METHOD(mul);
TEST_METHOD(add);
};
TEST_CLASS(parser)
{
public:
TEST_METHOD(wtest);
TEST_METHOD(sgml_test);
TEST_METHOD(http_test);
};
TEST_CLASS(pool)
{
public:
TEST_METHOD(test);
};
TEST_CLASS(ring)
{
public:
TEST_METHOD(test);
};
TEST_CLASS(sgml)
{
public:
TEST_METHOD(sgml2str);
TEST_METHOD(str2sgml);
};
TEST_CLASS(stream)
{
public:
TEST_METHOD(async);
TEST_METHOD(replicator);
TEST_METHOD(open_close);
TEST_METHOD(file_stat);
};
TEST_CLASS(string)
{
public:
TEST_METHOD(strncpy);
TEST_METHOD(sprintf);
TEST_METHOD(snprintf);
};
TEST_CLASS(unicode)
{
public:
TEST_METHOD(str2wstr);
TEST_METHOD(wstr2str);
TEST_METHOD(charset_encoder);
TEST_METHOD(normalize);
};
TEST_CLASS(watchdog)
{
public:
TEST_METHOD(test);
};
TEST_CLASS(zlib)
{
public:
TEST_METHOD(test);
};
}

36
UnitTests/pool.cpp Normal file
View File

@ -0,0 +1,36 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
namespace UnitTests
{
void pool::test()
{
using worker_t = unique_ptr<int>;
using pool_t = stdex::pool<worker_t>;
pool_t pool;
list<thread> workers;
for (auto n = thread::hardware_concurrency(); n--; ) {
workers.push_back(thread([](_Inout_ pool_t& pool)
{
for (size_t n = 10000; n--; ) {
worker_t el = pool.pop();
if (!el)
el.reset(new int(1));
pool.push(std::move(el));
}
}, ref(pool)));
}
for (auto& w : workers)
w.join();
}
}

60
UnitTests/ring.cpp Normal file
View File

@ -0,0 +1,60 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
namespace UnitTests
{
constexpr size_t ring_capacity = 50;
void ring::test()
{
using ring_t = stdex::ring<int, ring_capacity>;
ring_t ring;
thread writer([](_Inout_ ring_t& ring)
{
int seed = 0;
for (size_t retries = 1000; retries--;) {
for (size_t to_write =
#ifdef _WIN32
static_cast<size_t>(static_cast<uint64_t>(::rand()) * ring_capacity / 5 / RAND_MAX);
#else
::arc4random_uniform(ring_capacity / 5);
#endif
to_write;)
{
int* ptr; size_t num_write;
tie(ptr, num_write) = ring.back();
if (to_write < num_write)
num_write = to_write;
for (size_t i = 0; i < num_write; i++)
ptr[i] = seed++;
ring.push(num_write);
to_write -= num_write;
}
}
ring.quit();
}, ref(ring));
int seed = 0;
for (;;) {
int* ptr; size_t num_read;
tie(ptr, num_read) = ring.front();
if (!ptr) _Unlikely_
break;
if (num_read > 7)
num_read = 7;
for (size_t i = 0; i < num_read; ++i)
Assert::IsTrue(ptr[i] == seed++);
ring.pop(num_read);
}
writer.join();
}
}

63
UnitTests/sgml.cpp Normal file
View File

@ -0,0 +1,63 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
namespace UnitTests
{
void sgml::sgml2str()
{
Assert::AreEqual(L"This is a test.", stdex::sgml2str("This is a test.", SIZE_MAX).c_str());
Assert::AreEqual(L"Th\u00ed\u0161 i\u22c5 a te\u0073\u0304t.&unknown;😀😅", stdex::sgml2str("Th&iacute;&scaron; i&sdot; &#97; te&smacr;t.&unknown;&#x1F600;&#X1f605;", SIZE_MAX).c_str());
Assert::AreEqual(L"This", stdex::sgml2str("This is a test.", 4).c_str());
Assert::AreEqual(L"T\u0068\u0301", stdex::sgml2str("T&hacute;is is a test.", 9).c_str());
Assert::AreEqual(L"T&hac", stdex::sgml2str("T&hacute;is is a test.", 5).c_str());
Assert::AreEqual(L"The &quot;quoted&quot; &amp; text.", stdex::sgml2str("The &quot;quoted&quot; &amp; text.", SIZE_MAX, stdex::sgml_c).c_str());
stdex::mapping_vector<size_t> map;
constexpr size_t i = 0;
constexpr size_t j = 0;
stdex::sgml2str("Th&iacute;&scaron; i&sdot; &#97; te&smacr;t.&unknown;&#x1F600;&#X1f605;", SIZE_MAX, 0, stdex::mapping<size_t>(i, j), &map);
Assert::IsTrue(stdex::mapping_vector<size_t>{
{ i + 2, j + 2 },
{ i + 10, j + 3 },
{ i + 10, j + 3 },
{ i + 18, j + 4 },
{ i + 20, j + 6 },
{ i + 26, j + 7 },
{ i + 27, j + 8 },
{ i + 32, j + 9 },
{ i + 35, j + 12 },
{ i + 42, j + 14 },
{ i + 53, j + 25 },
#ifdef _WIN32 // wchar_t* is UTF-16
{ i + 62, j + 27 },
{ i + 62, j + 27 },
{ i + 71, j + 29 },
#else // wchar_t* is UTF-32
{ i + 62, j + 26 },
{ i + 62, j + 26 },
{ i + 71, j + 27 },
#endif
} == map);
}
void sgml::str2sgml()
{
Assert::AreEqual("This is a test.", stdex::str2sgml(L"This is a test.", SIZE_MAX).c_str());
Assert::AreEqual("Th&iacute;&scaron; i&sdot; a te&smacr;t.&amp;unknown;&#x1f600;&#x1f605;", stdex::str2sgml(L"Th\u00ed\u0161 i\u22c5 a te\u0073\u0304t.&unknown;😀😅", SIZE_MAX).c_str());
Assert::AreEqual("This", stdex::str2sgml(L"This is a test.", 4, 0).c_str());
Assert::AreEqual("te&smacr;", stdex::str2sgml(L"te\u0073\u0304t", 4, 0).c_str());
Assert::AreEqual("tes", stdex::str2sgml(L"te\u0073\u0304t", 3, 0).c_str());
Assert::AreEqual("&#x2318;&permil;&#x362;", stdex::str2sgml(L"⌘‰͢", SIZE_MAX).c_str());
Assert::AreEqual("$\"<>&amp;", stdex::str2sgml(L"$\"<>&", SIZE_MAX).c_str());
Assert::AreEqual("$&quot;<>&amp;", stdex::str2sgml(L"$\"<>&", SIZE_MAX, stdex::sgml_c).c_str());
}
}

156
UnitTests/stream.cpp Normal file
View File

@ -0,0 +1,156 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
using namespace stdex;
using namespace stdex::stream;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
static stdex::sstring temp_path()
{
#ifdef _WIN32
TCHAR temp_path[MAX_PATH];
Assert::IsTrue(ExpandEnvironmentStrings(_T("%TEMP%\\"), temp_path, _countof(temp_path)) < MAX_PATH);
return temp_path;
#else
return "/tmp/";
#endif
}
namespace UnitTests
{
void stream::async()
{
constexpr uint32_t total = 1000;
memory_file source(stdex::mul(total, sizeof(uint32_t)));
{
async_writer<70> writer(source);
for (uint32_t i = 0; i < total; ++i) {
Assert::IsTrue(writer.ok());
writer << i;
}
}
Assert::IsTrue(source.seekbeg(0) == 0);
{
async_reader<50> reader(source);
uint32_t x;
for (uint32_t i = 0; i < total; ++i) {
reader >> x;
Assert::IsTrue(reader.ok());
Assert::IsTrue(x == i);
}
reader >> x;
Assert::IsFalse(reader.ok());
}
}
void stream::replicator()
{
constexpr uint32_t total = 1000;
memory_file f1(stdex::mul(total, sizeof(uint32_t)));
stdex::sstring filename2, filename3;
filename2 = filename3 = temp_path();
filename2 += _T("stdex-stream-replicator-2.tmp");
file f2(
filename2.c_str(),
mode_for_reading | mode_for_writing | mode_create | mode_binary);
filename3 += _T("stdex-stream-replicator-3.tmp");
cached_file f3(
filename3.c_str(),
mode_for_reading | mode_for_writing | mode_create | mode_binary,
128);
{
stdex::stream::replicator writer;
buffer f2_buf(f2, 0, 32);
writer.push_back(&f1);
writer.push_back(&f2_buf);
writer.push_back(&f3);
for (uint32_t i = 0; i < total; ++i) {
Assert::IsTrue(writer.ok());
writer << i;
}
}
f1.seekbeg(0);
f2.seekbeg(0);
f3.seekbeg(0);
{
buffer f2_buf(f2, 64, 0);
uint32_t x;
for (uint32_t i = 0; i < total; ++i) {
f1 >> x;
Assert::IsTrue(f1.ok());
Assert::IsTrue(x == i);
f2_buf >> x;
Assert::IsTrue(f2_buf.ok());
Assert::IsTrue(x == i);
f3 >> x;
Assert::IsTrue(f3.ok());
Assert::IsTrue(x == i);
}
f1 >> x;
Assert::IsFalse(f1.ok());
f2_buf >> x;
Assert::IsFalse(f2_buf.ok());
f3 >> x;
Assert::IsFalse(f3.ok());
}
f2.close();
std::filesystem::remove(filename2);
f3.close();
std::filesystem::remove(filename3);
}
void stream::open_close()
{
cached_file dat(stdex::invalid_handle, state_t::fail, 4096);
const stdex::sstring filepath = temp_path();
constexpr uint32_t count = 3;
stdex::sstring filename[count];
stdex::stream::fpos_t start[count];
for (uint32_t i = 0; i < count; ++i) {
filename[i] = filepath + stdex::sprintf(_T("stdex-stream-open_close%u.tmp"), NULL, i);
dat.open(filename[i].c_str(), mode_for_reading | mode_for_writing | share_none | mode_preserve_existing | mode_binary);
Assert::IsTrue(dat.ok());
start[i] = dat.tell();
Assert::AreNotEqual(fpos_max, start[i]);
for (uint32_t j = 0; j < 31 + 11 * i; ++j) {
dat << j * count + i;
Assert::IsTrue(dat.ok());
}
dat.close();
}
for (uint32_t i = 0; i < count; ++i) {
dat.open(filename[i].c_str(), mode_for_reading | mode_open_existing | share_none | mode_binary);
Assert::IsTrue(dat.ok());
for (;;) {
uint32_t x;
dat >> x;
if (!dat.ok())
break;
Assert::IsTrue(x % count == i);
}
}
dat.close();
for (uint32_t i = 0; i < count; ++i)
std::filesystem::remove(filename[i]);
}
void stream::file_stat()
{
stdex::sstring path(temp_path());
Assert::IsTrue(file::exists(path));
Assert::IsFalse(file::readonly(path));
}
}

75
UnitTests/string.cpp Normal file
View File

@ -0,0 +1,75 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
namespace UnitTests
{
void string::strncpy()
{
stdex::utf32_t tmp[0x100];
stdex::strncpy(tmp, u"This is a 🐔Test🐮.");
tmp[_countof(tmp) - 1] = 0;
Assert::IsTrue(stdex::strcmp(U"This is a 🐔Test🐮.", tmp) == 0);
}
void string::sprintf()
{
stdex::locale locale(stdex::create_locale(LC_ALL, "en_US.UTF-8"));
Assert::AreEqual(L"This is a test.", stdex::sprintf(L"This is %ls.", locale, L"a test").c_str());
Assert::IsTrue(stdex::sprintf(L"This is %ls.", locale, L"a test").size() == 15);
Assert::AreEqual("This is a test.", stdex::sprintf("This is %s.", locale, "a test").c_str());
Assert::IsTrue(stdex::sprintf("This is %s.", locale, "a test").size() == 15);
Assert::AreEqual(L"This is a 🐔Test🐮.", stdex::sprintf(L"This is %ls.", locale, L"a 🐔Test🐮").c_str());
Assert::AreEqual("This is a 🐔Test🐮.", stdex::sprintf("This is %s.", locale, "a 🐔Test🐮").c_str());
wstring wstr;
std::string str;
for (size_t i = 0; i < 200; i++) {
wstr += L"🐔Test🐮\r\n";
str += "🐔Test🐮\r\n";
}
Assert::AreEqual(wstr.c_str(), stdex::sprintf(L"%ls", locale, wstr.data()).c_str());
Assert::IsTrue(stdex::sprintf(L"%ls", locale, wstr.data()).size() == wstr.size());
Assert::AreEqual(str.c_str(), stdex::sprintf("%s", locale, str.data()).c_str());
Assert::IsTrue(stdex::sprintf("%s", locale, str.data()).size() == str.size());
}
void string::snprintf()
{
stdex::locale locale(stdex::create_locale(LC_ALL, "en_US.UTF-8"));
{
wchar_t buf[0x100];
Assert::IsTrue(stdex::snprintf(buf, _countof(buf), L"This is %ls.", locale, L"a test") == 15);
Assert::AreEqual(L"This is a test.", buf);
}
{
char buf[0x100];
Assert::IsTrue(stdex::snprintf(buf, _countof(buf), "This is %s.", locale, "a test") == 15);
Assert::AreEqual("This is a test.", buf);
}
{
wchar_t buf[8];
size_t n = stdex::snprintf(buf, _countof(buf), L"This is %ls.", locale, L"a test");
Assert::IsTrue(n <= _countof(buf));
Assert::IsTrue(stdex::strncmp(L"This is a test.", buf, n) == 0);
}
{
char buf[8];
size_t n = stdex::snprintf(buf, _countof(buf), "This is %s.", locale, "a test");
Assert::IsTrue(n <= _countof(buf));
Assert::IsTrue(stdex::strncmp("This is a test.", buf, n) == 0);
}
}
}

111
UnitTests/unicode.cpp Normal file
View File

@ -0,0 +1,111 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
namespace UnitTests
{
void unicode::str2wstr()
{
Assert::AreEqual(
L"This is a test.",
stdex::str2wstr("This is a test.", stdex::charset_id::utf8).c_str());
Assert::AreEqual(
L"Th\u00ed\u0161 i\u22c5 a te\u0073\u0304t. 😀😅",
stdex::str2wstr("Thíš i⋅ a tes̄t. 😀😅", stdex::charset_id::utf8).c_str());
std::string src;
std::wstring dst;
for (size_t i = 0; i < 2000; i++) {
src += "🐔Test🐮\r\n";
dst += L"🐔Test🐮\r\n";
}
Assert::AreEqual(dst.c_str(), stdex::str2wstr(src, stdex::charset_id::utf8).c_str());
Assert::AreEqual(
L"",
stdex::str2wstr("test", 0, stdex::charset_id::utf8).c_str());
Assert::AreEqual(
L"",
stdex::str2wstr(nullptr, 0, stdex::charset_id::utf8).c_str());
}
void unicode::wstr2str()
{
Assert::AreEqual(
"This is a test.",
stdex::wstr2str(L"This is a test.", stdex::charset_id::utf8).c_str());
Assert::AreEqual(
"Th\xc3\xad\xc5\xa1 i\xe2\x8b\x85 a tes\xcc\x84t. \xf0\x9f\x98\x80\xf0\x9f\x98\x85",
stdex::wstr2str(L"Thíš i⋅ a tes̄t. 😀😅", stdex::charset_id::utf8).c_str());
std::wstring src;
std::string dst;
for (size_t i = 0; i < 2000; i++) {
src += L"🐔Test🐮\r\n";
dst += "🐔Test🐮\r\n";
}
Assert::AreEqual(dst.c_str(), stdex::wstr2str(src, stdex::charset_id::utf8).c_str());
Assert::AreEqual(
"",
stdex::wstr2str(L"test", 0, stdex::charset_id::utf8).c_str());
Assert::AreEqual(
"",
stdex::wstr2str(nullptr, 0, stdex::charset_id::utf8).c_str());
}
void unicode::charset_encoder()
{
{
stdex::charset_encoder<char, char> win1250_to_utf8(stdex::charset_id::windows1250, stdex::charset_id::utf8);
Assert::AreEqual(
"This is a test.",
win1250_to_utf8.convert("This is a test.").c_str());
Assert::AreEqual(
"Thíš i· a teşt.",
win1250_to_utf8.convert("Th\xed\x9a i\xb7 a te\xbat.").c_str());
std::string src, dst;
for (size_t i = 0; i < 1000; i++) {
src += "V ko\x9eu\x9a\xe8ku zlobnega mizarja stopiclja fant in kli\xe8" "e 0123456789.\r\n";
dst += "V kožuščku zlobnega mizarja stopiclja fant in kliče 0123456789.\r\n";
}
Assert::AreEqual(dst.c_str(), win1250_to_utf8.convert(src).c_str());
Assert::AreEqual(
"",
win1250_to_utf8.convert("test", 0).c_str());
Assert::AreEqual(
"",
win1250_to_utf8.convert(nullptr, 0).c_str());
}
{
stdex::charset_encoder<char16_t, char> encode(stdex::charset_id::utf16, stdex::charset_id::ascii, '?');
Assert::AreEqual("Te?t.", encode.convert(u"Tešt.").c_str());
}
}
void unicode::normalize()
{
#ifdef _WIN32
Assert::AreEqual(
L"tést",
stdex::normalize(L"tést").c_str());
Assert::AreEqual(
L"",
stdex::normalize(nullptr, 0).c_str());
#endif
}
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

28
UnitTests/watchdog.cpp Normal file
View File

@ -0,0 +1,28 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
namespace UnitTests
{
void watchdog::test()
{
volatile bool wd_called = false;
stdex::watchdog<std::chrono::steady_clock> wd(
std::chrono::milliseconds(100), [&] { wd_called = true; });
for (int i = 0; i < 100; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
Assert::IsFalse(wd_called);
wd.reset();
}
std::this_thread::sleep_for(std::chrono::milliseconds(300));
Assert::IsTrue(wd_called);
}
}

1
UnitTests/zlib Submodule

@ -0,0 +1 @@
Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf

36
UnitTests/zlib.cpp Normal file
View File

@ -0,0 +1,36 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2024-2025 Amebis
*/
#include "pch.hpp"
using namespace std;
#ifdef _WIN32
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#endif
namespace UnitTests
{
void zlib::test()
{
static const char inflated[] = "This is a test.";
stdex::stream::memory_file dat_deflated;
{
stdex::zlib_writer zlib(dat_deflated, 9, 4);
zlib.write(inflated, sizeof(inflated) - sizeof(*inflated));
}
static const uint8_t deflated[] = { 0x78, 0xda, 0x0b, 0xc9, 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85, 0x92, 0xd4, 0xe2, 0x12, 0x3d, 0x00, 0x29, 0x97, 0x05, 0x24 };
Assert::IsTrue(dat_deflated.size() == sizeof(deflated));
Assert::IsTrue(memcmp(deflated, dat_deflated.data(), sizeof(deflated)) == 0);
dat_deflated.seekbeg(0);
stdex::stream::memory_file dat_inflated;
{
stdex::zlib_reader zlib(dat_deflated, 3);
dat_inflated.write_stream(zlib);
}
Assert::IsTrue(dat_inflated.size() == sizeof(inflated) - sizeof(*inflated));
Assert::IsTrue(memcmp(inflated, dat_inflated.data(), sizeof(inflated) - sizeof(*inflated)) == 0);
}
}

19
appveyor.yml Normal file
View File

@ -0,0 +1,19 @@
version: master.{build}
image:
- Visual Studio 2022
- Visual Studio 2019
configuration:
- Debug
- Release
platform:
- x64
- x86
install:
- cd %APPVEYOR_BUILD_FOLDER%
- git submodule update --init --recursive
build:
project: UnitTests\UnitTests.sln
parallel: true
verbosity: minimal
test_script:
- cmd: vstest.console /logger:Appveyor UnitTests\.tmp\UnitTests\%platform%\%configuration%\UnitTests.dll

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros">
<stdexVersion>10</stdexVersion>
</PropertyGroup>
<PropertyGroup>
<OutDir>..\..\..\output\$(Platform).$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>STDEX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<BuildMacro Include="stdexVersion">
<Value>$(stdexVersion)</Value>
</BuildMacro>
</ItemGroup>
</Project>

View File

@ -1,107 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\stdex\common.h" />
<ClInclude Include="..\include\stdex\idrec.h" />
<ClInclude Include="..\src\stdafx.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\res\stdex.rc" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{518777CC-0A59-4415-A12A-82751ED75343}</ProjectGuid>
<RootNamespace>stdex</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\..\include\Win32.props" />
<Import Project="..\..\..\include\Debug.props" />
<Import Project="stdex.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\..\include\x64.props" />
<Import Project="..\..\..\include\Debug.props" />
<Import Project="stdex.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\..\include\Win32.props" />
<Import Project="..\..\..\include\Release.props" />
<Import Project="stdex.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\..\include\x64.props" />
<Import Project="..\..\..\include\Release.props" />
<Import Project="stdex.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>$(ProjectName)$(stdexVersion)ud_vc$(PlatformToolsetVersion)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetName>$(ProjectName)$(stdexVersion)ud_vc$(PlatformToolsetVersion)_$(Platform)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetName>$(ProjectName)$(stdexVersion)u_vc$(PlatformToolsetVersion)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetName>$(ProjectName)$(stdexVersion)u_vc$(PlatformToolsetVersion)_$(Platform)</TargetName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\src\stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\stdex\common.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\include\stdex\idrec.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\res\stdex.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

67
include/stdex/assert.hpp Normal file
View File

@ -0,0 +1,67 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#ifdef _WIN32
#include "windows.h"
#endif
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef NDEBUG
#define stdex_assert(e) { _Analysis_assume_(e); ((void)0); }
#define stdex_verify(e) ((void)(e))
#else
#if defined(_WIN32)
#define stdex_assert(e) (!!(e) ? (void)0 : stdex::do_assert(_L(__FILE__), (unsigned)(__LINE__), _L(#e)))
#elif defined(__APPLE__)
#define stdex_assert(e) (!!(e) ? (void)0 : stdex::do_assert(__func__, __ASSERT_FILE_NAME, __LINE__, #e))
#else
#error Implement!
#endif
#define stdex_verify(e) stdex_assert(e)
namespace stdex
{
/// \cond internal
#if defined(_WIN32)
inline void do_assert(const wchar_t* file, unsigned line, const wchar_t* expression)
{
// Non-interactive processes (NT services, ISAPI and ActiveX DLLs running in IIS etc.)
// MUST NOT raise asserts. It'd block the process, and process host (SCM, IIS) would
// continue to see the process as alive but non-responding, preventing recovery.
// RaiseException instead to have the process terminated and possibly trigger Windows
// Error Reporting or AHroščar.
// For interactive processes, it is more convenient to alert the user looking at the
// desktop right now. Maybe it is the developer and debugging the very process is
// possible?
HWINSTA hWinSta = GetProcessWindowStation();
if (hWinSta) {
WCHAR sName[MAX_PATH];
if (GetUserObjectInformationW(hWinSta, UOI_NAME, sName, sizeof(sName), NULL)) {
sName[_countof(sName) - 1] = 0;
// Only "WinSta0" is interactive (Source: KB171890)
if (_wcsicmp(sName, L"WinSta0") == 0) {
_wassert(expression, file, line);
return;
}
}
}
RaiseException(STATUS_ASSERTION_FAILURE, EXCEPTION_NONCONTINUABLE, 0, NULL);
}
#elif defined(__APPLE__)
inline void do_assert(const char* function, const char* file, int line, const char* expression)
{
__assert_rtn(function, file, line, expression);
}
#else
#error Implement!
#endif
/// \endcond
}
#endif

463
include/stdex/base64.hpp Normal file
View File

@ -0,0 +1,463 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include "stream.hpp"
#include <cstdint>
#include <string>
#include <vector>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#endif
namespace stdex
{
/// \cond internal
inline const char base64_enc_lookup[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};
inline const uint8_t base64_dec_lookup[256] = {
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
/* 0 */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* 1 */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* 2 */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
/* 3 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 64, 255, 255,
/* 4 */ 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
/* 5 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
/* 6 */ 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
/* 7 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255,
/* 8 */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* 9 */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* A */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* B */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* C */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* D */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* E */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
/* F */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
};
/// \endcond
///
/// Base64 encoding session
///
class base64_enc
{
public:
///
/// Constructs blank encoding session
///
base64_enc() noexcept : m_num(0)
{
m_buf[0] = 0;
m_buf[1] = 0;
m_buf[2] = 0;
}
///
/// Encodes one block of information, and _appends_ it to the output
///
/// \param[in,out] out Output
/// \param[in] data Data to encode
/// \param[in] size Length of `data` in bytes
/// \param[in] is_last Is this the last block of data?
///
template<class T, class TR, class AX>
void encode(_Inout_ std::basic_string<T, TR, AX> &out, _In_bytecount_(size) const void *data, _In_ size_t size, _In_opt_ bool is_last = true)
{
stdex_assert(data || !size);
// Preallocate output
out.reserve(out.size() + enc_size(size));
// Convert data character by character.
for (size_t i = 0;; i++) {
if (m_num >= 3) {
encode(out);
m_num = 0;
}
if (i >= size)
break;
m_buf[m_num++] = reinterpret_cast<const uint8_t*>(data)[i];
}
// If this is the last block, flush the buffer.
if (is_last && m_num) {
encode(out, m_num);
m_num = 0;
}
}
///
/// Resets encoding session
///
void clear() noexcept
{
m_num = 0;
}
///
/// Returns maximum encoded size
///
/// \param[in] size Number of bytes to encode
///
/// \returns Maximum number of bytes for the encoded data of `size` length
///
size_t enc_size(_In_ size_t size) const noexcept
{
return ((m_num + size + 2)/3)*4;
}
protected:
///
/// Encodes one complete internal buffer of data
///
template<class T, class TR, class AX>
void encode(_Inout_ std::basic_string<T, TR, AX> &out)
{
out += base64_enc_lookup[ m_buf[0] >> 2 ];
out += base64_enc_lookup[((m_buf[0] << 4) | (m_buf[1] >> 4)) & 0x3f];
out += base64_enc_lookup[((m_buf[1] << 2) | (m_buf[2] >> 6)) & 0x3f];
out += base64_enc_lookup[ m_buf[2] & 0x3f];
}
///
/// Encodes partial internal buffer of data
///
template<class T, class TR, class AX>
void encode(_Inout_ std::basic_string<T, TR, AX> &out, _In_ size_t size)
{
if (size > 0) {
out += base64_enc_lookup[m_buf[0] >> 2];
if (size > 1) {
out += base64_enc_lookup[((m_buf[0] << 4) | (m_buf[1] >> 4)) & 0x3f];
if (size > 2) {
out += base64_enc_lookup[((m_buf[1] << 2) | (m_buf[2] >> 6)) & 0x3f];
out += base64_enc_lookup[m_buf[2] & 0x3f];
} else {
out += base64_enc_lookup[(m_buf[1] << 2) & 0x3f];
out += '=';
}
} else {
out += base64_enc_lookup[(m_buf[0] << 4) & 0x3f];
out += '=';
out += '=';
}
} else {
out += '=';
out += '=';
out += '=';
out += '=';
}
}
protected:
uint8_t m_buf[3]; ///< Internal buffer
size_t m_num; ///< Number of bytes used in `m_buf`
};
///
/// Converts to Base64 when writing to a stream
///
class base64_writer : public stdex::stream::converter, protected base64_enc
{
public:
base64_writer(_Inout_ stdex::stream::basic& source, _In_ size_t max_blocks = 19) :
stdex::stream::converter(source),
m_max_blocks(max_blocks),
m_num_blocks(0)
{}
virtual ~base64_writer()
{
// Flush the buffer.
if (m_num) {
if (++m_num_blocks > m_max_blocks) {
*m_source << '\n';
m_num_blocks = 1;
}
encode(m_num);
}
}
virtual _Success_(return != 0) size_t write(
_In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
{
stdex_assert(data || !length);
for (size_t i = 0;; i++) {
if (m_num >= 3) {
if (++m_num_blocks > m_max_blocks) {
*m_source << '\n';
m_num_blocks = 1;
}
encode();
if (!m_source->ok()) _Unlikely_ {
m_state = m_source->state();
return length - i;
}
m_num = 0;
}
if (i >= length) {
m_state = stdex::stream::state_t::ok;
return length;
}
m_buf[m_num++] = reinterpret_cast<const uint8_t*>(data)[i];
}
}
protected:
///
/// Encodes one complete internal buffer of data
///
void encode()
{
char out[4];
out[0] = base64_enc_lookup[ m_buf[0] >> 2 ];
out[1] = base64_enc_lookup[((m_buf[0] << 4) | (m_buf[1] >> 4)) & 0x3f];
out[2] = base64_enc_lookup[((m_buf[1] << 2) | (m_buf[2] >> 6)) & 0x3f];
out[3] = base64_enc_lookup[ m_buf[2] & 0x3f];
m_source->write_array(out, sizeof(*out), _countof(out));
}
///
/// Encodes partial internal buffer of data
///
void encode(_In_ size_t size)
{
char out[4];
if (size > 0) {
out[0] = base64_enc_lookup[m_buf[0] >> 2];
if (size > 1) {
out[1] = base64_enc_lookup[((m_buf[0] << 4) | (m_buf[1] >> 4)) & 0x3f];
if (size > 2) {
out[2] = base64_enc_lookup[((m_buf[1] << 2) | (m_buf[2] >> 6)) & 0x3f];
out[3] = base64_enc_lookup[m_buf[2] & 0x3f];
} else {
out[2] = base64_enc_lookup[(m_buf[1] << 2) & 0x3f];
out[3] = '=';
}
} else {
out[1] = base64_enc_lookup[(m_buf[0] << 4) & 0x3f];
out[2] = '=';
out[3] = '=';
}
} else {
out[0] = '=';
out[1] = '=';
out[2] = '=';
out[3] = '=';
}
m_source->write_array(out, sizeof(*out), _countof(out));
}
protected:
size_t
m_max_blocks, ///> Maximum number of Base64 blocks (4 chars) to write without a line break (SIZE_MAX no line breaks)
m_num_blocks; ///> Number of Base64 blocks (4 chars), written after last line break
};
///
/// Base64 decoding session
///
class base64_dec
{
public:
///
/// Constructs blank decoding session
///
base64_dec() noexcept : m_num(0)
{
m_buf[0] = 0;
m_buf[1] = 0;
m_buf[2] = 0;
m_buf[3] = 0;
}
///
/// Decodes one block of information, and _appends_ it to the output
///
/// \param[in,out] out Output
/// \param[in] is_last Was this the last block of data?
/// \param[in] data Data to decode
/// \param[in] size Length of `data` in bytes
///
template<class T_to, class AX, class T_from>
void decode(_Inout_ std::vector<T_to, AX> &out, _Out_ bool &is_last, _In_z_count_(size) const T_from *data, _In_ size_t size)
{
is_last = false;
// Trim data size to first terminator.
for (size_t k = 0; k < size; k++)
if (!data[k]) { size = k; break; }
// Preallocate output
out.reserve(out.size() + dec_size(size));
for (size_t i = 0;; i++) {
if (m_num >= 4) {
// Buffer full; decode it.
size_t nibbles = decode(out);
if (nibbles < 3) {
is_last = true;
break;
}
}
if (i >= size)
break;
size_t x = static_cast<size_t>(data[i]);
stdex_assert(m_num < _countof(m_buf));
if ((m_buf[m_num] = x < _countof(base64_dec_lookup) ? base64_dec_lookup[x] : 255) != 255)
m_num++;
}
}
///
/// Resets decoding session
///
void clear() noexcept
{
m_num = 0;
}
///
/// Returns maximum decoded size
///
/// \param[in] size Number of bytes to decode
///
/// \returns Maximum number of bytes for the decoded data of `size` length
///
size_t dec_size(_In_ size_t size) const noexcept
{
return ((m_num + size + 3)/4)*3;
}
protected:
///
/// Decodes one complete internal buffer of data
///
template<class T, class AX>
size_t decode(_Inout_ std::vector<T, AX> &out)
{
m_num = 0;
out.push_back((T)(((m_buf[0] << 2) | (m_buf[1] >> 4)) & 0xff));
if (m_buf[2] < 64) {
out.push_back((T)(((m_buf[1] << 4) | (m_buf[2] >> 2)) & 0xff));
if (m_buf[3] < 64) {
out.push_back((T)(((m_buf[2] << 6) | m_buf[3]) & 0xff));
return 3;
} else
return 2;
} else
return 1;
}
protected:
uint8_t m_buf[4]; ///< Internal buffer
size_t m_num; ///< Number of bytes used in `m_buf`
};
///
/// Converts from Base64 when reading from a stream
///
class base64_reader : public stdex::stream::converter, protected base64_dec
{
public:
base64_reader(_Inout_ stdex::stream::basic& source) :
stdex::stream::converter(source),
m_temp_off(0),
m_temp_len(0)
{
m_temp[0] = 0;
m_temp[1] = 0;
m_temp[2] = 0;
}
#pragma warning(suppress: 6101) // See [1] below
virtual _Success_(return != 0 || length == 0) size_t read(
_Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
{
stdex_assert(data || !length);
for (size_t to_read = length;;) {
if (m_temp_len >= to_read) {
memcpy(data, m_temp + m_temp_off, to_read);
m_temp_off += to_read;
m_temp_len -= to_read;
m_state = stdex::stream::state_t::ok;
return length;
}
if (m_temp_len) {
memcpy(data, m_temp + m_temp_off, m_temp_len);
reinterpret_cast<uint8_t*&>(data) += m_temp_len;
to_read -= m_temp_len;
m_temp_off = 0;
m_temp_len = 0;
}
// Read one Base64 block (4 chars)
while (m_num < 4) {
uint8_t x;
*m_source >> x;
if (!m_source->ok()) _Unlikely_ {
m_state = m_source->state();
return length - to_read; // [1] Code analysis misses `length - to_read` bytes were written to data in previous loop iterations.
}
if ((m_buf[m_num] = base64_dec_lookup[x]) != 255)
m_num++;
}
decode();
if (m_temp_len < 3 && to_read >= 3) {
// If Base64 indicates end of data, truncate read to hint the client, end of Base64 data has been reached.
memcpy(data, m_temp + m_temp_off, m_temp_len);
m_temp_off = 0;
m_temp_len = 0;
to_read -= m_temp_len;
m_state = stdex::stream::state_t::ok;
return length - to_read; // [1] Code analysis misses `length - to_read` bytes were written to data in previous loop iterations.
}
}
}
protected:
///
/// Decodes one complete internal buffer of data
///
void decode()
{
m_num = 0;
m_temp_off = 0;
m_temp[0] = static_cast<char>(((m_buf[0] << 2) | (m_buf[1] >> 4)) & 0xff);
if (m_buf[2] < 64) {
m_temp[1] = static_cast<char>(((m_buf[1] << 4) | (m_buf[2] >> 2)) & 0xff);
if (m_buf[3] < 64) {
m_temp[2] = static_cast<char>(((m_buf[2] << 6) | m_buf[3]) & 0xff);
m_temp_len = 3;
} else
m_temp_len = 2;
} else
m_temp_len = 1;
}
protected:
char m_temp[3]; ///< Temporary buffer
size_t
m_temp_off, ///< Index of data start in `m_temp`
m_temp_len; ///< Number of bytes of data in `m_temp`
};
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

488
include/stdex/chrono.hpp Normal file
View File

@ -0,0 +1,488 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "math.hpp"
#include "system.hpp"
#include "locale.hpp"
#include <stdint.h>
#include <chrono>
#include <ctime>
#include <memory>
#include <stdexcept>
namespace stdex {
namespace chrono
{
///
/// AOsn date
///
struct aosn_date
{
using rep = int32_t;
using period = std::ratio<86400>; // 1 day
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<aosn_date>;
static constexpr bool is_steady = false;
///
/// Gets current date
///
static time_point now() noexcept
{
#ifdef _WIN32
FILETIME t;
GetSystemTimeAsFileTime(&t);
return from_system(t);
#else
time_t t;
time(&t);
return from_time_t(t);
#endif
}
///
/// Returns time_t from time point
///
static std::time_t to_time_t(_In_ const time_point tp) noexcept
{
return static_cast<std::time_t>(tp.time_since_epoch().count()) * 86400 - 210866803200;
}
///
/// Returns time point from time_t
///
static time_point from_time_t(_In_ std::time_t t) noexcept
{
return time_point(duration(static_cast<rep>((t + 210866803200) / 86400)));
}
#ifdef _WIN32
///
/// Returns time point from SYSTEMTIME
///
static time_point from_system(_In_ const SYSTEMTIME& t) noexcept
{
return from_dmy(static_cast<uint8_t>(t.wDay), static_cast<uint8_t>(t.wMonth), static_cast<int32_t>(t.wYear));
}
///
/// Returns time point from FILETIME
///
static time_point from_system(_In_ const FILETIME& t) noexcept
{
uint64_t x = ((static_cast<uint64_t>(t.dwHighDateTime)) << 32) | t.dwLowDateTime;
return time_point(duration(static_cast<rep>(x / 864000000000 + 2305814))); // Convert from 100 ns to 1-day interval and adjust epoch
}
///
/// Returns time point from DATE
///
static time_point from_system(_In_ DATE t)
{
SYSTEMTIME st;
if (!VariantTimeToSystemTime(t, &st))
throw std::invalid_argument("failed to convert date from VARIANT_DATE");
return from_system(st);
}
#else
///
/// Returns time point from struct timespec
///
static time_point from_system(_In_ const struct timespec& t) noexcept
{
return from_time_t(t.tv_sec);
}
#endif
#ifdef _WIN32
///
/// Converts time point to SYSTEMTIME
///
static void to_system(_In_ time_point tp, _Out_ SYSTEMTIME& t)
{
uint8_t day, month;
int32_t year;
to_dmy(tp, &day, &month, &year);
t.wDay = day;
t.wMonth = month;
if (year > WORD_MAX) _Unlikely_
throw std::range_error("year too big");
t.wYear = static_cast<WORD>(year);
t.wDayOfWeek = static_cast<int>(day_of_week(tp) + 1) % 7;
t.wHour = 0;
t.wMinute = 0;
t.wSecond = 0;
t.wMilliseconds = 0;
}
///
/// Returns time point from FILETIME
///
static void to_system(_In_ time_point tp, _Out_ FILETIME& t) noexcept
{
uint64_t x = (tp.time_since_epoch().count() - 2305814) * 864000000000; // Adjust epoch and convert 1-day interval to 100 ns
t.dwHighDateTime = static_cast<DWORD>(x >> 32);
t.dwLowDateTime = static_cast<DWORD>(x & 0xffffffff);
}
///
/// Returns time point from DATE
///
static void to_system(_In_ time_point tp, _Out_ DATE& t)
{
SYSTEMTIME st;
to_system(tp, st);
if (!SystemTimeToVariantTime(&st, &t))
throw std::invalid_argument("failed to convert date to VARIANT_DATE");
}
#endif
///
/// Returns time point from calendar day, month and year
///
static time_point from_dmy(_In_ uint8_t day, _In_ uint8_t month, _In_ int32_t year) noexcept
{
int32_t mtmp, ytmp;
if (month > 2) {
mtmp = month - 3;
ytmp = year;
}
else {
mtmp = month + 9;
ytmp = year - 1;
}
int32_t ctmp = (ytmp / 100);
int32_t dtmp = ytmp - (100 * ctmp);
int32_t result1 = 146097L * ctmp / 4;
int32_t result2 = (1461 * dtmp) / 4;
int32_t result3 = (153 * mtmp + 2) / 5;
return time_point(duration(static_cast<int32_t>(result1) + day + result2 + 1721119L + result3));
}
///
/// Returns calendar day, month and year from time point
///
static void to_dmy(_In_ const time_point tp, _Out_opt_ uint8_t* day, _Out_opt_ uint8_t* month, _Out_opt_ int32_t* year) noexcept
{
int32_t mtmp = tp.time_since_epoch().count() - 1721119L;
int32_t yr = (4 * mtmp - 1) / 146097L;
mtmp = 4 * mtmp - 1 - 146097L * yr;
int32_t da = mtmp / 4;
mtmp = (4 * da + 3) / 1461;
da = 4 * da + 3 - 1461 * mtmp;
da = (da + 4) / 4;
int32_t mo = (5 * da - 3) / 153;
da = 5 * da - 3 - 153 * mo;
da = (da + 5) / 5;
yr = 100 * yr + mtmp;
if (mo < 10)
mo += 3;
else {
mo -= 9;
yr++;
}
if (day) *day = static_cast<uint8_t>(da);
if (month) *month = static_cast<uint8_t>(mo);
if (year) *year = yr;
}
///
/// Returns day-of-week from time point (0 = Mon, 1 = Tue...)
///
static uint8_t day_of_week(_In_ const time_point tp)
{
return static_cast<uint8_t>(tp.time_since_epoch().count() % 7);
}
///
/// Returns day-of-week from calendar day, month and year (0 = Mon, 1 = Tue...)
///
static uint8_t day_of_week(_In_ uint8_t day, _In_ uint8_t month, _In_ int32_t year)
{
return static_cast<uint8_t>(from_dmy(day, month, year).time_since_epoch().count() % 7);
}
};
///
/// AOsn timestamp
///
struct aosn_timestamp
{
using rep = int64_t;
using period = std::milli;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<aosn_timestamp>;
static constexpr bool is_steady = false;
static constexpr rep f_second = 1000; // number of milliseconds per second
static constexpr rep f_minute = 60; // number of seconds per minute
static constexpr rep f_hour = 60; // number of minutes per hour
static constexpr rep f_day = 24; // number of hours per day
static constexpr rep f_week = 7; // number of days per week
static constexpr rep one_second = f_second; // number of milliseconds per second
static constexpr rep one_minute = f_minute * one_second; // number of milliseconds per minute
static constexpr rep one_hour = f_hour * one_minute; // number of milliseconds per hour
static constexpr rep one_day = f_day * one_hour; // number of milliseconds per day
static constexpr rep one_week = f_week * one_day; // number of milliseconds per week
///
/// Gets current timestamp
///
static time_point now() noexcept
{
#ifdef _WIN32
FILETIME t;
GetSystemTimeAsFileTime(&t);
return from_system(t);
#else
time_t t;
time(&t);
return from_time_t(t);
#endif
}
///
/// Returns time_t from time point
///
static std::time_t to_time_t(_In_ const time_point tp) noexcept
{
return tp.time_since_epoch().count() / one_second - 210866803200;
}
///
/// Returns time point from time_t
///
static time_point from_time_t(_In_ std::time_t t) noexcept
{
return time_point(duration((static_cast<rep>(t) + 210866803200) * one_second));
}
#ifdef _WIN32
///
/// Returns time point from SYSTEMTIME
///
static time_point from_system(_In_ const SYSTEMTIME& t) noexcept
{
return from_dmy(
static_cast<uint8_t>(t.wDay), static_cast<uint8_t>(t.wMonth), static_cast<int32_t>(t.wYear),
static_cast<uint8_t>(t.wHour), static_cast<uint8_t>(t.wMinute), static_cast<uint8_t>(t.wSecond), static_cast<uint16_t>(t.wMilliseconds));
}
///
/// Returns time point from FILETIME
///
static time_point from_system(_In_ const FILETIME& t) noexcept
{
uint64_t x = ((static_cast<uint64_t>(t.dwHighDateTime)) << 32) | t.dwLowDateTime;
return time_point(duration(static_cast<rep>(x / 10000 + 199222329600000))); // Convert from 100 ns to 1 ms interval and adjust epoch
}
///
/// Returns time point from DATE
///
static time_point from_system(_In_ DATE t)
{
SYSTEMTIME st;
if (!VariantTimeToSystemTime(t, &st))
throw std::invalid_argument("failed to convert date from VARIANT_DATE");
return from_system(st);
}
#else
///
/// Returns time point from struct timespec
///
static time_point from_system(_In_ const struct timespec& t) noexcept
{
return time_point(duration(static_cast<rep>(from_time_t(t.tv_sec).time_since_epoch().count() + t.tv_nsec / 1000)));
}
#endif
static void to_system(_In_ time_point tp, _Out_ struct tm& date) noexcept
{
uint8_t day, month, hour, minute, second;
uint16_t millisecond;
int32_t year;
to_dmy(tp, &day, &month, &year, &hour, &minute, &second, &millisecond);
date.tm_sec = second;
date.tm_min = minute;
date.tm_hour = hour;
date.tm_mday = day;
date.tm_mon = month - 1;
date.tm_year = year - 1900;
date.tm_wday = (static_cast<int>(aosn_date::day_of_week(to_date(tp))) + 1) % 7;
date.tm_yday = 0;
date.tm_isdst = 0;
}
#ifdef _WIN32
///
/// Converts time point to SYSTEMTIME
///
static void to_system(_In_ time_point tp, _Out_ SYSTEMTIME& t)
{
uint8_t day, month, hour, minute, second;
uint16_t millisecond;
int32_t year;
to_dmy(tp,
&day, &month, &year,
&hour, &minute, &second, &millisecond);
t.wDay = day;
t.wMonth = month;
if (year > WORD_MAX) _Unlikely_
throw std::range_error("year too big");
t.wYear = static_cast<WORD>(year);
t.wDayOfWeek = (static_cast<int>(aosn_date::day_of_week(to_date(tp))) + 1) % 7;
t.wHour = hour;
t.wMinute = minute;
t.wSecond = second;
t.wMilliseconds = millisecond;
}
///
/// Returns time point from FILETIME
///
static void to_system(_In_ time_point tp, _Out_ FILETIME& t) noexcept
{
uint64_t x = (tp.time_since_epoch().count() - 199222329600000) * 10000; // Adjust epoch and convert 1 ms interval to 100 ns
t.dwHighDateTime = static_cast<DWORD>(x >> 32);
t.dwLowDateTime = static_cast<DWORD>(x & 0xffffffff);
}
///
/// Returns time point from DATE
///
static void to_system(_In_ time_point tp, _Out_ DATE& t)
{
SYSTEMTIME st;
to_system(tp, st);
if (!SystemTimeToVariantTime(&st, &t))
throw std::invalid_argument("failed to convert date to VARIANT_DATE");
}
#endif
///
/// Returns aosn_date::time_point from time point
///
static aosn_date::time_point to_date(_In_ time_point tp) noexcept
{
return aosn_date::time_point(aosn_date::duration(static_cast<aosn_date::rep>(tp.time_since_epoch().count() / one_day)));
}
///
/// Returns time point from aosn_date::time_point
///
static time_point from_date(_In_ aosn_date::time_point date) noexcept
{
return time_point(duration(static_cast<rep>(date.time_since_epoch().count()) * one_day));
}
///
/// Returns time point from calendar day, month, year and time
///
static time_point from_dmy(
_In_ uint8_t day, _In_ uint8_t month, _In_ int32_t year,
_In_ uint8_t hour, _In_ uint8_t minute, _In_ uint8_t second, _In_ uint16_t millisecond) noexcept
{
return time_point(duration(
(static_cast<rep>(aosn_date::from_dmy(day, month, year).time_since_epoch().count()) * one_day) +
(static_cast<rep>(hour) * one_hour + static_cast<rep>(minute) * one_minute + static_cast<rep>(second) * one_second + millisecond)));
}
///
/// Returns calendar day, month, year and time from time point
///
static void to_dmy(_In_ const time_point tp,
_Out_opt_ uint8_t* day, _Out_opt_ uint8_t* month, _Out_opt_ int32_t* year,
_Out_opt_ uint8_t* hour, _Out_opt_ uint8_t* minute, _Out_opt_ uint8_t* second, _Out_opt_ uint16_t* millisecond) noexcept
{
aosn_date::to_dmy(to_date(tp), day, month, year);
int32_t u = static_cast<int32_t>(tp.time_since_epoch().count() % one_day);
if (millisecond) *millisecond = static_cast<uint16_t>(u % f_second);
u = u / f_second;
if (second) *second = static_cast<uint8_t>(u % f_minute);
u = u / f_minute;
if (minute) *minute = static_cast<uint8_t>(u % f_hour);
u = u / f_hour;
if (hour) *hour = static_cast<uint8_t>(u);
}
template<class TR = std::char_traits<char>, class AX = std::allocator<char>>
static std::basic_string<char, TR, AX> to_str(_In_ const time_point tp, _In_z_ const char* format, _In_opt_ locale_t locale)
{
struct tm date;
to_system(tp, date);
std::basic_string<char, TR, AX> str;
char stack_buffer[1024 / sizeof(char)];
size_t n;
#if _WIN32
n = _strftime_l(stack_buffer, _countof(stack_buffer), format, &date, locale);
#else
n = strftime_l(stack_buffer, _countof(stack_buffer), format, &date, locale);
#endif
if (n) {
str.assign(stack_buffer, stack_buffer + n);
return str;
}
size_t num_chars = stdex::mul(_countof(stack_buffer), 2);
for (;;) {
std::unique_ptr<char> buf(new char[num_chars]);
#if _WIN32
n = _strftime_l(buf.get(), num_chars, format, &date, locale);
#else
n = strftime_l(buf.get(), num_chars, format, &date, locale);
#endif
if (n) {
str.assign(buf.get(), buf.get() + n);
return str;
}
num_chars = stdex::mul(num_chars, 2);
}
}
template<class TR = std::char_traits<wchar_t>, class AX = std::allocator<wchar_t>>
static std::basic_string<wchar_t, TR, AX> to_str(_In_ const time_point tp, _In_z_ const wchar_t* format, _In_opt_ locale_t locale)
{
struct tm date;
to_system(tp, date);
std::basic_string<wchar_t, TR, AX> str;
wchar_t stack_buffer[1024 / sizeof(wchar_t)];
size_t n;
#if _WIN32
n = _wcsftime_l(stack_buffer, _countof(stack_buffer), format, &date, locale);
#else
n = wcsftime_l(stack_buffer, _countof(stack_buffer), format, &date, locale);
#endif
if (n) {
str.assign(stack_buffer, stack_buffer + n);
return str;
}
size_t num_chars = stdex::mul(_countof(stack_buffer), 2);
for (;;) {
std::unique_ptr<wchar_t> buf(new wchar_t[num_chars]);
#if _WIN32
n = _wcsftime_l(buf.get(), num_chars, format, &date, locale);
#else
n = wcsftime_l(buf.get(), num_chars, format, &date, locale);
#endif
if (n) {
str.assign(buf.get(), buf.get() + n);
return str;
}
num_chars = stdex::mul(num_chars, 2);
}
}
template<class TR = std::char_traits<char>, class AX = std::allocator<char>>
static std::basic_string<char, TR, AX> to_rfc822(_In_ const time_point tp)
{
return to_str(tp, "%a, %d %b %Y %H:%M:%S GMT", stdex::locale_C);
}
};
}
}

View File

@ -1,59 +0,0 @@
/*
Copyright 2016-2017 Amebis
This file is part of stdex.
stdex 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 of the License, or
(at your option) any later version.
stdex 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.
You should have received a copy of the GNU General Public License
along with stdex. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <assert.h>
#include <Windows.h>
#include <vector>
///
/// Public function calling convention
///
#ifdef STDEX
#define STDEX_API __declspec(dllexport)
#else
#define STDEX_API __declspec(dllimport)
#endif
#define STDEX_NOVTABLE __declspec(novtable)
//
// Product version as a single DWORD
// Note: Used for version comparison within C/C++ code.
//
#define STDEX_VERSION 0x01000100
//
// Product version by components
// Note: Resource Compiler has limited preprocessing capability,
// thus we need to specify major, minor and other version components
// separately.
//
#define STDEX_VERSION_MAJ 1
#define STDEX_VERSION_MIN 0
#define STDEX_VERSION_REV 1
#define STDEX_VERSION_BUILD 0
//
// Human readable product version and build year for UI
//
#define STDEX_VERSION_STR "1.0.1"
#define STDEX_BUILD_YEAR_STR "2016"

225
include/stdex/compat.hpp Normal file
View File

@ -0,0 +1,225 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2022-2025 Amebis
*/
#pragma once
#include <stddef.h>
#ifdef _WIN32
#include "windows.h"
#include <sal.h>
#include <tchar.h>
#endif
#include <type_traits>
#ifndef _In_
#define _In_
#endif
#ifndef _In_bytecount_
#define _In_bytecount_(p)
#endif
#ifndef _In_count_
#define _In_count_(p)
#endif
#ifndef _In_opt_
#define _In_opt_
#endif
#ifndef _In_opt_count_
#define _In_opt_count_(p)
#endif
#ifndef _In_opt_z_count_
#define _In_opt_z_count_(p)
#endif
#ifndef _In_z_
#define _In_z_
#endif
#ifndef _In_opt_z_
#define _In_opt_z_
#endif
#ifndef _In_z_count_
#define _In_z_count_(p)
#endif
#ifndef _In_reads_
#define _In_reads_(p)
#endif
#ifndef _In_reads_bytes_
#define _In_reads_bytes_(p)
#endif
#ifndef _In_reads_z_
#define _In_reads_z_(p)
#endif
#ifndef _In_reads_opt_
#define _In_reads_opt_(p)
#endif
#ifndef _In_reads_opt_z_
#define _In_reads_opt_z_(p)
#endif
#ifndef _In_reads_or_z_
#define _In_reads_or_z_(p)
#endif
#ifndef _In_reads_or_z_opt_
#define _In_reads_or_z_opt_(p)
#endif
#ifndef _In_reads_bytes_opt_
#define _In_reads_bytes_opt_(p)
#endif
#ifndef _Printf_format_string_
#define _Printf_format_string_
#endif
#ifndef _Printf_format_string_params_
#define _Printf_format_string_params_(n)
#endif
#ifndef _Inout_
#define _Inout_
#endif
#ifndef _Inout_opt_
#define _Inout_opt_
#endif
#ifndef _Inout_z_
#define _Inout_z_
#endif
#ifndef _Inout_z_count_
#define _Inout_z_count_(p)
#endif
#ifndef _Inout_cap_
#define _Inout_cap_(p)
#endif
#ifndef _Inout_count_
#define _Inout_count_(p)
#endif
#ifndef _Inout_updates_z_
#define _Inout_updates_z_(p)
#endif
#ifndef _Use_decl_annotations_
#define _Use_decl_annotations_
#endif
#ifndef _Out_
#define _Out_
#endif
#ifndef _Out_opt_
#define _Out_opt_
#endif
#ifndef _Out_z_cap_
#define _Out_z_cap_(p)
#endif
#ifndef _Out_writes_
#define _Out_writes_(p)
#endif
#ifndef _Out_writes_opt_
#define _Out_writes_opt_(p)
#endif
#ifndef _Out_writes_all_opt_
#define _Out_writes_all_opt_(p)
#endif
#ifndef _Out_writes_opt_z_
#define _Out_writes_opt_z_(p)
#endif
#ifndef _Out_writes_bytes_
#define _Out_writes_bytes_(p)
#endif
#ifndef _Out_writes_to_
#define _Out_writes_to_(p, q)
#endif
#ifndef _Out_writes_all_
#define _Out_writes_all_(p)
#endif
#ifndef _Out_writes_z_
#define _Out_writes_z_(p)
#endif
#ifndef _Out_writes_bytes_to_opt_
#define _Out_writes_bytes_to_opt_(p, q)
#endif
#ifndef _Success_
#define _Success_(p)
#endif
#ifndef _Ret_maybenull_
#define _Ret_maybenull_
#endif
#ifndef _Ret_maybenull_z_
#define _Ret_maybenull_z_
#endif
#ifndef _Ret_notnull_
#define _Ret_notnull_
#endif
#ifndef _Ret_z_
#define _Ret_z_
#endif
#ifndef _Must_inspect_result_
#define _Must_inspect_result_
#endif
#ifndef _Check_return_
#define _Check_return_
#endif
#ifndef _Post_maybez_
#define _Post_maybez_
#endif
#ifndef _Null_terminated_
#define _Null_terminated_
#endif
#ifndef _Acquires_lock_
#define _Acquires_lock_(l)
#endif
#ifndef _L
#define __L(x) L ## x
#define _L(x) __L(x)
#endif
#ifndef _T
#define _T(x) x
#endif
#ifndef _Likely_
#if _HAS_CXX20
#define _Likely_ [[likely]]
#else
#define _Likely_
#endif
#endif
#ifndef _Unlikely_
#if _HAS_CXX20
#define _Unlikely_ [[unlikely]]
#else
#define _Unlikely_
#endif
#endif
#ifdef _MSC_VER
#define _Deprecated_(message) __declspec(deprecated(message))
#define _NoReturn_ __declspec(noreturn)
#else
#define _Deprecated_(message) [[deprecated(message)]]
#define _NoReturn_ [[noreturn]]
#endif
#ifdef _WIN32
#define _Unreferenced_(x) UNREFERENCED_PARAMETER(x)
#else
#define _Unreferenced_(x) (void)(x)
#endif
#ifndef _WIN32
template <class T, size_t N>
size_t _countof(const T (&arr)[N])
{
_Unreferenced_(arr);
return std::extent<T[N]>::value;
}
#endif
#ifndef _Analysis_assume_
#define _Analysis_assume_(p)
#endif
#ifdef __APPLE__
#define off64_t off_t
#define lseek64 lseek
#define lockf64 lockf
#define ftruncate64 ftruncate
#endif

122
include/stdex/curl.hpp Normal file
View File

@ -0,0 +1,122 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "exception.hpp"
#include <curl/curl.h>
#include <memory>
#include <string>
#include <stdexcept>
namespace stdex
{
///
/// CURL runtime error
///
class curl_runtime_error : public num_runtime_error<CURLcode>
{
public:
///
/// Constructs an exception
///
/// \param[in] num CURL error code
///
curl_runtime_error(_In_ error_type num) : stdex::num_runtime_error<CURLcode>(num, curl_easy_strerror(num))
{
}
///
/// Constructs an exception
///
/// \param[in] num CURL error code
/// \param[in] msg Error message
///
curl_runtime_error(_In_ error_type num, _In_ const std::string& msg) : stdex::num_runtime_error<CURLcode>(num, msg + ": " + curl_easy_strerror(num))
{
}
///
/// Constructs an exception
///
/// \param[in] num CURL error code
/// \param[in] msg Error message
///
curl_runtime_error(_In_ error_type num, _In_z_ const char *msg) : stdex::num_runtime_error<CURLcode>(num, std::string(msg) + ": " + curl_easy_strerror(num))
{
}
protected:
error_type m_num; ///< Numeric error code
};
///
/// Deleter for unique_ptr using curl_easy_cleanup
///
struct curl_easy_cleanup_delete
{
///
/// Delete a pointer
///
void operator()(_In_ CURL* ptr) const
{
curl_easy_cleanup(ptr);
}
};
///
/// CURL handle
///
using curl = std::unique_ptr<CURL, curl_easy_cleanup_delete>;
///
/// Deleter for unique_ptr using curl_slist_free_all
///
struct curl_slist_free_all_delete
{
///
/// Delete a pointer
///
void operator()(_In_ struct ::curl_slist* ptr) const
{
curl_slist_free_all(ptr);
}
};
///
/// curl_slist struct
///
using curl_slist = std::unique_ptr<struct ::curl_slist, curl_slist_free_all_delete>;
///
/// Context scope automatic CURL (un)initialization
///
class curl_initializer
{
public:
///
/// Initializes the CURL library.
///
/// \sa [curl_global_init function](https://curl.se/libcurl/c/curl_global_init.html)
///
curl_initializer(_In_ long flags = CURL_GLOBAL_DEFAULT)
{
auto code = curl_global_init(flags);
if (code != CURLE_OK) _Unlikely_
throw curl_runtime_error(code, "CURL failed to initialize");
}
///
/// Uninitializes CURL.
///
/// \sa [curl_global_cleanup function](https://curl.se/libcurl/c/curl_global_cleanup.html)
///
virtual ~curl_initializer()
{
curl_global_cleanup();
}
};
}

150
include/stdex/debug.hpp Normal file
View File

@ -0,0 +1,150 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "string.hpp"
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <chrono>
namespace stdex
{
namespace diag {
/// \cond internal
inline void vprintf(_In_z_ _Printf_format_string_ const char* format, _In_ va_list arg)
{
#if defined(NDEBUG)
_Unreferenced_(format);
_Unreferenced_(arg);
#elif defined(_WIN32)
auto tmp = stdex::vsprintf(format, stdex::locale_default, arg);
OutputDebugStringA(tmp.c_str());
#else
vfprintf(stdout, format, arg);
#endif
}
inline void vprintf(_In_z_ _Printf_format_string_ const wchar_t* format, _In_ va_list arg)
{
#if defined(NDEBUG)
_Unreferenced_(format);
_Unreferenced_(arg);
#elif defined(_WIN32)
auto tmp = stdex::vsprintf(format, stdex::locale_default, arg);
OutputDebugStringW(tmp.c_str());
#else
vfwprintf(stdout, format, arg);
#endif
}
/// \endcond
///
/// Outputs diagnostic message
///
/// On Windows, the message is sent using `OutputDebugStringA`. On other platforms, message is printed to stdout.
///
/// \note When compiled with #define NDEBUG, no output is performed.
///
/// \param[in] format String template using `printf()` style
///
template <class T>
inline void printf(_In_z_ _Printf_format_string_ const T* format, ...)
{
#if defined(NDEBUG)
_Unreferenced_(format);
#else
va_list arg;
va_start(arg, format);
vprintf(format, arg);
va_end(arg);
#endif
}
}
namespace err {
/// \cond internal
inline void vprintf(_In_z_ _Printf_format_string_ const char* format, _In_ va_list arg)
{
#if defined(NDEBUG)
_Unreferenced_(format);
_Unreferenced_(arg);
#elif defined(_WIN32)
auto tmp = stdex::vsprintf(format, stdex::locale_default, arg);
OutputDebugStringA(tmp.c_str());
#else
vfprintf(stderr, format, arg);
#endif
}
inline void vprintf(_In_z_ _Printf_format_string_ const wchar_t* format, _In_ va_list arg)
{
#if defined(NDEBUG)
_Unreferenced_(format);
_Unreferenced_(arg);
#elif defined(_WIN32)
auto tmp = stdex::vsprintf(format, stdex::locale_default, arg);
OutputDebugStringW(tmp.c_str());
#else
vfwprintf(stderr, format, arg);
#endif
}
/// \endcond
///
/// Outputs error message
///
/// On Windows, the message is sent using `OutputDebugStringA`. On other platforms, message is printed to stderr.
///
/// \note When compiled with #define NDEBUG, no output is performed.
///
/// \param[in] format String template using `printf()` style
///
template <class T>
inline void printf(_In_z_ _Printf_format_string_ const T* format, ...)
{
#if defined(NDEBUG)
_Unreferenced_(format);
#else
va_list arg;
va_start(arg, format);
vprintf(format, arg);
va_end(arg);
#endif
}
}
///
/// Measures time between initialization and going out of scope
///
class benchmark
{
public:
///
/// Starts the measurement
///
/// \param[in] task_name Name of the task. The string must remain resident for the lifetime of this object
///
benchmark(_In_z_ const char* task_name) :
m_task_name(task_name),
m_start(std::chrono::high_resolution_clock::now())
{}
///
/// Stops the measurement and outputs the result to the diagnostic console
///
~benchmark()
{
auto duration(std::chrono::high_resolution_clock::now() - m_start);
stdex::diag::printf("%s took %I64i ns\n", m_task_name, static_cast<int64_t>(duration.count()));
}
protected:
const char* m_task_name;
std::chrono::time_point<std::chrono::high_resolution_clock> m_start;
};
}

141
include/stdex/endian.hpp Normal file
View File

@ -0,0 +1,141 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include "system.hpp"
#include <stdint.h>
#ifndef LITTLE_ENDIAN
#define LITTLE_ENDIAN 1234
#endif
#ifndef BIG_ENDIAN
#define BIG_ENDIAN 4321
#endif
#ifndef BYTE_ORDER
#if defined(_WIN32)
#if REG_DWORD == REG_DWORD_LITTLE_ENDIAN
#define BYTE_ORDER LITTLE_ENDIAN
#elif REG_DWORD == REG_DWORD_BIG_ENDIAN
#define BYTE_ORDER BIG_ENDIAN
#endif
#elif defined(__APPLE__)
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define BYTE_ORDER LITTLE_ENDIAN
#elif __BYTE_ORDER == __ORDER_BIG_ENDIAN__
#define BYTE_ORDER BIG_ENDIAN
#endif
#else
#include <endian.h>
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define BYTE_ORDER LITTLE_ENDIAN
#elif __BYTE_ORDER == __BIG_ENDIAN
#define BYTE_ORDER BIG_ENDIAN
#endif
#endif
#ifndef BYTE_ORDER
#error Unknown endian
#endif
#endif
namespace stdex
{
inline constexpr uint8_t byteswap(_In_ const uint8_t value)
{
return value;
}
inline uint16_t byteswap(_In_ const uint16_t value)
{
#if _MSC_VER >= 1300
return _byteswap_ushort(value);
#elif defined(_MSC_VER)
uint16_t t = (value & 0x00ff) << 8;
t |= (value) >> 8;
return t;
#else
return __builtin_bswap16(value);
#endif
}
inline uint32_t byteswap(_In_ const uint32_t value)
{
#if _MSC_VER >= 1300
return _byteswap_ulong(value);
#elif defined(_MSC_VER)
uint32_t t = (value & 0x000000ff) << 24;
t |= (value & 0x0000ff00) << 8;
t |= (value & 0x00ff0000) >> 8;
t |= (value) >> 24;
return t;
#else
return __builtin_bswap32(value);
#endif
}
inline uint64_t byteswap(_In_ const uint64_t value)
{
#if _MSC_VER >= 1300
return _byteswap_uint64(value);
#elif defined(_MSC_VER)
uint64_t t = (value & 0x00000000000000ff) << 56;
t |= (value & 0x000000000000ff00) << 40;
t |= (value & 0x0000000000ff0000) << 24;
t |= (value & 0x00000000ff000000) << 8;
t |= (value & 0x000000ff00000000) >> 8;
t |= (value & 0x0000ff0000000000) >> 24;
t |= (value & 0x00ff000000000000) >> 40;
t |= (value) >> 56;
return t;
#else
return __builtin_bswap64(value);
#endif
}
inline constexpr int8_t byteswap(_In_ const char value) { return static_cast<int8_t>(byteswap(static_cast<uint8_t>(value))); }
inline constexpr int8_t byteswap(_In_ const int8_t value) { return static_cast<int8_t>(byteswap(static_cast<uint8_t>(value))); }
inline int16_t byteswap(_In_ const int16_t value) { return static_cast<int16_t>(byteswap(static_cast<uint16_t>(value))); }
inline int32_t byteswap(_In_ const int32_t value) { return static_cast<int32_t>(byteswap(static_cast<uint32_t>(value))); }
inline int64_t byteswap(_In_ const int64_t value) { return static_cast<int64_t>(byteswap(static_cast<uint64_t>(value))); }
inline float byteswap(_In_ const float value)
{
uint32_t r = byteswap(*reinterpret_cast<const uint32_t*>(&value));
return *reinterpret_cast<float*>(&r);
}
inline double byteswap(_In_ const double value)
{
uint64_t r = byteswap(*reinterpret_cast<const uint64_t*>(&value));
return *reinterpret_cast<double*>(&r);
}
inline void byteswap(_Inout_ uint8_t* value) { stdex_assert(value); *value = byteswap(*value); }
inline void byteswap(_Inout_ uint16_t* value) { stdex_assert(value); *value = byteswap(*value); }
inline void byteswap(_Inout_ uint32_t* value) { stdex_assert(value); *value = byteswap(*value); }
inline void byteswap(_Inout_ uint64_t* value) { stdex_assert(value); *value = byteswap(*value); }
inline void byteswap(_Inout_ char* value) { byteswap(reinterpret_cast<uint8_t*>(value)); }
inline void byteswap(_Inout_ int8_t* value) { byteswap(reinterpret_cast<uint8_t*>(value)); }
inline void byteswap(_Inout_ int16_t* value) { byteswap(reinterpret_cast<uint16_t*>(value)); }
inline void byteswap(_Inout_ int32_t* value) { byteswap(reinterpret_cast<uint32_t*>(value)); }
inline void byteswap(_Inout_ int64_t* value) { byteswap(reinterpret_cast<uint64_t*>(value)); }
inline void byteswap(_Inout_ float* value) { byteswap(reinterpret_cast<uint32_t*>(value)); }
inline void byteswap(_Inout_ double* value) { byteswap(reinterpret_cast<uint64_t*>(value)); }
}
#if BYTE_ORDER == BIG_ENDIAN
#define LE2HE(x) stdex::byteswap(x)
#define BE2HE(x) (x)
#define HE2LE(x) stdex::byteswap(x)
#define HE2BE(x) (x)
#else
#define LE2HE(x) (x)
#define BE2HE(x) stdex::byteswap(x)
#define HE2LE(x) (x)
#define HE2BE(x) stdex::byteswap(x)
#endif

View File

@ -0,0 +1,88 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include <stdexcept>
#include <string>
namespace stdex
{
///
/// User cancelled exception
///
class user_cancelled : public std::runtime_error
{
public:
///
/// Constructs an exception
///
/// \param[in] msg Error message
///
user_cancelled(_In_opt_z_ const char* msg = "operation cancelled") : runtime_error(msg) {}
};
///
/// Concatenates error messages when exception is nested.
///
/// \param ex Exception
///
/// \returns Concatenated exception messages with ": " delimiter.
///
inline std::string exception_msg(const std::exception& ex)
{
try { std::rethrow_if_nested(ex); }
catch (const std::exception& nested_ex) {
return std::string(ex.what()) + ": " + exception_msg(nested_ex);
}
catch (...) {}
return ex.what();
}
///
/// Numerical runtime error
///
template <typename _Tn>
class num_runtime_error : public std::runtime_error
{
public:
typedef _Tn error_type; ///< Error number type
public:
///
/// Constructs an exception
///
/// \param[in] num Numeric error code
/// \param[in] msg Error message
///
num_runtime_error(_In_ error_type num, _In_ const std::string& msg) :
m_num(num),
runtime_error(msg)
{}
///
/// Constructs an exception
///
/// \param[in] num Numeric error code
/// \param[in] msg Error message
///
num_runtime_error(_In_ error_type num, _In_z_ const char *msg) :
m_num(num),
runtime_error(msg)
{}
///
/// Returns the error number
///
error_type number() const
{
return m_num;
}
protected:
error_type m_num; ///< Numeric error code
};
}

648
include/stdex/hash.hpp Normal file
View File

@ -0,0 +1,648 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include "endian.hpp"
#include "math.hpp"
#include "stream.hpp"
#include <stdint.h>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#endif
namespace stdex
{
///
/// Basic hashing operations
///
template<class T>
class basic_hash
{
public:
basic_hash() : m_value() {}
virtual ~basic_hash() {}
///
/// Initializes hash value and internal state
///
virtual void clear() = 0;
///
/// Hashes block of data
///
/// \param[in] data Pointer to data
/// \param[in] length Amount of data in bytes
///
virtual void hash(_In_reads_bytes_opt_(length) const void* data, _In_ size_t length) = 0;
///
/// Finalizes hash value
///
virtual void finalize() = 0;
///
/// Returns size of the hash value in bytes
///
static size_t size() { return sizeof(T); }
///
/// Returns hash value
///
const T& data() { return m_value; };
///
/// Returns hash value
///
operator const T& () const { return m_value; };
protected:
T m_value;
};
///
/// Hashing in blocks
///
template<class T>
class block_hash : public basic_hash<T>
{
public:
block_hash()
{
m_counter = 0;
memset(m_queue, 0, sizeof(m_queue));
}
virtual void clear()
{
m_counter = 0;
}
virtual void hash(_In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
{
stdex_assert(data || !length);
size_t j = static_cast<size_t>(m_counter & 63);
m_counter += length;
while (length) {
size_t n = std::min(64 - j, length);
memcpy(&m_queue[j], data, n);
j += n;
if (j >= 64) {
hash_block();
j = 0;
}
reinterpret_cast<const uint8_t*&>(data) += n;
length -= n;
}
}
protected:
virtual void hash_block() = 0;
protected:
uint64_t m_counter; // Number of bytes hashed
union {
uint8_t m_queue[64];
uint32_t m_temp[16];
};
};
///
/// Hashes read to or write from data of the stream
///
template<class T>
class stream_hasher : public stdex::stream::converter
{
public:
stream_hasher(_Inout_ basic_hash<T>& hash, _Inout_ stdex::stream::basic& source) :
stdex::stream::converter(source),
m_hash(hash)
{}
virtual _Success_(return != 0 || length == 0) size_t read(
_Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
{
size_t num_read = stdex::stream::converter::read(data, length);
m_hash.hash(data, num_read);
return num_read;
}
virtual _Success_(return != 0) size_t write(
_In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
{
size_t num_written = stdex::stream::converter::write(data, length);
m_hash.hash(data, num_written);
return num_written;
}
protected:
basic_hash<T>& m_hash;
};
///
/// CRC32 hash value
///
using crc32_t = uint32_t;
///
/// Hashes as CRC32
///
class crc32_hash : public basic_hash<crc32_t>
{
public:
crc32_hash(crc32_t crc = 0)
{
m_value = ~crc;
}
virtual void clear()
{
m_value = 0xffffffff;
}
virtual void hash(_In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
{
static const uint32_t crc32_table[256] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
0x2d02ef8d
};
stdex_assert(data || !length);
for (size_t i = 0; i < length; i++)
m_value = crc32_table[(m_value ^ reinterpret_cast<const uint8_t*>(data)[i]) & 0xff] ^ (m_value >> 8);
}
virtual void finalize()
{
m_value = ~m_value;
}
};
///
/// MD2 hash value
///
union md2_t
{
uint8_t data8[16];
uint32_t data32[4];
bool operator !=(_In_ const stdex::md2_t& other) const
{
return
(data32[0] ^ other.data32[0]) |
(data32[1] ^ other.data32[1]) |
(data32[2] ^ other.data32[2]) |
(data32[3] ^ other.data32[3]);
}
bool operator ==(_In_ const stdex::md2_t& other) const
{
return !operator !=(other);
}
friend inline stdex::stream::basic& operator >>(_Inout_ stdex::stream::basic& stream, _Out_ stdex::md2_t& data)
{
if (!stream.ok()) _Unlikely_{
memset(&data, 0, sizeof(data));
return stream;
}
stream.read_array(&data, sizeof(data), 1);
return stream;
}
friend inline stdex::stream::basic& operator <<(_Inout_ stdex::stream::basic& stream, _In_ const stdex::md2_t& data)
{
if (!stream.ok()) _Unlikely_ return stream;
stream.write_array(&data, sizeof(data), 1);
return stream;
}
};
///
/// MD5 hash value
///
using md5_t = md2_t;
///
/// Hashes as MD5
///
class md5_hash : public block_hash<md5_t>
{
public:
md5_hash()
{
clear();
}
virtual void clear()
{
block_hash::clear();
m_state[0] = 0x67452301;
m_state[1] = 0xefcdab89;
m_state[2] = 0x98badcfe;
m_state[3] = 0x10325476;
}
virtual void finalize()
{
static const uint8_t md5_padding[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
// Save number of bits.
uint8_t final[] = {
static_cast<uint8_t>((m_counter << 3) & 0xff),
static_cast<uint8_t>((m_counter >> 5) & 0xff),
static_cast<uint8_t>((m_counter >> 13) & 0xff),
static_cast<uint8_t>((m_counter >> 21) & 0xff),
static_cast<uint8_t>((m_counter >> 29) & 0xff),
static_cast<uint8_t>((m_counter >> 37) & 0xff),
static_cast<uint8_t>((m_counter >> 45) & 0xff),
static_cast<uint8_t>((m_counter >> 53) & 0xff),
};
// Pad out to 56 mod 64.
size_t index = m_counter & 0x3f;
size_t remainder = index < 56 ? 56 - index : 120 - index;
hash(md5_padding, remainder);
// Append length (before padding).
hash(final, 8);
// Store m_state in m_value.
memcpy(&m_value, m_state, sizeof(md5_t));
}
protected:
virtual void hash_block()
{
constexpr int S11 = 7;
constexpr int S12 = 12;
constexpr int S13 = 17;
constexpr int S14 = 22;
constexpr int S21 = 5;
constexpr int S22 = 9;
constexpr int S23 = 14;
constexpr int S24 = 20;
constexpr int S31 = 4;
constexpr int S32 = 11;
constexpr int S33 = 16;
constexpr int S34 = 23;
constexpr int S41 = 6;
constexpr int S42 = 10;
constexpr int S43 = 15;
constexpr int S44 = 21;
// Copy m_state[] to working vars.
uint32_t a = m_state[0], b = m_state[1], c = m_state[2], d = m_state[3];
// MD5 rounds
#define MD5_R1(a, b, c, d, i, s, ac) { (a) += (((b) & (c)) | ((~b) & (d))) + m_temp[(i)] + static_cast<uint32_t>(ac); (a) = rol((a), (s)); (a) += (b); }
#define MD5_R2(a, b, c, d, i, s, ac) { (a) += (((b) & (d)) | ((c) & (~d))) + m_temp[(i)] + static_cast<uint32_t>(ac); (a) = rol((a), (s)); (a) += (b); }
#define MD5_R3(a, b, c, d, i, s, ac) { (a) += ((b) ^ (c) ^ (d)) + m_temp[(i)] + static_cast<uint32_t>(ac); (a) = rol((a), (s)); (a) += (b); }
#define MD5_R4(a, b, c, d, i, s, ac) { (a) += ((c) ^ ((b) | (~d))) + m_temp[(i)] + static_cast<uint32_t>(ac); (a) = rol((a), (s)); (a) += (b); }
// 4 rounds of 16 operations each. Loop unrolled.
MD5_R1(a, b, c, d, 0, S11, 0xd76aa478);
MD5_R1(d, a, b, c, 1, S12, 0xe8c7b756);
MD5_R1(c, d, a, b, 2, S13, 0x242070db);
MD5_R1(b, c, d, a, 3, S14, 0xc1bdceee);
MD5_R1(a, b, c, d, 4, S11, 0xf57c0faf);
MD5_R1(d, a, b, c, 5, S12, 0x4787c62a);
MD5_R1(c, d, a, b, 6, S13, 0xa8304613);
MD5_R1(b, c, d, a, 7, S14, 0xfd469501);
MD5_R1(a, b, c, d, 8, S11, 0x698098d8);
MD5_R1(d, a, b, c, 9, S12, 0x8b44f7af);
MD5_R1(c, d, a, b, 10, S13, 0xffff5bb1);
MD5_R1(b, c, d, a, 11, S14, 0x895cd7be);
MD5_R1(a, b, c, d, 12, S11, 0x6b901122);
MD5_R1(d, a, b, c, 13, S12, 0xfd987193);
MD5_R1(c, d, a, b, 14, S13, 0xa679438e);
MD5_R1(b, c, d, a, 15, S14, 0x49b40821);
MD5_R2(a, b, c, d, 1, S21, 0xf61e2562);
MD5_R2(d, a, b, c, 6, S22, 0xc040b340);
MD5_R2(c, d, a, b, 11, S23, 0x265e5a51);
MD5_R2(b, c, d, a, 0, S24, 0xe9b6c7aa);
MD5_R2(a, b, c, d, 5, S21, 0xd62f105d);
MD5_R2(d, a, b, c, 10, S22, 0x2441453);
MD5_R2(c, d, a, b, 15, S23, 0xd8a1e681);
MD5_R2(b, c, d, a, 4, S24, 0xe7d3fbc8);
MD5_R2(a, b, c, d, 9, S21, 0x21e1cde6);
MD5_R2(d, a, b, c, 14, S22, 0xc33707d6);
MD5_R2(c, d, a, b, 3, S23, 0xf4d50d87);
MD5_R2(b, c, d, a, 8, S24, 0x455a14ed);
MD5_R2(a, b, c, d, 13, S21, 0xa9e3e905);
MD5_R2(d, a, b, c, 2, S22, 0xfcefa3f8);
MD5_R2(c, d, a, b, 7, S23, 0x676f02d9);
MD5_R2(b, c, d, a, 12, S24, 0x8d2a4c8a);
MD5_R3(a, b, c, d, 5, S31, 0xfffa3942);
MD5_R3(d, a, b, c, 8, S32, 0x8771f681);
MD5_R3(c, d, a, b, 11, S33, 0x6d9d6122);
MD5_R3(b, c, d, a, 14, S34, 0xfde5380c);
MD5_R3(a, b, c, d, 1, S31, 0xa4beea44);
MD5_R3(d, a, b, c, 4, S32, 0x4bdecfa9);
MD5_R3(c, d, a, b, 7, S33, 0xf6bb4b60);
MD5_R3(b, c, d, a, 10, S34, 0xbebfbc70);
MD5_R3(a, b, c, d, 13, S31, 0x289b7ec6);
MD5_R3(d, a, b, c, 0, S32, 0xeaa127fa);
MD5_R3(c, d, a, b, 3, S33, 0xd4ef3085);
MD5_R3(b, c, d, a, 6, S34, 0x4881d05);
MD5_R3(a, b, c, d, 9, S31, 0xd9d4d039);
MD5_R3(d, a, b, c, 12, S32, 0xe6db99e5);
MD5_R3(c, d, a, b, 15, S33, 0x1fa27cf8);
MD5_R3(b, c, d, a, 2, S34, 0xc4ac5665);
MD5_R4(a, b, c, d, 0, S41, 0xf4292244);
MD5_R4(d, a, b, c, 7, S42, 0x432aff97);
MD5_R4(c, d, a, b, 14, S43, 0xab9423a7);
MD5_R4(b, c, d, a, 5, S44, 0xfc93a039);
MD5_R4(a, b, c, d, 12, S41, 0x655b59c3);
MD5_R4(d, a, b, c, 3, S42, 0x8f0ccc92);
MD5_R4(c, d, a, b, 10, S43, 0xffeff47d);
MD5_R4(b, c, d, a, 1, S44, 0x85845dd1);
MD5_R4(a, b, c, d, 8, S41, 0x6fa87e4f);
MD5_R4(d, a, b, c, 15, S42, 0xfe2ce6e0);
MD5_R4(c, d, a, b, 6, S43, 0xa3014314);
MD5_R4(b, c, d, a, 13, S44, 0x4e0811a1);
MD5_R4(a, b, c, d, 4, S41, 0xf7537e82);
MD5_R4(d, a, b, c, 11, S42, 0xbd3af235);
MD5_R4(c, d, a, b, 2, S43, 0x2ad7d2bb);
MD5_R4(b, c, d, a, 9, S44, 0xeb86d391);
#undef MD5_R1
#undef MD5_R2
#undef MD5_R3
#undef MD5_R4
// Add the working vars back into internal state.
m_state[0] += a;
m_state[1] += b;
m_state[2] += c;
m_state[3] += d;
}
protected:
uint32_t m_state[4];
};
///
/// SHA hash value
///
union sha_t
{
uint8_t data8[20];
uint32_t data32[5];
bool operator !=(_In_ const stdex::sha_t& other) const
{
return
(data32[0] ^ other.data32[0]) |
(data32[1] ^ other.data32[1]) |
(data32[2] ^ other.data32[2]) |
(data32[3] ^ other.data32[3]) |
(data32[4] ^ other.data32[4]);
}
bool operator ==(_In_ const stdex::sha_t& other) const
{
return !operator !=(other);
}
friend inline stdex::stream::basic& operator >>(_Inout_ stdex::stream::basic& stream, _Out_ stdex::sha_t& data)
{
if (!stream.ok()) _Unlikely_{
memset(&data, 0, sizeof(data));
return stream;
}
stream.read_array(&data, sizeof(data), 1);
return stream;
}
friend inline stdex::stream::basic& operator <<(_Inout_ stdex::stream::basic& stream, _In_ const stdex::sha_t data)
{
if (!stream.ok()) _Unlikely_ return stream;
stream.write_array(&data, sizeof(data), 1);
return stream;
}
};
///
/// SHA1 hash value
///
using sha1_t = sha_t;
///
/// Hashes as SHA1
///
class sha1_hash : public block_hash<sha1_t>
{
public:
sha1_hash()
{
clear();
}
virtual void clear()
{
block_hash::clear();
// SHA1 initialization constants
m_state[0] = 0x67452301;
m_state[1] = 0xEFCDAB89;
m_state[2] = 0x98BADCFE;
m_state[3] = 0x10325476;
m_state[4] = 0xC3D2E1F0;
}
virtual void finalize()
{
// Save number of bits.
uint8_t final[] = {
static_cast<uint8_t>((m_counter >> 53) & 0xff),
static_cast<uint8_t>((m_counter >> 45) & 0xff),
static_cast<uint8_t>((m_counter >> 37) & 0xff),
static_cast<uint8_t>((m_counter >> 29) & 0xff),
static_cast<uint8_t>((m_counter >> 21) & 0xff),
static_cast<uint8_t>((m_counter >> 13) & 0xff),
static_cast<uint8_t>((m_counter >> 5) & 0xff),
static_cast<uint8_t>((m_counter << 3) & 0xff),
};
hash("\200", 1);
while ((m_counter & 63) != 56)
hash("\0", 1);
hash(final, 8); // Cause a SHA1Transform()
// Store m_state in m_value.
for (size_t i = 0; i < 20; i++)
m_value.data8[i] = static_cast<uint8_t>((m_state[i >> 2] >> ((3 - (i & 3)) * 8)) & 0xff);
}
protected:
virtual void hash_block()
{
// Copy m_state[] to working vars.
uint32_t a = m_state[0], b = m_state[1], c = m_state[2], d = m_state[3], e = m_state[4];
#if BYTE_ORDER == BIG_ENDIAN
#define SHA1BLK0(i) (m_temp[i])
#else
#define SHA1BLK0(i) (m_temp[i] = (rol(m_temp[i],24) & 0xFF00FF00) | (rol(m_temp[i],8) & 0x00FF00FF))
#endif
#define SHA1BLK(i) (m_temp[i&15] = rol(m_temp[(i+13)&15] ^ m_temp[(i+8)&15] ^ m_temp[(i+2)&15] ^ m_temp[i&15],1))
// SHA1 rounds
#define SHA1_R0(v, w, x, y, z, i) { (z) += (((w)&((x)^(y)))^(y))+SHA1BLK0((i))+0x5A827999+rol((v),5); (w)=rol((w),30); }
#define SHA1_R1(v, w, x, y, z, i) { (z) += (((w)&((x)^(y)))^(y))+SHA1BLK((i))+0x5A827999+rol((v),5); (w)=rol((w),30); }
#define SHA1_R2(v, w, x, y, z, i) { (z) += ((w)^(x)^(y))+SHA1BLK((i))+0x6ED9EBA1+rol((v),5); (w)=rol((w),30); }
#define SHA1_R3(v, w, x, y, z, i) { (z) += ((((w)|(x))&(y))|((w)&(x)))+SHA1BLK((i))+0x8F1BBCDC+rol((v),5); (w)=rol((w),30); }
#define SHA1_R4(v, w, x, y, z, i) { (z) += ((w)^(x)^(y))+SHA1BLK((i))+0xCA62C1D6+rol((v),5); (w)=rol((w),30); }
// 5 rounds of 16 operations each. Loop unrolled.
SHA1_R0(a, b, c, d, e, 0); SHA1_R0(e, a, b, c, d, 1); SHA1_R0(d, e, a, b, c, 2); SHA1_R0(c, d, e, a, b, 3);
SHA1_R0(b, c, d, e, a, 4); SHA1_R0(a, b, c, d, e, 5); SHA1_R0(e, a, b, c, d, 6); SHA1_R0(d, e, a, b, c, 7);
SHA1_R0(c, d, e, a, b, 8); SHA1_R0(b, c, d, e, a, 9); SHA1_R0(a, b, c, d, e, 10); SHA1_R0(e, a, b, c, d, 11);
SHA1_R0(d, e, a, b, c, 12); SHA1_R0(c, d, e, a, b, 13); SHA1_R0(b, c, d, e, a, 14); SHA1_R0(a, b, c, d, e, 15);
SHA1_R1(e, a, b, c, d, 16); SHA1_R1(d, e, a, b, c, 17); SHA1_R1(c, d, e, a, b, 18); SHA1_R1(b, c, d, e, a, 19);
SHA1_R2(a, b, c, d, e, 20); SHA1_R2(e, a, b, c, d, 21); SHA1_R2(d, e, a, b, c, 22); SHA1_R2(c, d, e, a, b, 23);
SHA1_R2(b, c, d, e, a, 24); SHA1_R2(a, b, c, d, e, 25); SHA1_R2(e, a, b, c, d, 26); SHA1_R2(d, e, a, b, c, 27);
SHA1_R2(c, d, e, a, b, 28); SHA1_R2(b, c, d, e, a, 29); SHA1_R2(a, b, c, d, e, 30); SHA1_R2(e, a, b, c, d, 31);
SHA1_R2(d, e, a, b, c, 32); SHA1_R2(c, d, e, a, b, 33); SHA1_R2(b, c, d, e, a, 34); SHA1_R2(a, b, c, d, e, 35);
SHA1_R2(e, a, b, c, d, 36); SHA1_R2(d, e, a, b, c, 37); SHA1_R2(c, d, e, a, b, 38); SHA1_R2(b, c, d, e, a, 39);
SHA1_R3(a, b, c, d, e, 40); SHA1_R3(e, a, b, c, d, 41); SHA1_R3(d, e, a, b, c, 42); SHA1_R3(c, d, e, a, b, 43);
SHA1_R3(b, c, d, e, a, 44); SHA1_R3(a, b, c, d, e, 45); SHA1_R3(e, a, b, c, d, 46); SHA1_R3(d, e, a, b, c, 47);
SHA1_R3(c, d, e, a, b, 48); SHA1_R3(b, c, d, e, a, 49); SHA1_R3(a, b, c, d, e, 50); SHA1_R3(e, a, b, c, d, 51);
SHA1_R3(d, e, a, b, c, 52); SHA1_R3(c, d, e, a, b, 53); SHA1_R3(b, c, d, e, a, 54); SHA1_R3(a, b, c, d, e, 55);
SHA1_R3(e, a, b, c, d, 56); SHA1_R3(d, e, a, b, c, 57); SHA1_R3(c, d, e, a, b, 58); SHA1_R3(b, c, d, e, a, 59);
SHA1_R4(a, b, c, d, e, 60); SHA1_R4(e, a, b, c, d, 61); SHA1_R4(d, e, a, b, c, 62); SHA1_R4(c, d, e, a, b, 63);
SHA1_R4(b, c, d, e, a, 64); SHA1_R4(a, b, c, d, e, 65); SHA1_R4(e, a, b, c, d, 66); SHA1_R4(d, e, a, b, c, 67);
SHA1_R4(c, d, e, a, b, 68); SHA1_R4(b, c, d, e, a, 69); SHA1_R4(a, b, c, d, e, 70); SHA1_R4(e, a, b, c, d, 71);
SHA1_R4(d, e, a, b, c, 72); SHA1_R4(c, d, e, a, b, 73); SHA1_R4(b, c, d, e, a, 74); SHA1_R4(a, b, c, d, e, 75);
SHA1_R4(e, a, b, c, d, 76); SHA1_R4(d, e, a, b, c, 77); SHA1_R4(c, d, e, a, b, 78); SHA1_R4(b, c, d, e, a, 79);
// Add the working vars back into m_state.
m_state[0] += a;
m_state[1] += b;
m_state[2] += c;
m_state[3] += d;
m_state[4] += e;
#undef SHA1_R0
#undef SHA1_R1
#undef SHA1_R2
#undef SHA1_R3
#undef SHA1_R4
#undef SHA1BLK0
#undef SHA1BLK0
#undef SHA1BLK
}
protected:
uint32_t m_state[5];
};
///
/// SHA256 hash value
///
union sha256_t
{
uint8_t data8[32];
uint32_t data32[8];
bool operator !=(_In_ const stdex::sha256_t& other) const
{
return
(data32[0] ^ other.data32[0]) |
(data32[1] ^ other.data32[1]) |
(data32[2] ^ other.data32[2]) |
(data32[3] ^ other.data32[3]) |
(data32[4] ^ other.data32[4]) |
(data32[5] ^ other.data32[5]) |
(data32[6] ^ other.data32[6]) |
(data32[7] ^ other.data32[7]);
}
bool operator ==(_In_ const stdex::sha256_t& other) const
{
return !operator !=(other);
}
friend inline stdex::stream::basic& operator >>(_Inout_ stdex::stream::basic& stream, _Out_ stdex::sha256_t& data)
{
if (!stream.ok()) _Unlikely_{
memset(&data, 0, sizeof(data));
return stream;
}
stream.read_array(&data, sizeof(data), 1);
return stream;
}
friend inline stdex::stream::basic& operator <<(_Inout_ stdex::stream::basic& stream, _In_ const stdex::sha256_t& data)
{
if (!stream.ok()) _Unlikely_ return stream;
stream.write_array(&data, sizeof(data), 1);
return stream;
}
};
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

152
include/stdex/hex.hpp Normal file
View File

@ -0,0 +1,152 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace stdex
{
///
/// Hexadecimal encoding session
///
class hex_enc
{
public:
///
/// Constructs blank encoding session
///
hex_enc() noexcept
{}
///
/// Encodes one block of information, and _appends_ it to the output
///
/// \param[in,out] out Output
/// \param[in] data Data to encode
/// \param[in] size Length of `data` in bytes
///
template<class T, class TR, class AX>
void encode(_Inout_ std::basic_string<T, TR, AX> &out, _In_bytecount_(size) const void *data, _In_ size_t size)
{
stdex_assert(data || !size);
// Preallocate output
out.reserve(out.size() + enc_size(size));
// Convert data character by character.
for (size_t i = 0; i < size; i++) {
uint8_t
x = reinterpret_cast<const uint8_t*>(data)[i],
x_h = ((x & 0xf0) >> 4),
x_l = ((x & 0x0f) );
out += x_h < 10 ? '0' + x_h : 'A' - 10 + x_h;
out += x_l < 10 ? '0' + x_l : 'A' - 10 + x_l;
}
}
///
/// Returns maximum encoded size
///
/// \param[in] size Number of bytes to encode
///
/// \returns Maximum number of bytes for the encoded data of `size` length
///
size_t enc_size(_In_ size_t size) const noexcept
{
return size*2;
}
};
///
/// Hexadecimal decoding session
///
class hex_dec
{
public:
///
/// Constructs blank decoding session
///
hex_dec() noexcept :
buf(0),
num(0)
{}
///
/// Decodes one block of information, and _appends_ it to the output
///
/// \param[in,out] out Output
/// \param[out] is_last Was this the last block of data? Actually, is this block of data complete?
/// \param[in] data Data to decode
/// \param[in] size Length of `data` in bytes
///
template<class T_to, class AX, class T_from>
void decode(_Inout_ std::vector<T_to, AX> &out, _Out_ bool &is_last, _In_z_count_(size) const T_from *data, _In_ size_t size)
{
is_last = false;
// Trim data size to first terminator.
for (size_t k = 0; k < size; k++)
if (!data[k]) { size = k; break; }
// Preallocate output
out.reserve(out.size() + dec_size(size));
for (size_t i = 0;; i++) {
if (num >= 2) {
// Buffer full.
out.push_back(buf);
num = 0;
is_last = true;
} else
is_last = false;
if (i >= size)
break;
auto x = data[i];
if ('0' <= x && x <= '9') {
buf = ((buf & 0xf) << 4) | static_cast<uint8_t>(x - '0');
num++;
} else if ('A' <= x && x <= 'F') {
buf = ((buf & 0xf) << 4) | static_cast<uint8_t>(x - ('A' - 10));
num++;
} else if ('a' <= x && x <= 'f') {
buf = ((buf & 0xf) << 4) | static_cast<uint8_t>(x - ('a' - 10));
num++;
}
}
}
///
/// Resets decoding session
///
void clear() noexcept
{
num = 0;
}
///
/// Returns maximum decoded size
///
/// \param[in] size Number of bytes to decode
///
/// \returns Maximum number of bytes for the decoded data of `size` length
///
size_t dec_size(_In_ size_t size) const noexcept
{
return (size + 1)/2;
}
protected:
uint8_t buf; ///< Internal buffer
size_t num; ///< Number of nibbles used in `buf`
};
}

2698
include/stdex/html.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,304 +0,0 @@
/*
Copyright 2016-2017 Amebis
This file is part of stdex.
stdex 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 of the License, or
(at your option) any later version.
stdex 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.
You should have received a copy of the GNU General Public License
along with stdex. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common.h"
#include <ios>
#include <istream>
#include <ostream>
namespace stdex {
namespace idrec {
///
/// Reads record ID
///
/// \param[in] stream Input stream
/// \param[out] id Record ID
/// \param[in] end Position limit. Default is -1 (no limit).
///
/// \returns
/// - \c true when succeeded
/// - \c false otherwise
///
template <class T_ID>
inline bool read_id(_In_ std::istream& stream, _Out_ T_ID &id, _In_opt_ std::streamoff end = (std::streamoff)-1)
{
if (end == (std::streamoff)-1 || stream.tellg() < end) {
stream.read((char*)&id, sizeof(id));
return stream.good();
} else
return false;
}
///
/// Skips current record data
///
/// \param[in] stream Input stream
///
/// \returns
/// - \c true when successful
/// - \c false otherwise
///
template <class T_SIZE, unsigned int ALIGN>
inline bool ignore(_In_ std::istream& stream)
{
// Read record size.
T_SIZE size;
stream.read((char*)&size, sizeof(size));
if (!stream.good()) return false;
// Skip the record data.
size += (T_SIZE)(ALIGN - size) % ALIGN;
stream.ignore(size);
if (!stream.good()) return false;
return true;
}
///
/// Finds record data
///
/// \param[in] stream Input stream
/// \param[in] id Record ID
/// \param[in] end Position limit. Default is -1 (no limit).
///
/// \returns
/// - \c true when found
/// - \c false otherwise
///
template <class T_ID, class T_SIZE, unsigned int ALIGN>
inline bool find(_In_ std::istream& stream, _In_ T_ID id, _In_opt_ std::streamoff end = (std::streamoff)-1)
{
T_ID _id;
while (end == (std::streamoff)-1 || stream.tellg() < end) {
stream.read((char*)&_id, sizeof(_id));
if (!stream.good()) return false;
if (_id == id) {
// The record was found.
return true;
} else
ignore<T_SIZE, ALIGN>(stream);
}
return false;
}
///
/// Writes record header
///
/// \param[in] stream Output stream
/// \param[in] id Record ID
///
/// \returns Position of the record header start in \p stream. Save for later \c close call.
///
template <class T_ID, class T_SIZE>
inline std::streamoff open(_In_ std::ostream& stream, _In_ T_ID id)
{
std::streamoff start = stream.tellp();
// Write ID.
if (stream.fail()) return (std::streamoff)-1;
stream.write((const char*)&id, sizeof(id));
// Write 0 as a placeholder for data size.
if (stream.fail()) return (std::streamoff)-1;
T_SIZE size = 0;
stream.write((const char*)&size, sizeof(size));
return start;
}
///
/// Updates record header
///
/// \param[in] stream Output stream
/// \param[in] start Start position of the record in \p stream
///
/// \returns Position of the record end in \p stream
///
template <class T_ID, class T_SIZE, unsigned int ALIGN>
inline std::streamoff close(_In_ std::ostream& stream, _In_ std::streamoff start)
{
std::streamoff end = stream.tellp();
T_SIZE
size = (T_SIZE)(end - start - sizeof(T_ID) - sizeof(T_SIZE)),
remainder = (T_SIZE)(ALIGN - size) % ALIGN; // Number of bytes we need to add, to keep the data integral number of ALIGN blocks long
if (remainder) {
// Append padding.
static const char padding[ALIGN] = {};
stream.write(padding, remainder);
end += remainder;
}
// Update the data size.
if (stream.fail()) return (std::streamoff)-1;
stream.seekp(start + sizeof(T_ID));
stream.write((const char*)&size, sizeof(size));
stream.seekp(end);
return end;
}
///
/// Helper class for read/write of records to/from memory
///
template <class T, class T_ID, class T_SIZE, unsigned int ALIGN>
class record
{
public:
///
/// Constructs the class
///
/// \param[in] d Reference to record data
///
inline record(_In_ T &d) : data(d) {}
///
/// Constructs the class
///
/// \param[in] d Reference to record data
///
inline record(_In_ const T &d) : data((T&)d) {}
///
/// Assignment operator
///
/// \param[in] r Source record
///
/// \returns A const reference to this struct
///
inline const record<T, T_ID, T_SIZE, ALIGN>& operator =(_In_ const record<T, T_ID, T_SIZE, ALIGN> &r)
{
data = r.data;
return *this;
}
///
/// Writes record header
///
/// \param[in] stream Output stream
///
/// \returns Position of the record header start in \p stream. Save for later \c close call.
///
static inline std::streamoff open(_In_ std::ostream& stream)
{
return stdex::idrec::open<T_ID, T_SIZE>(stream, id);
}
///
/// Updates record header
///
/// \param[in] stream Output stream
/// \param[in] start Start position of the record in \p stream
///
/// \returns Position of the record end in \p stream
///
static inline std::streamoff close(_In_ std::ostream& stream, _In_ std::streamoff start)
{
return stdex::idrec::close<T_ID, T_SIZE, ALIGN>(stream, start);
}
///
/// Finds record data
///
/// \param[in] stream Input stream
/// \param[in] end Position limit. Default is -1 (no limit).
///
/// \returns
/// - \c true when found
/// - \c false otherwise
///
static inline bool find(_In_ std::istream& stream, _In_opt_ std::streamoff end = (std::streamoff)-1)
{
return stdex::idrec::find<T_ID, T_SIZE, ALIGN>(stream, id, end);
}
static const T_ID id; ///< Record id
T &data; ///< Record data reference
};
};
};
///
/// Writes record to a stream
///
/// \param[in] stream Output stream
/// \param[in] r Record
///
/// \returns The stream \p stream
///
template <class T, class T_ID, class T_SIZE, unsigned int ALIGN>
inline std::ostream& operator <<(_In_ std::ostream& stream, _In_ const stdex::idrec::record<T, T_ID, T_SIZE, ALIGN> r)
{
// Parameter r does not need to be passed by reference. It has only one field (data), which is a reference itself already. The id field is static anyway.
std::streamoff start = r.open(stream);
if (stream.fail()) return stream;
stream << r.data;
r.close(stream, start);
return stream;
}
///
/// Reads record from a stream
///
/// \param[in] stream Input stream
/// \param[out] r Record
///
/// \returns The stream \p stream
///
template <class T, class T_ID, class T_SIZE, unsigned int ALIGN>
inline std::istream& operator >>(_In_ std::istream& stream, _Out_ stdex::idrec::record<T, T_ID, T_SIZE, ALIGN> r)
{
// Parameter r does not need to be passed by reference. It has only one field (data), which is a reference itself already. The id field is static anyway.
// Read data size.
T_SIZE size;
stream.read((char*)&size, sizeof(size));
if (!stream.good()) return stream;
// Read data.
std::streamoff start = stream.tellg();
stream >> r.data; // TODO: operator >> should not read past the record data! Make a size limited stream and read from it instead.
size += (T_SIZE)(ALIGN - size) % ALIGN;
stream.seekg(start + size);
return stream;
}

561
include/stdex/idrec.hpp Normal file
View File

@ -0,0 +1,561 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "stream.hpp"
#include <ios>
#include <istream>
#include <ostream>
namespace stdex {
namespace idrec {
///
/// Reads record ID
///
/// \param[in] stream Input stream
/// \param[out] id Record ID
/// \param[in] end Position limit. Default is -1 (no limit).
///
/// \returns
/// - \c true when succeeded
/// - \c false otherwise
///
template <class T_id>
_Success_(return) bool read_id(_In_ std::istream& stream, _Out_ T_id &id, _In_opt_ std::streamoff end = (std::streamoff)-1)
{
if (end == (std::streamoff)-1 || stream.tellg() < end) {
stream.read((char*)&id, sizeof(id));
return stream.good();
} else
return false;
}
///
/// Reads record ID
///
/// \param[in] stream Input stream
/// \param[out] id Record ID
/// \param[in] end Position limit. Default is -1 (no limit).
///
/// \returns
/// - \c true when succeeded
/// - \c false otherwise
///
template <class T_id>
_Success_(return) bool read_id(_In_ stdex::stream::basic_file& stream, _Out_ T_id &id, _In_opt_ stdex::stream::fpos_t end = stdex::stream::fpos_max)
{
if (end == stdex::stream::fpos_max || stream.tell() < end) {
stream >> id;
return stream.ok();
} else
return false;
}
///
/// Calculates required padding
///
/// \param[in] size Actual data size
///
/// \return Number of bytes needed to add to the data to align it on `N_align` boundary
///
template <class T_size, T_size N_align>
T_size padding(_In_ T_size size)
{
return (N_align - (size % N_align)) % N_align;
}
///
/// Skips current record data
///
/// \param[in] stream Input stream
///
/// \returns
/// - \c true when successful
/// - \c false otherwise
///
template <class T_size, T_size N_align>
bool ignore(_In_ std::istream& stream)
{
// Read record size.
T_size size;
stream.read((char*)&size, sizeof(size));
if (!stream.good()) _Unlikely_ return false;
// Skip the record data.
size += padding<T_size, N_align>(size);
stream.ignore(size);
if (!stream.good()) _Unlikely_ return false;
return true;
}
///
/// Skips current record data
///
/// \param[in] stream Input stream
///
/// \returns
/// - \c true when successful
/// - \c false otherwise
///
template <class T_size, T_size N_align>
bool ignore(_In_ stdex::stream::basic& stream)
{
// Read record size.
T_size size;
stream >> size;
if (!stream.ok()) _Unlikely_ return false;
// Skip the record data.
size += padding<T_size, N_align>(size);
stream.skip(size);
if (!stream.ok()) _Unlikely_ return false;
return true;
}
///
/// Finds record data
///
/// \param[in] stream Input stream
/// \param[in] id Record ID
/// \param[in] end Position limit. Default is -1 (no limit).
///
/// \returns
/// - \c true when found
/// - \c false otherwise
///
template <class T_id, class T_size, T_size N_align>
bool find(_In_ std::istream& stream, _In_ T_id id, _In_opt_ std::streamoff end = (std::streamoff)-1)
{
T_id _id;
while (end == (std::streamoff)-1 || stream.tellg() < end) {
stream.read((char*)&_id, sizeof(_id));
if (!stream.good()) _Unlikely_ return false;
if (_id == id) {
// The record was found.
return true;
} else
ignore<T_size, N_align>(stream);
}
return false;
}
///
/// Finds record data
///
/// \param[in] stream Input stream
/// \param[in] id Record ID
/// \param[in] end Position limit. Default is -1 (no limit).
///
/// \returns
/// - \c true when found
/// - \c false otherwise
///
template <class T_id, class T_size, T_size N_align>
bool find(_In_ stdex::stream::basic_file& stream, _In_ T_id id, _In_opt_ stdex::stream::fpos_t end = stdex::stream::fpos_max)
{
T_id _id;
while (end == stdex::stream::fpos_max || stream.tell() < end) {
stream >> _id;
if (!stream.ok()) _Unlikely_ return false;
if (_id == id) {
// The record was found.
return true;
} else
ignore<T_size, N_align>(stream);
}
return false;
}
///
/// Writes record header
///
/// \param[in] stream Output stream
/// \param[in] id Record ID
///
/// \returns Position of the record header start in \p stream. Save for later \c close call.
///
template <class T_id, class T_size>
std::streamoff open(_In_ std::ostream& stream, _In_ T_id id)
{
std::streamoff start = stream.tellp();
// Write ID.
if (stream.fail()) _Unlikely_ return (std::streamoff)-1;
stream.write((const char*)&id, sizeof(id));
// Write 0 as a placeholder for data size.
if (stream.fail()) _Unlikely_ return (std::streamoff)-1;
T_size size = 0;
stream.write((const char*)&size, sizeof(size));
return start;
}
///
/// Writes record header
///
/// \param[in] stream Output stream
/// \param[in] id Record ID
///
/// \returns Position of the record header start in \p stream. Save for later \c close call.
///
template <class T_id, class T_size>
stdex::stream::fpos_t open(_In_ stdex::stream::basic_file& stream, _In_ T_id id)
{
auto start = stream.tell();
// Write ID.
stream << id;
// Write 0 as a placeholder for data size.
stream << static_cast<T_size>(0);
return start;
}
///
/// Updates record header
///
/// \param[in] stream Output stream
/// \param[in] start Start position of the record in \p stream
///
/// \returns Position of the record end in \p stream
///
template <class T_id, class T_size, T_size N_align>
std::streamoff close(_In_ std::ostream& stream, _In_ std::streamoff start)
{
std::streamoff end = stream.tellp();
T_size
size = static_cast<T_size>(end - start - sizeof(T_id) - sizeof(T_size)),
remainder = padding<T_size, N_align>(size);
if (remainder) {
// Append padding.
static const char padding[N_align] = {};
stream.write(padding, remainder);
end += remainder;
}
// Update the data size.
if (stream.fail()) _Unlikely_ return (std::streamoff)-1;
stream.seekp(start + sizeof(T_id));
stream.write(reinterpret_cast<const char*>(&size), sizeof(size));
stream.seekp(end);
return end;
}
///
/// Updates record header
///
/// \param[in] stream Output stream
/// \param[in] start Start position of the record in \p stream
///
/// \returns Position of the record end in \p stream
///
template <class T_id, class T_size, T_size N_align>
stdex::stream::fpos_t close(_In_ stdex::stream::basic_file& stream, _In_ stdex::stream::fpos_t start)
{
auto end = stream.tell();
T_size
size = static_cast<T_size>(end - start - sizeof(T_id) - sizeof(T_size)),
remainder = padding<T_size, N_align>(size);
if (remainder) {
// Append padding.
static const char padding[N_align] = {};
stream.write_array(padding, sizeof(char), remainder);
end += remainder;
}
// Update the data size.
if (!stream.ok()) _Unlikely_ return stdex::stream::fpos_max;
stream.seekbeg(start + sizeof(T_id));
stream << size;
stream.seekbeg(end);
return end;
}
///
/// Helper class for read/write of records to/from memory
///
template <class T, class T_id, const T_id ID, class T_size, T_size N_align>
class record
{
public:
///
/// Constructs the class
///
/// \param[in] d Reference to record data
///
record(_In_ T &d) : data(d) {}
///
/// Constructs the class
///
/// \param[in] d Reference to record data
///
record(_In_ const T &d) : data((T&)d) {}
///
/// Returns record id
///
static constexpr T_id id()
{
return ID;
}
///
/// Assignment operator
///
/// \param[in] r Source record
///
/// \returns A const reference to this struct
///
const record<T, T_id, ID, T_size, N_align>& operator =(_In_ const record<T, T_id, ID, T_size, N_align> &r)
{
data = r.data;
return *this;
}
///
/// Writes record header
///
/// \param[in] stream Output stream
///
/// \returns Position of the record header start in \p stream. Save for later \c close call.
///
static std::streamoff open(_In_ std::ostream& stream)
{
return stdex::idrec::open<T_id, T_size>(stream, ID);
}
///
/// Writes record header
///
/// \param[in] stream Output stream
///
/// \returns Position of the record header start in \p stream. Save for later \c close call.
///
static stdex::stream::fpos_t open(_In_ stdex::stream::basic_file& stream)
{
return stdex::idrec::open<T_id, T_size>(stream, ID);
}
///
/// Updates record header
///
/// \param[in] stream Output stream
/// \param[in] start Start position of the record in \p stream
///
/// \returns Position of the record end in \p stream
///
static std::streamoff close(_In_ std::ostream& stream, _In_ std::streamoff start)
{
return stdex::idrec::close<T_id, T_size, N_align>(stream, start);
}
///
/// Updates record header
///
/// \param[in] stream Output stream
/// \param[in] start Start position of the record in \p stream
///
/// \returns Position of the record end in \p stream
///
static stdex::stream::fpos_t close(_In_ stdex::stream::basic_file& stream, _In_ stdex::stream::fpos_t start)
{
return stdex::idrec::close<T_id, T_size, N_align>(stream, start);
}
///
/// Finds record data
///
/// \param[in] stream Input stream
/// \param[in] end Position limit. Default is -1 (no limit).
///
/// \returns
/// - \c true when found
/// - \c false otherwise
///
static bool find(_In_ std::istream& stream, _In_opt_ std::streamoff end = (std::streamoff)-1)
{
return stdex::idrec::find<T_id, T_size, N_align>(stream, ID, end);
}
///
/// Finds record data
///
/// \param[in] stream Input stream
/// \param[in] end Position limit. Default is stdex::stream::fpos_max (no limit).
///
/// \returns
/// - \c true when found
/// - \c false otherwise
///
static bool find(_In_ stdex::stream::basic_file& stream, _In_opt_ stdex::stream::fpos_t end = stdex::stream::fpos_max)
{
return stdex::idrec::find<T_id, T_size, N_align>(stream, ID, end);
}
T &data; ///< Record data reference
///
/// Writes record to a stream
///
/// \param[in] stream Output stream
/// \param[in] r Record
///
/// \returns The stream \p stream
///
friend std::ostream& operator <<(_In_ std::ostream& stream, _In_ const record<T, T_id, ID, T_size, N_align> r)
{
// Parameter r does not need to be passed by reference. It has only one field (data), which is a reference itself already.
auto start = r.open(stream);
if (stream.fail()) _Unlikely_ return stream;
stream << r.data;
r.close(stream, start);
return stream;
}
///
/// Writes record to a file
///
/// \param[in] stream Output file
/// \param[in] r Record
///
/// \returns The stream \p stream
///
friend stdex::stream::basic_file& operator <<(_In_ stdex::stream::basic_file& stream, _In_ const record<T, T_id, ID, T_size, N_align> r)
{
// Parameter r does not need to be passed by reference. It has only one field (data), which is a reference itself already.
auto start = r.open(stream);
if (!stream.ok()) _Unlikely_ return stream;
stream << r.data;
r.close(stream, start);
return stream;
}
///
/// Writes record to a stream
///
/// \param[in] stream Output stream
/// \param[in] r Record
///
/// \returns The stream \p stream
///
friend stdex::stream::basic& operator <<(_In_ stdex::stream::basic& stream, _In_ const record<T, T_id, ID, T_size, N_align> r)
{
// Parameter r does not need to be passed by reference. It has only one field (data), which is a reference itself already.
stdex::stream::memory_file temp;
auto start = r.open(temp);
if (!temp.ok()) _Unlikely_ return stream;
temp << r.data;
r.close(temp, start);
temp.seekbeg(0);
stream.write_stream(temp);
return stream;
}
///
/// Reads record from a stream
///
/// \param[in] stream Input stream
/// \param[out] r Record
///
/// \returns The stream \p stream
///
friend std::istream& operator >>(_In_ std::istream& stream, _In_ record<T, T_id, ID, T_size, N_align> r)
{
// Parameter r does not need to be passed by reference. It has only one field (data), which is a reference itself already.
// Read data size.
T_size size;
stream.read((char*)&size, sizeof(size));
if (!stream.good()) _Unlikely_ return stream;
// Read data.
std::streamoff start = stream.tellg();
stream >> r.data; // TODO: operator >> should not read past the record data! Make a size limited stream and read from it instead.
if (!stream.good()) _Unlikely_ return stream;
size += padding<T_size, N_align>(size);
stream.seekg(start + size);
return stream;
}
///
/// Reads record from a file
///
/// \param[in] stream Input file
/// \param[out] r Record
///
/// \returns The stream \p stream
///
friend stdex::stream::basic_file& operator >>(_In_ stdex::stream::basic_file& stream, _In_ record<T, T_id, ID, T_size, N_align> r)
{
// Parameter r does not need to be passed by reference. It has only one field (data), which is a reference itself already.
// Read data size.
T_size size;
stream >> size;
if (!stream.ok()) _Unlikely_ return stream;
// Read data.
auto start = stream.tell();
{
stdex::stream::limiter limiter(stream, size, 0);
limiter >> r.data;
if (limiter.state() == stdex::stream::state_t::fail) _Unlikely_ return stream;
}
size += padding<T_size, N_align>(size);
stream.seekbeg(start + static_cast<stdex::stream::fpos_t>(size));
return stream;
}
///
/// Reads record from a stream
///
/// \param[in] stream Input stream
/// \param[out] r Record
///
/// \returns The stream \p stream
///
friend stdex::stream::basic& operator >>(_In_ stdex::stream::basic& stream, _In_ record<T, T_id, ID, T_size, N_align> r)
{
// Parameter r does not need to be passed by reference. It has only one field (data), which is a reference itself already.
// Read data size.
T_size size;
stream >> size;
if (!stream.ok()) _Unlikely_ return stream;
{
stdex::stream::limiter limiter(stream, size, 0);
limiter >> r.data;
if (limiter.state() == stdex::stream::state_t::fail) _Unlikely_ return stream;
limiter.skip(limiter.read_limit);
}
stream.skip(padding<T_size, N_align>(size));
return stream;
}
};
};
};

234
include/stdex/interval.hpp Normal file
View File

@ -0,0 +1,234 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include <vector>
namespace stdex
{
///
/// Numerical interval
///
template <class T>
struct interval
{
T start; ///< interval start
T end; ///< interval end
///
/// Constructs an invalid interval
///
interval() noexcept : start(static_cast<T>(1)), end(static_cast<T>(0)) {}
///
/// Constructs a zero-size interval
///
/// \param[in] x Interval start and end value
///
interval(_In_ T x) noexcept : start(x), end(x) {}
///
/// Constructs an interval
///
/// \param[in] _start Interval start value
/// \param[in] _end Interval end value
///
interval(_In_ T _start, _In_ T _end) noexcept : start(_start), end(_end) {}
///
/// Returns interval size
///
/// \returns Interval size or 0 if interval is invalid
///
T size() const { return start <= end ? end - start : 0; }
///
/// Is interval empty?
///
/// \returns true if interval is empty or false otherwise
///
bool empty() const { return start >= end; }
///
/// Invalidates interval
///
void invalidate()
{
start = static_cast<T>(1);
end = static_cast<T>(0);
}
///
/// Is interval valid?
///
/// \returns true if interval is valid or false otherwise
///
operator bool() const { return start <= end; }
///
/// Is value in interval?
///
/// \param[in] x Value to test
///
/// \returns true if x is in [start, end) or false otherwise
///
bool contains(_In_ T x) const { return start <= x && x < end; }
///
/// Adds two intervals by components
///
/// \param[in] other Second interval
///
/// \returns Resulting interval
///
interval operator+(_In_ const interval& other) const
{
return interval(start + other.start, end + other.end);
}
///
/// Moves interval towards the end by a number
///
/// \param[in] x Amount to move for
///
/// \returns Moved interval
///
interval operator+(_In_ const T x) const
{
return interval(start + x, end + x);
}
///
/// Moves interval towards the end by a number
///
/// \param[in] x Amount to move for
///
/// \returns Resulting interval
///
interval operator+=(_In_ const T x)
{
start += x;
end += x;
return *this;
}
///
/// Moves interval towards the end by one
///
/// \returns Moved interval
///
interval operator++()
{
++start;
++end;
return *this;
}
///
/// Moves interval towards the end by one
///
/// \returns Original interval
///
interval operator++(int) // Postfix increment operator.
{
interval r = *this;
++start;
++end;
return r;
}
///
/// Subtracts two intervals by components
///
/// \param[in] other Second interval
///
/// \returns Resulting interval
///
interval operator-(_In_ const interval& other) const
{
return interval(start - other.start, end - other.end);
}
///
/// Moves interval towards the beginning by a number
///
/// \param[in] x Amount to move for
///
/// \returns Moved interval
///
interval operator-(_In_ const T x) const
{
return interval(start - x, end - x);
}
///
/// Moves interval towards the beginning by a number
///
/// \param[in] x Amount to move for
///
/// \returns Resulting interval
///
interval operator-=(_In_ const T x)
{
start -= x;
end -= x;
return *this;
}
///
/// Moves interval towards the beginning by one
///
/// \returns Moved interval
///
interval operator--()
{
--start;
--end;
return *this;
}
///
/// Moves interval towards the begginning by one
///
/// \returns Original interval
///
interval operator--(int) // Postfix decrement operator.
{
interval r = *this;
--start;
--end;
return r;
}
///
/// Are intervals identical?
///
/// \param[in] other Second interval to compare
///
/// \returns true if intervals are identical or false otherwise
///
bool operator==(_In_ const interval& other) const
{
return start == other.start && end == other.end;
}
///
/// Are intervals different?
///
/// \param[in] other Second interval to compare
///
/// \returns true if intervals are different or false otherwise
///
bool operator!=(_In_ const interval& other) const
{
return !operator ==(other);
}
};
template <class T, class AX = std::allocator<interval<T>>>
using interval_vector = std::vector<interval<T>, AX>;
}

100
include/stdex/json.hpp Normal file
View File

@ -0,0 +1,100 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include <string>
namespace stdex
{
namespace json
{
///
/// Appends escaped JSON string
///
/// \param[in,out] dst String to append to
/// \param[in] src Source string
/// \param[in] num_chars Code unit limit in string `src`
///
template<class TR = std::char_traits<char>, class AX = std::allocator<char>>
void escape(
_Inout_ std::basic_string<char, TR, AX>& dst,
_In_reads_or_z_opt_(num_chars) const char* src, _In_ size_t num_chars = SIZE_MAX)
{
stdex_assert(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i]; ++i) {
switch (src[i]) {
case '\"': dst += "\\\""; break;
case '\\': dst += "\\\\"; break;
case '/' : dst += "\\/"; break;
case '\b': dst += "\\b"; break;
case '\f': dst += "\\f"; break;
case '\n': dst += "\\n"; break;
case '\r': dst += "\\r"; break;
case '\t': dst += "\\t"; break;
default: dst += src[i]; break;
}
}
}
///
/// Appends escaped JSON string
///
/// \param[in,out] dst String to append to
/// \param[in] src Source string
/// \param[in] num_chars Code unit limit in string `src`
///
template<class TR = std::char_traits<wchar_t>, class AX = std::allocator<wchar_t>>
void escape(
_Inout_ std::basic_string<wchar_t, TR, AX>& dst,
_In_reads_or_z_opt_(num_chars) const wchar_t* src, _In_ size_t num_chars = SIZE_MAX)
{
stdex_assert(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i]; ++i) {
switch (src[i]) {
case L'\"': dst += L"\\\""; break;
case L'\\': dst += L"\\\\"; break;
case L'/' : dst += L"\\/"; break;
case L'\b': dst += L"\\b"; break;
case L'\f': dst += L"\\f"; break;
case L'\n': dst += L"\\n"; break;
case L'\r': dst += L"\\r"; break;
case L'\t': dst += L"\\t"; break;
default: dst += src[i]; break;
}
}
}
///
/// Appends escaped JSON string
///
/// \param[in,out] dst String to append to
/// \param[in] src Source string
///
template<class T, size_t N, class TR = std::char_traits<T>, class AX = std::allocator<T>>
void escape(
_Inout_ std::basic_string<T, TR, AX>& dst,
_In_ const T (&src)[N])
{
escape(dst, src, N);
}
///
/// Appends escaped JSON string
///
/// \param[in,out] dst String to append to
/// \param[in] src Source string
///
template<class T, class TR_dst = std::char_traits<T>, class AX_dst = std::allocator<T>, class TR_src = std::char_traits<T>, class AX_src = std::allocator<T>>
void escape(
_Inout_ std::basic_string<T, TR_dst, AX_dst>& dst,
_In_ const std::basic_string<T, TR_src, AX_src>& src)
{
escape(dst, src.data(), src.size());
}
}
}

996
include/stdex/langid.hpp Normal file
View File

@ -0,0 +1,996 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2024-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "string.hpp"
#include "unicode.hpp"
#ifdef _WIN32
#include "windows.h"
#endif
#include <stddef.h>
#include <stdint.h>
#include <map>
#include <string>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wexit-time-destructors"
#endif
namespace stdex
{
#ifdef _WIN32
using langid = LANGID;
#else
using langid = uint16_t;
#endif
constexpr langid langid_neutral = 0x0;
constexpr langid langid_unknown = 0x7f;
constexpr langid langid_system = 0x800;
constexpr langid sublangid_neutral = 0 << 10; ///< Language neutral
constexpr langid sublangid_default = 1 << 10; ///< User default
constexpr langid sublangid_sys_default = 2 << 10; ///< System default
constexpr langid sublangid_custom_default = 3 << 10; ///< Default custom language/locale
constexpr langid sublangid_custom_unspecified = 4 << 10; ///< Custom language/locale
constexpr langid sublangid_ui_custom_default = 5 << 10; ///< Default custom MUI language/locale
///
/// Simplifies language code to base language
///
/// \param[in] lang Language code
///
/// \return Language code of the base language
///
inline constexpr langid primary_langid(_In_ langid lang)
{
return lang & 0x3ff;
}
///
/// Isolates language variant from the language code
///
/// \param[in] lang Language code
///
/// \return Language variant code
///
inline constexpr langid sub_langid(_In_ langid lang)
{
return lang & 0xfc00;
}
///
/// Parses language name and returns matching language code
///
/// \param[in] rfc1766 Language name in RFC1766 syntax
///
/// \returns Language code or `langid_unknown` if match not found
///
inline langid langid_from_rfc1766(_In_z_ const char* rfc1766)
{
struct stricmp_less
{
bool operator()(_In_z_ const char* str1, _In_z_ const char* str2) const
{
stdex_assert(str1);
stdex_assert(str2);
size_t i;
for (i = 0; ; ++i) {
auto a = stdex::tolower(str1[i]);
auto b = stdex::tolower(str2[i]);
if (!a && !b) return false;
if (!b) return false;
if (!a) return true;
auto a_punct = stdex::ispunct(a);
auto b_punct = stdex::ispunct(b);
if (a_punct && b_punct) continue;
if (b_punct) return false;
if (a_punct) return true;
if (a > b) return false;
if (a < b) return true;
}
}
};
static const std::map<const char*, int, stricmp_less> languages = {
{"af-ZA", 0x436}, // Afrikaans (South Africa)
{"af", 0x36}, // Afrikaans
{"am-ET", 0x45e}, // Amharic (Ethiopia)
{"am", 0x5e}, // Amharic
{"ar-AE", 0x3801}, // Arabic (United Arab Emirates)
{"ar-BH", 0x3c01}, // Arabic (Bahrain)
{"ar-DZ", 0x1401}, // Arabic (Algeria)
{"ar-EG", 0xc01}, // Arabic (Egypt)
{"ar-IQ", 0x801}, // Arabic (Iraq)
{"ar-JO", 0x2c01}, // Arabic (Jordan)
{"ar-KW", 0x3401}, // Arabic (Kuwait)
{"ar-LB", 0x3001}, // Arabic (Lebanon)
{"ar-LY", 0x1001}, // Arabic (Libya)
{"ar-MA", 0x1801}, // Arabic (Morocco)
{"ar-OM", 0x2001}, // Arabic (Oman)
{"ar-QA", 0x4001}, // Arabic (Qatar)
{"ar-SA", 0x401}, // Arabic (Saudi Arabia)
{"ar-SY", 0x2801}, // Arabic (Syria)
{"ar-TN", 0x1c01}, // Arabic (Tunisia)
{"ar-YE", 0x2401}, // Arabic (Yemen)
{"ar", 0x1}, // Arabic
{"arn-CL", 0x47a}, // Mapuche (Chile)
{"arn", 0x7a}, // Mapuche
{"as-IN", 0x44d}, // Assamese (India)
{"as", 0x4d}, // Assamese
{"az-Cyrl-AZ", 0x742c}, // Azerbaijani (Cyrillic, Azerbaijan)
{"az-Cyrl-AZ", 0x82c}, // Azerbaijani (Cyrillic, Azerbaijan)
{"az-Latn-AZ", 0x42c}, // Azerbaijani (Latin, Azerbaijan)
{"az-Latn-AZ", 0x782c}, // Azerbaijani (Latin, Azerbaijan)
{"az", 0x2c}, // Azerbaijani
{"ba-RU", 0x46d}, // Bashkir (Russia)
{"ba", 0x6d}, // Bashkir
{"be-BY", 0x423}, // Belarusian (Belarus)
{"be", 0x23}, // Belarusian
{"bg-BG", 0x402}, // Bulgarian (Bulgaria)
{"bg", 0x2}, // Bulgarian
{"bin-NG", 0x466}, // Edo (Nigeria)
{"bin", 0x66}, // Edo
{"bn-BD", 0x845}, // Bangla (Bangladesh)
{"bn-IN", 0x445}, // Bengali (India)
{"bn", 0x45}, // Bangla
{"bo-CN", 0x451}, // Tibetan (China)
{"bo", 0x51}, // Tibetan
{"br-FR", 0x47e}, // Breton (France)
{"br", 0x7e}, // Breton
{"bs-Cyrl-BA", 0x201a}, // Bosnian (Cyrillic, Bosnia and Herzegovina)
{"bs-Cyrl-BA", 0x641a}, // Bosnian (Cyrillic, Bosnia and Herzegovina)
{"bs-Latn-BA", 0x141a}, // Bosnian (Latin, Bosnia & Herzegovina)
{"bs-Latn-BA", 0x681a}, // Bosnian (Latin, Bosnia & Herzegovina)
{"bs-Latn-BA", 0x781a}, // Bosnian (Latin, Bosnia & Herzegovina)
{"ca-ES-valencia", 0x803}, // Valencian (Spain)
{"ca-ES", 0x403}, // Catalan (Catalan)
{"ca", 0x3}, // Catalan
{"chr-Cher-US", 0x45c}, // Cherokee (Cherokee, United States)
{"chr-Cher-US", 0x7c5c}, // Cherokee (Cherokee, United States)
{"chr", 0x5c}, // Cherokee
{"co-FR", 0x483}, // Corsican (France)
{"co", 0x83}, // Corsican
{"cs-CZ", 0x405}, // Czech (Czechia)
{"cs", 0x5}, // Czech
{"cy-GB", 0x452}, // Welsh (United Kingdom)
{"cy", 0x52}, // Welsh
{"da-DK", 0x406}, // Danish (Denmark)
{"da", 0x6}, // Danish
{"de-AT", 0xc07}, // German (Austria)
{"de-CH", 0x807}, // German (Switzerland)
{"de-DE", 0x407}, // German (Germany)
{"de-LI", 0x1407}, // German (Liechtenstein)
{"de-LU", 0x1007}, // German (Luxembourg)
{"de", 0x7}, // German
{"dsb-DE", 0x7c2e}, // Lower Sorbian (Germany)
{"dsb-DE", 0x82e}, // Lower Sorbian (Germany)
{"dv-MV", 0x465}, // Divehi (Maldives)
{"dv", 0x65}, // Divehi
{"dz-BT", 0xc51}, // Dzongkha (Bhutan)
{"el-GR", 0x408}, // Greek (Greece)
{"el", 0x8}, // Greek
{"en-029", 0x2409}, // English (Caribbean)
{"en-AE", 0x4c09}, // English (United Arab Emirates)
{"en-AU", 0xc09}, // English (Australia)
{"en-BZ", 0x2809}, // English (Belize)
{"en-CA", 0x1009}, // English (Canada)
{"en-GB", 0x809}, // English (United Kingdom)
{"en-HK", 0x3c09}, // English (Hong Kong SAR)
{"en-ID", 0x3809}, // English (Indonesia)
{"en-IE", 0x1809}, // English (Ireland)
{"en-IN", 0x4009}, // English (India)
{"en-JM", 0x2009}, // English (Jamaica)
{"en-MY", 0x4409}, // English (Malaysia)
{"en-NZ", 0x1409}, // English (New Zealand)
{"en-PH", 0x3409}, // English (Philippines)
{"en-SG", 0x4809}, // English (Singapore)
{"en-TT", 0x2c09}, // English (Trinidad & Tobago)
{"en-US", 0x409}, // English (United States)
{"en-ZA", 0x1c09}, // English (South Africa)
{"en-ZW", 0x3009}, // English (Zimbabwe)
{"en", 0x9}, // English
{"es-419", 0x580a}, // Spanish (Latin America)
{"es-AR", 0x2c0a}, // Spanish (Argentina)
{"es-BO", 0x400a}, // Spanish (Bolivia)
{"es-CL", 0x340a}, // Spanish (Chile)
{"es-CO", 0x240a}, // Spanish (Colombia)
{"es-CR", 0x140a}, // Spanish (Costa Rica)
{"es-CU", 0x5c0a}, // Spanish (Cuba)
{"es-DO", 0x1c0a}, // Spanish (Dominican Republic)
{"es-EC", 0x300a}, // Spanish (Ecuador)
{"es-ES_tradnl", 0x40a}, // Spanish (Spain, Traditional Sort)
{"es-ES", 0xc0a}, // Spanish (Spain, International Sort)
{"es-GT", 0x100a}, // Spanish (Guatemala)
{"es-HN", 0x480a}, // Spanish (Honduras)
{"es-MX", 0x80a}, // Spanish (Mexico)
{"es-NI", 0x4c0a}, // Spanish (Nicaragua)
{"es-PA", 0x180a}, // Spanish (Panama)
{"es-PE", 0x280a}, // Spanish (Peru)
{"es-PR", 0x500a}, // Spanish (Puerto Rico)
{"es-PY", 0x3c0a}, // Spanish (Paraguay)
{"es-SV", 0x440a}, // Spanish (El Salvador)
{"es-US", 0x540a}, // Spanish (United States)
{"es-UY", 0x380a}, // Spanish (Uruguay)
{"es-VE", 0x200a}, // Spanish (Venezuela)
{"es", 0xa}, // Spanish
{"et-EE", 0x425}, // Estonian (Estonia)
{"et", 0x25}, // Estonian
{"eu-ES", 0x42d}, // Basque (Basque)
{"eu", 0x2d}, // Basque
{"fa-AF", 0x48c}, // Persian (Afghanistan)
{"fa-IR", 0x429}, // Persian (Iran)
{"fa", 0x29}, // Persian
{"fa", 0x8c}, // Persian
{"ff-Latn-NG", 0x467}, // Fulah (Latin, Nigeria)
{"ff-Latn-SN", 0x7c67}, // Fulah (Latin, Senegal)
{"ff-Latn-SN", 0x867}, // Fulah (Latin, Senegal)
{"ff", 0x67}, // Fulah
{"fi-FI", 0x40b}, // Finnish (Finland)
{"fi", 0xb}, // Finnish
{"fil-PH", 0x464}, // Filipino (Philippines)
{"fil", 0x64}, // Filipino
{"fo-FO", 0x438}, // Faroese (Faroe Islands)
{"fo", 0x38}, // Faroese
{"fr-029", 0x1c0c}, // French (Caribbean)
{"fr-BE", 0x80c}, // French (Belgium)
{"fr-CA", 0xc0c}, // French (Canada)
{"fr-CD", 0x240c}, // French Congo (DRC)
{"fr-CH", 0x100c}, // French (Switzerland)
{"fr-CI", 0x300c}, // French (Côte dIvoire)
{"fr-CM", 0x2c0c}, // French (Cameroon)
{"fr-FR", 0x40c}, // French (France)
{"fr-HT", 0x3c0c}, // French (Haiti)
{"fr-LU", 0x140c}, // French (Luxembourg)
{"fr-MA", 0x380c}, // French (Morocco)
{"fr-MC", 0x180c}, // French (Monaco)
{"fr-ML", 0x340c}, // French (Mali)
{"fr-RE", 0x200c}, // French (Réunion)
{"fr-SN", 0x280c}, // French (Senegal)
{"fr", 0xc}, // French
{"fy-NL", 0x462}, // Western Frisian (Netherlands)
{"fy", 0x62}, // Western Frisian
{"ga-IE", 0x83c}, // Irish (Ireland)
{"ga", 0x3c}, // Irish
{"gd-GB", 0x491}, // Scottish Gaelic (United Kingdom)
{"gd", 0x91}, // Scottish Gaelic
{"gl-ES", 0x456}, // Galician (Galician)
{"gl", 0x56}, // Galician
{"gn-PY", 0x474}, // Guarani (Paraguay)
{"gn", 0x74}, // Guarani
{"gsw-FR", 0x484}, // Alsatian (France)
{"gsw", 0x84}, // Swiss German
{"gu-IN", 0x447}, // Gujarati (India)
{"gu", 0x47}, // Gujarati
{"ha-Latn-NG", 0x468}, // Hausa (Latin, Nigeria)
{"ha-Latn-NG", 0x7c68}, // Hausa (Latin, Nigeria)
{"ha", 0x68}, // Hausa
{"haw-US", 0x475}, // Hawaiian (United States)
{"haw", 0x75}, // Hawaiian
{"he-IL", 0x40d}, // Hebrew (Israel)
{"he", 0xd}, // Hebrew
{"hi-IN", 0x439}, // Hindi (India)
{"hi", 0x39}, // Hindi
{"hr-BA", 0x101a}, // Croatian (Bosnia & Herzegovina)
{"hr-HR", 0x41a}, // Croatian (Croatia)
{"hr", 0x1a}, // Croatian
{"hsb-DE", 0x42e}, // Upper Sorbian (Germany)
{"hsb", 0x2e}, // Upper Sorbian
{"hu-HU", 0x40e}, // Hungarian (Hungary)
{"hu", 0xe}, // Hungarian
{"hy-AM", 0x42b}, // Armenian (Armenia)
{"hy", 0x2b}, // Armenian
{"ibb-NG", 0x469}, // Ibibio (Nigeria)
{"ibb", 0x69}, // Ibibio
{"id-ID", 0x421}, // Indonesian (Indonesia)
{"id", 0x21}, // Indonesian
{"ig-NG", 0x470}, // Igbo (Nigeria)
{"ig", 0x70}, // Igbo
{"ii-CN", 0x478}, // Yi (China)
{"ii", 0x78}, // Yi
{"is-IS", 0x40f}, // Icelandic (Iceland)
{"is", 0xf}, // Icelandic
{"it-CH", 0x810}, // Italian (Switzerland)
{"it-IT", 0x410}, // Italian (Italy)
{"it", 0x10}, // Italian
{"iu-Cans-CA", 0x45d}, // Inuktitut (Syllabics, Canada)
{"iu-Cans-CA", 0x785d}, // Inuktitut (Syllabics, Canada)
{"iu-Latn-CA", 0x7c5d}, // Inuktitut (Latin, Canada)
{"iu-Latn-CA", 0x85d}, // Inuktitut (Latin, Canada)
{"iu", 0x5d}, // Inuktitut
{"ja-JP", 0x411}, // Japanese (Japan)
{"ja", 0x11}, // Japanese
{"ka-GE", 0x437}, // Georgian (Georgia)
{"ka", 0x37}, // Georgian
{"kk-KZ", 0x43f}, // Kazakh (Kazakhstan)
{"kk", 0x3f}, // Kazakh
{"kl-GL", 0x46f}, // Kalaallisut (Greenland)
{"kl", 0x6f}, // Kalaallisut
{"km-KH", 0x453}, // Khmer (Cambodia)
{"km", 0x53}, // Khmer
{"kn-IN", 0x44b}, // Kannada (India)
{"kn", 0x4b}, // Kannada
{"ko-KR", 0x412}, // Korean (Korea)
{"ko", 0x12}, // Korean
{"kok-IN", 0x457}, // Konkani (India)
{"kok", 0x57}, // Konkani
{"kr-Latn-NG", 0x471}, // Kanuri (Latin, Nigeria)
{"kr", 0x71}, // Kanuri
{"ks-Arab-IN", 0x460}, // Kashmiri (Arabic)
{"ks-Deva-IN", 0x860}, // Kashmiri (Devanagari)
{"ks", 0x60}, // Kashmiri
{"ku-Arab-IQ", 0x492}, // Central Kurdish (Iraq)
{"ku-Arab-IQ", 0x7c92}, // Central Kurdish (Iraq)
{"ku", 0x92}, // Central Kurdish
{"ky-KG", 0x440}, // Kyrgyz (Kyrgyzstan)
{"ky", 0x40}, // Kyrgyz
{"la-VA", 0x476}, // Latin (Vatican City)
{"la", 0x76}, // Latin
{"lb-LU", 0x46e}, // Luxembourgish (Luxembourg)
{"lb", 0x6e}, // Luxembourgish
{"lo-LA", 0x454}, // Lao (Laos)
{"lo", 0x54}, // Lao
{"lt-LT", 0x427}, // Lithuanian (Lithuania)
{"lt", 0x27}, // Lithuanian
{"lv-LV", 0x426}, // Latvian (Latvia)
{"lv", 0x26}, // Latvian
{"mi-NZ", 0x481}, // Maori (New Zealand)
{"mi", 0x81}, // Maori
{"mk-MK", 0x42f}, // Macedonian (North Macedonia)
{"mk", 0x2f}, // Macedonian
{"ml-IN", 0x44c}, // Malayalam (India)
{"ml", 0x4c}, // Malayalam
{"mn-MN", 0x450}, // Mongolian (Mongolia)
{"mn-MN", 0x7850}, // Mongolian (Mongolia)
{"mn-Mong-CN", 0x7c50}, // Mongolian (Traditional Mongolian, China)
{"mn-Mong-CN", 0x850}, // Mongolian (Traditional Mongolian, China)
{"mn-Mong-MN", 0xc50}, // Mongolian (Traditional Mongolian, Mongolia)
{"mn", 0x50}, // Mongolian
{"mni-IN", 0x458}, // Manipuri (Bangla, India)
{"mni", 0x58}, // Manipuri
{"moh-CA", 0x47c}, // Mohawk (Canada)
{"moh", 0x7c}, // Mohawk
{"mr-IN", 0x44e}, // Marathi (India)
{"mr", 0x4e}, // Marathi
{"ms-BN", 0x83e}, // Malay (Brunei)
{"ms-MY", 0x43e}, // Malay (Malaysia)
{"ms", 0x3e}, // Malay
{"mt-MT", 0x43a}, // Maltese (Malta)
{"mt", 0x3a}, // Maltese
{"my-MM", 0x455}, // Burmese (Myanmar)
{"my", 0x55}, // Burmese
{"nb-NO", 0x414}, // Norwegian Bokmål (Norway)
{"nb-NO", 0x7c14}, // Norwegian Bokmål (Norway)
{"nb", 0x14}, // Norwegian Bokmål
{"ne-IN", 0x861}, // Nepali (India)
{"ne-NP", 0x461}, // Nepali (Nepal)
{"ne", 0x61}, // Nepali
{"nl-BE", 0x813}, // Dutch (Belgium)
{"nl-NL", 0x413}, // Dutch (Netherlands)
{"nl", 0x13}, // Dutch
{"nn-NO", 0x7814}, // Norwegian Nynorsk (Norway)
{"nn-NO", 0x814}, // Norwegian Nynorsk (Norway)
{"nso-ZA", 0x46c}, // Sesotho sa Leboa (South Africa)
{"nso", 0x6c}, // Sesotho sa Leboa
{"oc-FR", 0x482}, // Occitan (France)
{"oc", 0x82}, // Occitan
{"om-ET", 0x472}, // Oromo (Ethiopia)
{"om", 0x72}, // Oromo
{"or-IN", 0x448}, // Odia (India)
{"or", 0x48}, // Odia
{"pa-Arab-PK", 0x7c46}, // Punjabi (Pakistan)
{"pa-Arab-PK", 0x846}, // Punjabi (Pakistan)
{"pa-IN", 0x446}, // Punjabi (India)
{"pa", 0x46}, // Punjabi
{"pap-029", 0x479}, // Papiamento (Caribbean)
{"pap", 0x79}, // Papiamento
{"pl-PL", 0x415}, // Polish (Poland)
{"pl", 0x15}, // Polish
{"ps-AF", 0x463}, // Pashto (Afghanistan)
{"ps", 0x63}, // Pashto
{"pt-BR", 0x416}, // Portuguese (Brazil)
{"pt-PT", 0x816}, // Portuguese (Portugal)
{"pt", 0x16}, // Portuguese
{"qps-Latn-x-sh", 0x901}, // Pseudo (Pseudo Selfhost)
{"qps-ploc", 0x501}, // Pseudo (Pseudo)
{"qps-ploca", 0x5fe}, // Pseudo (Pseudo Asia)
{"qps-plocm", 0x9ff}, // Pseudo (Pseudo Mirrored)
{"quc-Latn-GT", 0x486}, // Kʼicheʼ (Latin, Guatemala)
{"quc-Latn-GT", 0x7c86}, // Kʼicheʼ (Latin, Guatemala)
{"quc", 0x86}, // Kʼicheʼ
{"quz-BO", 0x46b}, // Quechua (Bolivia)
{"quz-EC", 0x86b}, // Quechua (Ecuador)
{"quz-PE", 0xc6b}, // Quechua (Peru)
{"quz", 0x6b}, // Quechua
{"rm-CH", 0x417}, // Romansh (Switzerland)
{"rm", 0x17}, // Romansh
{"ro-MD", 0x818}, // Romanian (Moldova)
{"ro-RO", 0x418}, // Romanian (Romania)
{"ro", 0x18}, // Romanian
{"ru-MD", 0x819}, // Russian (Moldova)
{"ru-RU", 0x419}, // Russian (Russia)
{"ru", 0x19}, // Russian
{"rw-RW", 0x487}, // Kinyarwanda (Rwanda)
{"rw", 0x87}, // Kinyarwanda
{"sa-IN", 0x44f}, // Sanskrit (India)
{"sa", 0x4f}, // Sanskrit
{"sah-RU", 0x485}, // Sakha (Russia)
{"sah", 0x85}, // Sakha
{"sd-Arab-PK", 0x7c59}, // Sindhi (Pakistan)
{"sd-Arab-PK", 0x859}, // Sindhi (Pakistan)
{"sd-Deva-IN", 0x459}, // Sindhi (Devanagari, India)
{"sd", 0x59}, // Sindhi
{"se-FI", 0xc3b}, // Sami, Northern (Finland)
{"se-NO", 0x43b}, // Sami, Northern (Norway)
{"se-SE", 0x83b}, // Sami, Northern (Sweden)
{"se", 0x3b}, // Sami, Northern
{"si-LK", 0x45b}, // Sinhala (Sri Lanka)
{"si", 0x5b}, // Sinhala
{"sk-SK", 0x41b}, // Slovak (Slovakia)
{"sk", 0x1b}, // Slovak
{"sl-SI", 0x424}, // Slovenian (Slovenia)
{"sl", 0x24}, // Slovenian
{"sma-NO", 0x183b}, // Sami, Southern (Norway)
{"sma-SE", 0x1c3b}, // Sami, Southern (Sweden)
{"sma-SE", 0x783b}, // Sami, Southern (Sweden)
{"smj-NO", 0x103b}, // Sami, Lule (Norway)
{"smj-SE", 0x143b}, // Sami, Lule (Sweden)
{"smj-SE", 0x7c3b}, // Sami, Lule (Sweden)
{"smn-FI", 0x243b}, // Sami, Inari (Finland)
{"smn-FI", 0x703b}, // Sami, Inari (Finland)
{"sms-FI", 0x203b}, // Sami, Skolt (Finland)
{"sms-FI", 0x743b}, // Sami, Skolt (Finland)
{"so-SO", 0x477}, // Somali (Somalia)
{"so", 0x77}, // Somali
{"sq-AL", 0x41c}, // Albanian (Albania)
{"sq", 0x1c}, // Albanian
{"sr-Cyrl-BA", 0x1c1a}, // Serbian (Cyrillic, Bosnia and Herzegovina)
{"sr-Cyrl-CS", 0xc1a}, // Serbian (Cyrillic, Serbia and Montenegro (Former))
{"sr-Cyrl-ME", 0x301a}, // Serbian (Cyrillic, Montenegro)
{"sr-Cyrl-RS", 0x281a}, // Serbian (Cyrillic, Serbia)
{"sr-Cyrl-RS", 0x6c1a}, // Serbian (Cyrillic, Serbia)
{"sr-Latn-BA", 0x181a}, // Serbian (Latin, Bosnia & Herzegovina)
{"sr-Latn-CS", 0x81a}, // Serbian (Latin, Serbia and Montenegro (Former))
{"sr-Latn-ME", 0x2c1a}, // Serbian (Latin, Montenegro)
{"sr-Latn-RS", 0x241a}, // Serbian (Latin, Serbia)
{"sr-Latn-RS", 0x701a}, // Serbian (Latin, Serbia)
{"sr-Latn-RS", 0x7c1a}, // Serbian (Latin, Serbia)
{"st-ZA", 0x430}, // Sesotho (South Africa)
{"st", 0x30}, // Sesotho
{"sv-FI", 0x81d}, // Swedish (Finland)
{"sv-SE", 0x41d}, // Swedish (Sweden)
{"sv", 0x1d}, // Swedish
{"sw-KE", 0x441}, // Kiswahili (Kenya)
{"sw", 0x41}, // Kiswahili
{"syr-SY", 0x45a}, // Syriac (Syria)
{"syr", 0x5a}, // Syriac
{"ta-IN", 0x449}, // Tamil (India)
{"ta-LK", 0x849}, // Tamil (Sri Lanka)
{"ta", 0x49}, // Tamil
{"te-IN", 0x44a}, // Telugu (India)
{"te", 0x4a}, // Telugu
{"tg-Cyrl-TJ", 0x428}, // Tajik (Cyrillic, Tajikistan)
{"tg-Cyrl-TJ", 0x7c28}, // Tajik (Cyrillic, Tajikistan)
{"tg", 0x28}, // Tajik
{"th-TH", 0x41e}, // Thai (Thailand)
{"th", 0x1e}, // Thai
{"ti-ER", 0x873}, // Tigrinya (Eritrea)
{"ti-ET", 0x473}, // Tigrinya (Ethiopia)
{"ti", 0x73}, // Tigrinya
{"tk-TM", 0x442}, // Turkmen (Turkmenistan)
{"tk", 0x42}, // Turkmen
{"tn-BW", 0x832}, // Setswana (Botswana)
{"tn-ZA", 0x432}, // Setswana (South Africa)
{"tn", 0x32}, // Setswana
{"tr-TR", 0x41f}, // Turkish (Türkiye)
{"tr", 0x1f}, // Turkish
{"ts-ZA", 0x431}, // Xitsonga (South Africa)
{"ts", 0x31}, // Xitsonga
{"tt-RU", 0x444}, // Tatar (Russia)
{"tt", 0x44}, // Tatar
{"tzm-Arab-MA", 0x45f}, // Central Atlas Tamazight (Arabic, Morocco)
{"tzm-Latn-DZ", 0x7c5f}, // Central Atlas Tamazight (Latin, Algeria)
{"tzm-Latn-DZ", 0x85f}, // Central Atlas Tamazight (Latin, Algeria)
{"tzm-Tfng-MA", 0x105f}, // Central Atlas Tamazight (Tifinagh, Morocco)
{"tzm-Tfng-MA", 0x785f}, // Central Atlas Tamazight (Tifinagh, Morocco)
{"tzm", 0x5f}, // Central Atlas Tamazight
{"ug-CN", 0x480}, // Uyghur (China)
{"ug", 0x80}, // Uyghur
{"uk-UA", 0x422}, // Ukrainian (Ukraine)
{"uk", 0x22}, // Ukrainian
{"ur-IN", 0x820}, // Urdu (India)
{"ur-PK", 0x420}, // Urdu (Pakistan)
{"ur", 0x20}, // Urdu
{"uz-Cyrl-UZ", 0x7843}, // Uzbek (Cyrillic, Uzbekistan)
{"uz-Cyrl-UZ", 0x843}, // Uzbek (Cyrillic, Uzbekistan)
{"uz-Latn-UZ", 0x443}, // Uzbek (Latin, Uzbekistan)
{"uz-Latn-UZ", 0x7c43}, // Uzbek (Latin, Uzbekistan)
{"uz", 0x43}, // Uzbek
{"ve-ZA", 0x433}, // Venda (South Africa)
{"ve", 0x33}, // Venda
{"vi-VN", 0x42a}, // Vietnamese (Vietnam)
{"vi", 0x2a}, // Vietnamese
{"wo-SN", 0x488}, // Wolof (Senegal)
{"wo", 0x88}, // Wolof
{"xh-ZA", 0x434}, // isiXhosa (South Africa)
{"xh", 0x34}, // isiXhosa
{"yi-001", 0x43d}, // Yiddish (World)
{"yi", 0x3d}, // Yiddish
{"yo-NG", 0x46a}, // Yoruba (Nigeria)
{"yo", 0x6a}, // Yoruba
{"zh-CN", 0x7804}, // Chinese (Simplified, China)
{"zh-CN", 0x804}, // Chinese (Simplified, China)
{"zh-HK", 0x7c04}, // Chinese (Traditional, Hong Kong SAR)
{"zh-HK", 0xc04}, // Chinese (Traditional, Hong Kong SAR)
{"zh-MO", 0x1404}, // Chinese (Traditional, Macao SAR)
{"zh-SG", 0x1004}, // Chinese (Simplified, Singapore)
{"zh-TW", 0x404}, // Chinese (Traditional, Taiwan)
{"zh", 0x4}, // Chinese
{"zu-ZA", 0x435}, // isiZulu (South Africa)
{"zu", 0x35}, // isiZulu
};
if (auto el = languages.find(rfc1766); el != languages.end())
return static_cast<langid>(el->second);
return langid_unknown;
}
///
/// Converts language code to language name
///
/// \param[in] lang Language code
/// \param[in] fallback Fallback value to return, should language name could not be determined.
///
/// \returns Language name in RFC1766 syntax or `fallback` if not found.
///
inline _Ret_maybenull_z_ const char* rfc1766_from_langid(_In_ langid lang, _In_opt_z_ const char* fallback = nullptr)
{
static const std::map<int, const char*> languages = {
{0x1, "ar"}, // Arabic
{0x401, "ar-SA"}, // Arabic (Saudi Arabia)
{0x801, "ar-IQ"}, // Arabic (Iraq)
{0xc01, "ar-EG"}, // Arabic (Egypt)
{0x1001, "ar-LY"}, // Arabic (Libya)
{0x1401, "ar-DZ"}, // Arabic (Algeria)
{0x1801, "ar-MA"}, // Arabic (Morocco)
{0x1c01, "ar-TN"}, // Arabic (Tunisia)
{0x2001, "ar-OM"}, // Arabic (Oman)
{0x2401, "ar-YE"}, // Arabic (Yemen)
{0x2801, "ar-SY"}, // Arabic (Syria)
{0x2c01, "ar-JO"}, // Arabic (Jordan)
{0x3001, "ar-LB"}, // Arabic (Lebanon)
{0x3401, "ar-KW"}, // Arabic (Kuwait)
{0x3801, "ar-AE"}, // Arabic (United Arab Emirates)
{0x3c01, "ar-BH"}, // Arabic (Bahrain)
{0x4001, "ar-QA"}, // Arabic (Qatar)
{0x2, "bg"}, // Bulgarian
{0x402, "bg-BG"}, // Bulgarian (Bulgaria)
{0x3, "ca"}, // Catalan
{0x403, "ca-ES"}, // Catalan (Catalan)
{0x803, "ca-ES-valencia"}, // Valencian (Spain)
{0x4, "zh"}, // Chinese
{0x404, "zh-TW"}, // Chinese (Traditional, Taiwan)
{0x804, "zh-CN"}, // Chinese (Simplified, China)
{0xc04, "zh-HK"}, // Chinese (Traditional, Hong Kong SAR)
{0x1004, "zh-SG"}, // Chinese (Simplified, Singapore)
{0x1404, "zh-MO"}, // Chinese (Traditional, Macao SAR)
{0x7804, "zh-CN"}, // Chinese (Simplified, China)
{0x7c04, "zh-HK"}, // Chinese (Traditional, Hong Kong SAR)
{0x5, "cs"}, // Czech
{0x405, "cs-CZ"}, // Czech (Czechia)
{0x6, "da"}, // Danish
{0x406, "da-DK"}, // Danish (Denmark)
{0x7, "de"}, // German
{0x407, "de-DE"}, // German (Germany)
{0x807, "de-CH"}, // German (Switzerland)
{0xc07, "de-AT"}, // German (Austria)
{0x1007, "de-LU"}, // German (Luxembourg)
{0x1407, "de-LI"}, // German (Liechtenstein)
{0x8, "el"}, // Greek
{0x408, "el-GR"}, // Greek (Greece)
{0x9, "en"}, // English
{0x409, "en-US"}, // English (United States)
{0x809, "en-GB"}, // English (United Kingdom)
{0xc09, "en-AU"}, // English (Australia)
{0x1009, "en-CA"}, // English (Canada)
{0x1409, "en-NZ"}, // English (New Zealand)
{0x1809, "en-IE"}, // English (Ireland)
{0x1c09, "en-ZA"}, // English (South Africa)
{0x2009, "en-JM"}, // English (Jamaica)
{0x2409, "en-029"}, // English (Caribbean)
{0x2809, "en-BZ"}, // English (Belize)
{0x2c09, "en-TT"}, // English (Trinidad & Tobago)
{0x3009, "en-ZW"}, // English (Zimbabwe)
{0x3409, "en-PH"}, // English (Philippines)
{0x3809, "en-ID"}, // English (Indonesia)
{0x3c09, "en-HK"}, // English (Hong Kong SAR)
{0x4009, "en-IN"}, // English (India)
{0x4409, "en-MY"}, // English (Malaysia)
{0x4809, "en-SG"}, // English (Singapore)
{0x4c09, "en-AE"}, // English (United Arab Emirates)
{0xa, "es"}, // Spanish
{0x40a, "es-ES_tradnl"}, // Spanish (Spain, Traditional Sort)
{0x80a, "es-MX"}, // Spanish (Mexico)
{0xc0a, "es-ES"}, // Spanish (Spain, International Sort)
{0x100a, "es-GT"}, // Spanish (Guatemala)
{0x140a, "es-CR"}, // Spanish (Costa Rica)
{0x180a, "es-PA"}, // Spanish (Panama)
{0x1c0a, "es-DO"}, // Spanish (Dominican Republic)
{0x200a, "es-VE"}, // Spanish (Venezuela)
{0x240a, "es-CO"}, // Spanish (Colombia)
{0x280a, "es-PE"}, // Spanish (Peru)
{0x2c0a, "es-AR"}, // Spanish (Argentina)
{0x300a, "es-EC"}, // Spanish (Ecuador)
{0x340a, "es-CL"}, // Spanish (Chile)
{0x380a, "es-UY"}, // Spanish (Uruguay)
{0x3c0a, "es-PY"}, // Spanish (Paraguay)
{0x400a, "es-BO"}, // Spanish (Bolivia)
{0x440a, "es-SV"}, // Spanish (El Salvador)
{0x480a, "es-HN"}, // Spanish (Honduras)
{0x4c0a, "es-NI"}, // Spanish (Nicaragua)
{0x500a, "es-PR"}, // Spanish (Puerto Rico)
{0x540a, "es-US"}, // Spanish (United States)
{0x580a, "es-419"}, // Spanish (Latin America)
{0x5c0a, "es-CU"}, // Spanish (Cuba)
{0xb, "fi"}, // Finnish
{0x40b, "fi-FI"}, // Finnish (Finland)
{0xc, "fr"}, // French
{0x40c, "fr-FR"}, // French (France)
{0x80c, "fr-BE"}, // French (Belgium)
{0xc0c, "fr-CA"}, // French (Canada)
{0x100c, "fr-CH"}, // French (Switzerland)
{0x140c, "fr-LU"}, // French (Luxembourg)
{0x180c, "fr-MC"}, // French (Monaco)
{0x1c0c, "fr-029"}, // French (Caribbean)
{0x200c, "fr-RE"}, // French (Réunion)
{0x240c, "fr-CD"}, // French Congo (DRC)
{0x280c, "fr-SN"}, // French (Senegal)
{0x2c0c, "fr-CM"}, // French (Cameroon)
{0x300c, "fr-CI"}, // French (Côte dIvoire)
{0x340c, "fr-ML"}, // French (Mali)
{0x380c, "fr-MA"}, // French (Morocco)
{0x3c0c, "fr-HT"}, // French (Haiti)
{0xd, "he"}, // Hebrew
{0x40d, "he-IL"}, // Hebrew (Israel)
{0xe, "hu"}, // Hungarian
{0x40e, "hu-HU"}, // Hungarian (Hungary)
{0xf, "is"}, // Icelandic
{0x40f, "is-IS"}, // Icelandic (Iceland)
{0x10, "it"}, // Italian
{0x410, "it-IT"}, // Italian (Italy)
{0x810, "it-CH"}, // Italian (Switzerland)
{0x11, "ja"}, // Japanese
{0x411, "ja-JP"}, // Japanese (Japan)
{0x12, "ko"}, // Korean
{0x412, "ko-KR"}, // Korean (Korea)
{0x13, "nl"}, // Dutch
{0x413, "nl-NL"}, // Dutch (Netherlands)
{0x813, "nl-BE"}, // Dutch (Belgium)
{0x14, "nb"}, // Norwegian Bokmål
{0x414, "nb-NO"}, // Norwegian Bokmål (Norway)
{0x814, "nn-NO"}, // Norwegian Nynorsk (Norway)
{0x7814, "nn-NO"}, // Norwegian Nynorsk (Norway)
{0x7c14, "nb-NO"}, // Norwegian Bokmål (Norway)
{0x15, "pl"}, // Polish
{0x415, "pl-PL"}, // Polish (Poland)
{0x16, "pt"}, // Portuguese
{0x416, "pt-BR"}, // Portuguese (Brazil)
{0x816, "pt-PT"}, // Portuguese (Portugal)
{0x17, "rm"}, // Romansh
{0x417, "rm-CH"}, // Romansh (Switzerland)
{0x18, "ro"}, // Romanian
{0x418, "ro-RO"}, // Romanian (Romania)
{0x818, "ro-MD"}, // Romanian (Moldova)
{0x19, "ru"}, // Russian
{0x419, "ru-RU"}, // Russian (Russia)
{0x819, "ru-MD"}, // Russian (Moldova)
{0x1a, "hr"}, // Croatian
{0x41a, "hr-HR"}, // Croatian (Croatia)
{0x81a, "sr-Latn-CS"}, // Serbian (Latin, Serbia and Montenegro (Former))
{0xc1a, "sr-Cyrl-CS"}, // Serbian (Cyrillic, Serbia and Montenegro (Former))
{0x101a, "hr-BA"}, // Croatian (Bosnia & Herzegovina)
{0x141a, "bs-Latn-BA"}, // Bosnian (Latin, Bosnia & Herzegovina)
{0x181a, "sr-Latn-BA"}, // Serbian (Latin, Bosnia & Herzegovina)
{0x1c1a, "sr-Cyrl-BA"}, // Serbian (Cyrillic, Bosnia and Herzegovina)
{0x201a, "bs-Cyrl-BA"}, // Bosnian (Cyrillic, Bosnia and Herzegovina)
{0x241a, "sr-Latn-RS"}, // Serbian (Latin, Serbia)
{0x281a, "sr-Cyrl-RS"}, // Serbian (Cyrillic, Serbia)
{0x2c1a, "sr-Latn-ME"}, // Serbian (Latin, Montenegro)
{0x301a, "sr-Cyrl-ME"}, // Serbian (Cyrillic, Montenegro)
{0x641a, "bs-Cyrl-BA"}, // Bosnian (Cyrillic, Bosnia and Herzegovina)
{0x681a, "bs-Latn-BA"}, // Bosnian (Latin, Bosnia & Herzegovina)
{0x6c1a, "sr-Cyrl-RS"}, // Serbian (Cyrillic, Serbia)
{0x701a, "sr-Latn-RS"}, // Serbian (Latin, Serbia)
{0x781a, "bs-Latn-BA"}, // Bosnian (Latin, Bosnia & Herzegovina)
{0x7c1a, "sr-Latn-RS"}, // Serbian (Latin, Serbia)
{0x1b, "sk"}, // Slovak
{0x41b, "sk-SK"}, // Slovak (Slovakia)
{0x1c, "sq"}, // Albanian
{0x41c, "sq-AL"}, // Albanian (Albania)
{0x1d, "sv"}, // Swedish
{0x41d, "sv-SE"}, // Swedish (Sweden)
{0x81d, "sv-FI"}, // Swedish (Finland)
{0x1e, "th"}, // Thai
{0x41e, "th-TH"}, // Thai (Thailand)
{0x1f, "tr"}, // Turkish
{0x41f, "tr-TR"}, // Turkish (Türkiye)
{0x20, "ur"}, // Urdu
{0x420, "ur-PK"}, // Urdu (Pakistan)
{0x820, "ur-IN"}, // Urdu (India)
{0x21, "id"}, // Indonesian
{0x421, "id-ID"}, // Indonesian (Indonesia)
{0x22, "uk"}, // Ukrainian
{0x422, "uk-UA"}, // Ukrainian (Ukraine)
{0x23, "be"}, // Belarusian
{0x423, "be-BY"}, // Belarusian (Belarus)
{0x24, "sl"}, // Slovenian
{0x424, "sl-SI"}, // Slovenian (Slovenia)
{0x25, "et"}, // Estonian
{0x425, "et-EE"}, // Estonian (Estonia)
{0x26, "lv"}, // Latvian
{0x426, "lv-LV"}, // Latvian (Latvia)
{0x27, "lt"}, // Lithuanian
{0x427, "lt-LT"}, // Lithuanian (Lithuania)
{0x28, "tg"}, // Tajik
{0x428, "tg-Cyrl-TJ"}, // Tajik (Cyrillic, Tajikistan)
{0x7c28, "tg-Cyrl-TJ"}, // Tajik (Cyrillic, Tajikistan)
{0x29, "fa"}, // Persian
{0x429, "fa-IR"}, // Persian (Iran)
{0x2a, "vi"}, // Vietnamese
{0x42a, "vi-VN"}, // Vietnamese (Vietnam)
{0x2b, "hy"}, // Armenian
{0x42b, "hy-AM"}, // Armenian (Armenia)
{0x2c, "az"}, // Azerbaijani
{0x42c, "az-Latn-AZ"}, // Azerbaijani (Latin, Azerbaijan)
{0x82c, "az-Cyrl-AZ"}, // Azerbaijani (Cyrillic, Azerbaijan)
{0x742c, "az-Cyrl-AZ"}, // Azerbaijani (Cyrillic, Azerbaijan)
{0x782c, "az-Latn-AZ"}, // Azerbaijani (Latin, Azerbaijan)
{0x2d, "eu"}, // Basque
{0x42d, "eu-ES"}, // Basque (Basque)
{0x2e, "hsb"}, // Upper Sorbian
{0x42e, "hsb-DE"}, // Upper Sorbian (Germany)
{0x82e, "dsb-DE"}, // Lower Sorbian (Germany)
{0x7c2e, "dsb-DE"}, // Lower Sorbian (Germany)
{0x2f, "mk"}, // Macedonian
{0x42f, "mk-MK"}, // Macedonian (North Macedonia)
{0x30, "st"}, // Sesotho
{0x430, "st-ZA"}, // Sesotho (South Africa)
{0x31, "ts"}, // Xitsonga
{0x431, "ts-ZA"}, // Xitsonga (South Africa)
{0x32, "tn"}, // Setswana
{0x432, "tn-ZA"}, // Setswana (South Africa)
{0x832, "tn-BW"}, // Setswana (Botswana)
{0x33, "ve"}, // Venda
{0x433, "ve-ZA"}, // Venda (South Africa)
{0x34, "xh"}, // isiXhosa
{0x434, "xh-ZA"}, // isiXhosa (South Africa)
{0x35, "zu"}, // isiZulu
{0x435, "zu-ZA"}, // isiZulu (South Africa)
{0x36, "af"}, // Afrikaans
{0x436, "af-ZA"}, // Afrikaans (South Africa)
{0x37, "ka"}, // Georgian
{0x437, "ka-GE"}, // Georgian (Georgia)
{0x38, "fo"}, // Faroese
{0x438, "fo-FO"}, // Faroese (Faroe Islands)
{0x39, "hi"}, // Hindi
{0x439, "hi-IN"}, // Hindi (India)
{0x3a, "mt"}, // Maltese
{0x43a, "mt-MT"}, // Maltese (Malta)
{0x3b, "se"}, // Sami, Northern
{0x43b, "se-NO"}, // Sami, Northern (Norway)
{0x83b, "se-SE"}, // Sami, Northern (Sweden)
{0xc3b, "se-FI"}, // Sami, Northern (Finland)
{0x103b, "smj-NO"}, // Sami, Lule (Norway)
{0x143b, "smj-SE"}, // Sami, Lule (Sweden)
{0x183b, "sma-NO"}, // Sami, Southern (Norway)
{0x1c3b, "sma-SE"}, // Sami, Southern (Sweden)
{0x203b, "sms-FI"}, // Sami, Skolt (Finland)
{0x243b, "smn-FI"}, // Sami, Inari (Finland)
{0x703b, "smn-FI"}, // Sami, Inari (Finland)
{0x743b, "sms-FI"}, // Sami, Skolt (Finland)
{0x783b, "sma-SE"}, // Sami, Southern (Sweden)
{0x7c3b, "smj-SE"}, // Sami, Lule (Sweden)
{0x3c, "ga"}, // Irish
{0x83c, "ga-IE"}, // Irish (Ireland)
{0x3d, "yi"}, // Yiddish
{0x43d, "yi-001"}, // Yiddish (World)
{0x3e, "ms"}, // Malay
{0x43e, "ms-MY"}, // Malay (Malaysia)
{0x83e, "ms-BN"}, // Malay (Brunei)
{0x3f, "kk"}, // Kazakh
{0x43f, "kk-KZ"}, // Kazakh (Kazakhstan)
{0x40, "ky"}, // Kyrgyz
{0x440, "ky-KG"}, // Kyrgyz (Kyrgyzstan)
{0x41, "sw"}, // Kiswahili
{0x441, "sw-KE"}, // Kiswahili (Kenya)
{0x42, "tk"}, // Turkmen
{0x442, "tk-TM"}, // Turkmen (Turkmenistan)
{0x43, "uz"}, // Uzbek
{0x443, "uz-Latn-UZ"}, // Uzbek (Latin, Uzbekistan)
{0x843, "uz-Cyrl-UZ"}, // Uzbek (Cyrillic, Uzbekistan)
{0x7843, "uz-Cyrl-UZ"}, // Uzbek (Cyrillic, Uzbekistan)
{0x7c43, "uz-Latn-UZ"}, // Uzbek (Latin, Uzbekistan)
{0x44, "tt"}, // Tatar
{0x444, "tt-RU"}, // Tatar (Russia)
{0x45, "bn"}, // Bangla
{0x445, "bn-IN"}, // Bengali (India)
{0x845, "bn-BD"}, // Bangla (Bangladesh)
{0x46, "pa"}, // Punjabi
{0x446, "pa-IN"}, // Punjabi (India)
{0x846, "pa-Arab-PK"}, // Punjabi (Pakistan)
{0x7c46, "pa-Arab-PK"}, // Punjabi (Pakistan)
{0x47, "gu"}, // Gujarati
{0x447, "gu-IN"}, // Gujarati (India)
{0x48, "or"}, // Odia
{0x448, "or-IN"}, // Odia (India)
{0x49, "ta"}, // Tamil
{0x449, "ta-IN"}, // Tamil (India)
{0x849, "ta-LK"}, // Tamil (Sri Lanka)
{0x4a, "te"}, // Telugu
{0x44a, "te-IN"}, // Telugu (India)
{0x4b, "kn"}, // Kannada
{0x44b, "kn-IN"}, // Kannada (India)
{0x4c, "ml"}, // Malayalam
{0x44c, "ml-IN"}, // Malayalam (India)
{0x4d, "as"}, // Assamese
{0x44d, "as-IN"}, // Assamese (India)
{0x4e, "mr"}, // Marathi
{0x44e, "mr-IN"}, // Marathi (India)
{0x4f, "sa"}, // Sanskrit
{0x44f, "sa-IN"}, // Sanskrit (India)
{0x50, "mn"}, // Mongolian
{0x450, "mn-MN"}, // Mongolian (Mongolia)
{0x850, "mn-Mong-CN"}, // Mongolian (Traditional Mongolian, China)
{0xc50, "mn-Mong-MN"}, // Mongolian (Traditional Mongolian, Mongolia)
{0x7850, "mn-MN"}, // Mongolian (Mongolia)
{0x7c50, "mn-Mong-CN"}, // Mongolian (Traditional Mongolian, China)
{0x51, "bo"}, // Tibetan
{0x451, "bo-CN"}, // Tibetan (China)
{0xc51, "dz-BT"}, // Dzongkha (Bhutan)
{0x52, "cy"}, // Welsh
{0x452, "cy-GB"}, // Welsh (United Kingdom)
{0x53, "km"}, // Khmer
{0x453, "km-KH"}, // Khmer (Cambodia)
{0x54, "lo"}, // Lao
{0x454, "lo-LA"}, // Lao (Laos)
{0x55, "my"}, // Burmese
{0x455, "my-MM"}, // Burmese (Myanmar)
{0x56, "gl"}, // Galician
{0x456, "gl-ES"}, // Galician (Galician)
{0x57, "kok"}, // Konkani
{0x457, "kok-IN"}, // Konkani (India)
{0x58, "mni"}, // Manipuri
{0x458, "mni-IN"}, // Manipuri (Bangla, India)
{0x59, "sd"}, // Sindhi
{0x459, "sd-Deva-IN"}, // Sindhi (Devanagari, India)
{0x859, "sd-Arab-PK"}, // Sindhi (Pakistan)
{0x7c59, "sd-Arab-PK"}, // Sindhi (Pakistan)
{0x5a, "syr"}, // Syriac
{0x45a, "syr-SY"}, // Syriac (Syria)
{0x5b, "si"}, // Sinhala
{0x45b, "si-LK"}, // Sinhala (Sri Lanka)
{0x5c, "chr"}, // Cherokee
{0x45c, "chr-Cher-US"}, // Cherokee (Cherokee, United States)
{0x7c5c, "chr-Cher-US"}, // Cherokee (Cherokee, United States)
{0x5d, "iu"}, // Inuktitut
{0x45d, "iu-Cans-CA"}, // Inuktitut (Syllabics, Canada)
{0x85d, "iu-Latn-CA"}, // Inuktitut (Latin, Canada)
{0x785d, "iu-Cans-CA"}, // Inuktitut (Syllabics, Canada)
{0x7c5d, "iu-Latn-CA"}, // Inuktitut (Latin, Canada)
{0x5e, "am"}, // Amharic
{0x45e, "am-ET"}, // Amharic (Ethiopia)
{0x5f, "tzm"}, // Central Atlas Tamazight
{0x45f, "tzm-Arab-MA"}, // Central Atlas Tamazight (Arabic, Morocco)
{0x85f, "tzm-Latn-DZ"}, // Central Atlas Tamazight (Latin, Algeria)
{0x105f, "tzm-Tfng-MA"}, // Central Atlas Tamazight (Tifinagh, Morocco)
{0x785f, "tzm-Tfng-MA"}, // Central Atlas Tamazight (Tifinagh, Morocco)
{0x7c5f, "tzm-Latn-DZ"}, // Central Atlas Tamazight (Latin, Algeria)
{0x60, "ks"}, // Kashmiri
{0x460, "ks-Arab-IN"}, // Kashmiri (Arabic)
{0x860, "ks-Deva-IN"}, // Kashmiri (Devanagari)
{0x61, "ne"}, // Nepali
{0x461, "ne-NP"}, // Nepali (Nepal)
{0x861, "ne-IN"}, // Nepali (India)
{0x62, "fy"}, // Western Frisian
{0x462, "fy-NL"}, // Western Frisian (Netherlands)
{0x63, "ps"}, // Pashto
{0x463, "ps-AF"}, // Pashto (Afghanistan)
{0x64, "fil"}, // Filipino
{0x464, "fil-PH"}, // Filipino (Philippines)
{0x65, "dv"}, // Divehi
{0x465, "dv-MV"}, // Divehi (Maldives)
{0x66, "bin"}, // Edo
{0x466, "bin-NG"}, // Edo (Nigeria)
{0x67, "ff"}, // Fulah
{0x467, "ff-Latn-NG"}, // Fulah (Latin, Nigeria)
{0x867, "ff-Latn-SN"}, // Fulah (Latin, Senegal)
{0x7c67, "ff-Latn-SN"}, // Fulah (Latin, Senegal)
{0x68, "ha"}, // Hausa
{0x468, "ha-Latn-NG"}, // Hausa (Latin, Nigeria)
{0x7c68, "ha-Latn-NG"}, // Hausa (Latin, Nigeria)
{0x69, "ibb"}, // Ibibio
{0x469, "ibb-NG"}, // Ibibio (Nigeria)
{0x6a, "yo"}, // Yoruba
{0x46a, "yo-NG"}, // Yoruba (Nigeria)
{0x6b, "quz"}, // Quechua
{0x46b, "quz-BO"}, // Quechua (Bolivia)
{0x86b, "quz-EC"}, // Quechua (Ecuador)
{0xc6b, "quz-PE"}, // Quechua (Peru)
{0x6c, "nso"}, // Sesotho sa Leboa
{0x46c, "nso-ZA"}, // Sesotho sa Leboa (South Africa)
{0x6d, "ba"}, // Bashkir
{0x46d, "ba-RU"}, // Bashkir (Russia)
{0x6e, "lb"}, // Luxembourgish
{0x46e, "lb-LU"}, // Luxembourgish (Luxembourg)
{0x6f, "kl"}, // Kalaallisut
{0x46f, "kl-GL"}, // Kalaallisut (Greenland)
{0x70, "ig"}, // Igbo
{0x470, "ig-NG"}, // Igbo (Nigeria)
{0x71, "kr"}, // Kanuri
{0x471, "kr-Latn-NG"}, // Kanuri (Latin, Nigeria)
{0x72, "om"}, // Oromo
{0x472, "om-ET"}, // Oromo (Ethiopia)
{0x73, "ti"}, // Tigrinya
{0x473, "ti-ET"}, // Tigrinya (Ethiopia)
{0x873, "ti-ER"}, // Tigrinya (Eritrea)
{0x74, "gn"}, // Guarani
{0x474, "gn-PY"}, // Guarani (Paraguay)
{0x75, "haw"}, // Hawaiian
{0x475, "haw-US"}, // Hawaiian (United States)
{0x76, "la"}, // Latin
{0x476, "la-VA"}, // Latin (Vatican City)
{0x77, "so"}, // Somali
{0x477, "so-SO"}, // Somali (Somalia)
{0x78, "ii"}, // Yi
{0x478, "ii-CN"}, // Yi (China)
{0x79, "pap"}, // Papiamento
{0x479, "pap-029"}, // Papiamento (Caribbean)
{0x7a, "arn"}, // Mapuche
{0x47a, "arn-CL"}, // Mapuche (Chile)
{0x7c, "moh"}, // Mohawk
{0x47c, "moh-CA"}, // Mohawk (Canada)
{0x7e, "br"}, // Breton
{0x47e, "br-FR"}, // Breton (France)
{0x80, "ug"}, // Uyghur
{0x480, "ug-CN"}, // Uyghur (China)
{0x81, "mi"}, // Maori
{0x481, "mi-NZ"}, // Maori (New Zealand)
{0x82, "oc"}, // Occitan
{0x482, "oc-FR"}, // Occitan (France)
{0x83, "co"}, // Corsican
{0x483, "co-FR"}, // Corsican (France)
{0x84, "gsw"}, // Swiss German
{0x484, "gsw-FR"}, // Alsatian (France)
{0x85, "sah"}, // Sakha
{0x485, "sah-RU"}, // Sakha (Russia)
{0x86, "quc"}, // Kʼicheʼ
{0x486, "quc-Latn-GT"}, // Kʼicheʼ (Latin, Guatemala)
{0x7c86, "quc-Latn-GT"}, // Kʼicheʼ (Latin, Guatemala)
{0x87, "rw"}, // Kinyarwanda
{0x487, "rw-RW"}, // Kinyarwanda (Rwanda)
{0x88, "wo"}, // Wolof
{0x488, "wo-SN"}, // Wolof (Senegal)
{0x8c, "fa"}, // Persian
{0x48c, "fa-AF"}, // Persian (Afghanistan)
{0x91, "gd"}, // Scottish Gaelic
{0x491, "gd-GB"}, // Scottish Gaelic (United Kingdom)
{0x92, "ku"}, // Central Kurdish
{0x492, "ku-Arab-IQ"}, // Central Kurdish (Iraq)
{0x7c92, "ku-Arab-IQ"}, // Central Kurdish (Iraq)
{0x501, "qps-ploc"}, // Pseudo (Pseudo)
{0x901, "qps-Latn-x-sh"}, // Pseudo (Pseudo Selfhost)
{0x5fe, "qps-ploca"}, // Pseudo (Pseudo Asia)
{0x9ff, "qps-plocm"}, // Pseudo (Pseudo Mirrored)
};
if (auto el = languages.find(lang); el != languages.end())
return el->second;
return fallback;
}
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

116
include/stdex/locale.hpp Normal file
View File

@ -0,0 +1,116 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include <locale.h>
#ifndef _WIN32
#include <xlocale.h>
#endif
#include <memory>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wexit-time-destructors"
#endif
namespace stdex
{
#ifdef _WIN32
using locale_t = _locale_t;
inline locale_t create_locale(_In_ int category, _In_z_ const char* locale) { return _create_locale(category, locale); }
inline locale_t create_locale(_In_ int category, _In_z_ const wchar_t* locale) { return _wcreate_locale(category, locale); }
inline void free_locale(_In_opt_ locale_t locale) { _free_locale(locale); }
#else
using locale_t = ::locale_t;
inline locale_t create_locale(_In_ int category, _In_z_ const char* locale)
{
int mask = 0;
switch (category) {
case LC_ALL : mask = LC_ALL_MASK ; break;
case LC_COLLATE : mask = LC_COLLATE_MASK ; break;
case LC_CTYPE : mask = LC_CTYPE_MASK ; break;
case LC_MESSAGES: mask = LC_MESSAGES_MASK; break;
case LC_MONETARY: mask = LC_MONETARY_MASK; break;
case LC_NUMERIC : mask = LC_NUMERIC_MASK ; break;
case LC_TIME : mask = LC_TIME_MASK ; break;
}
return newlocale(mask, locale, LC_GLOBAL_LOCALE);
}
inline void free_locale(_In_opt_ locale_t locale) { freelocale(locale); }
#endif
///
/// Deleter for unique_ptr using free_locale
///
struct free_locale_delete
{
///
/// Delete a pointer
///
void operator()(_In_ locale_t locale) const
{
free_locale(locale);
}
};
/// \cond internal
#if defined(_WIN32)
using _locale_t_ref = __crt_locale_pointers;
#elif defined(__APPLE__)
using _locale_t_ref = struct _xlocale;
#else
using _locale_t_ref = struct __locale_struct;
#endif
/// \endcond
///
/// locale_t helper class to free_locale when going out of scope.
///
class locale : public std::unique_ptr<_locale_t_ref, free_locale_delete>
{
public:
locale() = default;
locale(_In_ locale_t ptr) :
std::unique_ptr<_locale_t_ref, free_locale_delete>(ptr)
{}
locale(_In_ int category, _In_z_ const char* locale) :
stdex::locale(create_locale(category, locale))
{}
#ifdef _WIN32
locale(_In_ int category, _In_z_ const wchar_t* locale) :
stdex::locale(create_locale(category, locale))
{}
#endif
operator locale_t() const { return get(); }
};
///
/// Reusable C-locale
///
const inline locale locale_C(create_locale(LC_ALL, "C"));
///
/// Reusable UTF-8 locale
///
const inline locale locale_utf8(create_locale(LC_ALL, ".UTF-8"));
///
/// Reusable default locale
///
inline locale locale_default(nullptr);
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

253
include/stdex/mapping.hpp Normal file
View File

@ -0,0 +1,253 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include <algorithm>
#include <vector>
namespace stdex
{
///
/// Maps index in source string to index in destination string
///
template <class T>
struct mapping {
T from; // index in source string
T to; // index in destination string
///
/// Constructs a zero to zero mapping
///
mapping() : from(0), to(0) {}
///
/// Constructs an id mapping
///
/// \param[in] x Mapping from and to value
///
mapping(_In_ T x) : from(x), to(x) {}
///
/// Constructs a mapping
///
/// \param[in] _from Mapping from value
/// \param[in] _to Mapping to value
///
mapping(_In_ T _from, _In_ T _to) : from(_from), to(_to) {}
///
/// Are mappings identical?
///
/// \param[in] other Other mapping to compare against
///
/// \returns true if mappings are identical or false otherwise
///
bool operator==(const mapping& other) const { return from == other.from && to == other.to; }
///
/// Are mappings different?
///
/// \param[in] other Other mapping to compare against
///
/// \returns true if mappings are different or false otherwise
///
bool operator!=(const mapping& other) const { return !operator==(other); }
///
/// Adds two mappings by components
///
/// \param[in] other Second mapping
///
/// \returns Resulting mapping
///
mapping operator+(_In_ const mapping& other) const
{
return mapping(from + other.from, to + other.to);
}
///
/// Reverses source and destination indexes
///
void invert()
{
T tmp = from;
from = to;
to = tmp;
}
};
template <class T, class AX = std::allocator<mapping<T>>>
using mapping_vector = std::vector<mapping<T>, AX>;
///
/// Transforms destination index to source index
///
/// \param[in] mapping Ordered vector of source to destination mappings
/// \param[in] to Index in destination string
///
/// \returns Index in source string
///
template <class T, class AX = std::allocator<mapping<T>>>
T dst2src(_In_ const std::vector<stdex::mapping<T>, AX>& mapping, _In_ T to)
{
if (mapping.empty())
return to;
for (size_t l = 0, r = mapping.size();;) {
if (l < r) {
auto m = (l + r) / 2;
const auto& el = mapping[m];
if (to < el.to) r = m;
else if (el.to < to) l = m + 1;
else return el.from;
}
else if (l) {
const auto& el = mapping[l - 1];
return el.from + (to - el.to);
}
else {
const auto& el = mapping[0];
return std::min<T>(to, el.from);
}
}
}
///
/// Transforms destination index to source index
///
/// \param[in] mapping Ordered vector of source to destination mappings
/// \param[in] to Index in destination string
/// \param[in,out] m Hint to speed-up bisection when calling this function in a loop and successive destination string indexes are in vicinity. Initialize to 0.
///
/// \returns Index in source string
///
template <class T, class AX = std::allocator<mapping<T>>>
T dst2src(_In_ const std::vector<stdex::mapping<T>, AX>& mapping, _In_ T to, _Inout_opt_ size_t& m)
{
if (mapping.empty())
return to;
size_t l, r;
{
const auto& el = mapping[m];
if (to < el.to) {
l = 0;
r = m;
}
else if (el.to < to) {
if (mapping.size() - 1 <= m || to < mapping[m + 1].to)
return el.from + (to - el.to);
l = m + 1;
r = mapping.size();
}
else
return el.from;
}
for (;;) {
if (l < r) {
m = (l + r) / 2;
const auto& el = mapping[m];
if (to < el.to) r = m;
else if (el.to < to) l = m + 1;
else return el.from;
}
else if (l) {
const auto& el = mapping[m = l - 1];
return el.from + (to - el.to);
}
else {
const auto& el = mapping[m = 0];
return std::min<T>(to, el.from);
}
}
}
///
/// Transforms source index to destination index
///
/// \param[in] mapping Ordered vector of source to destination mappings
/// \param[in] from Index in source string
///
/// \returns Index in destination string
///
template <class T, class AX = std::allocator<mapping<T>>>
T src2dst(_In_ const std::vector<stdex::mapping<T>, AX>& mapping, _In_ T from)
{
if (mapping.empty())
return from;
for (size_t l = 0, r = mapping.size();;) {
if (l < r) {
auto m = (l + r) / 2;
const auto& el = mapping[m];
if (from < el.from) r = m;
else if (el.from < from) l = m + 1;
else return el.to;
}
else if (l) {
const auto& el = mapping[l - 1];
return el.to + (from - el.from);
}
else {
const auto& el = mapping[0];
return std::min<T>(from, el.to);
}
}
}
///
/// Transforms source index to destination index
///
/// \param[in] mapping Ordered vector of source to destination mappings
/// \param[in] from Index in source string
/// \param[in,out] m Hint to speed-up bisection when calling this function in a loop and successive source string indexes are in vicinity. Initialize to 0.
///
/// \returns Index in destination string
///
template <class T, class AX = std::allocator<mapping<T>>>
T src2dst(_In_ const std::vector<stdex::mapping<T>, AX>& mapping, _In_ T from, _Inout_opt_ size_t& m)
{
if (mapping.empty())
return from;
size_t l, r;
{
const auto& el = mapping[m];
if (from < el.from) {
l = 0;
r = m;
}
else if (el.from < from) {
if (mapping.size() - 1 <= m || from < mapping[m + 1].from)
return el.to + (from - el.from);
l = m + 1;
r = mapping.size();
}
else
return el.to;
}
for (;;) {
if (l < r) {
m = (l + r) / 2;
const auto& el = mapping[m];
if (from < el.from) r = m;
else if (el.from < from) l = m + 1;
else return el.to;
}
else if (l) {
const auto& el = mapping[m = l - 1];
return el.to + (from - el.from);
}
else {
const auto& el = mapping[m = 0];
return std::min<T>(from, el.to);
}
}
}
}

170
include/stdex/math.hpp Normal file
View File

@ -0,0 +1,170 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#ifdef _WIN32
#include "windows.h"
#include <intrin.h>
#include <intsafe.h>
#endif
#include <stdexcept>
namespace stdex
{
///
/// Multiplies two numbers and throws on overflow
///
/// \param[in] a First operand
/// \param[in] b Second operand
///
/// \return a * b
///
inline size_t mul(size_t a, size_t b)
{
#if _MSC_VER >= 1300
SIZE_T result;
if (SUCCEEDED(SIZETMult(a, b, &result)))
return result;
#elif defined(_MSC_VER)
if (a == 0)
return 0;
if (b <= SIZE_MAX / a)
return a * b;
#else
size_t result;
if (!__builtin_mul_overflow(a, b, &result))
return result;
#endif
throw std::invalid_argument("multiply overflow");
}
///
/// Adds two numbers and throws on overflow
///
/// \param[in] a First operand
/// \param[in] b Second operand
///
/// \return a + b
///
inline size_t add(size_t a, size_t b)
{
#if _MSC_VER >= 1300
SIZE_T result;
if (SUCCEEDED(SIZETAdd(a, b, &result)))
return result;
#elif defined(_MSC_VER)
if (a <= SIZE_MAX - b)
return a + b;
#else
size_t result;
if (!__builtin_add_overflow(a, b, &result))
return result;
#endif
throw std::invalid_argument("add overflow");
}
///
/// Rounds number down and aligns it to have given number of lower bits zero.
///
/// \param a Number to round down
/// \param n Number of lower bits to be zero after rounding
///
/// \return The biggest number lower or equal to a that has n lower bits zero
///
inline constexpr size_t align_down(size_t a, int n)
{
const size_t mask = SIZE_MAX << n;
return a & mask;
}
///
/// Rounds number up and aligns it to have given number of lower bits zero. It throws on overflow.
///
/// \param a Number to round up
/// \param n Number of lower bits to be zero after rounding
///
/// \return The first number greater or equal to a that has n lower bits zero
///
inline size_t align_up(size_t a, int n)
{
const size_t mask = SIZE_MAX << n;
return add(a, ~mask) & mask;
}
///
/// Bitwise rotates left
///
/// \param[in] value Value to rotate
/// \param[in] bits Amount of bits to rotate
///
/// \return Rotated value
///
inline uint32_t rol(_In_ uint32_t value, _In_ int bits)
{
#ifdef _WIN32
return _rotl(value, bits);
#else
return (value << bits) | (value >> (32 - bits));
#endif
}
///
/// Calculate n*k/q
///
/// \param[in] n Number
/// \param[in] k Numerator
/// \param[in] q Denominator
///
/// \return n*k/q
///
inline constexpr int32_t muldiv(int32_t n, int32_t k, int32_t q)
{
return static_cast<int32_t>(static_cast<int64_t>(n) * k / q);
}
///
/// Calculate n*k/q
///
/// \param[in] n Number
/// \param[in] k Numerator
/// \param[in] q Denominator
///
/// \return n*k/q
///
inline constexpr uint32_t muldiv(uint32_t n, uint32_t k, uint32_t q)
{
return static_cast<uint32_t>(static_cast<uint64_t>(n) * k / q);
}
///
/// Calculate n*k/q
///
/// \param[in] n Number
/// \param[in] k Numerator
/// \param[in] q Denominator
///
/// \return n*k/q
///
inline constexpr int64_t muldiv(int64_t n, int64_t k, int64_t q)
{
return n * k / q;
}
///
/// Calculate n*k/q
///
/// \param[in] n Number
/// \param[in] k Numerator
/// \param[in] q Denominator
///
/// \return n*k/q
///
inline constexpr uint64_t muldiv(uint64_t n, uint64_t k, uint64_t q)
{
return n * k / q;
}
}

219
include/stdex/mbedtls.hpp Normal file
View File

@ -0,0 +1,219 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "exception.hpp"
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdocumentation-deprecated-sync"
#endif
#include <mbedtls/bignum.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/pk.h>
#include <mbedtls/x509_crt.h>
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#ifdef MBEDTLS_USE_PSA_CRYPTO
#include <psa/crypto.h>
#endif
#if !defined(_WIN32)
#include <stdio.h>
#include <unistd.h>
#endif
#include <stdexcept>
namespace stdex
{
namespace mbedtls
{
///
/// MbedTLS runtime error
///
class runtime_error : public stdex::num_runtime_error<int>
{
public:
///
/// Constructs an exception
///
/// \param[in] num MbedTLS error code
///
runtime_error(error_type num) : stdex::num_runtime_error<int>(num, message(num))
{
}
///
/// Constructs an exception
///
/// \param[in] num MbedTLS error code
/// \param[in] msg Error message
///
runtime_error(error_type num, const std::string &msg) : stdex::num_runtime_error<int>(num, msg + ": " + message(num))
{
}
///
/// Constructs an exception
///
/// \param[in] num MbedTLS error code
/// \param[in] msg Error message
///
runtime_error(error_type num, const char *msg) : stdex::num_runtime_error<int>(num, std::string(msg) + ": " + message(num))
{
}
protected:
///
/// Returns a string explaining the meaning of a security result code
///
/// \sa [mbedtls_strerror function](https://mbed-ce.github.io/mbed-os/group__mbedtls__errors.html#ga8c41c149b77a4807115b19c2af858558)
///
static std::string message(error_type num)
{
char buf[1024];
memset(buf, 0, sizeof(buf));
mbedtls_strerror(num, buf, sizeof(buf));
return buf;
}
};
///
/// MbedTLS entropy context
///
class entropy_context : public mbedtls_entropy_context
{
public:
///
/// Initializes MbedTLS entropy context
///
/// \sa [mbedtls_entropy_init](https://mbed-ce.github.io/mbed-os/group__mbedtls__entropy__module.html#gaa901e027093c6fe65dee5760db78aced)
///
entropy_context()
{
mbedtls_entropy_init(this);
#if !defined(_WIN32) && defined(MBEDTLS_FS_IO)
int ret = mbedtls_entropy_add_source(this, dev_random_entropy_poll, NULL, 32, MBEDTLS_ENTROPY_SOURCE_STRONG);
if (ret != 0)
throw runtime_error(ret, "mbedtls_entropy_add_source failed");
#endif
}
///
/// Frees MbedTLS entropy context
///
/// \sa [mbedtls_entropy_free](https://mbed-ce.github.io/mbed-os/group__mbedtls__entropy__module.html#ga06778439f8a0e2daa2d3b444e06ad8dd)
///
~entropy_context()
{
mbedtls_entropy_free(this);
}
protected:
#if !defined(_WIN32) && defined(MBEDTLS_FS_IO)
static int dev_random_entropy_poll(void* data, unsigned char* output, size_t len, size_t* olen)
{
_Unreferenced_(data);
*olen = 0;
FILE* file = fopen("/dev/random", "rb");
if (file == NULL)
return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED;
size_t left = len;
unsigned char* p = output;
while (left) {
// /dev/random can return much less than requested. If so, try again.
size_t ret = fread(p, 1, left, file);
if (ret == 0 && ferror(file)) {
fclose(file);
return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED;
}
p += ret;
left -= ret;
if (!left)
break;
sleep(1);
}
fclose(file);
*olen = len;
return 0;
}
#endif
};
///
/// The MbedTLS CTR_DRBG context structure
///
struct ctr_drbg_context : public mbedtls_ctr_drbg_context
{
ctr_drbg_context() { mbedtls_ctr_drbg_init(this); }
~ctr_drbg_context() { mbedtls_ctr_drbg_free(this); }
};
///
/// MbedTLS MPI
///
struct mpi : public mbedtls_mpi
{
mpi() { mbedtls_mpi_init(this); }
~mpi() { mbedtls_mpi_free(this); }
};
///
/// MbedTLS public key container
///
struct pk_context : public mbedtls_pk_context
{
pk_context() { mbedtls_pk_init(this); }
~pk_context() { mbedtls_pk_free(this); }
};
///
/// MbedTLS container for an X.509 certificate
///
struct x509_crt : public mbedtls_x509_crt
{
x509_crt() { mbedtls_x509_crt_init(this); }
~x509_crt() { mbedtls_x509_crt_free(this); }
};
///
/// MbedTLS Container for writing a certificate (CRT)
///
struct x509write_cert : public mbedtls_x509write_cert
{
x509write_cert() { mbedtls_x509write_crt_init(this); }
~x509write_cert() { mbedtls_x509write_crt_free(this); }
};
///
/// MbedTLS global initializer
///
class initializer
{
public:
///
/// Initializes MbedTLS library
///
initializer()
{
#ifdef MBEDTLS_USE_PSA_CRYPTO
auto status = psa_crypto_init();
if (status != PSA_SUCCESS)
throw runtime_error("Failed to initialize PSA Crypto implementation");
#endif
}
~initializer()
{
#ifdef MBEDTLS_USE_PSA_CRYPTO
mbedtls_psa_crypto_free();
#endif
}
};
}
}

300
include/stdex/memory.hpp Normal file
View File

@ -0,0 +1,300 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include <memory>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#endif
namespace stdex
{
///
/// Noop deleter
///
template <class T>
struct no_delete {
constexpr no_delete() noexcept = default;
template <class T2, std::enable_if_t<std::is_convertible_v<T2*, T*>, int> = 0>
no_delete(const no_delete<T2>&) noexcept {}
void operator()(T* p) const noexcept { _Unreferenced_(p); }
};
///
/// Noop array deleter
///
template <class T>
struct no_delete<T[]> {
constexpr no_delete() noexcept = default;
template <class T2, std::enable_if_t<std::is_convertible_v<T2(*)[], T(*)[]>, int> = 0>
no_delete(const no_delete<T2[]>&) noexcept {}
template <class T2, std::enable_if_t<std::is_convertible_v<T2(*)[], T(*)[]>, int> = 0>
void operator()(T2* p) const noexcept { p; }
};
///
/// Create shared_ptr with noop deleter.
///
/// This may be used to wrap data on stack in a shared_ptr. However, be extra careful
/// that shared_ptr is completely dereferenced _before_ stack data goes out of scope.
///
/// \param[in] p Pointer to assign to shared_ptr
///
template <class T>
std::shared_ptr<T> make_shared_no_delete(_In_ T* p)
{
return std::shared_ptr<T>(p, no_delete<T>{});
}
// sanitizing_allocator::destroy() member generates p parameter not used warning for primitive datatypes T.
#pragma warning(push)
#pragma warning(disable: 4100)
///
/// An allocator template that sanitizes each memory block before it is destroyed or reallocated
///
/// \note
/// `sanitizing_allocator` introduces a performance penalty. However, it provides an additional level of security.
/// Use for security sensitive data memory storage only.
///
template <class T>
class sanitizing_allocator : public std::allocator<T>
{
public:
///
/// Convert this type to sanitizing_allocator<T2>
///
template <class T2>
struct rebind
{
typedef sanitizing_allocator<T2> other; ///< Other type
};
///
/// Construct default allocator
///
sanitizing_allocator() noexcept : std::allocator<T>()
{}
///
/// Construct by copying
///
sanitizing_allocator(_In_ const sanitizing_allocator<T> &other) : std::allocator<T>(other)
{}
///
/// Construct from a related allocator
///
template <class T2>
sanitizing_allocator(_In_ const sanitizing_allocator<T2> &other) noexcept : std::allocator<T>(other)
{}
///
/// Deallocate object at p sanitizing its content first
///
void deallocate(_In_ T* const p, _In_ const std::size_t n)
{
#ifdef _WIN32
SecureZeroMemory(p, sizeof(T) * n);
#else
memset(p, 0, sizeof(T) * n);
#endif
std::allocator<T>::deallocate(p, n);
}
};
#pragma warning(pop)
///
/// Sanitizing BLOB
///
template <size_t N>
class sanitizing_blob
{
public:
sanitizing_blob()
{
memset(m_data, 0, N);
}
~sanitizing_blob()
{
#ifdef _WIN32
SecureZeroMemory(m_data, N);
#else
memset(m_data, 0, N);
#endif
}
public:
unsigned char m_data[N]; ///< BLOB data
};
///
/// Helper class for returning pointers to std::unique_ptr
///
template <typename T, typename D>
class ref_unique_ptr
{
public:
///
/// Takes ownership of the pointer
///
/// \param[in,out] owner Object to attach helper to
///
ref_unique_ptr(_Inout_ std::unique_ptr<T, D> &owner) :
m_own(owner),
m_ptr(owner.release())
{}
///
/// Moves object
///
/// \param[in,out] other Source object
///
ref_unique_ptr(_Inout_ ref_unique_ptr<T, D> &&other) :
m_own(other.m_own),
m_ptr(other.m_ptr)
{
other.m_ptr = nullptr;
}
///
/// Returns ownership of the pointer
///
~ref_unique_ptr()
{
if (m_ptr != nullptr)
m_own.reset(m_ptr);
}
///
/// Operator for pointer-to-pointer parameters by value use-cases.
///
/// \return Pointer to the pointer
///
operator T**()
{
return &m_ptr;
}
///
/// Operator for reverence-to-pointer parameters by value use-cases.
///
/// \return Reference to the pointer
///
operator T*&()
{
return m_ptr;
}
protected:
std::unique_ptr<T, D> &m_own; ///< Original owner of the pointer
T *m_ptr; ///< Pointer
};
///
/// Helper function template for returning pointers to std::unique_ptr
///
/// \param[in,out] owner Original owner of the pointer
///
/// \returns A helper wrapper class to handle returning a reference to the pointer
///
template<class T, class D>
ref_unique_ptr<T, D> get_ptr(_Inout_ std::unique_ptr<T, D> &owner) noexcept
{
return ref_unique_ptr<T, D>(owner);
}
///
/// Helper class for returning pointers to std::unique_ptr
/// (specialization for arrays)
///
template<typename T, typename D>
class ref_unique_ptr<T[], D>
{
public:
///
/// Takes ownership of the pointer
///
/// \param[in,out] owner Object to attach helper to
///
ref_unique_ptr(_Inout_ std::unique_ptr<T[], D> &owner) noexcept :
m_own(owner),
m_ptr(owner.release())
{}
///
/// Moves object
///
/// \param[in,out] other Source object
///
ref_unique_ptr(_Inout_ ref_unique_ptr<T[], D> &&other) :
m_own(other.m_own),
m_ptr(other.m_ptr)
{
other.m_ptr = nullptr;
}
///
/// Returns ownership of the pointer
///
virtual ~ref_unique_ptr()
{
if (m_ptr != nullptr)
m_own.reset(m_ptr);
}
///
/// Operator for pointer-to-pointer parameters by value use-cases.
///
/// \return Pointer to the pointer
///
operator T**() noexcept
{
return &m_ptr;
}
///
/// Operator for reverence-to-pointer parameters by value use-cases.
///
/// \return Reference to the pointer
///
operator T*&()
{
return m_ptr;
}
protected:
std::unique_ptr<T[], D> &m_own; ///< Original owner of the pointer
T *m_ptr; ///< Pointer
};
///
/// Helper function template for returning pointers to std::unique_ptr
/// (specialization for arrays)
///
/// \param[in,out] owner Original owner of the pointer
///
/// \returns A helper wrapper class to handle returning a reference to the pointer
///
template<class T, class D>
ref_unique_ptr<T[], D> get_ptr(_Inout_ std::unique_ptr<T[], D>& owner) noexcept
{
return ref_unique_ptr<T[], D>(owner);
}
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

128
include/stdex/minisign.hpp Normal file
View File

@ -0,0 +1,128 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "base64.hpp"
#include "compat.hpp"
#include "parser.hpp"
#include "stream.hpp"
#include <stdint.h>
#include <string>
#include <vector>
namespace stdex
{
namespace minisign
{
///
/// Test for "untrusted comment:"
///
class untrusted_comment : public stdex::parser::parser
{
protected:
virtual bool do_match(
_In_reads_or_z_opt_(end) const char* text,
_In_ size_t start = 0,
_In_ size_t end = SIZE_MAX,
_In_ int flags = stdex::parser::match_default)
{
_Unreferenced_(flags);
stdex_assert(text || start >= end);
if (start < end && start + 17 < end &&
text[start] == 'u' &&
text[start + 1] == 'n' &&
text[start + 2] == 't' &&
text[start + 3] == 'r' &&
text[start + 4] == 'u' &&
text[start + 5] == 's' &&
text[start + 6] == 't' &&
text[start + 7] == 'e' &&
text[start + 8] == 'd' &&
text[start + 9] == ' ' &&
text[start + 10] == 'c' &&
text[start + 11] == 'o' &&
text[start + 12] == 'm' &&
text[start + 13] == 'm' &&
text[start + 14] == 'e' &&
text[start + 15] == 'n' &&
text[start + 16] == 't' &&
text[start + 17] == ':')
{
this->interval.end = (this->interval.start = start) + 18;
return true;
}
this->interval.invalidate();
return false;
}
};
///
/// Test for CRLF or LF
///
class line_break : public stdex::parser::parser
{
protected:
virtual bool do_match(
_In_reads_or_z_opt_(end) const char* text,
_In_ size_t start = 0,
_In_ size_t end = SIZE_MAX,
_In_ int flags = stdex::parser::match_default)
{
_Unreferenced_(flags);
stdex_assert(text || start >= end);
if (start < end && start + 1 < end &&
text[start] == '\r' &&
text[start + 1] == '\n')
{
this->interval.end = (this->interval.start = start) + 2;
return true;
}
stdex_assert(text || start >= end);
if (start < end && text[start] == '\n') {
this->interval.end = (this->interval.start = start) + 1;
return true;
}
this->interval.invalidate();
return false;
}
};
///
/// Parses .minisig file
///
/// \param[in,out] minisig Stream with position set to the beginning of the Minisign signature. Typically a .minisig file.
/// \param[out] algorithm Minisign algorithm used to create signature: 'd' legacy, 'D' hashed
/// \param[out] key_id 8 random bytes, matching the public key used to sign content
/// \param[out] signature ed25519(<file data>) when using legacy algorithm; ed25519(Blake2b-512(<file data>)) when using hashed algorithm.
///
inline void parse_minisig(_Inout_ stdex::stream::basic& minisig, _Out_ uint8_t& algorithm, _Out_writes_all_(8) uint8_t key_id[8], _Out_writes_all_(64) uint8_t signature[64])
{
std::vector<uint8_t> data;
minisign::untrusted_comment untrusted_comment;
std::string line;
for (;;) {
minisig.readln(line);
if (!minisig.ok())
break;
if (line.empty() ||
untrusted_comment.match(line.data(), 0, line.size()))
continue;
stdex::base64_dec decoder; bool is_last;
decoder.decode(data, is_last, line.data(), line.size());
break;
}
if (data.size() < 74)
throw std::runtime_error("Minisign signature is too short");
if (data[0] != 'E')
throw std::runtime_error("not a Minisign signature");
algorithm = data[1];
memcpy(&key_id[0], &data[2], 8);
memcpy(&signature[0], &data[10], 64);
}
}
}

8377
include/stdex/parser.hpp Normal file

File diff suppressed because it is too large Load Diff

108
include/stdex/pool.hpp Normal file
View File

@ -0,0 +1,108 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "spinlock.hpp"
#ifdef _WIN32
#include "windows.h"
#endif
#include <list>
#include <map>
#include <mutex>
namespace stdex
{
///
/// Per-NUMA pool of items
///
template <class T>
class pool
{
public:
#ifdef _WIN32
using numaid_t = USHORT;
#else
using numaid_t = int;
#endif
private:
struct numaentry_t {
mutable spinlock lock;
std::list<T> list;
};
mutable std::mutex m_mutex;
std::map<numaid_t, numaentry_t> m_available;
private:
static numaid_t numa_node()
{
#if defined(_WIN32)
PROCESSOR_NUMBER Processor;
GetCurrentProcessorNumberEx(&Processor);
USHORT NodeNumber = 0;
return GetNumaProcessorNodeEx(&Processor, &NodeNumber) ? NodeNumber : 0;
#elif defined(__APPLE__)
return 0;
#else
return numa_node_of_cpu(sched_getcpu());
#endif
}
numaentry_t& numa_entry(numaid_t numa = numa_node())
{
const std::lock_guard<std::mutex> guard(m_mutex);
return m_available[numa];
}
public:
///
/// Removes an item from the pool
///
/// \param[in] numa NUMA node to identify subpool to remove item from
///
/// \returns An item from the pool or default value if pool is empty
///
T pop(_In_ numaid_t numa = numa_node())
{
auto& ne = numa_entry(numa);
const std::lock_guard<spinlock> guard(ne.lock);
if (!ne.list.empty()) {
auto r = std::move(ne.list.front());
ne.list.pop_front();
return r;
}
return T();
}
///
/// Adds an item to the pool
///
/// \param[in] r Item to add
/// \param[in] numa NUMA node to identify subpool to add item to
///
void push(_Inout_ T&& r, _In_ numaid_t numa = numa_node())
{
auto& ne = numa_entry(numa);
const std::lock_guard<spinlock> guard(ne.lock);
ne.list.push_front(std::move(r));
}
///
/// Removes all items from the pool
///
void clear()
{
const std::lock_guard<std::mutex> guard_m(m_mutex);
for (auto& ne : m_available) {
const std::lock_guard<spinlock> guard_l(ne.second.lock);
while (!ne.second.list.empty())
ne.second.list.pop_front();
}
}
};
}

537
include/stdex/progress.hpp Normal file
View File

@ -0,0 +1,537 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "interval.hpp"
#include <atomic>
#include <chrono>
#include <shared_mutex>
#include <vector>
namespace stdex
{
///
/// Progress indicator base class
///
template <class T>
class progress
{
public:
virtual ~progress() {}
///
/// Set progress indicator text
///
/// \param[in] msg Text to display
///
virtual void set_text(_In_z_ const char* msg)
{
_Unreferenced_(msg);
}
///
/// Set progress range extent
///
/// \param[in] start Minimum value of the progress
/// \param[in] end Maximum value of the progress
///
virtual void set_range(_In_ T start, _In_ T end)
{
start; end;
}
///
/// Set current progress
///
/// \param[in] value Current value of the progress. Must be between start and end parameters provided in set_range() call.
///
virtual void set(_In_ T value)
{
value;
}
///
/// Show or hide progress
///
/// \param[in] show Shows or hides progress indicator
///
virtual void show(_In_ bool show = true)
{
_Unreferenced_(show);
}
///
/// Query whether user requested abort
///
virtual bool cancel()
{
return false;
}
};
///
/// Lazy progress indicator
///
/// Use with expensive progress reporting to suppress progress indication for a period of time.
///
template <class T>
class lazy_progress : public progress<T>
{
public:
using clock = std::chrono::high_resolution_clock;
public:
///
/// Constructs a lazy progress indicator
///
/// \param[in] timeout Timeout to wait before forwarding progress
///
lazy_progress(_In_ const std::chrono::nanoseconds& timeout = std::chrono::milliseconds(500)) :
m_timeout(timeout),
m_start(0),
m_end(0),
m_value(static_cast<T>(-1))
{}
///
/// Set progress range extent
///
/// \param[in] start Minimum value of the progress
/// \param[in] end Maximum value of the progress
///
virtual void set_range(_In_ T start, _In_ T end)
{
m_start = start;
m_end = end;
}
///
/// Set current progress
///
/// \param[in] value Current value of the progress. Must be between start and end parameters provided in set_range() call.
///
virtual void set(_In_ T value)
{
if (value == m_start || value == m_end)
m_last = clock::now();
else if (value == m_value)
return;
else {
auto now = clock::now();
if (now - m_last < m_timeout)
return;
m_last = now;
}
m_value = value;
do_set();
}
protected:
///
/// Called when progress reporting is due. Should override this method to implement actual progress refresh.
///
virtual void do_set() {}
protected:
std::chrono::nanoseconds m_timeout;
clock::time_point m_last;
T m_start, m_end, m_value;
};
///
/// Timeout progress indicator
///
/// Use to cancel long running jobs after the deadline.
///
template <class T>
class timeout_progress : public progress<T>
{
public:
using clock = std::chrono::high_resolution_clock;
public:
///
/// Constructs a timeout progress indicator
///
/// \param[in] timeout Timeout when to cancel the progress
///
timeout_progress(_In_ const std::chrono::nanoseconds& timeout = std::chrono::seconds(60), _In_opt_ progress<T>* host = nullptr) :
m_host(host),
m_deadline(clock::now() + timeout)
{}
///
/// Set progress indicator text
///
/// \param[in] msg Text to display
///
virtual void set_text(_In_z_ const char* msg)
{
if (m_host)
m_host->set_text(msg);
}
///
/// Set progress range extent
///
/// \param[in] start Minimum value of the progress
/// \param[in] end Maximum value of the progress
///
virtual void set_range(_In_ T start, _In_ T end)
{
if (m_host)
m_host->set_range(start, end);
}
///
/// Set current progress
///
/// \param[in] value Current value of the progress. Must be between start and end parameters provided in set_range() call.
///
virtual void set(_In_ T value)
{
if (m_host)
m_host->set(value);
}
///
/// Show or hide progress
///
/// \param[in] show Shows or hides progress indicator
///
virtual void show(_In_ bool show = true)
{
if (m_host)
m_host->show(show);
}
///
/// Query whether user requested abort
///
virtual bool cancel()
{
return
(m_host && m_host->cancel()) ||
m_deadline < clock::now();
}
protected:
progress<T>* m_host;
clock::time_point m_deadline;
};
///
/// Global progress indicator
///
/// Use to report progress of a phase or section as a part of a whole progress.
///
template <class T>
class global_progress : public progress<T>
{
public:
///
/// Constructs a progress indicator
///
/// \param[in] host Host progress indicator
///
global_progress(_In_opt_ progress<T>* host = nullptr) : m_host(host)
{}
///
/// Attach to a host progress indicator
///
/// \param[in] host Host progress indicator
///
void attach(_In_opt_ progress<T>* host)
{
m_host = host;
}
///
/// Detach host progress indicator
///
/// \returns Old host progress indicator
///
progress<T>* detach()
{
progress<T>* k = m_host;
m_host = nullptr;
return k;
}
///
/// Set global extend of the progress indicator
///
/// \param[in] start Minimum value of the progress
/// \param[in] end Maximum value of the progress
///
void set_global_range(_In_ T start, _In_ T end)
{
m_global.start = start;
m_global.end = end;
if (m_host)
m_host->set_range(m_global.start, m_global.end);
}
///
/// Set section extend of the progress indicator
///
/// \param[in] start Minimum value of the progress
/// \param[in] end Maximum value of the progress
///
void set_section_range(_In_ T start, _In_ T end)
{
m_section.start = start;
m_section.end = end;
}
///
/// Set progress indicator text
///
/// \param[in] msg Text to display
///
virtual void set_text(_In_ const char* msg)
{
if (m_host)
m_host->set_text(msg);
}
///
/// Set local extend of the progress indicator
///
/// \param[in] start Minimum value of the progress
/// \param[in] end Maximum value of the progress
///
virtual void set_range(_In_ T start, _In_ T end)
{
m_local.start = start;
m_local.end = end;
}
///
/// Set local current progress
///
/// \param[in] value Current value of the progress. Must be between start and end parameters provided in set_range() call.
///
virtual void set(_In_ T value)
{
if (m_host) {
T size = m_local.size();
if (size != 0) {
// TODO: Implement with muldiv.
m_host->set(((value - m_local.start) * m_section.size() / size) + m_section.start);
}
}
}
///
/// Show or hide progress
///
/// \param[in] show Shows or hides progress indicator
///
virtual void show(_In_ bool show = true)
{
if (m_host)
m_host->show(show);
}
///
/// Query whether user requested abort
///
virtual bool cancel()
{
return m_host && m_host->cancel();
}
protected:
progress<T>* m_host;
interval<T> m_local, m_global, m_section;
};
///
/// Progress indicator switcher
///
/// Use to inject global_progress indicator inplace of another progress indicator.
///
template <class T>
class progress_switcher : public global_progress<T>
{
public:
progress_switcher(progress<T>*& host) :
global_progress<T>(host),
m_host_ref(host)
{
m_host_ref = this;
}
~progress_switcher()
{
m_host_ref = this->detach();
}
protected:
progress<T>*& m_host_ref;
};
///
/// Aggregated progress indicator
///
/// Use to report combined progress from multiple workers.
///
template <class T>
class aggregate_progress
{
protected:
///
/// Progress indicator for individual worker
///
class worker_progress : public progress<T>
{
protected:
aggregate_progress<T>& m_host;
T m_start, m_end, m_value;
public:
worker_progress(_Inout_ aggregate_progress<T>& host) :
m_host(host),
m_start(0), m_end(0),
m_value(0)
{}
///
/// Set progress indicator text
///
/// \param[in] msg Text to display
///
virtual void set_text(_In_ const char* msg)
{
std::shared_lock<std::shared_mutex> lk(m_host.m_mutex);
if (m_host.m_host)
m_host.m_host->set_text(msg);
}
///
/// Set local extend of the progress indicator
///
/// \param[in] start Minimum value of the progress
/// \param[in] end Maximum value of the progress
///
virtual void set_range(_In_ T start, _In_ T end)
{
T
combined_start = m_host.m_start += start - m_start,
combined_end = m_host.m_end += end - m_end;
m_start = start;
m_end = end;
std::shared_lock<std::shared_mutex> lk(m_host.m_mutex);
if (m_host.m_host)
m_host.m_host->set_range(combined_start, combined_end);
}
///
/// Set local current progress
///
/// \param[in] value Current value of the progress. Must be between start and end parameters provided in set_range() call.
///
virtual void set(_In_ T value)
{
T combined_value = m_host.m_value += value - m_value;
m_value = value;
std::shared_lock<std::shared_mutex> lk(m_host.m_mutex);
if (m_host.m_host)
m_host.m_host->set(combined_value);
}
///
/// Show or hide progress
///
/// \param[in] show Shows or hides progress indicator
///
virtual void show(_In_ bool show = true)
{
std::shared_lock<std::shared_mutex> lk(m_host.m_mutex);
if (m_host.m_host)
m_host.m_host->show(show);
}
///
/// Query whether user requested abort
///
virtual bool cancel()
{
std::shared_lock<std::shared_mutex> lk(m_host.m_mutex);
return m_host.m_host && m_host.m_host->cancel();
}
};
progress<T>* m_host;
std::atomic<T> m_start, m_end, m_value;
std::vector<worker_progress> m_workers;
std::shared_mutex m_mutex;
public:
///
/// Constructs a progress indicator
///
/// \param[in] num_workers Total number of workers
/// \param[in] host Host progress indicator
///
aggregate_progress(_In_ size_t num_workers, _In_opt_ progress<T>* host = nullptr) :
m_host(host),
m_start(0), m_end(0),
m_value(0)
{
m_workers.reserve(num_workers);
for (size_t i = 0; i < num_workers; ++i)
m_workers.push_back(std::move(worker_progress(*this)));
if (m_host) {
m_host->set_range(m_start, m_end);
m_host->set(m_value);
}
}
///
/// Attach to a host progress indicator
///
/// \param[in] host Host progress indicator
///
void attach(_In_opt_ progress<T>* host)
{
std::unique_lock<std::shared_mutex> lk(m_mutex);
m_host = host;
if (m_host) {
m_host->set_range(m_start, m_end);
m_host->set(m_value);
}
}
///
/// Detach host progress indicator
///
/// \returns Old host progress indicator
///
progress<T>* detach()
{
std::unique_lock<std::shared_mutex> lk(m_mutex);
progress<T>* k = m_host;
m_host = nullptr;
return k;
}
///
/// Returns progress indicator for specific worker
///
/// \param[in] index Index of worker
///
/// \returns Reference to specific worker progress indicator
///
progress<T>& operator[](_In_ size_t index)
{
return m_workers[index];
}
};
}

160
include/stdex/ring.hpp Normal file
View File

@ -0,0 +1,160 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include <condition_variable>
#include <mutex>
#include <tuple>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#endif
namespace stdex
{
///
/// Ring buffer
///
/// \tparam T Ring element type
/// \tparam N_cap Ring capacity (in number of elements)
///
template <class T, size_t N_cap>
class ring
{
public:
ring() :
m_head(0),
m_size(0),
m_quit(false)
{
m_data[0] = 0;
}
///
/// Allocates the data after the ring tail. Use push() after the allocated data is populated.
///
/// \return Pointer to data available for writing and maximum data size to write. Or, `{nullptr, 0}` if quit() has been called.
///
std::tuple<T*, size_t> back()
{
std::unique_lock<std::mutex> lk(m_mutex);
if (!space()) {
m_head_moved.wait(lk, [&] {return m_quit || space();});
if (m_quit) _Unlikely_
return { nullptr, 0 };
}
size_t tail = wrap(m_head + m_size);
return { &m_data[tail], m_head <= tail ? N_cap - tail : m_head - tail };
}
///
/// Notifies the receiver the data was populated.
///
/// \param[in] size Amount of data that was really populated
///
void push(_In_ size_t size)
{
{
const std::lock_guard<std::mutex> lg(m_mutex);
#ifndef NDEBUG
size_t tail = wrap(m_head + m_size);
stdex_assert(size <= (m_head <= tail ? N_cap - tail : m_head - tail));
#endif
m_size += size;
}
m_tail_moved.notify_one();
}
///
/// Peeks the data at the ring head. Use pop() after the data was consumed.
///
/// \return Pointer to data available for reading and maximum data size to read. Or, `{nullptr, 0}` if quit() has been called.
///
std::tuple<T*, size_t> front()
{
std::unique_lock<std::mutex> lk(m_mutex);
if (empty()) {
m_tail_moved.wait(lk, [&] {return m_quit || !empty();});
if (m_quit && empty()) _Unlikely_
return { nullptr, 0 };
}
size_t tail = wrap(m_head + m_size);
return { &m_data[m_head], m_head < tail ? m_size : N_cap - m_head };
}
///
/// Notifies the sender the data was consumed.
///
/// \param[in] size Amount of data that was really consumed
///
void pop(_In_ size_t size)
{
{
const std::lock_guard<std::mutex> lg(m_mutex);
#ifndef NDEBUG
size_t tail = wrap(m_head + m_size);
stdex_assert(size <= (m_head < tail ? m_size : N_cap - m_head));
#endif
m_head = wrap(m_head + size);
m_size -= size;
}
m_head_moved.notify_one();
}
///
/// Cancells waiting sender and receiver
///
void quit()
{
{
const std::lock_guard<std::mutex> lg(m_mutex);
m_quit = true;
}
m_head_moved.notify_one();
m_tail_moved.notify_one();
}
///
/// Waits until the ring is flush
///
void sync()
{
std::unique_lock<std::mutex> lk(m_mutex);
m_head_moved.wait(lk, [&] {return m_quit || empty();});
}
protected:
size_t wrap(_In_ size_t idx) const
{
// TODO: When N_cap is power of 2, use & ~(N_cap - 1) instead.
return idx % N_cap;
}
size_t space() const
{
return N_cap - m_size;
}
bool empty() const
{
return !m_size;
}
protected:
std::mutex m_mutex;
std::condition_variable m_head_moved, m_tail_moved;
size_t m_head, m_size;
bool m_quit;
T m_data[N_cap];
};
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

View File

@ -0,0 +1,43 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
namespace stdex
{
///
/// Executes one lambda immediately, and another when exiting the scope
///
/// \typeparam F_init Lambda to execute immediately
/// \typeparam F_done Lambda to execute when exiting the scope
///
template <typename F_init, typename F_done>
class scoped_executor
{
F_done&& m_done;
public:
///
/// Executes init immediately and saves done for destructor
///
/// \param[in] init Lambda to execute immediately
/// \param[in] done Lambda to execute in destructor
///
scoped_executor(_In_ F_init&& init, _In_ F_done&& done) : m_done(std::forward<F_done&&>(done))
{
std::forward<F_init&&>(init)();
}
///
/// Executes done lambda
///
~scoped_executor()
{
std::forward<F_done&&>(m_done)();
}
};
}

885
include/stdex/sgml.hpp Normal file
View File

@ -0,0 +1,885 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include "mapping.hpp"
#include "sgml_unicode.hpp"
#include "string.hpp"
#include <string.h>
#include <exception>
#include <string>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wexit-time-destructors"
#endif
namespace stdex
{
/// \cond internal
template <class T>
const utf32_t* sgml2uni(_In_reads_or_z_(count) const T* entity, _In_ size_t count, utf32_t buf[2])
{
stdex_assert(entity && count);
if (count < 2 || entity[0] != '#') {
for (size_t i = 0, j = _countof(sgml_unicode); i < j; ) {
size_t m = (i + j) / 2;
if (sgml_unicode[m].sgml[0] < entity[0])
i = m + 1;
else if (sgml_unicode[m].sgml[0] > entity[0])
j = m;
else {
auto r = strncmp<char, T>(sgml_unicode[m].sgml + 1, _countof(sgml_unicode[0].sgml) - 1, entity + 1, count - 1);
if (r < 0)
i = m + 1;
else if (r > 0)
j = m;
else {
for (; i < m && strncmp<char, T>(sgml_unicode[m - 1].sgml, _countof(sgml_unicode[0].sgml), entity, count) == 0; m--);
return sgml_unicode[m].unicode;
}
}
}
return nullptr;
}
buf[0] = entity[1] == 'x' || entity[1] == 'X' ?
static_cast<utf32_t>(strtou32(&entity[2], count - 2, nullptr, 16)) :
static_cast<utf32_t>(strtou32(&entity[1], count - 1, nullptr, 10));
buf[1] = 0;
return buf;
}
inline const utf16_t* utf32_to_wstr(_In_opt_z_ const utf32_t* str, utf16_t* buf)
{
if (!str)
return nullptr;
for (size_t i = 0, j = 0;; ++i) {
if (!str[i]) {
buf[j] = 0;
return buf;
}
if (str[i] < 0x10000)
buf[j++] = static_cast<utf16_t>(str[i]);
else {
ucs4_to_surrogate_pair(&buf[j], str[i]);
j += 2;
}
}
}
inline const utf32_t* utf32_to_wstr(_In_opt_z_ const utf32_t* str, utf32_t* buf)
{
_Unreferenced_(buf);
return str;
}
template <class T>
const T* sgmlend(
_In_reads_or_z_opt_(count) const T* str, _In_ size_t count)
{
stdex_assert(str || !count);
for (size_t i = 0; i < count; i++) {
if (str[i] == ';')
return str + i;
if (!str[i] || str[i] == '&' || isspace(str[i]))
break;
}
return nullptr;
}
/// \endcond
constexpr int sgml_full = 0x40000000;
constexpr int sgml_quot = 0x00000001;
constexpr int sgml_apos = 0x00000002;
constexpr int sgml_quot_apos = sgml_quot | sgml_apos;
constexpr int sgml_amp = 0x00000004;
constexpr int sgml_lt_gt = 0x00000008;
constexpr int sgml_bsol = 0x00000010;
constexpr int sgml_dollar = 0x00000020;
constexpr int sgml_percnt = 0x00000040;
constexpr int sgml_commat = 0x00000080;
constexpr int sgml_num = 0x00000100;
constexpr int sgml_lpar_rpar = 0x00000200;
constexpr int sgml_lcub_rcub = 0x00000400;
constexpr int sgml_lsqb_rsqb = 0x00000800;
constexpr int sgml_sgml = sgml_amp | sgml_lt_gt;
constexpr int sgml_ml_attrib = sgml_amp | sgml_quot_apos;
constexpr int sgml_c = sgml_amp | sgml_bsol | sgml_quot_apos;
// constexpr int sgml_kolos = sgml_amp | sgml_quot | sgml_dollar | sgml_percnt | sgml_lt_gt | sgml_bsol/* | sgml_commat | sgml_num*/ | sgml_lpar_rpar | sgml_lcub_rcub | sgml_lsqb_rsqb;
///
/// Checks SGML string for error
///
/// \param[in] src SGML string
/// \param[in] count_src SGML string character count limit
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra checks. Currently, only stdex::sgml_full is used, which enforces 7-bit/ASCII checking.
///
/// \return Index of error; or stdex::npos if no error detected.
///
template <class T_from>
size_t sgmlerr(
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int what = 0)
{
stdex_assert(src || !count_src);
const bool
do_ascii = (what & sgml_full) == 0;
for (size_t i = 0; i < count_src && src[i];) {
if (src[i] == '&') {
auto end = sgmlend(&src[i + 1], count_src - i - 1);
if (end) {
utf32_t chr[2];
size_t n = end - src - i - 1;
auto entity_w = sgml2uni(&src[i + 1], n, chr);
if (entity_w) {
i = end - src + 1;
continue;
}
// Unknown entity.
return i;
}
// Unterminated entity.
return i;
}
if (do_ascii && !is7bit(src[i])) {
// Non-ASCII character
return i;
}
i++;
}
return npos;
}
///
/// Checks SGML string for error
///
/// \param[in] src SGML string
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra checks. Currently, only stdex::sgml_full is used, which enforces 7-bit/ASCII checking.
///
/// \return Index of error; or stdex::npos if no error detected.
///
template <class T_from, class TR_from = std::char_traits<T_from>, class AX_from = std::allocator<T_from>>
size_t sgmlerr(
_In_ const std::basic_string<T_from, TR_from, AX_from>& src,
_In_ int what = 0)
{
return sgmlerr(src.data(), src.size(), what);
}
///
/// Convert SGML string to Unicode and append to string
///
/// \param[in,out] dst String to append Unicode to
/// \param[in] src SGML string
/// \param[in] count_src SGML string character count limit
/// \param[in] skip Bitwise flag of stdex::sgml_* constants that list SGML entities to skip converting
/// \param[in] offset Logical starting offset of source and destination strings. Unused when map parameter is nullptr.
/// \param[in,out] map The vector to append index mapping between source and destination string to.
///
template <class T_to, class T_from, class TR_to = std::char_traits<T_to>, class AX_to = std::allocator<T_to>>
void sgml2strcat(
_Inout_ std::basic_string<T_to, TR_to, AX_to>& dst,
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int skip = 0,
_In_ const mapping<size_t>& offset = mapping<size_t>(0, 0),
_Inout_opt_ mapping_vector<size_t>* map = nullptr)
{
stdex_assert(src || !count_src);
const bool
skip_quot = (skip & sgml_quot) == 0,
skip_apos = (skip & sgml_apos) == 0,
skip_amp = (skip & sgml_amp) == 0,
skip_lt_gt = (skip & sgml_lt_gt) == 0,
skip_bsol = (skip & sgml_bsol) == 0,
skip_dollar = (skip & sgml_dollar) == 0,
skip_percnt = (skip & sgml_percnt) == 0,
skip_commat = (skip & sgml_commat) == 0,
skip_num = (skip & sgml_num) == 0,
skip_lpar_rpar = (skip & sgml_lpar_rpar) == 0,
skip_lcub_rcub = (skip & sgml_lcub_rcub) == 0,
skip_lsqb_rsqb = (skip & sgml_lsqb_rsqb) == 0;
count_src = strnlen(src, count_src);
dst.reserve(dst.size() + count_src);
for (size_t i = 0; i < count_src;) {
if (src[i] == '&') {
auto end = sgmlend(&src[i + 1], count_src - i - 1);
if (end) {
utf32_t chr32[2];
stdex_assert(&src[i + 1] <= end);
size_t n = static_cast<size_t>(end - src) - i - 1;
T_to chr[5];
auto entity_w = utf32_to_wstr(sgml2uni(&src[i + 1], n, chr32), chr);
if (entity_w &&
(skip_quot || (entity_w[0] != '"')) &&
(skip_apos || (entity_w[0] != '\'')) &&
(skip_amp || (entity_w[0] != '&')) &&
(skip_lt_gt || (entity_w[0] != '<' && entity_w[0] != '>')) &&
(skip_bsol || (entity_w[0] != '\\')) &&
(skip_dollar || (entity_w[0] != '$')) &&
(skip_percnt || (entity_w[0] != '%')) &&
(skip_commat || (entity_w[0] != '@')) &&
(skip_num || (entity_w[0] != '#')) &&
(skip_lpar_rpar || (entity_w[0] != '(' && entity_w[0] != ')')) &&
(skip_lcub_rcub || (entity_w[0] != '{' && entity_w[0] != '}')) &&
(skip_lsqb_rsqb || (entity_w[0] != '[' && entity_w[0] != ']')))
{
if (map) map->push_back(mapping<size_t>(offset.from + i, offset.to + dst.size()));
dst.append(entity_w);
stdex_assert(src <= end);
i = static_cast<size_t>(end - src) + 1;
if (map) map->push_back(mapping<size_t>(offset.from + i, offset.to + dst.size()));
continue;
}
}
}
dst.append(1, src[i++]);
}
}
///
/// Convert SGML string to Unicode and append to string
///
/// \param[in,out] dst String to append Unicode to
/// \param[in] src SGML string
/// \param[in] skip Bitwise flag of stdex::sgml_* constants that list SGML entities to skip converting
/// \param[in] offset Logical starting offset of source and destination strings. Unused when map parameter is nullptr.
/// \param[in,out] map The vector to append index mapping between source and destination string to.
///
template <class T_to, class T_from, class TR_to = std::char_traits<T_to>, class AX_to = std::allocator<T_to>, class TR_from = std::char_traits<T_from>, class AX_from = std::allocator<T_from>>
void sgml2strcat(
_Inout_ std::basic_string<T_to, TR_to, AX_to>& dst,
_In_ const std::basic_string<T_from, TR_from, AX_from>& src,
_In_ int skip = 0,
_In_ const mapping<size_t>& offset = mapping<size_t>(0, 0),
_Inout_opt_ mapping_vector<size_t>* map = nullptr)
{
sgml2strcat(dst, src.data(), src.size(), skip, offset, map);
}
///
/// Convert SGML string to Unicode and append to string
///
/// \param[in,out] dst String to append Unicode to
/// \param[in] count_dst Unicode string character count limit. Function throws std::invalid_argument if there is not enough space in Unicode string (including space for zero-terminator).
/// \param[in] src SGML string
/// \param[in] count_src SGML string character count limit
/// \param[in] skip Bitwise flag of stdex::sgml_* constants that list SGML entities to skip converting
/// \param[in] offset Logical starting offset of source and destination strings. Unused when map parameter is nullptr.
/// \param[in,out] map The vector to append index mapping between source and destination string to.
///
/// \return Final length of SGML string in code points excluding zero-terminator
///
template <class T_to, class T_from>
size_t sgml2strcat(
_Inout_cap_(count_dst) T_to* dst, _In_ size_t count_dst,
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int skip = 0,
_In_ const mapping<size_t>& offset = mapping<size_t>(0, 0),
_Inout_opt_ mapping_vector<size_t>* map = nullptr)
{
stdex_assert(dst || !count_dst);
stdex_assert(src || !count_src);
static const std::invalid_argument buffer_overrun("buffer overrun");
const bool
skip_quot = (skip & sgml_quot) == 0,
skip_apos = (skip & sgml_apos) == 0,
skip_amp = (skip & sgml_amp) == 0,
skip_lt_gt = (skip & sgml_lt_gt) == 0,
skip_bsol = (skip & sgml_bsol) == 0,
skip_dollar = (skip & sgml_dollar) == 0,
skip_percnt = (skip & sgml_percnt) == 0,
skip_commat = (skip & sgml_commat) == 0,
skip_num = (skip & sgml_num) == 0,
skip_lpar_rpar = (skip & sgml_lpar_rpar) == 0,
skip_lcub_rcub = (skip & sgml_lcub_rcub) == 0,
skip_lsqb_rsqb = (skip & sgml_lsqb_rsqb) == 0;
size_t j = strnlen(dst, count_dst);
count_src = strnlen(src, count_src);
for (size_t i = 0; i < count_src;) {
if (src[i] == '&') {
auto end = sgmlend(&src[i + 1], count_src - i - 1);
if (end) {
utf32_t chr32[2];
T_to chr[5];
size_t n = end - src - i - 1;
auto entity_w = utf32_to_wstr(sgml2uni(&src[i + 1], n, chr32), chr);
if (entity_w &&
(skip_quot || (entity_w[0] != '"')) &&
(skip_apos || (entity_w[0] != '\'')) &&
(skip_amp || (entity_w[0] != '&')) &&
(skip_lt_gt || (entity_w[0] != '<' && entity_w[0] != '>')) &&
(skip_bsol || (entity_w[0] != '\\')) &&
(skip_dollar || (entity_w[0] != '$')) &&
(skip_percnt || (entity_w[0] != '%')) &&
(skip_commat || (entity_w[0] != '@')) &&
(skip_num || (entity_w[0] != '#')) &&
(skip_lpar_rpar || (entity_w[0] != '(' && entity_w[0] != ')')) &&
(skip_lcub_rcub || (entity_w[0] != '{' && entity_w[0] != '}')) &&
(skip_lsqb_rsqb || (entity_w[0] != '[' && entity_w[0] != ']')))
{
if (map) map->push_back(mapping<size_t>(offset.from + i, offset.to + j));
size_t m = strlen(entity_w);
if (j + m >= count_dst)
throw buffer_overrun;
memcpy(dst + j, entity_w, m * sizeof(*entity_w)); j += m;
i = end - src + 1;
if (map) map->push_back(mapping<size_t>(offset.from + i, offset.to + j));
continue;
}
}
}
if (j + 1 >= count_dst)
throw buffer_overrun;
dst[j++] = src[i++];
}
if (j >= count_dst)
throw buffer_overrun;
dst[j] = 0;
return j;
}
///
/// Convert SGML string to Unicode
///
/// \param[in,out] dst String to write Unicode to
/// \param[in] src SGML string
/// \param[in] count_src SGML string character count limit
/// \param[in] skip Bitwise flag of stdex::sgml_* constants that list SGML entities to skip converting
/// \param[in] offset Logical starting offset of source and destination strings. Unused when map parameter is nullptr.
/// \param[in,out] map The vector to write index mapping between source and destination string to.
///
template <class T_to, class T_from, class TR_to = std::char_traits<T_to>, class AX_to = std::allocator<T_to>>
void sgml2strcpy(
_Inout_ std::basic_string<T_to, TR_to, AX_to>& dst,
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int skip = 0,
_In_ const mapping<size_t>& offset = mapping<size_t>(0, 0),
_Inout_opt_ mapping_vector<size_t>* map = nullptr)
{
dst.clear();
if (map)
map->clear();
sgml2strcat(dst, src, count_src, skip, offset, map);
}
///
/// Convert SGML string to Unicode
///
/// \param[in,out] dst String to write Unicode to
/// \param[in] src SGML string
/// \param[in] skip Bitwise flag of stdex::sgml_* constants that list SGML entities to skip converting
/// \param[in] offset Logical starting offset of source and destination strings. Unused when map parameter is nullptr.
/// \param[in,out] map The vector to write index mapping between source and destination string to.
///
template<class T_to, class T_from, class TR_to = std::char_traits<T_to>, class AX_to = std::allocator<T_to>, class TR_from = std::char_traits<T_from>, class AX_from = std::allocator<T_from>>
void sgml2strcpy(
_Inout_ std::basic_string<T_to, TR_to, AX_to>& dst,
_In_ const std::basic_string<T_from, TR_from, AX_from>& src,
_In_ int skip = 0,
_In_ const mapping<size_t>& offset = mapping<size_t>(0, 0),
_Inout_opt_ mapping_vector<size_t>* map = nullptr)
{
sgml2strcpy(dst, src.data(), src.size(), skip, offset, map);
}
///
/// Convert SGML string to Unicode
///
/// \param[in,out] dst String to write Unicode to
/// \param[in] count_dst Unicode string character count limit. Function throws std::invalid_argument if there is not enough space in Unicode string (including space for zero-terminator).
/// \param[in] src SGML string
/// \param[in] count_src SGML string character count limit
/// \param[in] skip Bitwise flag of stdex::sgml_* constants that list SGML entities to skip converting
/// \param[in] offset Logical starting offset of source and destination strings. Unused when map parameter is nullptr.
/// \param[in,out] map The vector to write index mapping between source and destination string to.
///
/// \return Final length of SGML string in code points excluding zero-terminator
///
template <class T_to, class T_from>
size_t sgml2strcpy(
_Inout_cap_(count_dst) T_to* dst, _In_ size_t count_dst,
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int skip = 0,
_In_ const mapping<size_t>& offset = mapping<size_t>(0, 0),
_Inout_opt_ mapping_vector<size_t>* map = nullptr)
{
stdex_assert(dst || !count_dst);
if (count_dst)
dst[0] = 0;
if (map)
map->clear();
return sgml2strcat(dst, count_dst, src, count_src, skip, offset, map);
}
///
/// Convert SGML string to Unicode string
///
/// \param[in] src SGML string
/// \param[in] count_src SGML string character count limit
/// \param[in] skip Bitwise flag of stdex::sgml_* constants that list SGML entities to skip converting
/// \param[in] offset Logical starting offset of source and destination strings. Unused when map parameter is nullptr.
/// \param[in,out] map The vector to append index mapping between source and destination string to.
///
/// \return Unicode string
///
template <class T_to = wchar_t, class T_from, class TR_to = std::char_traits<T_to>, class AX_to = std::allocator<T_to>>
std::basic_string<T_to, TR_to, AX_to> sgml2str(
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int skip = 0,
_In_ const mapping<size_t>& offset = mapping<size_t>(0, 0),
_Inout_opt_ mapping_vector<size_t>* map = nullptr)
{
std::basic_string<T_to, TR_to, AX_to> dst;
sgml2strcat(dst, src, count_src, skip, offset, map);
return dst;
}
///
/// Convert SGML string to Unicode string (UTF-16 on Windows)
///
/// \param[in] src SGML string
/// \param[in] skip Bitwise flag of stdex::sgml_* constants that list SGML entities to skip converting
/// \param[in] offset Logical starting offset of source and destination strings. Unused when map parameter is nullptr.
/// \param[in,out] map The vector to append index mapping between source and destination string to.
///
/// \return Unicode string
///
template <class T_to = wchar_t, class T_from, class TR_to = std::char_traits<T_to>, class AX_to = std::allocator<T_to>, class TR_from = std::char_traits<T_from>, class AX_from = std::allocator<T_from>>
std::basic_string<T_to, TR_to, AX_to> sgml2str(
_In_ const std::basic_string<T_from, TR_from, AX_from>& src,
_In_ int skip = 0,
_In_ const mapping<size_t>& offset = mapping<size_t>(0, 0),
_Inout_opt_ mapping_vector<size_t>* map = nullptr)
{
return sgml2str<T_to, T_from, TR_to, AX_to>(src.data(), src.size(), skip, offset, map);
}
/// \cond internal
inline const char* chr2sgml(_In_reads_or_z_(count) const utf16_t* entity, _In_ size_t count)
{
stdex_assert(entity && count);
utf32_t e2;
size_t offset;
if (count < 2 || !is_surrogate_pair(entity)) {
e2 = static_cast<utf32_t>(entity[0]);
offset = 1;
}
else {
e2 = surrogate_pair_to_ucs4(entity);
offset = 2;
}
for (size_t i = 0, j = _countof(unicode_sgml); i < j; ) {
size_t m = (i + j) / 2;
auto e1 = sgml_unicode[unicode_sgml[m]].unicode[0];
if (e1 < e2)
i = m + 1;
else if (e1 > e2)
j = m;
else {
auto r = strncmp(sgml_unicode[unicode_sgml[m]].unicode + 1, _countof(sgml_unicode[0].unicode) - 1, entity + offset, count - offset);
if (r < 0)
i = m + 1;
else if (r > 0)
j = m;
else {
for (; i < m && sgml_unicode[unicode_sgml[m - 1]].unicode[0] == e2 && strncmp(sgml_unicode[unicode_sgml[m - 1]].unicode + 1, _countof(sgml_unicode[0].unicode) - 1, entity + offset, count - offset) == 0; m--);
return sgml_unicode[unicode_sgml[m]].sgml;
}
}
}
return nullptr;
}
inline const char* chr2sgml(_In_reads_or_z_(count) const utf32_t* entity, _In_ size_t count)
{
stdex_assert(entity && count);
utf32_t e2 = entity[0];
for (size_t i = 0, j = _countof(unicode_sgml); i < j; ) {
size_t m = (i + j) / 2;
auto e1 = sgml_unicode[unicode_sgml[m]].unicode[0];
if (e1 < e2)
i = m + 1;
else if (e1 > e2)
j = m;
else {
auto r = strncmp(sgml_unicode[unicode_sgml[m]].unicode + 1, _countof(sgml_unicode[0].unicode) - 1, entity + 1, count - 1);
if (r < 0)
i = m + 1;
else if (r > 0)
j = m;
else {
for (; i < m && sgml_unicode[unicode_sgml[m - 1]].unicode[0] == e2 && strncmp(sgml_unicode[unicode_sgml[m - 1]].unicode + 1, _countof(sgml_unicode[0].unicode) - 1, entity + 1, count - 1) == 0; m--);
return sgml_unicode[unicode_sgml[m]].sgml;
}
}
}
return nullptr;
}
inline utf32_t wstr_to_utf32(_In_reads_(end) const utf16_t* src, _Inout_ size_t& i, _In_ size_t end)
{
stdex_assert(i < end);
if (i + 1 >= end || !is_surrogate_pair(src + i))
return src[i++];
utf32_t unicode = surrogate_pair_to_ucs4(src + i);
i += 2;
return unicode;
}
inline utf32_t wstr_to_utf32(_In_reads_(end) const utf32_t* src, _Inout_ size_t& i, _In_ size_t end)
{
_Unreferenced_(end);
stdex_assert(i < end);
return src[i++];
}
/// \endcond
///
/// Convert Unicode string to SGML and append to string
///
/// \param[in,out] dst String to append SGML to
/// \param[in] src Unicode string
/// \param[in] count_src Unicode string character count limit
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra characters otherwise not converted to SGML
///
template <class T_from, class TR_to = std::char_traits<char>, class AX_to = std::allocator<char>>
void str2sgmlcat(
_Inout_ std::basic_string<char, TR_to, AX_to>& dst,
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int what = 0)
{
stdex_assert(src || !count_src);
const bool
do_ascii = (what & sgml_full) == 0,
do_quot = (what & sgml_quot) == 0,
do_apos = (what & sgml_apos) == 0,
do_lt_gt = (what & sgml_lt_gt) == 0,
do_bsol = (what & sgml_bsol) == 0,
do_dollar = (what & sgml_dollar) == 0,
do_percnt = (what & sgml_percnt) == 0,
do_commat = (what & sgml_commat) == 0,
do_num = (what & sgml_num) == 0,
do_lpar_rpar = (what & sgml_lpar_rpar) == 0,
do_lcub_rcub = (what & sgml_lcub_rcub) == 0,
do_lsqb_rsqb = (what & sgml_lsqb_rsqb) == 0;
count_src = strnlen(src, count_src);
dst.reserve(dst.size() + count_src);
for (size_t i = 0; i < count_src;) {
size_t n = glyphlen(src + i, count_src - i);
if (n == 1 &&
do_ascii && is7bit(src[i]) &&
src[i] != '&' &&
(do_quot || (src[i] != '"')) &&
(do_apos || (src[i] != '\'')) &&
(do_lt_gt || (src[i] != '<' && src[i] != '>')) &&
(do_bsol || (src[i] != '\\')) &&
(do_dollar || (src[i] != '$')) &&
(do_percnt || (src[i] != '%')) &&
(do_commat || (src[i] != '@')) &&
(do_num || (src[i] != '#')) &&
(do_lpar_rpar || (src[i] != '(' && src[i] != ')')) &&
(do_lcub_rcub || (src[i] != '{' && src[i] != '}')) &&
(do_lsqb_rsqb || (src[i] != '[' && src[i] != ']')))
{
// 7-bit ASCII and no desire to encode it as an SGML entity.
dst.append(1, static_cast<char>(src[i++]));
}
else {
const char* entity = chr2sgml(src + i, n);
if (entity) {
dst.append(1, '&');
dst.append(entity);
dst.append(1, ';');
i += n;
}
else if (n == 1) {
// Trivial character (1 code unit, 1 glyph), no entity available.
if (is7bit(src[i]))
dst.append(1, static_cast<char>(src[i++]));
else {
char tmp[3 + 8 + 1 + 1];
::snprintf(tmp, _countof(tmp), "&#x%x;", static_cast<unsigned int>(src[i++]));
dst.append(tmp);
}
}
else {
// Non-trivial character. Decompose.
const size_t end = i + n;
while (i < end) {
if ((entity = chr2sgml(src + i, 1)) != nullptr) {
dst.append(1, '&');
dst.append(entity);
dst.append(1, ';');
i++;
}
else if (is7bit(src[i]))
dst.append(1, static_cast<char>(src[i++]));
else {
char tmp[3 + 8 + 1 + 1];
::snprintf(tmp, _countof(tmp), "&#x%x;", static_cast<unsigned int>(wstr_to_utf32(src, i, end)));
dst.append(tmp);
}
}
}
}
}
}
///
/// Convert Unicode string to SGML and append to string
///
/// \param[in,out] dst String to append SGML to
/// \param[in] src Unicode string
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra characters otherwise not converted to SGML
///
template <class T_from, class TR_to = std::char_traits<char>, class AX_to = std::allocator<char>, class TR_from = std::char_traits<T_from>, class AX_from = std::allocator<T_from>>
void str2sgmlcat(
_Inout_ std::basic_string<char, TR_to, AX_to>& dst,
_In_ const std::basic_string<T_from, TR_from, AX_from>& src,
_In_ int what = 0)
{
str2sgmlcat(dst, src.data(), src.size(), what);
}
///
/// Convert Unicode string to SGML and append to string
///
/// \param[in,out] dst String to append SGML to
/// \param[in] count_dst SGML string character count limit. Function throws std::invalid_argument if there is not enough space in SGML string (including space for zero-terminator).
/// \param[in] src Unicode string
/// \param[in] count_src Unicode string character count limit
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra characters otherwise not converted to SGML
///
/// \return Final length of SGML string in code points excluding zero-terminator
///
template <class T_from>
size_t str2sgmlcat(
_Inout_cap_(count_dst) char* dst, _In_ size_t count_dst,
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int what = 0)
{
stdex_assert(dst || !count_dst);
stdex_assert(src || !count_src);
static const std::invalid_argument buffer_overrun("buffer overrun");
const bool
do_ascii = (what & sgml_full) == 0,
do_quot = (what & sgml_quot) == 0,
do_apos = (what & sgml_apos) == 0,
do_lt_gt = (what & sgml_lt_gt) == 0,
do_bsol = (what & sgml_bsol) == 0,
do_dollar = (what & sgml_dollar) == 0,
do_percnt = (what & sgml_percnt) == 0,
do_commat = (what & sgml_commat) == 0,
do_num = (what & sgml_num) == 0,
do_lpar_rpar = (what & sgml_lpar_rpar) == 0,
do_lcub_rcub = (what & sgml_lcub_rcub) == 0,
do_lsqb_rsqb = (what & sgml_lsqb_rsqb) == 0;
size_t j = strnlen(dst, count_dst);
count_src = strnlen(src, count_src);
for (size_t i = 0; i < count_src;) {
size_t n = glyphlen(src + i, count_src - i);
if (n == 1 &&
do_ascii && is7bit(src[i]) &&
src[i] != '&' &&
(do_quot || (src[i] != '"')) &&
(do_apos || (src[i] != '\'')) &&
(do_lt_gt || (src[i] != '<' && src[i] != '>')) &&
(do_bsol || (src[i] != '\\')) &&
(do_dollar || (src[i] != '$')) &&
(do_percnt || (src[i] != '%')) &&
(do_commat || (src[i] != '@')) &&
(do_num || (src[i] != '#')) &&
(do_lpar_rpar || (src[i] != '(' && src[i] != ')')) &&
(do_lcub_rcub || (src[i] != '{' && src[i] != '}')) &&
(do_lsqb_rsqb || (src[i] != '[' && src[i] != ']')))
{
// 7-bit ASCII and no desire to encode it as an SGML entity.
if (j + 1 >= count_dst)
throw buffer_overrun;
dst[j++] = static_cast<char>(src[i++]);
}
else {
const char* entity = chr2sgml(src + i, n);
if (entity) {
size_t m = strlen(entity);
if (j + m + 2 >= count_dst)
throw buffer_overrun;
dst[j++] = '&';
memcpy(dst + j, entity, m * sizeof(char)); j += m;
dst[j++] = ';';
i += n;
}
else if (n == 1) {
// Trivial character (1 code unit, 1 glyph), no entity available.
if (is7bit(src[i])) {
if (j + 1 >= count_dst)
throw buffer_overrun;
dst[j++] = static_cast<char>(src[i++]);
}
else {
char tmp[3 + 8 + 1 + 1];
int m = ::snprintf(tmp, _countof(tmp), "&#x%x;", static_cast<unsigned int>(src[i++]));
stdex_assert(m >= 0);
if (static_cast<size_t>(m) >= count_dst)
throw buffer_overrun;
memcpy(dst + j, tmp, static_cast<size_t>(m) * sizeof(char));
j += static_cast<size_t>(m);
}
}
else {
// Non-trivial character. Decompose.
const size_t end = i + n;
while (i < end) {
if ((entity = chr2sgml(src + i, 1)) != nullptr) {
size_t m = strlen(entity);
if (j + m + 2 >= count_dst)
throw buffer_overrun;
dst[j++] = '&';
memcpy(dst + j, entity, m * sizeof(char)); j += m;
dst[j++] = ';';
i++;
}
else if (is7bit(src[i])) {
if (j + 1 >= count_dst)
throw buffer_overrun;
dst[j++] = static_cast<char>(src[i++]);
}
else {
char tmp[3 + 8 + 1 + 1];
int m = ::snprintf(tmp, _countof(tmp), "&#x%x;", static_cast<unsigned int>(wstr_to_utf32(src, i, end)));
stdex_assert(m >= 0);
if (static_cast<size_t>(m) >= count_dst)
throw buffer_overrun;
memcpy(dst + j, tmp, static_cast<size_t>(m) * sizeof(char));
j += static_cast<size_t>(m);
}
}
}
}
}
if (j >= count_dst)
throw buffer_overrun;
dst[j] = 0;
return j;
}
///
/// Convert Unicode string to SGML
///
/// \param[in,out] dst String to write SGML to
/// \param[in] src Unicode string
/// \param[in] count_src Unicode string character count limit
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra characters otherwise not converted to SGML
///
template <class T_from, class TR_to = std::char_traits<char>, class AX_to = std::allocator<char>>
void str2sgmlcpy(
_Inout_ std::basic_string<char, TR_to, AX_to>& dst,
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int what = 0)
{
dst.clear();
str2sgmlcat(dst, src, count_src, what);
}
///
/// Convert Unicode string to SGML
///
/// \param[in,out] dst String to write SGML to
/// \param[in] src Unicode string
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra characters otherwise not converted to SGML
///
template <class T_from, class TR_to = std::char_traits<char>, class AX_to = std::allocator<char>, class TR_from = std::char_traits<T_from>, class AX_from = std::allocator<T_from>>
void str2sgmlcpy(
_Inout_ std::basic_string<char, TR_to, AX_to>& dst,
_In_ const std::basic_string<T_from, TR_from, AX_from>& src,
_In_ int what = 0)
{
str2sgmlcpy(dst, src.data(), src.size(), what);
}
///
/// Convert Unicode string to SGML
///
/// \param[in,out] dst String to write SGML to
/// \param[in] count_dst SGML string character count limit. Function throws std::invalid_argument if there is not enough space in SGML string (including space for zero-terminator).
/// \param[in] src Unicode string
/// \param[in] count_src Unicode string character count limit
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra characters otherwise not converted to SGML
///
/// \return Final length of SGML string in code points excluding zero-terminator
///
template <class T_from>
size_t str2sgmlcpy(
_Inout_cap_(count_dst) char* dst, _In_ size_t count_dst,
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int what = 0)
{
stdex_assert(dst || !count_dst);
if (count_dst)
dst[0] = 0;
return str2sgmlcat(dst, count_dst, src, count_src, what);
}
///
/// Convert Unicode string to SGML string
///
/// \param[in] src Unicode string
/// \param[in] count_src Unicode string character count limit
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra characters otherwise not converted to SGML
///
/// \return SGML string
///
template <class T_from>
std::string str2sgml(
_In_reads_or_z_opt_(count_src) const T_from* src, _In_ size_t count_src,
_In_ int what = 0)
{
std::string dst;
str2sgmlcat(dst, src, count_src, what);
return dst;
}
///
/// Convert Unicode string to SGML string
///
/// \param[in] src Unicode string
/// \param[in] what Bitwise flag of stdex::sgml_* constants that force extra characters otherwise not converted to SGML
///
/// \return SGML string
///
template <class T_from, class TR_from = std::char_traits<T_from>, class AX_from = std::allocator<T_from>>
std::string str2sgml(
_In_ const std::basic_string<T_from, TR_from, AX_from>& src,
_In_ int what = 0)
{
return str2sgml(src.data(), src.size(), what);
}
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

File diff suppressed because it is too large Load Diff

115
include/stdex/socket.hpp Normal file
View File

@ -0,0 +1,115 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "system.hpp"
#if defined(_WIN32)
#include "windows.h"
#include <WinSock2.h>
#include <WS2tcpip.h>
#else
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <memory>
namespace stdex
{
#ifdef _WIN32
using socket_t = SOCKET;
constexpr socket_t invalid_socket = INVALID_SOCKET;
inline int closesocket(_In_ socket_t socket) { return ::closesocket(socket); }
#else
using socket_t = int;
constexpr socket_t invalid_socket = ((socket_t)-1);
inline int closesocket(_In_ socket_t socket) { return ::close(socket); }
#endif
///
/// Socket operations
///
struct socket_traits
{
static inline const socket_t invalid_handle = stdex::invalid_socket;
///
/// Closes socket
///
static void close(_In_ socket_t h)
{
int result = closesocket(h);
#ifdef _WIN32
int werrno = WSAGetLastError();
if (result >= 0 || werrno == WSAENOTSOCK)
return;
throw std::system_error(werrno, std::system_category(), "closesocket failed");
#else
if (result >= 0 || errno == EBADF)
return;
throw std::system_error(errno, std::system_category(), "closesocket failed");
#endif
}
};
///
/// Socket
///
using socket = basic_sys_object<socket_t, socket_traits>;
///
/// Deleter for unique_ptr using freeaddrinfo
///
struct freeaddrinfo_delete
{
///
/// Delete a pointer
///
void operator()(_In_ struct ::addrinfo* ptr) const
{
freeaddrinfo(ptr);
}
};
///
/// addrinfo struct
///
using addrinfo = std::unique_ptr<struct addrinfo, freeaddrinfo_delete>;
#ifdef _WIN32
///
/// Deleter for unique_ptr using FreeAddrInfoW
///
struct FreeAddrInfoW_delete
{
///
/// Delete a pointer
///
void operator()(_In_ ADDRINFOW* ptr) const
{
FreeAddrInfoW(ptr);
}
};
///
/// addrinfo struct
///
using waddrinfo = std::unique_ptr<ADDRINFOW, FreeAddrInfoW_delete>;
///
/// Multi-byte / Wide-character ADDRINFO wrapper class (according to _UNICODE)
///
#ifdef UNICODE
using saddrinfo = waddrinfo;
#else
using saddrinfo = addrinfo;
#endif
#else
using saddrinfo = addrinfo;
#endif
}

View File

@ -0,0 +1,76 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#ifdef _WIN32
#include "windows.h"
#include <intrin.h>
#endif
#include <atomic>
namespace stdex
{
///
/// Spin-lock
///
/// \sa [Correctly implementing a spinlock in C++](https://rigtorp.se/spinlock/)
///
class spinlock
{
private:
std::atomic<bool> m_lock = { false };
public:
///
/// Blocks until a lock can be acquired for the current execution agent (thread, process, task).
///
void lock() noexcept
{
for (;;) {
// Optimistically assume the lock is free on the first try
if (!m_lock.exchange(true, std::memory_order_acquire))
return;
// Wait for lock to be released without generating cache misses
while (m_lock.load(std::memory_order_relaxed)) {
// Issue X86 PAUSE or ARM YIELD instruction to reduce contention between
// hyper-threads
#if _M_ARM || _M_ARM64
__yield();
#elif _M_IX86 || _M_X64
_mm_pause();
#elif __aarch64__
asm volatile("yield");
#elif __i386__ || __x86_64__
__builtin_ia32_pause();
#endif
}
}
}
///
/// Attempts to acquire the lock for the current execution agent (thread, process, task) without blocking.
///
/// \returns true if the lock was acquired, false otherwise
///
bool try_lock() noexcept
{
// First do a relaxed load to check if lock is free in order to prevent
// unnecessary cache misses if someone does while(!try_lock())
return
!m_lock.load(std::memory_order_relaxed) &&
!m_lock.exchange(true, std::memory_order_acquire);
}
///
/// Releases the non-shared lock held by the execution agent.
///
void unlock() noexcept
{
m_lock.store(false, std::memory_order_release);
}
};
}

4394
include/stdex/stream.hpp Normal file

File diff suppressed because it is too large Load Diff

3497
include/stdex/string.hpp Normal file

File diff suppressed because it is too large Load Diff

281
include/stdex/sys_info.hpp Normal file
View File

@ -0,0 +1,281 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include "string.hpp"
#include "system.hpp"
#if defined(_WIN32)
#include "windows.h"
#include <security.h>
#include <stdlib.h>
#include <tchar.h>
#else
#include <sys/utsname.h>
#endif
#include <map>
#include <memory>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wexit-time-destructors"
#endif
namespace stdex
{
///
/// Platform ID
///
enum class platform_id : uint16_t {
#ifdef _WIN32
unknown = IMAGE_FILE_MACHINE_UNKNOWN,
i386 = IMAGE_FILE_MACHINE_I386,
x86_64 = IMAGE_FILE_MACHINE_AMD64,
arm = IMAGE_FILE_MACHINE_ARMNT,
aarch64 = IMAGE_FILE_MACHINE_ARM64,
#else
unknown = 0,
i386 = 0x014c,
x86_64 = 0x8664,
arm = 0x01c4,
aarch64 = 0xaa64,
#endif
};
///
/// Parses platform name and returns matching platform code
///
/// \param[in] name Platform name
///
/// \returns Platform code or `platform_id::unknown` if match not found
///
inline platform_id platform_from_name(_In_z_ const char* name)
{
struct platform_less {
bool operator()(_In_z_ const char* a, _In_z_ const char* b) const
{
return stricmp(a, b) < 0;
}
};
static const std::map<const char*, platform_id, platform_less> platforms = {
{ "aarch64", platform_id::aarch64 },
{ "arm", platform_id::arm },
{ "i386", platform_id::i386 },
{ "x86_64", platform_id::x86_64 },
};
if (auto el = platforms.find(name); el != platforms.end())
return el->second;
return platform_id::unknown;
}
///
/// System information
///
inline const struct sys_info_t
{
///
/// The platform this process was compiled for
///
#if _M_IX86 || __i386__
static constexpr platform_id process_platform = platform_id::i386;
#elif _M_X64 /* _M_ARM64EC is introducing as x64 */ || __x86_64__
static constexpr platform_id process_platform = platform_id::x86_64;
#elif _M_ARM || __arm__
static constexpr platform_id process_platform = platform_id::arm;
#elif _M_ARM64 || __aarch64__
static constexpr platform_id process_platform = platform_id::aarch64;
#else
#error Unknown platform
#endif
///
/// The operating system platform
///
platform_id os_platform;
#ifdef _WIN32
///
/// Is a Windows-on-Windows64 process?
///
bool wow64;
#endif
///
/// Is interactive process?
///
bool interactive_process;
///
/// Is member of local group Administrators (Windows) or member of group wheel/sudoers (others)?
///
bool admin;
///
/// Is elevated process (Windows) or running as root (others)?
///
bool elevated;
///
/// Currently signed in user
///
sstring username;
sys_info_t() :
os_platform(platform_id::unknown),
#ifdef _WIN32
wow64(false),
#endif
interactive_process(true),
admin(false),
elevated(false)
{
#ifdef _WIN32
HMODULE kernel32_handle;
kernel32_handle = LoadLibrary(_T("kernel32.dll"));
stdex_assert(kernel32_handle);
BOOL(WINAPI * IsWow64Process2)(HANDLE hProcess, USHORT * pProcessMachine, USHORT * pNativeMachine);
*reinterpret_cast<FARPROC*>(&IsWow64Process2) = GetProcAddress(kernel32_handle, "IsWow64Process2");
HANDLE process = GetCurrentProcess();
USHORT process_machine;
#ifndef _WIN64
BOOL Wow64Process;
#endif
if (IsWow64Process2 && IsWow64Process2(process, &process_machine, reinterpret_cast<USHORT*>(&os_platform))) {
wow64 = process_machine != IMAGE_FILE_MACHINE_UNKNOWN;
}
#ifdef _WIN64
else {
os_platform = process_platform;
wow64 = false;
}
#else
else if (IsWow64Process(process, &Wow64Process)) {
if (Wow64Process) {
os_platform = platform_id::x86_64;
wow64 = true;
}
else {
os_platform = process_platform;
wow64 = false;
}
}
#endif
FreeLibrary(kernel32_handle);
#else
memset(&m_utsn, 0, sizeof(m_utsn));
if (uname(&m_utsn) != -1)
os_platform = platform_from_name(m_utsn.machine);
#endif
#ifdef _WIN32
HWINSTA hWinSta = GetProcessWindowStation();
if (hWinSta) {
TCHAR sName[MAX_PATH];
if (GetUserObjectInformation(hWinSta, UOI_NAME, sName, sizeof(sName), NULL)) {
sName[_countof(sName) - 1] = 0;
// Only "WinSta0" is interactive (Source: KB171890)
interactive_process = _tcsicmp(sName, _T("WinSta0")) == 0;
}
}
#else
// TODO: Research interactive process vs service/agent/daemon on this platform.
#endif
#if defined(_WIN32)
{
HANDLE token_h;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_h)) {
sys_object token(token_h);
TOKEN_ELEVATION elevation;
DWORD size = sizeof(TOKEN_ELEVATION);
if (GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &size))
elevated = elevation.TokenIsElevated;
GetTokenInformation(token, TokenGroups, NULL, 0, &size);
std::unique_ptr<TOKEN_GROUPS> groups((TOKEN_GROUPS*)new uint8_t[size]);
if (GetTokenInformation(token, TokenGroups, (LPVOID)groups.get(), size, &size)) {
SID_IDENTIFIER_AUTHORITY authority = SECURITY_NT_AUTHORITY;
PSID sid_admins_h = NULL;
if (AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &sid_admins_h)) {
struct SID_delete { void operator()(_In_ PSID p) const { FreeSid(p); } };
std::unique_ptr<void, SID_delete> sid_admins(sid_admins_h);
for (DWORD i = 0; i < groups->GroupCount; ++i)
if (EqualSid(sid_admins.get(), groups->Groups[i].Sid)) {
admin = true;
break;
}
}
}
}
}
#elif defined(__APPLE__)
{
gid_t gids[NGROUPS_MAX];
for (int i = 0, n = getgroups(NGROUPS_MAX, gids); i < n; ++i) {
struct group* group = getgrgid(gids[i]);
if (!group) continue;
if (strcmp(group->gr_name, "admin") == 0) {
admin = true;
break;
}
}
}
elevated = geteuid() == 0;
#else
// TODO: Set admin.
elevated = geteuid() == 0;
#endif
#ifdef _WIN32
#if defined(SECURITY_WIN32) || defined(SECURITY_KERNEL)
{
TCHAR szStackBuffer[0x100];
ULONG ulSize = _countof(szStackBuffer);
if (GetUserNameEx(NameSamCompatible, szStackBuffer, &ulSize))
username.assign(szStackBuffer, ulSize);
if (GetLastError() == ERROR_MORE_DATA) {
// Allocate buffer on heap and retry.
username.resize(ulSize - 1);
if (!GetUserNameEx(NameSamCompatible, &username[0], &ulSize))
username.clear();
}
}
#endif
#else
{
struct passwd *pw = getpwuid(geteuid());
if (pw && pw->pw_name)
username = pw->pw_name;
}
#endif
}
///
/// Is screen reader currently active?
///
static bool is_screen_reader()
{
#ifdef _WIN32
BOOL b;
return SystemParametersInfo(SPI_GETSCREENREADER, 0, &b, 0) && b;
#else
return false;
#endif
}
protected:
#ifndef _WIN32
struct utsname m_utsn;
#endif
} sys_info;
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

316
include/stdex/system.hpp Normal file
View File

@ -0,0 +1,316 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#if defined(_WIN32)
#include "windows.h"
#include <oaidl.h>
#include <tchar.h>
#else
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE // TODO: Make this -D compile-time project setting
#endif
#include <grp.h>
#include <pwd.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <regex>
#include <stdexcept>
#include <string_view>
#include <string>
#if defined(_WIN32)
#define PATH_SEPARATOR '\\'
#define PATH_SEPARATOR_STR "\\"
#else
#define PATH_SEPARATOR '/'
#define PATH_SEPARATOR_STR "/"
#endif
namespace stdex
{
///
/// Operating system handle
///
#if defined(_WIN32)
using sys_handle = HANDLE;
const sys_handle invalid_handle = INVALID_HANDLE_VALUE;
#else
using sys_handle = int;
const sys_handle invalid_handle = (sys_handle)-1;
#endif
///
/// Last operation error
///
#if defined(_WIN32)
inline DWORD sys_error() { return GetLastError(); }
#else
inline int sys_error() { return errno; }
#endif
///
/// Character type for system functions
///
#if defined(_WIN32)
using schar_t = TCHAR;
#else
using schar_t = char;
#endif
///
/// Character type for system functions for backward compatibility
/// Use stdex::schar_t
///
using sys_char = schar_t;
///
/// String for system functions
///
using sstring = std::basic_string<stdex::schar_t>;
#ifdef UNICODE
inline sstring to_sstring(int value) { return std::to_wstring(value); }
inline sstring to_sstring(long value) { return std::to_wstring(value); }
inline sstring to_sstring(long long value) { return std::to_wstring(value); }
inline sstring to_sstring(unsigned value) { return std::to_wstring(value); }
inline sstring to_sstring(unsigned long value) { return std::to_wstring(value); }
inline sstring to_sstring(unsigned long long value) { return std::to_wstring(value); }
inline sstring to_sstring(float value) { return std::to_wstring(value); }
inline sstring to_sstring(double value) { return std::to_wstring(value); }
inline sstring to_sstring(long double value) { return std::to_wstring(value); }
#else
inline sstring to_sstring(int value) { return std::to_string(value); }
inline sstring to_sstring(long value) { return std::to_string(value); }
inline sstring to_sstring(long long value) { return std::to_string(value); }
inline sstring to_sstring(unsigned value) { return std::to_string(value); }
inline sstring to_sstring(unsigned long value) { return std::to_string(value); }
inline sstring to_sstring(unsigned long long value) { return std::to_string(value); }
inline sstring to_sstring(float value) { return std::to_string(value); }
inline sstring to_sstring(double value) { return std::to_string(value); }
inline sstring to_sstring(long double value) { return std::to_string(value); }
#endif
///
/// String for system functions for backward compatibility
/// Use stdex::sstring
///
using sys_string = sstring;
///
/// String view for system functions
///
using sstring_view = std::basic_string_view<stdex::schar_t, std::char_traits<stdex::schar_t>>;
///
/// Regular expressions for system strings
///
using sregex = std::basic_regex<stdex::schar_t>;
///
/// System object operations
///
struct sys_object_traits
{
static inline const sys_handle invalid_handle = stdex::invalid_handle;
///
/// Closes object
///
static void close(_In_ sys_handle h)
{
#if defined(_WIN32)
if (CloseHandle(h) || GetLastError() == ERROR_INVALID_HANDLE)
return;
throw std::system_error(GetLastError(), std::system_category(), "CloseHandle failed");
#else
if (::close(h) >= 0 || errno == EBADF)
return;
throw std::system_error(errno, std::system_category(), "close failed");
#endif
}
///
/// Duplicates given object
///
static sys_handle duplicate(_In_ sys_handle h, _In_ bool inherit = false)
{
sys_handle h_new;
#if defined(_WIN32)
HANDLE process = GetCurrentProcess();
if (DuplicateHandle(process, h, process, &h_new, 0, inherit, DUPLICATE_SAME_ACCESS))
return h_new;
throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle failed");
#else
_Unreferenced_(inherit);
if ((h_new = dup(h)) >= 0)
return h_new;
throw std::system_error(errno, std::system_category(), "dup failed");
#endif
}
};
///
/// Operating system object base class
///
template <class T = sys_handle, class TR = sys_object_traits>
class basic_sys_object
{
public:
basic_sys_object(_In_opt_ T h = TR::invalid_handle) : m_h(h) {}
basic_sys_object(_In_ const basic_sys_object<T, TR>& other) : m_h(other.m_h != TR::invalid_handle ? TR::duplicate(other.m_h) : TR::invalid_handle) {}
basic_sys_object& operator =(_In_ const basic_sys_object<T, TR>& other)
{
if (this != std::addressof(other)) {
if (m_h != TR::invalid_handle)
TR::close(m_h);
m_h = other.m_h != TR::invalid_handle ? TR::duplicate(other.m_h) : TR::invalid_handle;
}
return *this;
}
basic_sys_object(_Inout_ basic_sys_object<T, TR>&& other) noexcept : m_h(other.m_h)
{
other.m_h = TR::invalid_handle;
}
basic_sys_object& operator =(_Inout_ basic_sys_object<T, TR>&& other) noexcept
{
if (this != std::addressof(other)) {
if (m_h != TR::invalid_handle)
TR::close(m_h);
m_h = other.m_h;
other.m_h = TR::invalid_handle;
}
return *this;
}
virtual ~basic_sys_object()
{
if (m_h != TR::invalid_handle) {
try { TR::close(m_h); }
catch (...) {} // Failure to close a handle should not be that devastating as throwing in a destructor may be.
}
}
///
/// Closes object
///
virtual void close()
{
if (m_h != TR::invalid_handle) {
TR::close(m_h);
m_h = TR::invalid_handle;
}
}
///
/// Returns true if object has a valid handle
///
bool valid() const noexcept { return m_h != TR::invalid_handle; }
///
/// Returns object handle
///
operator T() const noexcept { return m_h; }
private:
// Force use of valid() method when testing handle.
operator bool() const;
protected:
T m_h;
};
///
/// Operating system object (file, pipe, anything with an OS handle etc.)
///
using sys_object = basic_sys_object<sys_handle, sys_object_traits>;
#ifdef _WIN32
template <class T>
class safearray_accessor
{
public:
safearray_accessor(_In_ LPSAFEARRAY sa) : m_sa(sa)
{
HRESULT hr = SafeArrayAccessData(sa, reinterpret_cast<void HUGEP**>(&m_data));
if (FAILED(hr))
throw std::system_error(hr, std::system_category(), "SafeArrayAccessData failed");
}
~safearray_accessor()
{
SafeArrayUnaccessData(m_sa);
}
T* data() const { return m_data; }
protected:
LPSAFEARRAY m_sa;
T* m_data;
};
template <class T>
class safearray_accessor_with_size : public safearray_accessor<T>
{
public:
safearray_accessor_with_size(_In_ LPSAFEARRAY sa) : safearray_accessor<T>(sa)
{
m_size = SafeArrayGetElemsize(sa);
for (UINT d = 1, dim = SafeArrayGetDim(sa); d <= dim; ++d) {
long ubound, lbound;
if (FAILED(SafeArrayGetUBound(sa, d, &ubound)) ||
FAILED(SafeArrayGetLBound(sa, d, &lbound)))
throw std::invalid_argument("SafeArrayGet[UL]Bound failed");
m_size *= static_cast<size_t>(ubound) - lbound + 1;
}
m_size /= sizeof(T);
}
///
/// Return size in number of elements
///
size_t size() const { return m_size; }
protected:
size_t m_size;
};
///
/// Deleter for unique_ptr using SafeArrayDestroy
///
struct SafeArrayDestroy_delete
{
///
/// Delete a pointer
///
void operator()(_In_ LPSAFEARRAY sa) const
{
SafeArrayDestroy(sa);
}
};
///
/// Deleter for unique_ptr using SysFreeString
///
struct SysFreeString_delete
{
///
/// Delete a pointer
///
void operator()(_In_ BSTR sa) const
{
SysFreeString(sa);
}
};
#endif
}

1010
include/stdex/unicode.hpp Normal file

File diff suppressed because it is too large Load Diff

176
include/stdex/uuid.hpp Normal file
View File

@ -0,0 +1,176 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include "string.hpp"
#include <stdint.h>
#include <stdio.h>
#if defined(_WIN32)
#include "windows.h"
#include <rpc.h>
#else
#include <uuid/uuid.h>
#include <wchar.h>
#endif
namespace stdex
{
///
/// Number of characters required to hold a registry string {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} GUID representation.
/// Including zero terminator.
///
constexpr size_t uuid_str_max = 39;
///
/// Formats GUID to a registry string {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}.
///
/// \param[out] str String to write GUID. Must point to at least `uuid_str_max` code points to write complete GUID including zero terminator.
/// \param[in ] id GUID to write.
///
inline void uuidtostr(_Out_writes_z_(uuid_str_max) char str[uuid_str_max], _In_ const uuid_t& id)
{
stdex_assert(str);
#ifdef _WIN32
_snprintf_s_l(str, uuid_str_max, _TRUNCATE, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", NULL,
id.Data1,
static_cast<unsigned int>(id.Data2),
static_cast<unsigned int>(id.Data3),
static_cast<unsigned int>(id.Data4[0]), static_cast<unsigned int>(id.Data4[1]),
static_cast<unsigned int>(id.Data4[2]), static_cast<unsigned int>(id.Data4[3]), static_cast<unsigned int>(id.Data4[4]), static_cast<unsigned int>(id.Data4[5]), static_cast<unsigned int>(id.Data4[6]), static_cast<unsigned int>(id.Data4[7]));
#else
::snprintf(str, uuid_str_max, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
*reinterpret_cast<const uint32_t*>(&id[0]),
static_cast<unsigned int>(*reinterpret_cast<const uint16_t*>(&id[4])),
static_cast<unsigned int>(*reinterpret_cast<const uint16_t*>(&id[6])),
static_cast<unsigned int>(id[8]), static_cast<unsigned int>(id[9]),
static_cast<unsigned int>(id[10]), static_cast<unsigned int>(id[11]), static_cast<unsigned int>(id[12]), static_cast<unsigned int>(id[13]), static_cast<unsigned int>(id[14]), static_cast<unsigned int>(id[15]));
#endif
}
///
/// Formats GUID to a registry string {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}.
///
/// \param[out] str String to write GUID. Must point to at least `uuid_str_max` code points to write complete GUID including zero terminator.
/// \param[in ] id GUID to write.
///
inline void uuidtostr(_Out_writes_z_(uuid_str_max) wchar_t str[uuid_str_max], _In_ const uuid_t& id)
{
stdex_assert(str);
#ifdef _WIN32
_snwprintf_s_l(str, uuid_str_max, _TRUNCATE, L"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", NULL,
id.Data1,
static_cast<unsigned int>(id.Data2),
static_cast<unsigned int>(id.Data3),
static_cast<unsigned int>(id.Data4[0]), static_cast<unsigned int>(id.Data4[1]),
static_cast<unsigned int>(id.Data4[2]), static_cast<unsigned int>(id.Data4[3]), static_cast<unsigned int>(id.Data4[4]), static_cast<unsigned int>(id.Data4[5]), static_cast<unsigned int>(id.Data4[6]), static_cast<unsigned int>(id.Data4[7]));
#else
swprintf(str, uuid_str_max, L"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", NULL,
*reinterpret_cast<const uint32_t*>(&id[0]),
static_cast<unsigned int>(*reinterpret_cast<const uint16_t*>(&id[4])),
static_cast<unsigned int>(*reinterpret_cast<const uint16_t*>(&id[6])),
static_cast<unsigned int>(id[8]), static_cast<unsigned int>(id[9]),
static_cast<unsigned int>(id[10]), static_cast<unsigned int>(id[11]), static_cast<unsigned int>(id[12]), static_cast<unsigned int>(id[13]), static_cast<unsigned int>(id[14]), static_cast<unsigned int>(id[15]));
#endif
}
///
/// Parses string for GUID in a form of {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
///
/// \param[in] str String
/// \param[in] count Code unit count limit
/// \param[out] id GUID to read
///
/// \returns true if parse was successful; false otherwise.
///
template <class T> inline _Success_(return != 0) bool strtouuid(
_In_reads_or_z_opt_(count) const T* str,
_In_ size_t count,
_Out_ uuid_t& id)
{
size_t i = 0, j;
uint64_t n;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
if (i >= count || str[i] != '{') return false;
++i;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
n = stdex::strtou64(&str[i], count - i, &j, 16);
if (n > UINT32_MAX) return false;
#ifdef _WIN32
id.Data1 = static_cast<unsigned long>(n);
#else
* reinterpret_cast<uint32_t*>(&id[0]) = static_cast<uint32_t>(n);
#endif
i += j;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
if (i >= count || str[i] != '-') return false;
++i;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
n = stdex::strtou64(&str[i], count - i, &j, 16);
if (n > UINT16_MAX) return false;
#ifdef _WIN32
id.Data2 = static_cast<unsigned short>(n);
#else
* reinterpret_cast<uint16_t*>(&id[4]) = static_cast<uint16_t>(n);
#endif
i += j;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
if (i >= count || str[i] != '-') return false;
++i;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
n = stdex::strtou64(&str[i], count - i, &j, 16);
if (n > UINT16_MAX) return false;
#ifdef _WIN32
id.Data3 = static_cast<unsigned short>(n);
#else
* reinterpret_cast<uint16_t*>(&id[6]) = static_cast<uint16_t>(n);
#endif
i += j;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
if (i >= count || str[i] != '-') return false;
++i;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
n = stdex::strtou64(&str[i], count - i, &j, 16);
if (n > UINT16_MAX) return false;
#ifdef _WIN32
id.Data4[0] = static_cast<unsigned char>((n >> 8) & 0xff);
id.Data4[1] = static_cast<unsigned char>((n) & 0xff);
#else
id[8] = static_cast<unsigned char>((n >> 8) & 0xff);
id[9] = static_cast<unsigned char>((n) & 0xff);
#endif
i += j;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
if (i >= count && str[i] != '-') return false;
++i;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
n = stdex::strtou64(&str[i], count - i, &j, 16);
if (n > 0xffffffffffff) return false;
#ifdef _WIN32
id.Data4[2] = static_cast<unsigned char>((n >> 40) & 0xff);
id.Data4[3] = static_cast<unsigned char>((n >> 32) & 0xff);
id.Data4[4] = static_cast<unsigned char>((n >> 24) & 0xff);
id.Data4[5] = static_cast<unsigned char>((n >> 16) & 0xff);
id.Data4[6] = static_cast<unsigned char>((n >> 8) & 0xff);
id.Data4[7] = static_cast<unsigned char>((n) & 0xff);
#else
id[10] = static_cast<unsigned char>((n >> 40) & 0xff);
id[11] = static_cast<unsigned char>((n >> 32) & 0xff);
id[12] = static_cast<unsigned char>((n >> 24) & 0xff);
id[13] = static_cast<unsigned char>((n >> 16) & 0xff);
id[14] = static_cast<unsigned char>((n >> 8) & 0xff);
id[15] = static_cast<unsigned char>((n) & 0xff);
#endif
i += j;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
if (i >= count || str[i] != '}') return false;
++i;
for (; i < count && str[i] && stdex::isspace(str[i]); ++i);
return i >= count || !str[i];
}
}

View File

@ -0,0 +1,417 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include <stdexcept>
#include <utility>
namespace stdex
{
///
/// Helper class to allow limited size FIFO queues implemented as vector of elements
///
template <class T>
class vector_queue
{
public:
///
/// Type to measure element count and indices in
///
typedef size_t size_type;
///
/// Element type
///
typedef T value_type;
///
/// Reference to element type
///
typedef T& reference;
///
/// Constant reference to element type
///
typedef const T& const_reference;
///
/// Pointer to element
///
typedef T* pointer;
///
/// Constant pointer to element
///
typedef const T* const_pointer;
public:
///
/// Construct queue of fixed size.
///
/// \param[in] size_max Maximum number of elements. Please note this cannot be changed later.
///
vector_queue(_In_ size_type size_max) :
m_data(new value_type[size_max]),
m_head(0),
m_count(0),
m_size_max(size_max)
{}
///
/// Copies existing queue.
///
/// \param[in] other Queue to copy from
///
vector_queue(_In_ const vector_queue<value_type> &other) :
m_data(new value_type[other.m_size_max]),
m_head(other.m_head),
m_count(other.m_count),
m_size_max(other.m_size_max)
{
// Copy elements.
for (size_type i = 0; i < m_count; i++) {
size_type i_l = abs(i);
m_data[i_l] = other.m_data[i_l];
}
}
///
/// Destroys the queue
///
virtual ~vector_queue()
{
if (m_data) delete [] m_data;
}
///
/// Moves existing queue.
///
/// \param[in,out] other Queue to move
///
vector_queue(_Inout_ vector_queue<value_type> &&other) :
m_data (std::move(other.m_data )),
m_head (std::move(other.m_head )),
m_count (std::move(other.m_count )),
m_size_max(std::move(other.m_size_max))
{
// Reset other to consistent state.
other.m_data = NULL;
other.m_head = 0;
other.m_count = 0;
other.m_size_max = 0;
}
///
/// Copies existing queue.
///
/// \param[in] other Queue to copy from
///
vector_queue<value_type>& operator=(_In_ const vector_queue<value_type> &other)
{
if (this != std::addressof(other)) {
m_head = other.m_head;
m_count = other.m_count;
m_size_max = other.m_size_max;
// Copy elements.
if (m_data) delete [] m_data;
m_data = new value_type[other.m_size_max];
for (size_type i = 0; i < m_count; i++) {
size_type i_l = abs(i);
m_data[i_l] = other.m_data[i_l];
}
}
return *this;
}
///
/// Moves existing queue.
///
/// \param[in,out] other Queue to move
///
vector_queue<value_type>& operator=(_Inout_ vector_queue<value_type> &&other)
{
if (this != std::addressof(other)) {
m_data = std::move(other.m_data );
m_head = std::move(other.m_head );
m_count = std::move(other.m_count );
m_size_max = std::move(other.m_size_max);
// Reset other to consistent state.
other.m_data = NULL;
other.m_head = 0;
other.m_count = 0;
other.m_size_max = 0;
}
return *this;
}
///
/// Returns the number of elements in the vector.
///
size_type size() const
{
return m_count;
}
///
/// Returns the number of elements that the queue can contain before overwriting head ones.
///
size_type capacity() const
{
return m_size_max;
}
///
/// Erases the elements of the queue.
///
void clear()
{
m_count = 0;
}
///
/// Tests if the queue is empty.
///
bool empty() const
{
return m_count == 0;
}
///
/// Returns a reference to the element at a specified location in the queue.
///
/// \param[in] pos The subscript or position number of the element to reference in the queue.
///
reference at(_In_ size_type pos)
{
if (pos >= m_count) throw std::invalid_argument("Invalid subscript");
return m_data[abs(pos)];
}
///
/// Returns a reference to the element at a specified location in the queue.
///
/// \param[in] pos The subscript or position number of the element to reference in the queue.
///
reference operator[](_In_ size_type pos)
{
if (pos >= m_count) throw std::invalid_argument("Invalid subscript");
return m_data[abs(pos)];
}
///
/// Returns a constant reference to the element at a specified location in the queue.
///
/// \param[in] pos The subscript or position number of the element to reference in the queue.
///
const_reference at(_In_ size_type pos) const
{
if (pos >= m_count) throw std::invalid_argument("Invalid subscript");
return m_data[abs(pos)];
}
///
/// Returns a constant reference to the element at a specified location in the queue.
///
/// \param[in] pos The subscript or position number of the element to reference in the queue.
///
const_reference operator[](_In_ size_type pos) const
{
if (pos >= m_count) throw std::invalid_argument("Invalid subscript");
return m_data[abs(pos)];
}
///
/// Returns a reference to the element at the absolute location in the queue.
///
/// \note Absolute means "measured from the beginning of the storage".
///
/// \param[in] pos The absolute subscript or position number of the element to reference in the queue.
///
reference at_abs(_In_ size_type pos)
{
if (pos >= m_size_max) throw std::invalid_argument("Invalid subscript");
return m_data[pos];
}
///
/// Returns a constant reference to the element at the absolute location in the queue: measured from the beginning of the storage.
///
/// \note Absolute means "measured from the beginning of the storage".
///
/// \param[in] pos The absolute subscript or position number of the element to reference in the queue.
///
const_reference at_abs(_In_ size_type pos) const
{
if (pos >= m_size_max) throw std::invalid_argument("Invalid subscript");
return m_data[pos];
}
///
/// Copies an existing element to the end of the queue, overriding the first one when queue is out of space.
///
/// \param[in] v Element to copy to the end of the queue.
///
/// \returns The absolute subscript or position number the element was copied to.
///
size_type push_back(_In_ const value_type &v)
{
if (m_count < m_size_max) {
size_type pos = abs(m_count);
m_data[pos] = v;
m_count++;
return pos;
} else {
size_type pos = m_head;
m_data[pos] = v;
m_head = abs(1);
return pos;
}
}
///
/// Moves the element to the end of the queue, overriding the first one when queue is out of space.
///
/// \param[in] v Element to move to the end of the queue.
///
/// \returns The absolute subscript or position number the element was moved to.
///
size_type push_back(_Inout_ value_type&&v)
{
if (m_count < m_size_max) {
size_type pos = abs(m_count);
m_data[pos] = std::move(v);
m_count++;
return pos;
} else {
size_type pos = m_head;
m_data[pos] = std::move(v);
m_head = abs(1);
return pos;
}
}
///
/// Removes (dequeues) the last element of the queue.
///
void pop_back()
{
if (!m_count) throw std::invalid_argument("Empty storage");
m_count--;
}
///
/// Copies an existing element to the head of the queue, overriding the last one when queue is out of space and moving all others one place right.
///
/// \param[in] v Element to copy to the head of the queue.
///
/// \returns The absolute subscript or position number the element was copied to.
///
size_type push_front(_In_ const value_type &v)
{
m_head = abs(-1);
if (m_count < m_size_max)
m_count++;
m_data[m_head] = v;
return m_head;
}
///
/// Moves the element to the head of the queue, overriding the last one when queue is out of space and moving all others one place right.
///
/// \param[in] v Element to move to the head of the queue.
///
/// \returns The absolute subscript or position number the element was moved to.
///
size_type push_front(_Inout_ value_type&&v)
{
m_head = abs(-1);
if (m_count < m_size_max)
m_count++;
m_data[m_head] = std::move(v);
return m_head;
}
///
/// Removes (dequeues) the head element of the queue.
///
void pop_front()
{
if (!m_count) throw std::invalid_argument("Empty storage");
m_head = abs(1);
m_count--;
}
///
/// Returns a reference to the head element in the queue.
///
reference front()
{
if (!m_count) throw std::invalid_argument("Empty storage");
return m_data[m_head];
}
///
/// Returns a constant reference to the head element in the queue.
///
const_reference front() const
{
if (!m_count) throw std::invalid_argument("Empty storage");
return m_data[m_head];
}
///
/// Returns a reference to the last element in the queue.
///
reference back()
{
return m_data[tail()];
}
///
/// Returns a constant reference to the last element in the queue.
///
const_reference back() const
{
return m_data[tail()];
}
///
/// Returns absolute subscript or position number of the head element in the queue. The element does not need to exist.
///
size_type head() const
{
return m_head;
}
///
/// Returns absolute subscript or position number of the last element in the queue. The element must exist.
///
size_type tail() const
{
if (!m_count) throw std::invalid_argument("Empty storage");
return abs(m_count - 1);
}
///
/// Returns absolute subscript or position number of the given element in the queue.
///
size_type abs(_In_ size_type pos) const
{
return (m_head + pos) % m_size_max;
}
protected:
value_type *m_data; ///< Underlying data container
size_type m_head; ///< Index of the first element
size_type m_count; ///< Number of elements
size_type m_size_max; ///< Maximum size
};
}

102
include/stdex/watchdog.hpp Normal file
View File

@ -0,0 +1,102 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include <chrono>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
namespace stdex
{
///
/// Triggers callback if not reset frequently enough
///
template <class _Clock, class _Duration = typename _Clock::duration>
class watchdog
{
public:
///
/// Starts the watchdog
///
/// \param[in] timeout How long the watchdog is waiting for a reset
/// \param[in] callback The function watchdog calls on timeout
///
watchdog(_In_ _Duration timeout, _In_ std::function<void()> callback) :
m_phase(0),
m_quit(false),
m_timeout(timeout),
m_callback(callback),
m_thread([](_Inout_ watchdog& wd) { wd.run(); }, std::ref(*this))
{}
///
/// Stops the watchdog
///
~watchdog()
{
{
const std::lock_guard<std::mutex> lk(m_mutex);
m_quit = true;
}
m_cv.notify_one();
if (m_thread.joinable())
m_thread.join();
}
///
/// Resets the watchdog
///
/// Must be called frequently enough not to timeout the watchdog
///
void reset()
{
{
const std::lock_guard<std::mutex> lk(m_mutex);
m_phase++;
}
m_cv.notify_one();
}
protected:
void run()
{
size_t phase;
for (;;) {
{
std::unique_lock<std::mutex> lk(m_mutex);
phase = m_phase;
if (m_cv.wait_for(lk, m_timeout, [&] {return m_quit || phase != m_phase; })) {
if (m_quit)
break;
// reset() called in time.
continue;
}
}
// Timeout
m_callback();
{
// Sleep until next reset().
std::unique_lock<std::mutex> lk(m_mutex);
m_cv.wait(lk, [&] {return m_quit || phase != m_phase; });
if (m_quit)
break;
}
}
}
protected:
size_t m_phase; ///< A counter we are incrementing to keep the watchdog happy
bool m_quit; ///< Quit the watchdog
_Duration m_timeout; ///< How long the watchdog is waiting for a reset
std::function<void()> m_callback; ///< The function watchdog calls on timeout
std::mutex m_mutex;
std::condition_variable m_cv;
std::thread m_thread;
};
}

657
include/stdex/wav.hpp Normal file
View File

@ -0,0 +1,657 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
#include "compat.hpp"
#include "idrec.hpp"
#include "stream.hpp"
#include "string.hpp"
#include <stdint.h>
#include <algorithm>
#include <cstdlib>
#include <string>
#include <vector>
namespace stdex
{
namespace wav {
///
/// Type of WAV block ID.
///
/// Actually, this should be char[4], but <idrec.hpp> is using integral IDs.
///
using id_t = uint32_t;
///
/// Type of WAV block length
///
using length_t = uint32_t;
///
/// Alignment of WAV blocks in bytes
///
constexpr size_t align = 2;
///
/// File header
///
struct header
{
id_t type = 0; ///< RIFF type
friend inline stdex::stream::basic& operator <<(_In_ stdex::stream::basic& dat, _In_ const header& data)
{
dat << static_cast<uint32_t>(data.type);
return dat;
}
friend inline stdex::stream::basic& operator >>(_In_ stdex::stream::basic& dat, _Out_ header& data)
{
uint32_t pom4;
dat >> pom4;
if (!dat.ok()) _Unlikely_ goto error1;
data.type = pom4;
return dat;
error1:
data.type = 0;
return dat;
}
using record = stdex::idrec::record<header, id_t, 0x46464952 /*"RIFF"*/, length_t, align>;
};
///
/// Waveform block
///
struct wave : public header
{
using record = stdex::idrec::record<wave, id_t, 0x45564157 /*"WAVE"*/, length_t, align>;
};
///
/// Waveform format
///
struct format
{
enum class compression_t : uint16_t {
unknown = 0x0000, ///< Unknown
pcm = 0x0001, ///< PCM/uncompressed integral
microsoft_adpcm = 0x0002, ///< Microsoft ADPCM
pcm_float = 0x0003, ///< PCM/uncompressed floating point
itu_g711_a_law = 0x0006, ///< ITU G.711 a-law
itu_g711_mu_law = 0x0007, ///< ITU G.711 µ-law
ima_adpcm = 0x0011, ///< IMA ADPCM
itu_g_723_adpcm = 0x0016, ///< ITU G.723 ADPCM (Yamaha)
gsm_6_10 = 0x0031, ///< GSM 6.10
itu_g_721_adpcm = 0x0040, ///< ITU G.721 ADPCM
mpeg = 0x0050, ///< MPEG
experimental = 0xffff, ///< Experimental
} compression = compression_t::unknown; ///< Waveform compression
uint16_t num_channels = 0; ///< The number of channels specifies how many separate audio signals that are encoded in the wave data chunk (1 - mono, 2 - stereo)
uint32_t sample_rate = 0; ///< The number of sample slices per second (Hz). This value is unaffected by the number of channels.
uint32_t bytes_per_second = 0; ///< How many bytes of wave data must be streamed to a D/A converter per second in order to play the wave file. This information is useful when determining if data can be streamed from the source fast enough to keep up with playback. This value can be easily calculated with the formula: `sample_rate * block_align`
uint16_t block_align = 0; ///< The number of bytes per sample slice (all channels). This value is not affected by the number of channels and can be calculated with the formula: `bits_per_channel / 8 * num_channels`
uint16_t bits_per_channel = 0; ///< The number of bits used to define each sample. This value is usually 8, 16, 24 or 32. If the number of bits is not byte aligned (a multiple of 8) then the number of bytes used per sample is rounded up to the nearest byte size and the unused bytes are set to 0 and ignored.
stdex::stream::memory_file extra; ///< Additional format data
friend inline stdex::stream::basic& operator <<(_In_ stdex::stream::basic& dat, _In_ const format& data)
{
dat << static_cast<uint16_t>(data.compression);
dat << data.num_channels;
dat << data.sample_rate;
dat << data.bytes_per_second;
dat << data.block_align;
dat << data.bits_per_channel;
if (auto size = data.extra.size(); size) {
if (size > UINT16_MAX) _Unlikely_
throw std::invalid_argument("extra data too big");
dat << static_cast<uint16_t>(size);
if (dat.ok())
dat.write(data.extra.data(), static_cast<size_t>(size));
}
return dat;
}
friend inline stdex::stream::basic& operator >>(_In_ stdex::stream::basic& dat, _Out_ format& data)
{
uint16_t tmp16;
dat >> tmp16;
if (!dat.ok()) _Unlikely_ goto error1;
data.compression = static_cast<format::compression_t>(tmp16);
dat >> data.num_channels;
dat >> data.sample_rate;
dat >> data.bytes_per_second;
dat >> data.block_align;
dat >> data.bits_per_channel;
dat >> tmp16;
if (!dat.ok() || !tmp16) goto error7;
data.extra.seek(0);
data.extra.write_stream(dat, tmp16);
data.extra.truncate();
return dat;
error1:
data.compression = format::compression_t::unknown;
data.num_channels = 0;
data.sample_rate = 0;
data.bytes_per_second = 0;
data.block_align = 0;
data.bits_per_channel = 0;
error7:
data.extra.seek(0);
data.extra.truncate();
return dat;
}
using record = stdex::idrec::record<format, id_t, 0x20746D66 /*"fmt "*/, length_t, align>;
};
///
/// Encoded waveform content
///
struct data
{
stdex::stream::memory_file content; ///< Encoded waveform
friend inline stdex::stream::basic& operator <<(_In_ stdex::stream::basic& dat, _In_ const data& data)
{
if (!dat.ok()) _Unlikely_ return dat;
dat.write(data.content.data(), static_cast<size_t>(data.content.size()));
return dat;
}
friend inline stdex::stream::basic& operator >>(_In_ stdex::stream::basic& dat, _Out_ data& data)
{
data.content.seek(0);
data.content.write_stream(dat);
data.content.truncate();
return dat;
}
using record = stdex::idrec::record<data, id_t, 0x61746164 /*"data"*/, length_t, align>;
};
///
/// Silence
///
struct silence
{
uint32_t num_samples = 0; ///< The number of silent samples that appear in the waveform at this point in the wave list chunk
friend inline stdex::stream::basic& operator <<(_In_ stdex::stream::basic& dat, _In_ const silence& data)
{
dat << data.num_samples;
return dat;
}
friend inline stdex::stream::basic& operator >>(_In_ stdex::stream::basic& dat, _Out_ silence& data)
{
dat >> data.num_samples;
return dat;
}
using record = stdex::idrec::record<silence, id_t, 0x746E6C73 /*"slnt"*/, length_t, align>;
};
///
/// Cue point
///
struct cue
{
uint32_t id = 0; ///< Each cue point has a unique identification value used to associate cue points with information in other chunks. For example, a Label chunk contains text that describes a point in the wave file by referencing the associated cue point.
uint32_t position = 0; ///< The sample offset associated with the cue point in terms of the sample's position in the final stream of samples generated by the play list. Said in another way, if a play list chunk is specified, the position value is equal to the sample number at which this cue point will occur during playback of the entire play list as defined by the play list's order. If no play list chunk is specified this value should be 0.
uint32_t chunk_id = 0; ///< The four byte ID used by the chunk containing the sample that corresponds to this cue point. A Wave file with no play list is always "data". A Wave file with a play list containing both sample data and silence may be either "data" or "slnt".
uint32_t chunk_offset = 0; ///< The byte offset into the Wave List Chunk of the chunk containing the sample that corresponds to this cue point. This is the same chunk described by the Data Chunk ID value. If no Wave List Chunk exists in the Wave file, this value is 0. If a Wave List Chunk exists, this is the offset into the "wavl" chunk. The first chunk in the Wave List Chunk would be specified with a value of 0.
uint32_t block_start = 0; ///< The byte offset into the "data" or "slnt" Chunk to the start of the block containing the sample. The start of a block is defined as the first byte in uncompressed PCM wave data or the last byte in compressed wave data where decompression can begin to find the value of the corresponding sample value.
uint32_t block_offset = 0; ///< An offset into the block (specified by Block Start) for the sample that corresponds to the cue point. In uncompressed PCM waveform data, this is simply the byte offset into the "data" chunk. In compressed waveform data, this value is equal to the number of samples (may or may not be bytes) from the Block Start to the sample that corresponds to the cue point.
};
inline int compare_by_id(_In_ const cue& a, _In_ const cue& b)
{
if (a.id < b.id) return -1;
if (a.id > b.id) return 1;
return 0;
}
inline int compare_by_pos(_In_ const cue& a, _In_ const cue& b)
{
if (a.position < b.position) return -1;
if (a.position > b.position) return 1;
return 0;
}
///
/// Cue point list
///
using cue_vector = std::vector<cue>;
using cue_vector_record = stdex::idrec::record<cue_vector, id_t, 0x20657563 /*"cue "*/, length_t, align>;
inline stdex::stream::basic& operator <<(_In_ stdex::stream::basic& dat, _In_ const cue_vector& data)
{
size_t num_cues = data.size();
if (num_cues > UINT32_MAX) _Unlikely_
throw std::invalid_argument("too many cues");
dat << static_cast<uint32_t>(num_cues);
if (dat.ok())
dat.write_array(data.data(), sizeof(cue), num_cues);
return dat;
}
inline stdex::stream::basic& operator >>(_In_ stdex::stream::basic& dat, _Out_ cue_vector& data)
{
uint32_t num_cues;
dat >> num_cues;
if (!dat.ok()) _Unlikely_ goto error1;
data.resize(num_cues);
data.resize(dat.read_array(data.data(), sizeof(cue), num_cues));
return dat;
error1:
data.clear();
return dat;
}
///
/// Labeled text
///
struct ltxt
{
uint32_t id = 0; ///< The starting sample point that corresponds to this text label by providing the ID of a Cue Point defined in the Cue Point List. The ID that associates this label with a Cue Point must be unique to all other note chunk Cue Point IDs.
uint32_t duration = 0; ///< How many samples from the cue point the region or section spans.
id_t purpose_id = 0; ///< What the text is used for. For example a value of "scrp" means script text, and "capt" means close-caption. There are several more purpose IDs, but they are meant to be used with other types of RIFF files (not usually found in WAVE files).
uint16_t country = 0; ///< Country code used by text
uint16_t language = 0; ///< Language code used by text
uint16_t dialect = 0; ///< Dialect code used by text
uint16_t charset = 0; ///< Charset used by text
std::string description; ///< Description text
friend inline stdex::stream::basic& operator <<(_In_ stdex::stream::basic& dat, _In_ const ltxt& data)
{
dat << data.id;
dat << data.duration;
dat << data.purpose_id;
dat << data.country;
dat << data.language;
dat << data.dialect;
dat << data.charset;
if (size_t num_chars = data.description.size(); num_chars && dat.ok()) {
dat.write_array(data.description.data(), sizeof(char), num_chars);
dat << '\0';
}
return dat;
}
friend inline stdex::stream::basic& operator >>(_In_ stdex::stream::basic& dat, _In_ ltxt& data)
{
dat >> data.id;
dat >> data.duration;
dat >> data.purpose_id;
dat >> data.country;
dat >> data.language;
dat >> data.dialect;
dat >> data.charset;
if (dat.ok()) {
auto tmp = dat.read_remainder();
data.description.assign(
reinterpret_cast<const char*>(tmp.data()),
stdex::strnlen(reinterpret_cast<const char*>(tmp.data()), tmp.size()));
}
else
data.description.clear();
return dat;
}
using record = stdex::idrec::record<ltxt, id_t, 0x7478746C /*"ltxt"*/, length_t, align>;
};
///
/// Label
///
struct label
{
uint32_t id = 0; ///< The sample point that corresponds to this text label by providing the ID of a Cue Point defined in the Cue Point List. The ID that associates this label with a Cue Point must be unique to all other label Cue Point IDs.
std::string title; ///< Title text
friend inline stdex::stream::basic& operator <<(_In_ stdex::stream::basic& dat, _In_ const label& data)
{
dat << data.id;
if (size_t num_chars = data.title.size(); num_chars && dat.ok()) {
dat.write_array(data.title.data(), sizeof(char), num_chars);
dat << '\0';
}
return dat;
}
friend inline stdex::stream::basic& operator >>(_In_ stdex::stream::basic& dat, _In_ label& data)
{
dat >> data.id;
if (dat.ok()) {
auto tmp = dat.read_remainder();
data.title.assign(
reinterpret_cast<const char*>(tmp.data()),
stdex::strnlen(reinterpret_cast<const char*>(tmp.data()), tmp.size()));
}
else
data.title.clear();
return dat;
}
using record = stdex::idrec::record<label, id_t, 0x6C62616C /*"labl"*/, length_t, align>;
};
///
/// Note
///
struct note
{
uint32_t id = 0; ///< The sample point that corresponds to this text comment by providing the ID of a Cue Point defined in the Cue Point List. The ID that associates this label with a Cue Point must be unique to all other note chunk Cue Point IDs.
std::string note; ///< Note text
friend inline stdex::stream::basic& operator <<(_In_ stdex::stream::basic& dat, _In_ const stdex::wav::note& data)
{
dat << data.id;
if (size_t num_chars = data.note.size(); num_chars && dat.ok()) {
dat.write_array(data.note.data(), sizeof(char), num_chars);
dat << '\0';
}
return dat;
}
friend inline stdex::stream::basic& operator >>(_In_ stdex::stream::basic& dat, _Out_ stdex::wav::note& data)
{
dat >> data.id;
if (dat.ok()) {
auto tmp = dat.read_remainder();
data.note.assign(
reinterpret_cast<const char*>(tmp.data()),
stdex::strnlen(reinterpret_cast<const char*>(tmp.data()), tmp.size()));
}
else
data.note.clear();
return dat;
}
using record = stdex::idrec::record<stdex::wav::note, id_t, 0x65746F6E /*"note"*/, length_t, align>;
};
///
/// Associated data list
///
struct list : public header
{
using record = stdex::idrec::record<list, id_t, 0x5453494C /*"LIST"*/, length_t, align>;
};
///
/// Extended cue
///
struct cue_ex : public cue
{
uint32_t duration = 0; ///< How many samples from the cue point the region or section spans.
id_t purpose_id = 0; ///< What the text is used for. For example a value of "scrp" means script text, and "capt" means close-caption. There are several more purpose IDs, but they are meant to be used with other types of RIFF files (not usually found in WAVE files).
uint16_t country = 0; ///< Country code used by texts
uint16_t language = 0; ///< Language code used by texts
uint16_t dialect = 0; ///< Dialect code used by texts
uint16_t charset = 0; ///< Charset used by texts
std::string description; ///< Description text
std::string title; ///< Title text
std::string note; ///< Note text
};
///
/// Storage for extended cues
///
using cue_ex_vector = std::vector<cue_ex>;
inline stdex::stream::basic_file& operator <<(_In_ stdex::stream::basic_file& dat, _In_ const cue_ex_vector& data)
{
auto start = stdex::idrec::open<id_t, length_t>(dat, cue_vector_record::id());
size_t num_cues = data.size();
if (num_cues > UINT32_MAX) _Unlikely_
throw std::invalid_argument("too many cues");
dat << static_cast<uint32_t>(num_cues);
for (size_t i = 0; i < num_cues && dat.ok(); ++i)
dat.write_array(static_cast<const cue*>(&data[i]), sizeof(cue), 1);
stdex::idrec::close<id_t, length_t, align>(dat, start);
start = stdex::idrec::open<id_t, length_t>(dat, list::record::id());
dat << *reinterpret_cast<const id_t*>("adtl");
for (size_t i = 0; i < num_cues && dat.ok(); ++i) {
const cue_ex& c = data[i];
ltxt ltxt;
ltxt.id = c.id;
ltxt.duration = c.duration;
ltxt.purpose_id = c.purpose_id;
ltxt.country = c.country;
ltxt.language = c.language;
ltxt.dialect = c.dialect;
ltxt.charset = c.charset;
ltxt.description = c.description;
dat << ltxt::record(ltxt);
if (!c.title.empty()) {
label title;
title.id = c.id;
title.title = c.title;
dat << label::record(title);
}
if (!c.note.empty()) {
note note;
note.id = c.id;
note.note = c.note;
dat << note::record(note);
}
}
stdex::idrec::close<id_t, length_t, align>(dat, start);
return dat;
}
template <class T>
_Success_(return!=0) bool find_first(
_In_ stdex::stream::basic_file& dat,
_In_ id_t subid,
_In_ stdex::stream::fpos_t block_end = stdex::stream::fpos_max,
_Out_opt_ stdex::stream::fpos_t* found_block_end = nullptr)
{
while (dat.tell() < block_end) {
if (!stdex::idrec::record<T, id_t, T::record::id(), length_t, align>::find(dat, block_end))
return false;
length_t size;
dat >> size;
if (!dat.ok()) _Unlikely_
return false;
stdex::stream::fpos_t end = dat.tell() + size;
id_t id;
dat >> id;
if (!dat.ok()) _Unlikely_
return false;
if (id == subid) {
if (found_block_end) *found_block_end = end;
return true;
}
// Block was found, but sub-ID is different.
end += (align - end) % align;
dat.seekbeg(end);
}
return false;
}
template <class T>
_Success_(return != 0) bool read_first(
_In_ stdex::stream::basic_file& dat,
_Inout_ T& content,
_In_ stdex::stream::fpos_t block_end = stdex::stream::fpos_max)
{
if (!stdex::idrec::record<T, id_t, T::record::id(), length_t, align>::find(dat, block_end))
return false;
dat >> stdex::idrec::record<T, id_t, T::record::id(), length_t, align>(content);
return dat.ok();
}
inline _Success_(return != 0) bool find_content(
_In_ stdex::stream::basic_file& dat,
_In_ stdex::stream::fpos_t block_end = stdex::stream::fpos_max,
_Out_opt_ stdex::stream::fpos_t* found_block_end = nullptr)
{
return find_first<header>(dat, wave::record::id(), block_end, found_block_end);
}
inline _Success_(return != 0) bool read_cues(
_In_ stdex::stream::basic_file& dat,
_Inout_ cue_ex_vector& cues, stdex::stream::fpos_t block_end = stdex::stream::fpos_max)
{
static auto cue_less = [](_In_ const cue& a, _In_ const cue& b) { return compare_by_id(a, b) < 0; };
static int (__cdecl* cue_cmp)(void const*, void const*) = [](_In_ void const* a, _In_ void const* b) { return compare_by_id(*reinterpret_cast<const cue*>(a), *reinterpret_cast<const cue*>(b)); };
stdex::stream::fpos_t start = dat.tell();
while (dat.tell() < block_end) {
if (stdex::idrec::record<cue_vector, id_t, cue_vector_record::id(), length_t, align>::find(dat, block_end)) {
length_t size;
dat >> size;
if (!dat.ok()) _Unlikely_ return false;
stdex::stream::file_window _dat(dat, dat.tell(), size);
uint32_t num_cues;
_dat >> num_cues;
if (!_dat.ok()) _Unlikely_ return false;
cues.resize(num_cues);
size_t i;
bool ordered = true;
for (i = 0; i < num_cues; i++) {
_dat.read_array(static_cast<cue*>(&cues[i]), sizeof(cue), 1);
if (!_dat.ok()) _Unlikely_
break;
if (i && cue_less(cues[i], cues[i - 1]))
ordered = false;
}
cues.resize(i);
if (!ordered)
std::sort(cues.begin(), cues.end(), cue_less);
}
}
// Cues are loaded. Add other data.
dat.seekbeg(start);
while (dat.tell() < block_end) {
stdex::stream::fpos_t found_block_end;
if (find_first<list>(dat, *(const id_t*)"adtl", block_end, &found_block_end)) {
while (dat.tell() < found_block_end) {
id_t id;
dat >> id;
if (!dat.ok()) break;
if (id == ltxt::record::id()) {
ltxt ltxt;
dat >> ltxt::record(ltxt);
cue_ex tmp;
tmp.id = ltxt.id;
cue_ex* c = reinterpret_cast<cue_ex*>(std::bsearch(&tmp, cues.data(), cues.size(), sizeof(cues[0]), cue_cmp));
if (c) {
c->duration = ltxt.duration;
c->purpose_id = ltxt.purpose_id;
c->country = ltxt.country;
c->language = ltxt.language;
c->dialect = ltxt.dialect;
c->charset = ltxt.charset;
c->description = ltxt.description;
}
}
else if (id == label::record::id()) {
label title;
dat >> label::record(title);
cue_ex tmp;
tmp.id = title.id;
cue_ex* c = reinterpret_cast<cue_ex*>(std::bsearch(&tmp, cues.data(), cues.size(), sizeof(cues[0]), cue_cmp));
if (c)
c->title = title.title;
}
else if (id == note::record::id()) {
note note;
dat >> note::record(note);
cue_ex tmp;
tmp.id = note.id;
cue_ex* c = reinterpret_cast<cue_ex*>(std::bsearch(&tmp, cues.data(), cues.size(), sizeof(cues[0]), cue_cmp));
if (c)
c->note = note.note;
}
else if (!stdex::idrec::ignore<length_t, align>(dat)) _Unlikely_
return false;
}
}
}
dat.seekbeg(start);
return true;
}
inline _Success_(return != 0) bool remove_cues(
_Inout_ stdex::stream::basic_file& input,
_Inout_ stdex::stream::basic_file& output,
_In_ stdex::stream::fpos_t block_end = stdex::stream::fpos_max)
{
static id_t removable[] = {
cue_vector_record::id(),
ltxt::record::id(),
label::record::id(),
note::record::id(),
};
while (input.tell() < block_end) {
id_t id;
input >> id;
if (!input.ok()) break;
for (size_t i = 0; i < _countof(removable); i++)
if (id == removable[i])
goto remove;
if (!stdex::idrec::ignore<length_t, align>(input)) _Unlikely_
return false;
continue;
remove:
length_t size;
input >> size;
if (!input.ok()) _Unlikely_ return false;
auto start = stdex::idrec::open<id_t, length_t>(output, id);
id_t id2 = id;
stdex::strupr(reinterpret_cast<char*>(&id2), sizeof(id_t) / sizeof(char));
if (id != id2 || id == *reinterpret_cast<const id_t*>("ICRD")) {
// ID is not all uppercase (or is an exception). Element is trivial and may be copied.
if (!output.ok()) _Unlikely_ return false;
output.write_stream(input, size);
if (!input.ok()) _Unlikely_ return false;
stdex::idrec::close<id_t, length_t, align>(output, start);
size = static_cast<length_t>(align - size) % align;
if (size)
input.seekcur(size);
}
else {
// ID is all uppercase. Needs recursive treatment.
id_t id3;
input >> id3;
if (!input.ok()) _Unlikely_ return false;
output << id3;
if (!output.ok()) _Unlikely_ return false;
stdex::stream::fpos_t end = output.tell();
if (!remove_cues(input, output, input.tell() + size - sizeof(id_t))) _Unlikely_
return false;
if (end != output.tell())
stdex::idrec::close<id_t, length_t, align>(output, start);
else
output.seekbeg(start);
}
}
output.truncate();
return true;
}
}
}

24
include/stdex/windows.h Normal file
View File

@ -0,0 +1,24 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2023-2025 Amebis
*/
#pragma once
// Windows.h #defines min and max, which collides with std::min and std::max.
// Not only the collision problem, #defining min and max is plain wrong as it
// causes multiple evaluations of parameter expressions.
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
// In case somebody #included <windows.h> before us without #defining NOMINMAX
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif

182
include/stdex/zlib.hpp Normal file
View File

@ -0,0 +1,182 @@
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2025 Amebis
*/
#pragma once
#include "assert.hpp"
#include "compat.hpp"
#include "stream.hpp"
#if _MSC_VER
#include <CodeAnalysis/Warnings.h>
#pragma warning(push)
#pragma warning(disable: ALL_CODE_ANALYSIS_WARNINGS)
#endif
#include <zlib.h>
#if _MSC_VER
#pragma warning(pop)
#endif
#include <memory>
#include <stdexcept>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#endif
namespace stdex
{
/// \cond internal
inline void throw_on_zlib_error(int result)
{
if (result >= 0)
return;
switch (result) {
case Z_ERRNO: throw std::system_error(errno, std::system_category(), "zlib failed with errno");
case Z_STREAM_ERROR: throw std::runtime_error("zlib stream error");
case Z_DATA_ERROR: throw std::runtime_error("zlib data error");
case Z_MEM_ERROR: throw std::bad_alloc();
case Z_BUF_ERROR: throw std::runtime_error("zlib buffer error");
case Z_VERSION_ERROR: throw std::runtime_error("zlib version error");
default: throw std::runtime_error("zlib unknown error");
}
}
/// \endcond
///
/// Compresses data when writing to a stream
///
class zlib_writer : public stdex::stream::converter
{
public:
zlib_writer(_Inout_ stdex::stream::basic& source, _In_ int compression_level = Z_BEST_COMPRESSION, _In_ uInt block_size = 0x10000) :
stdex::stream::converter(source),
m_block_size(block_size),
m_block(new Byte[block_size])
{
memset(&m_zlib, 0, sizeof(m_zlib));
throw_on_zlib_error(deflateInit(&m_zlib, compression_level));
}
virtual ~zlib_writer()
{
try {
m_zlib.avail_in = 0;
m_zlib.next_in = NULL;
do {
m_zlib.avail_out = m_block_size;
m_zlib.next_out = m_block.get();
throw_on_zlib_error(deflate(&m_zlib, Z_FINISH));
m_source->write(m_block.get(), m_block_size - m_zlib.avail_out);
if (!m_source->ok()) _Unlikely_
throw std::system_error(sys_error(), std::system_category(), "failed to flush compressed stream"); // Data loss occured
} while (m_zlib.avail_out == 0);
// m_zlib.avail_out = m_block_size;
// m_zlib.next_out = m_block.get();
// deflateReset(&m_zlib);
deflateEnd(&m_zlib);
}
catch (...) {} // TODO: Never throw in destructors. If we'd throw here exception stack unwinding would std::terminate() our process anyway. Is there a way to catch this?
}
virtual _Success_(return != 0) size_t write(
_In_reads_bytes_opt_(length) const void* data, _In_ size_t length)
{
stdex_assert(data || !length);
size_t num_written = 0;
while (length) {
uInt num_inflated = static_cast<uInt>(std::min<size_t>(length, UINT_MAX));
m_zlib.avail_in = num_inflated;
m_zlib.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(data));
do {
m_zlib.avail_out = m_block_size;
m_zlib.next_out = m_block.get();
throw_on_zlib_error(deflate(&m_zlib, Z_NO_FLUSH));
size_t num_deflated = m_block_size - m_zlib.avail_out;
if (num_deflated) {
m_source->write(m_block.get(), num_deflated);
if (!m_source->ok()) {
m_state = m_source->state();
return num_written;
}
}
} while (m_zlib.avail_out == 0);
num_written += num_inflated;
reinterpret_cast<const Bytef*&>(data) += num_inflated;
length -= num_inflated;
}
m_state = stdex::stream::state_t::ok;
return num_written;
}
protected:
z_stream m_zlib;
uInt m_block_size;
std::unique_ptr<Byte[]> m_block;
};
///
/// Decompresses data when reading from a stream
///
class zlib_reader : public stdex::stream::converter
{
public:
zlib_reader(_Inout_ stdex::stream::basic& source, _In_ uInt block_size = 0x10000) :
stdex::stream::converter(source),
m_block_size(block_size),
m_block(new Byte[block_size])
{
memset(&m_zlib, 0, sizeof(m_zlib));
throw_on_zlib_error(inflateInit(&m_zlib));
}
virtual ~zlib_reader()
{
inflateEnd(&m_zlib);
}
#pragma warning(suppress: 6101) // See [1] below
virtual _Success_(return != 0 || length == 0) size_t read(
_Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length)
{
stdex_assert(data || !length);
size_t num_read = 0;
while (length) {
uInt num_deflated = static_cast<uInt>(std::min<size_t>(length, UINT_MAX));
m_zlib.avail_out = num_deflated;
m_zlib.next_out = reinterpret_cast<Bytef*>(data);
do {
if (m_zlib.avail_in == 0) {
m_zlib.next_in = m_block.get();
m_zlib.avail_in = static_cast<uInt>(m_source->read(m_block.get(), m_block_size));
if (!m_zlib.avail_in) {
num_read += num_deflated - m_zlib.avail_out; // [1] Code analysis misses `num_deflated - m_zlib.avail_out` bytes were written to data in previous loop iterations.
if (num_read) {
m_state = stdex::stream::state_t::ok;
return num_read;
}
m_state = m_source->state();
return 0;
}
}
throw_on_zlib_error(inflate(&m_zlib, Z_NO_FLUSH));
} while (m_zlib.avail_out);
num_read += num_deflated;
reinterpret_cast<Bytef*&>(data) += num_deflated;
length -= num_deflated;
}
m_state = stdex::stream::state_t::ok;
return num_read;
}
protected:
z_stream m_zlib;
uInt m_block_size;
std::unique_ptr<Byte[]> m_block;
};
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

Binary file not shown.

View File

@ -1,20 +0,0 @@
/*
Copyright 2016-2017 Amebis
This file is part of stdex.
stdex 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 of the License, or
(at your option) any later version.
stdex 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.
You should have received a copy of the GNU General Public License
along with stdex. If not, see <http://www.gnu.org/licenses/>.
*/
#include "stdafx.h"

View File

@ -1,22 +0,0 @@
/*
Copyright 2016-2017 Amebis
This file is part of stdex.
stdex 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 of the License, or
(at your option) any later version.
stdex 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.
You should have received a copy of the GNU General Public License
along with stdex. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../include/stdex/idrec.h"