36 Commits

Author SHA1 Message Date
664763afbb test: add testbench for stl, windows and iconv encoding 2025-08-12 13:05:35 +08:00
a34bab07c1 refactor: rename encoding/stlcvt to encoding/stl 2025-08-12 09:45:44 +08:00
51d288ac4b test: add testbench for num/safe_cast 2025-08-11 22:19:02 +08:00
20a9ef4166 fix: fix comment for new added files.
- translate zh-CN comment into en-US.
- change some comment into Doxygen style.
- add lost Doxygen comment.
- enrich the testbench for ceil_div.
- add lost metaprogramming functions for some files in macro namespace.
2025-08-11 21:57:42 +08:00
17540072d3 fix: change the find order in PyCodec.
- now PyCodec will try to use Iconv first.
- re-claim the meaning of YYCC_FEAT_ICONV macro.
2025-08-05 14:04:20 +08:00
fcac886f07 refactor: migrate rust-like ops.
- migrate rust-like ops.
- migrate testbench for them but not finished.
2025-08-05 13:53:59 +08:00
27baf2a080 refactor: refactor old IOHelper.
- move pointer left padding macro into single header file.
- move utf8 fopen into single header but not finished.
- add testbench for pointer left padding macro.
- add system pointer size detector according to new migrated features requested.
2025-08-05 10:54:15 +08:00
b9f81c16a0 refactor: refactor enum helper as flag enum.
- refactor enum helper.
- add testbench for it.
2025-08-04 22:31:37 +08:00
54134b342e test: add testbench for macro/versiom_cmp 2025-08-04 21:15:49 +08:00
ce2b411b0b refactor: continue refactor to make the project can be built 2025-07-31 22:25:14 +08:00
5372af79f8 refactor: finish iconv refactor 2025-07-25 11:06:22 +08:00
b79df0c65e refactor: continue refactor project from C++17 to 23 2025-07-25 10:49:07 +08:00
4f0b3d19d1 refactor: update C++ from 17 to 23 2025-07-25 09:35:26 +08:00
f014e54604 feat: update pycodec.
- rename encoding::utf to encoding::stlcvt.
- use uv to manage script and add pycodec generator script.
- update script in modern python.
- fix added pycodec generator.
2025-07-23 16:07:49 +08:00
821a592f02 feat: add various detector.
- add endian and compiler detector, and modify os detector.
- now we use CMake to add detector-used macro, instead of using some C++ features to detect them.
- change Windows environment detection according to the change of os detector.
2025-07-23 10:18:01 +08:00
6043609709 feat: finish windows encoding 2025-07-22 21:52:09 +08:00
53e8a77f47 feat: finish iconv module 2025-07-22 14:15:53 +08:00
6d44c7605b refactor: update encoding namespace
- add all essential functions prototypes in iconv encoding.
- add lost UTF convertion for windows encoding.
2025-07-21 20:36:26 +08:00
c2f6e29c36 feat: finish iconv kernel 2025-07-18 15:57:33 +08:00
c102964703 refactor: write iconv.
- write iconv encoding (not finished).
- rename united_codec to pycodec.
2025-07-15 16:17:59 +08:00
3605151caf refactor: finish Windows encoding namespace.
- finish Windows encoding namespace.
- add std::expected polyfill for help.
2025-07-14 15:06:33 +08:00
fa52d7416f refactor: condense the shared test data of parse and stringify. 2025-07-14 09:43:23 +08:00
e42a3b6e58 refactor: move YYCC_U8 from reinterpret.hpp to string.hpp
- move YYCC_U8 def.
- create shared template in testbench.
2025-07-14 09:13:47 +08:00
cec6091996 refactor: add utf convertion namespace 2025-07-02 10:36:33 +08:00
6e884d865d test: add testbench for patch (starts ends with, and contains) 2025-07-01 14:04:02 +08:00
58ec960e9c refactor: move std patch into correct position 2025-07-01 11:00:09 +08:00
732a560a65 refactor: finish constraint builder and its testbench 2025-06-30 09:33:46 +08:00
3030a67ca3 refactor: re-place files into correct position according to namespace hierarchy 2025-06-30 08:45:18 +08:00
e166dc41ac refactor: finish rust parse and add testbench for it. 2025-06-26 10:27:33 +08:00
a6382d6a22 refactor: add document for some namespaces 2025-06-25 10:40:05 +08:00
adc99274f4 refactor: add testbench for parse and stringify 2025-06-24 11:29:01 +08:00
3abd0969c0 refactor: add Rust infrastructure: Option, Result and panic 2025-06-23 16:22:55 +08:00
28ff7008a8 add parse and stringify 2025-06-22 19:53:49 +08:00
ab8d74efe6 test: add testbench for string module 2025-06-22 17:14:49 +08:00
df3b602110 refactor: start to refactor project 2025-06-20 23:38:34 +08:00
bec36b4b3c fix: fix install path in CMake script.
- replace some CMake variables to our custom variables in install path, however it does nothing because they are equal in default.
2024-12-23 09:30:39 +08:00
113 changed files with 7329 additions and 2549 deletions

314
.clang-format Normal file
View File

@ -0,0 +1,314 @@
# yaml-language-server: $schema=https://json.schemastore.org/clang-format.json
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseArrows: false
AlignCaseColons: false
AlignConsecutiveTableGenBreakingDAGArgColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenCondOperatorColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenDefinitionColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignEscapedNewlines: DontAlign
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseExpressionOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AttributeMacros:
- __capability
BinPackArguments: false
BinPackParameters: false
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakAfterReturnType: None
BreakArrays: true
BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakFunctionDefinitionParameters: false
BreakInheritanceList: BeforeColon
BreakStringLiterals: false
BreakTemplateDeclarations: Yes
ColumnLimit: 140
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- forever
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<Q.*'
Priority: 200
SortPriority: 200
CaseSensitive: true
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLines:
AtEndOfFile: false
AtStartOfBlock: false
AtStartOfFile: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MainIncludeChar: Quote
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 150
PenaltyBreakBeforeFirstCallParameter: 300
PenaltyBreakComment: 500
PenaltyBreakFirstLessLess: 400
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 600
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 50
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 300
PointerAlignment: Right
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: Never
SortJavaStaticImport: Before
SortUsingDeclarations: Lexicographic
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
ExceptDoubleParentheses: false
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Auto
StatementAttributeLikeMacros:
- Q_EMIT
- emit
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
- Q_CLASSINFO
- Q_ENUM
- Q_ENUM_NS
- Q_FLAG
- Q_FLAG_NS
- Q_GADGET
- Q_GADGET_EXPORT
- Q_INTERFACES
- Q_LOGGING_CATEGORY
- Q_MOC_INCLUDE
- Q_NAMESPACE
- Q_NAMESPACE_EXPORT
- Q_OBJECT
- Q_PROPERTY
- Q_REVISION
- Q_DISABLE_COPY
- Q_DISABLE_COPY_MOVE
- Q_SET_OBJECT_NAME
- QT_BEGIN_NAMESPACE
- QT_END_NAMESPACE
- QML_ADDED_IN_MINOR_VERSION
- QML_ANONYMOUS
- QML_ATTACHED
- QML_DECLARE_TYPE
- QML_DECLARE_TYPEINFO
- QML_ELEMENT
- QML_EXTENDED
- QML_EXTENDED_NAMESPACE
- QML_EXTRA_VERSION
- QML_FOREIGN
- QML_FOREIGN_NAMESPACE
- QML_IMPLEMENTS_INTERFACES
- QML_INTERFACE
- QML_NAMED_ELEMENT
- QML_REMOVED_IN_MINOR_VERSION
- QML_SINGLETON
- QML_UNAVAILABLE
- QML_UNCREATABLE
- QML_VALUE_TYPE
- YYCC_DELETE_COPY
- YYCC_DELETE_MOVE
- YYCC_DELETE_COPY_MOVE
- YYCC_DEFAULT_COPY
- YYCC_DEFAULT_MOVE
- YYCC_DEFAULT_COPY_MOVE
TableGenBreakInsideDAGArg: DontBreak
TabWidth: 4
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...

3
.editorconfig Normal file
View File

@ -0,0 +1,3 @@
[*.{cpp,hpp}]
indent_style = space
indent_size = 4

15
.gitignore vendored
View File

@ -1,12 +1,17 @@
# -------------------- Output --------------------
## ===== Personal =====
# Ignore build resources
out/
src/YYCC/YYCCVersion.hpp
build/
install/
# Ignore CMake generated stuff
src/yycc/version.hpp
CMakeSettings.json
# -------------------- VSCode --------------------
## ===== VSCode =====
.vscode/
# -------------------- CMake --------------------
## ===== CMake =====
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
@ -19,7 +24,7 @@ compile_commands.json
CTestTestfile.cmake
_deps
# -------------------- Visual Studio --------------------
## ===== Visual Studio =====
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##

View File

@ -1,13 +1,18 @@
cmake_minimum_required(VERSION 3.23)
project(YYCC
VERSION 1.3.0
VERSION 2.0.0
LANGUAGES CXX
)
# Setup C++ standard
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Provide options
option(YYCC_BUILD_TESTBENCH "Build testbench of YYCCommonplace." OFF)
option(YYCC_BUILD_DOC "Build document of YYCCommonplace." OFF)
option(YYCC_DEBUG_UE_FILTER "YYCC developer used switch for testing Windows unhandled exception filter. Should not set to ON!!!" OFF)
option(YYCC_ENFORCE_ICONV "Enforce iconv support for this library (e.g. in MSYS2 environment)." OFF)
# Setup install path from CMake provided install path for convenient use.
include(GNUInstallDirs)
@ -20,6 +25,18 @@ set(YYCC_INSTALL_BIN_PATH ${CMAKE_INSTALL_BINDIR} CACHE PATH
set(YYCC_INSTALL_DOC_PATH ${CMAKE_INSTALL_DOCDIR} CACHE PATH
"Non-arch doc install path relative to CMAKE_INSTALL_PREFIX unless set to an absolute path.")
# Include dependency.
# GTest is required if we build testbench
if (YYCC_BUILD_TESTBENCH)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
find_package(GTest REQUIRED)
endif ()
# Iconv is required if we are not in Windows or user request it
if (YYCC_ENFORCE_ICONV OR (NOT WIN32))
find_package(Iconv REQUIRED)
endif ()
# Import 3 build targets
add_subdirectory(src)
if (YYCC_BUILD_TESTBENCH)
@ -46,7 +63,7 @@ write_basic_package_version_file(
configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/cmake/YYCCommonplaceConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfig.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/YYCCommonplace
INSTALL_DESTINATION ${YYCC_INSTALL_LIB_PATH}/cmake/YYCCommonplace
)
# Copy package files to install destination
install(
@ -54,6 +71,6 @@ FILES
"${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfigVersion.cmake"
DESTINATION
${CMAKE_INSTALL_LIBDIR}/cmake/YYCCommonplace
${YYCC_INSTALL_LIB_PATH}/cmake/YYCCommonplace
)

View File

@ -15,3 +15,8 @@ However, the documentation need CMake to build and you may don't know how to use
This project require at least CMake 3.23 to build. We suggest that you only use stable version (tagged commit). The latest commit may still work in progress and not stable.
See documentation for how to build this project.
> [!NOTE]
> When building with testbench, you may face link error with GoogleTest. This issue is caused by that the binary provided by your package manager is built in C++ 17 and its ABI is incompatible with C++ 23. The solution is that download GoogleTest source code and build it in C++ 23 on your own. See this [GitHub Issue](https://github.com/google/googletest/issues/4591) for more infomation.
> Oppositely, you don't need care about this issue if you just want to build YYCC self.

View File

@ -73,7 +73,7 @@ Assume \c blabla() function is Windows specific.
We have following example code:
\code
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
blabla();
#endif
\endcode

View File

@ -71,7 +71,7 @@ You can simply return \c false to terminate join process.
The argument you assigned to argument will not be taken into join process when you return false.
Then, you can pass the created #JoinDataProvider object to #Join function.
And specify decilmer at the same time.
And specify delimiter at the same time.
Then you can get the final joined string.
There is an example:
@ -88,7 +88,7 @@ auto joined_string = YYCC::StringHelper::Join(
++iter;
return true;
},
decilmer
delimiter
);
\endcode
@ -105,7 +105,7 @@ Otherwise this overload will throw template error.
std::vector<yycc_u8string> data {
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
};
auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), decilmer);
auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), delimiter);
\endcode
\section string_helper__lower_upper Lower Upper
@ -134,14 +134,14 @@ std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view&, const yycc_
\endcode
All these overloads take a string view as the first argument representing the string need to be split.
The second argument is a string view representing the decilmer for splitting.
The second argument is a string view representing the delimiter for splitting.
The only difference between these 2 split function are overt according to their names.
The first split function will return a list of copied string as its split result.
The second split function will return a list of string view as its split result,
and it will keep valid as long as the life time of your given string view argument.
It also means that the last overload will cost less memory if you don't need the copy of original string.
If the source string (the string need to be split) is empty, or the decilmer is empty,
If the source string (the string need to be split) is empty, or the delimiter is empty,
the result will only has 1 item and this item is source string itself.
There is no way that these methods return an empty list, except the code is buggy.

View File

@ -11,7 +11,7 @@ Due to legacy reason, Windows defines various things which are not compatible wi
YYCC has a way to solve the issue introduced above.
\code
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include <WinImportPrefix.hpp>
#include <Windows.h>
#include "other_header_depend_on_windows.h"

215
script/.gitignore vendored
View File

@ -1,3 +1,216 @@
# -------------------- Output --------------------
## ===== Myself =====
# Exclude VSCode
.vscode/
# Exclude generated files
win_build.bat
linux_build.sh
## ===== Python =====
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/

View File

@ -1,15 +1,16 @@
import jinja2
import argparse
import os
import io
import typing
import re
import shlex
from pathlib import Path
from dataclasses import dataclass
import jinja2
def validate_cpp_ver(ver: str) -> str:
if re.match(r'^[0-9]+$', ver) is not None: return ver
else: raise argparse.ArgumentTypeError('invalid version of C++ standard.')
def write_line(f: io.TextIOWrapper, val: str) -> None:
def write_line(f: typing.TextIO, val: str) -> None:
f.write(val)
f.write('\n')
@ -24,55 +25,51 @@ def escape_cmd_argument(arg):
def escape_sh_argument(arg):
return shlex.quote(arg)
@dataclass(frozen=True)
class ScriptSettings:
m_CppVersion: str
m_BuildDoc: bool
m_PIC: bool
def __init__(self, cpp_ver: str, build_doc: bool, pic: bool):
self.m_CppVersion = cpp_ver
self.m_BuildDoc = build_doc
self.m_PIC = pic
cpp_version: str
build_doc: bool
pic: bool
class TemplateRender:
m_Loader: jinja2.BaseLoader
m_Environment: jinja2.Environment
loader: jinja2.BaseLoader
environment: jinja2.Environment
m_WinTemplate: jinja2.Template
m_LinuxTemplate: jinja2.Template
win_template: jinja2.Template
linux_template: jinja2.Template
m_Settings: ScriptSettings
settings: ScriptSettings
def __init__(self, settings: ScriptSettings) -> None:
self.m_Loader = jinja2.FileSystemLoader(self.__get_dir())
self.m_Environment = jinja2.Environment(loader=self.m_Loader)
self.loader = jinja2.FileSystemLoader(self.__get_dir())
self.environment = jinja2.Environment(loader=self.loader)
self.m_WinTemplate = self.m_Environment.get_template('win_build.template.bat')
self.m_LinuxTemplate = self.m_Environment.get_template('linux_build.template.sh')
self.win_template = self.environment.get_template('win_build.bat.jinja')
self.linux_template = self.environment.get_template('linux_build.sh.jinja')
self.m_Settings = settings
self.settings = settings
def __get_dir(self) -> str:
return os.path.dirname(__file__)
def __get_dir(self) -> Path:
return Path(__file__).resolve().parent
def __escape_path(self, val: str, is_win: bool) -> str:
if is_win: return escape_cmd_argument(val)
else: return escape_sh_argument(val)
def __render(self, template: jinja2.Template, dest_file: str, is_win: bool) -> None:
with open(os.path.join(self.__get_dir(), dest_file), 'w', encoding='utf-8') as f:
with open(self.__get_dir() / dest_file, 'w', encoding='utf-8') as f:
f.write(template.render(
repo_root_dir = self.__escape_path(os.path.dirname(self.__get_dir()), is_win),
cpp_version = self.m_Settings.m_CppVersion,
build_doc = self.m_Settings.m_BuildDoc,
pic = settings.m_PIC
repo_root_dir = self.__escape_path(str(self.__get_dir().parent), is_win),
cpp_version = self.settings.cpp_version,
build_doc = self.settings.build_doc,
pic = settings.pic
))
def render_win_script(self) -> None:
self.__render(self.m_WinTemplate, 'win_build.bat', True)
self.__render(self.win_template, 'win_build.bat', True)
def render_linux_script(self) -> None:
self.__render(self.m_LinuxTemplate, 'linux_build.sh', False)
self.__render(self.linux_template, 'linux_build.sh', False)
if __name__ == '__main__':

2
script/pycodec/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Exclude result
*.cpp

7
script/pycodec/README.md Normal file
View File

@ -0,0 +1,7 @@
# PyCodec
This directory contain all stuff related to PyCodec.
PyCodec use different encoding system on different OS. In Windows it use Win32 functions, and it will use Iconv in other OS. So we need a table converting PyCodec universal encoding name to Windows Code Page or Iconv Code Name. These relation was stored in CSV file and Python script will render it into C++ source code.
For the format of CSV file, each line is a record. The first item in record is the standard PyCodec name. The second item is corresponding Windows Code Page. If there is no corresponding Code Page, it can be empty. The third item is corresponding Iconv Code Name. It also can be empty with same case. Then, the count of remain columns is variables after forth item (inclusive). All of them is the alias of this standard PyCodec name.

View File

@ -0,0 +1,54 @@
import csv
from pathlib import Path
import jinja2
class LanguageToken:
name: str
alias: tuple[str, ...]
code_page: str | None
iconv_code: str | None
def __init__(self, row: list[str]):
"""Init language token from CSV row."""
self.name = row[0].lower()
code_page = row[1]
self.code_page = None if len(code_page) == 0 else code_page
iconv_code = row[2]
self.iconv_code = None if len(iconv_code) == 0 else iconv_code
# For alias, we strip and to lower them first, and remove all empty entries
alias = row[3:]
self.alias = tuple(
filter(lambda x: len(x) != 0,
map(lambda x: x.strip().lower(), alias)))
def _get_self_dir() -> Path:
return Path(__file__).resolve().parent
def _extract_tokens() -> list[LanguageToken]:
rv: list[LanguageToken] = []
csv_file = _get_self_dir() / 'encoding_table.csv'
with open(csv_file, 'r', encoding='utf-8', newline='') as f:
reader = csv.reader(f, delimiter='\t')
for row in reader:
rv.append(LanguageToken(row))
return rv
def _render_cpp(tokens: list[LanguageToken]) -> None:
loader = jinja2.FileSystemLoader(_get_self_dir())
environment = jinja2.Environment(loader=loader)
template = environment.get_template('encoding_table.cpp.jinja')
cpp_file = _get_self_dir() / 'encoding_table.cpp'
with open(cpp_file, 'w', encoding='utf-8') as f:
f.write(template.render(tokens=tokens))
if __name__ == '__main__':
tokens = _extract_tokens()
_render_cpp(tokens)

View File

@ -0,0 +1,23 @@
static const std::map<std::u8string_view, std::u8string_view> ALIAS_MAP {
{% for token in tokens -%}
{% for alias in token.alias -%}
{ u8"{{ alias }}"sv, u8"{{ token.name }}"sv },
{% endfor -%}
{% endfor -%}
};
static const std::map<std::u8string_view, CodePage> WINCP_MAP {
{% for token in tokens -%}
{% if token.code_page is not none -%}
{ u8"{{ token.name }}"sv, static_cast<CodePage>({{ token.code_page }}u) },
{% endif -%}
{% endfor -%}
};
static const std::map<std::u8string_view, std::string_view> ICONV_MAP {
{% for token in tokens -%}
{% if token.iconv_code is not none -%}
{ u8"{{ token.name }}"sv, "{{ token.iconv_code }}"sv },
{% endif -%}
{% endfor -%}
};

View File

@ -0,0 +1,97 @@
ascii 437 ASCII 646 us-ascii
big5 950 BIG5 big5-tw csbig5
big5hkscs BIG5-HKSCS big5-hkscs hkscs
cp037 037 IBM037 IBM039
cp273 273 IBM273 csIBM273
cp424 EBCDIC-CP-HE IBM424
cp437 437 437 IBM437
cp500 500 EBCDIC-CP-BE EBCDIC-CP-CH IBM500
cp720 720
cp737 737
cp775 775 IBM775
cp850 850 CP850 850 IBM850
cp852 852 852 IBM852
cp855 855 855 IBM855
cp856
cp857 857 857 IBM857
cp858 858 858 IBM858
cp860 860 860 IBM860
cp861 861 861 CP-IS IBM861
cp862 862 CP862 862 IBM862
cp863 863 863 IBM863
cp864 864 IBM864
cp865 865 865 IBM865
cp866 866 CP866 866 IBM866
cp869 869 869 CP-GR IBM869
cp874 874 CP874
cp875 875
cp932 932 CP932 932 ms932 mskanji ms-kanji windows-31j
cp949 949 CP949 949 ms949 uhc
cp950 950 CP950 950 ms950
cp1006
cp1026 1026 ibm1026
cp1125 1125 ibm1125 cp866u ruscii
cp1140 1140 ibm1140
cp1250 1250 CP1250 windows-1250
cp1251 1251 CP1251 windows-1251
cp1252 1252 CP1252 windows-1252
cp1253 1253 CP1253 windows-1253
cp1254 1254 CP1254 windows-1254
cp1255 1255 CP1255 windows-1255
cp1256 1256 CP1256 windows-1256
cp1257 1257 CP1257 windows-1257
cp1258 1258 CP1258 windows-1258
euc_jp 20932 EUC-JP eucjp ujis u-jis
euc_jis_2004 jisx0213 eucjis2004
euc_jisx0213 eucjisx0213
euc_kr 51949 EUC-KR euckr korean ksc5601 ks_c-5601 ks_c-5601-1987 ksx1001 ks_x-1001
gb2312 936 CP936 chinese csiso58gb231280 euc-cn euccn eucgb2312-cn gb2312-1980 gb2312-80 iso-ir-58
gbk 936 GBK 936 cp936 ms936
gb18030 54936 GB18030 gb18030-2000
hz 52936 HZ hzgb hz-gb hz-gb-2312
iso2022_jp 50220 ISO-2022-JP csiso2022jp iso2022jp iso-2022-jp
iso2022_jp_1 ISO-2022-JP-1 iso2022jp-1 iso-2022-jp-1
iso2022_jp_2 ISO-2022-JP-2 iso2022jp-2 iso-2022-jp-2
iso2022_jp_2004 iso2022jp-2004 iso-2022-jp-2004
iso2022_jp_3 iso2022jp-3 iso-2022-jp-3
iso2022_jp_ext iso2022jp-ext iso-2022-jp-ext
iso2022_kr 50225 ISO-2022-KR csiso2022kr iso2022kr iso-2022-kr
latin_1 28591 ISO-8859-1 iso-8859-1 iso8859-1 8859 cp819 latin latin1 L1
iso8859_2 28592 ISO-8859-2 iso-8859-2 latin2 L2
iso8859_3 28593 ISO-8859-3 iso-8859-3 latin3 L3
iso8859_4 28594 ISO-8859-4 iso-8859-4 latin4 L4
iso8859_5 28595 ISO-8859-5 iso-8859-5 cyrillic
iso8859_6 28596 ISO-8859-6 iso-8859-6 arabic
iso8859_7 28597 ISO-8859-7 iso-8859-7 greek greek8
iso8859_8 28598 ISO-8859-8 iso-8859-8 hebrew
iso8859_9 28599 ISO-8859-9 iso-8859-9 latin5 L5
iso8859_10 ISO-8859-10 iso-8859-10 latin6 L6
iso8859_11 ISO-8859-11 iso-8859-11 thai
iso8859_13 28603 ISO-8859-13 iso-8859-13 latin7 L7
iso8859_14 ISO-8859-14 iso-8859-14 latin8 L8
iso8859_15 28605 ISO-8859-15 iso-8859-15 latin9 L9
iso8859_16 ISO-8859-16 iso-8859-16 latin10 L10
johab 1361 JOHAB cp1361 ms1361
koi8_r
koi8_t KOI8-T
koi8_u
kz1048 kz_1048 strk1048_2002 rk1048
mac_cyrillic 10007 MacCyrillic maccyrillic
mac_greek 10006 MacGreek macgreek
mac_iceland 10079 MacIceland maciceland
mac_latin2 maclatin2 maccentraleurope mac_centeuro
mac_roman MacRoman macroman macintosh
mac_turkish 10081 MacTurkish macturkish
ptcp154 PT154 csptcp154 pt154 cp154 cyrillic-asian
shift_jis 932 SHIFT_JIS csshiftjis shiftjis sjis s_jis
shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213
utf_32 UTF-32 U32 utf32
utf_32_be UTF-32BE UTF-32BE
utf_32_le UTF-32LE UTF-32LE
utf_16 UTF16 U16 utf16
utf_16_be UTF-16BE UTF-16BE
utf_16_le UTF-16LE UTF-16LE
utf_7 65000 UTF-7 U7 unicode-1-1-utf-7
utf_8 65001 UTF-8 U8 UTF utf8 utf-8 cp65001
utf_8_sig
1 ascii 437 ASCII 646 us-ascii
2 big5 950 BIG5 big5-tw csbig5
3 big5hkscs BIG5-HKSCS big5-hkscs hkscs
4 cp037 037 IBM037 IBM039
5 cp273 273 IBM273 csIBM273
6 cp424 EBCDIC-CP-HE IBM424
7 cp437 437 437 IBM437
8 cp500 500 EBCDIC-CP-BE EBCDIC-CP-CH IBM500
9 cp720 720
10 cp737 737
11 cp775 775 IBM775
12 cp850 850 CP850 850 IBM850
13 cp852 852 852 IBM852
14 cp855 855 855 IBM855
15 cp856
16 cp857 857 857 IBM857
17 cp858 858 858 IBM858
18 cp860 860 860 IBM860
19 cp861 861 861 CP-IS IBM861
20 cp862 862 CP862 862 IBM862
21 cp863 863 863 IBM863
22 cp864 864 IBM864
23 cp865 865 865 IBM865
24 cp866 866 CP866 866 IBM866
25 cp869 869 869 CP-GR IBM869
26 cp874 874 CP874
27 cp875 875
28 cp932 932 CP932 932 ms932 mskanji ms-kanji windows-31j
29 cp949 949 CP949 949 ms949 uhc
30 cp950 950 CP950 950 ms950
31 cp1006
32 cp1026 1026 ibm1026
33 cp1125 1125 ibm1125 cp866u ruscii
34 cp1140 1140 ibm1140
35 cp1250 1250 CP1250 windows-1250
36 cp1251 1251 CP1251 windows-1251
37 cp1252 1252 CP1252 windows-1252
38 cp1253 1253 CP1253 windows-1253
39 cp1254 1254 CP1254 windows-1254
40 cp1255 1255 CP1255 windows-1255
41 cp1256 1256 CP1256 windows-1256
42 cp1257 1257 CP1257 windows-1257
43 cp1258 1258 CP1258 windows-1258
44 euc_jp 20932 EUC-JP eucjp ujis u-jis
45 euc_jis_2004 jisx0213 eucjis2004
46 euc_jisx0213 eucjisx0213
47 euc_kr 51949 EUC-KR euckr korean ksc5601 ks_c-5601 ks_c-5601-1987 ksx1001 ks_x-1001
48 gb2312 936 CP936 chinese csiso58gb231280 euc-cn euccn eucgb2312-cn gb2312-1980 gb2312-80 iso-ir-58
49 gbk 936 GBK 936 cp936 ms936
50 gb18030 54936 GB18030 gb18030-2000
51 hz 52936 HZ hzgb hz-gb hz-gb-2312
52 iso2022_jp 50220 ISO-2022-JP csiso2022jp iso2022jp iso-2022-jp
53 iso2022_jp_1 ISO-2022-JP-1 iso2022jp-1 iso-2022-jp-1
54 iso2022_jp_2 ISO-2022-JP-2 iso2022jp-2 iso-2022-jp-2
55 iso2022_jp_2004 iso2022jp-2004 iso-2022-jp-2004
56 iso2022_jp_3 iso2022jp-3 iso-2022-jp-3
57 iso2022_jp_ext iso2022jp-ext iso-2022-jp-ext
58 iso2022_kr 50225 ISO-2022-KR csiso2022kr iso2022kr iso-2022-kr
59 latin_1 28591 ISO-8859-1 iso-8859-1 iso8859-1 8859 cp819 latin latin1 L1
60 iso8859_2 28592 ISO-8859-2 iso-8859-2 latin2 L2
61 iso8859_3 28593 ISO-8859-3 iso-8859-3 latin3 L3
62 iso8859_4 28594 ISO-8859-4 iso-8859-4 latin4 L4
63 iso8859_5 28595 ISO-8859-5 iso-8859-5 cyrillic
64 iso8859_6 28596 ISO-8859-6 iso-8859-6 arabic
65 iso8859_7 28597 ISO-8859-7 iso-8859-7 greek greek8
66 iso8859_8 28598 ISO-8859-8 iso-8859-8 hebrew
67 iso8859_9 28599 ISO-8859-9 iso-8859-9 latin5 L5
68 iso8859_10 ISO-8859-10 iso-8859-10 latin6 L6
69 iso8859_11 ISO-8859-11 iso-8859-11 thai
70 iso8859_13 28603 ISO-8859-13 iso-8859-13 latin7 L7
71 iso8859_14 ISO-8859-14 iso-8859-14 latin8 L8
72 iso8859_15 28605 ISO-8859-15 iso-8859-15 latin9 L9
73 iso8859_16 ISO-8859-16 iso-8859-16 latin10 L10
74 johab 1361 JOHAB cp1361 ms1361
75 koi8_r
76 koi8_t KOI8-T
77 koi8_u
78 kz1048 kz_1048 strk1048_2002 rk1048
79 mac_cyrillic 10007 MacCyrillic maccyrillic
80 mac_greek 10006 MacGreek macgreek
81 mac_iceland 10079 MacIceland maciceland
82 mac_latin2 maclatin2 maccentraleurope mac_centeuro
83 mac_roman MacRoman macroman macintosh
84 mac_turkish 10081 MacTurkish macturkish
85 ptcp154 PT154 csptcp154 pt154 cp154 cyrillic-asian
86 shift_jis 932 SHIFT_JIS csshiftjis shiftjis sjis s_jis
87 shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
88 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213
89 utf_32 UTF-32 U32 utf32
90 utf_32_be UTF-32BE UTF-32BE
91 utf_32_le UTF-32LE UTF-32LE
92 utf_16 UTF16 U16 utf16
93 utf_16_be UTF-16BE UTF-16BE
94 utf_16_le UTF-16LE UTF-16LE
95 utf_7 65000 UTF-7 U7 unicode-1-1-utf-7
96 utf_8 65001 UTF-8 U8 UTF utf8 utf-8 cp65001
97 utf_8_sig

7
script/pyproject.toml Normal file
View File

@ -0,0 +1,7 @@
[project]
name = "script"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"jinja2==3.1.2",
]

74
script/uv.lock generated Normal file
View File

@ -0,0 +1,74 @@
version = 1
revision = 2
requires-python = ">=3.11"
[[package]]
name = "jinja2"
version = "3.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7a/ff/75c28576a1d900e87eb6335b063fab47a8ef3c8b4d88524c4bf78f670cce/Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", size = 268239, upload-time = "2022-04-28T17:21:27.579Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", size = 133101, upload-time = "2022-04-28T17:21:25.336Z" },
]
[[package]]
name = "markupsafe"
version = "3.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" },
{ url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" },
{ url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" },
{ url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" },
{ url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" },
{ url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" },
{ url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" },
{ url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" },
{ url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" },
{ url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" },
{ url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
{ url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
{ url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
{ url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
{ url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
{ url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
{ url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
{ url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
{ url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
]
[[package]]
name = "script"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "jinja2" },
]
[package.metadata]
requires-dist = [{ name = "jinja2", specifier = "==3.1.2" }]

View File

@ -1,7 +1,7 @@
# Configure version file
configure_file(
${CMAKE_CURRENT_LIST_DIR}/../cmake/YYCCVersion.hpp.in
${CMAKE_CURRENT_LIST_DIR}/YYCC/YYCCVersion.hpp
${CMAKE_CURRENT_LIST_DIR}/yycc/version.hpp.in
${CMAKE_CURRENT_LIST_DIR}/yycc/version.hpp
@ONLY
)
@ -11,48 +11,52 @@ add_library(YYCCommonplace STATIC "")
target_sources(YYCCommonplace
PRIVATE
# Sources
YYCC/COMHelper.cpp
YYCC/ArgParser.cpp
YYCC/ConfigManager.cpp
YYCC/ConsoleHelper.cpp
YYCC/DialogHelper.cpp
YYCC/EncodingHelper.cpp
YYCC/ExceptionHelper.cpp
YYCC/StdPatch.cpp
YYCC/IOHelper.cpp
YYCC/StringHelper.cpp
YYCC/WinFctHelper.cpp
# Natvis (only for MSVC)
$<$<CXX_COMPILER_ID:MSVC>:YYCC.natvis>
yycc/string/reinterpret.cpp
yycc/string/op.cpp
yycc/patch/fopen.cpp
yycc/rust/panic.cpp
yycc/encoding/stl.cpp
yycc/encoding/windows.cpp
yycc/encoding/iconv.cpp
#yycc/encoding/pycodec.cpp
)
target_sources(YYCCommonplace
PUBLIC
FILE_SET HEADERS
FILES
# Headers
# Common headers
YYCC/Constraints.hpp
YYCC/COMHelper.hpp
YYCC/ArgParser.hpp
YYCC/ConfigManager.hpp
YYCC/ConsoleHelper.hpp
YYCC/DialogHelper.hpp
YYCC/EncodingHelper.hpp
YYCC/EnumHelper.hpp
YYCC/ExceptionHelper.hpp
YYCC/StdPatch.hpp
YYCC/IOHelper.hpp
YYCC/ParserHelper.hpp
YYCC/StringHelper.hpp
YYCC/WinFctHelper.hpp
# Windows including guard pair
YYCC/WinImportPrefix.hpp
YYCC/WinImportSuffix.hpp
# Internal
YYCC/YYCCVersion.hpp
YYCC/YYCCInternal.hpp
# Exposed
YYCCommonplace.hpp
yycc.hpp
yycc/version.hpp
yycc/macro/version_cmp.hpp
yycc/macro/os_detector.hpp
yycc/macro/stl_detector.hpp
yycc/macro/endian_detector.hpp
yycc/macro/compiler_detector.hpp
yycc/macro/ptr_size_detector.hpp
yycc/macro/class_copy_move.hpp
yycc/flag_enum.hpp
yycc/string/reinterpret.hpp
yycc/string/op.hpp
yycc/patch/ptr_pad.hpp
yycc/patch/fopen.hpp
yycc/num/parse.hpp
yycc/num/stringify.hpp
yycc/num/safe_cast.hpp
yycc/num/safe_op.hpp
yycc/num/op.hpp
yycc/rust/prelude.hpp
yycc/rust/primitive.hpp
yycc/rust/panic.hpp
yycc/rust/option.hpp
yycc/rust/result.hpp
yycc/windows/import_guard_head.hpp
yycc/windows/import_guard_tail.hpp
yycc/constraint.hpp
yycc/constraint/builder.hpp
yycc/encoding/stl.hpp
yycc/encoding/windows.hpp
yycc/encoding/iconv.hpp
yycc/encoding/pycodec.hpp
)
# Setup header infomations
target_include_directories(YYCCommonplace
@ -60,30 +64,64 @@ PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
# Link Iconv if we have import it
if (Iconv_FOUND)
target_link_libraries(YYCCommonplace
PRIVATE
Iconv::Iconv
)
endif ()
# Link with DbgHelp.lib on Windows
target_link_libraries(YYCCommonplace
PRIVATE
$<$<BOOL:${WIN32}>:DbgHelp.lib>
)
# Setup C++ standard
target_compile_features(YYCCommonplace PUBLIC cxx_std_17)
set_target_properties(YYCCommonplace PROPERTIES CXX_EXTENSION OFF)
# Setup macros
target_compile_definitions(YYCCommonplace
# Debug macro should populate to child projects
PUBLIC
$<$<BOOL:${YYCC_DEBUG_UE_FILTER}>:YYCC_DEBUG_UE_FILTER>
# Unicode charset for private using
PRIVATE
# Iconv environment macro
$<$<BOOL:${Iconv_FOUND}>:YYCC_FEAT_ICONV>
# OS macro
$<$<PLATFORM_ID:Windows>:YYCC_OS_WINDOWS>
$<$<PLATFORM_ID:Linux>:YYCC_OS_LINUX>
$<$<PLATFORM_ID:Darwin>:YYCC_OS_MACOS>
# Compiler macro
$<$<CXX_COMPILER_ID:GNU>:YYCC_CC_GCC>
$<$<CXX_COMPILER_ID:Clang>:YYCC_CC_CLANG>
$<$<CXX_COMPILER_ID:MSVC>:YYCC_CC_MSVC>
# Endian macro
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},LITTLE_ENDIAN>:YYCC_ENDIAN_LITTLE>
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},BIG_ENDIAN>:YYCC_ENDIAN_BIG>
# Pointer size macro
$<$<EQUAL:${CMAKE_SIZEOF_VOID_P},4>:YYCC_PTRSIZE_32>
$<$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>:YYCC_PTRSIZE_64>
# Use Unicode charset on MSVC
$<$<CXX_COMPILER_ID:MSVC>:UNICODE>
$<$<CXX_COMPILER_ID:MSVC>:_UNICODE>
# Fix MSVC shit
$<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>
$<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_DEPRECATE>
$<$<CXX_COMPILER_ID:MSVC>:_CRT_NONSTDC_NO_WARNINGS>
$<$<CXX_COMPILER_ID:MSVC>:_CRT_NONSTDC_NO_DEPRECATE>
# Fix Windows header file shit
$<$<BOOL:${WIN32}>:WIN32_LEAN_AND_MEAN>
$<$<BOOL:${WIN32}>:NOMINMAX>
)
target_compile_options(YYCCommonplace
# Order build as UTF-8 in MSVC
PRIVATE
PUBLIC
# Order build as UTF-8 in MSVC
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
)
# Fix GCC std::stacktrace link error
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
target_link_libraries(YYCCommonplace PRIVATE stdc++exp)
else ()
target_link_libraries(YYCCommonplace PRIVATE stdc++_libbacktrace)
endif ()
endif ()
# Install binary and headers
install(TARGETS YYCCommonplace
EXPORT YYCCommonplaceTargets

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) Microsoft Corporation, yyc12345.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<!-- Following XML are copied from Visual Studio embedded Natvis files. -->
<!-- <Microsoft Visual Studio Install Directory>\Common7\Packages\Debugger\Visualizers\stl.natvis -->
<!-- VC 2013 -->
<Type Name="std::basic_string&lt;YYCC::yycc_char8_t,*&gt;" Priority="MediumLow">
<AlternativeType Name="std::basic_string&lt;char8_t,*&gt;" />
<AlternativeType Name="std::basic_string&lt;unsigned char,*&gt;" />
<DisplayString Condition="_Myres &lt; _BUF_SIZE">{_Bx._Buf,s8}</DisplayString>
<DisplayString Condition="_Myres &gt;= _BUF_SIZE">{_Bx._Ptr,s8}</DisplayString>
<StringView Condition="_Myres &lt; _BUF_SIZE">_Bx._Buf,s8</StringView>
<StringView Condition="_Myres &gt;= _BUF_SIZE">_Bx._Ptr,s8</StringView>
<Expand>
<Item Name="[size]" ExcludeView="simple">_Mysize</Item>
<Item Name="[capacity]" ExcludeView="simple">_Myres</Item>
<ArrayItems>
<Size>_Mysize</Size>
<ValuePointer Condition="_Myres &lt; _BUF_SIZE">_Bx._Buf</ValuePointer>
<ValuePointer Condition="_Myres &gt;= _BUF_SIZE">_Bx._Ptr</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<!-- VC 2015+ ABI basic_string -->
<Type Name="std::basic_string&lt;YYCC::yycc_char8_t,*&gt;">
<AlternativeType Name="std::basic_string&lt;char8_t,*&gt;" />
<AlternativeType Name="std::basic_string&lt;unsigned char,*&gt;" />
<Intrinsic Name="size" Expression="_Mypair._Myval2._Mysize" />
<Intrinsic Name="capacity" Expression="_Mypair._Myval2._Myres" />
<!-- _BUF_SIZE = 16 / sizeof(char) &lt; 1 ? 1 : 16 / sizeof(char) == 16 -->
<Intrinsic Name="bufSize" Expression="16" />
<Intrinsic Name="isShortString" Expression="capacity() &lt; bufSize()" />
<Intrinsic Name="isLongString" Expression="capacity() &gt;= bufSize()" />
<DisplayString Condition="isShortString()">{_Mypair._Myval2._Bx._Buf,s8}</DisplayString>
<DisplayString Condition="isLongString()">{_Mypair._Myval2._Bx._Ptr,s8}</DisplayString>
<StringView Condition="isShortString()">_Mypair._Myval2._Bx._Buf,s8</StringView>
<StringView Condition="isLongString()">_Mypair._Myval2._Bx._Ptr,s8</StringView>
<Expand>
<Item Name="[size]" ExcludeView="simple">size()</Item>
<Item Name="[capacity]" ExcludeView="simple">capacity()</Item>
<Item Name="[allocator]" ExcludeView="simple">_Mypair</Item>
<ArrayItems>
<Size>_Mypair._Myval2._Mysize</Size>
<ValuePointer Condition="isShortString()">_Mypair._Myval2._Bx._Buf</ValuePointer>
<ValuePointer Condition="isLongString()">_Mypair._Myval2._Bx._Ptr</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="std::basic_string_view&lt;YYCC::yycc_char8_t,*&gt;">
<AlternativeType Name="std::basic_string_view&lt;char8_t,*&gt;" />
<AlternativeType Name="std::basic_string_view&lt;unsigned char,*&gt;" />
<Intrinsic Name="size" Expression="_Mysize" />
<Intrinsic Name="data" Expression="_Mydata" />
<DisplayString>{_Mydata,[_Mysize],s8}</DisplayString>
<StringView>_Mydata,[_Mysize],s8</StringView>
<Expand>
<Item Name="[size]" ExcludeView="simple">size()</Item>
<ArrayItems>
<Size>size()</Size>
<ValuePointer>data()</ValuePointer>
</ArrayItems>
</Expand>
</Type>
</AutoVisualizer>

View File

@ -1,409 +0,0 @@
#include "EncodingHelper.hpp"
#include <locale>
namespace YYCC::EncodingHelper {
#pragma region UTF8 Ordinary Convertion
const yycc_char8_t* ToUTF8(const char* src) {
return reinterpret_cast<const yycc_char8_t*>(src);
}
yycc_char8_t* ToUTF8(char* src) {
return reinterpret_cast<yycc_char8_t*>(src);
}
yycc_u8string ToUTF8(const std::string_view& src) {
return yycc_u8string(reinterpret_cast<const yycc_char8_t*>(src.data()), src.size());
}
yycc_u8string_view ToUTF8View(const std::string_view& src) {
return yycc_u8string_view(reinterpret_cast<const yycc_char8_t*>(src.data()), src.size());
}
const char* ToOrdinary(const yycc_char8_t* src) {
return reinterpret_cast<const char*>(src);
}
char* ToOrdinary(yycc_char8_t* src) {
return reinterpret_cast<char*>(src);
}
std::string ToOrdinary(const yycc_u8string_view& src) {
return std::string(reinterpret_cast<const char*>(src.data()), src.size());
}
std::string_view ToOrdinaryView(const yycc_u8string_view& src) {
return std::string_view(reinterpret_cast<const char*>(src.data()), src.size());
}
#pragma endregion
/* Define some assistant macros for easy writing. */
#define CONVFCT_TYPE2(fct_name, src_char_type, dst_char_type, ...) if (src == nullptr) return false; \
std::basic_string_view<src_char_type> cache(src); \
return fct_name(cache, dst, ##__VA_ARGS__);
#define CONVFCT_TYPE3(fct_name, src_char_type, dst_char_type, ...) std::basic_string<dst_char_type> ret; \
if (!fct_name(src, ret, ##__VA_ARGS__)) ret.clear(); \
return ret;
#define CONVFCT_TYPE4(fct_name, src_char_type, dst_char_type, ...) std::basic_string<dst_char_type> ret; \
if (!fct_name(src, ret, ##__VA_ARGS__)) ret.clear(); \
return ret;
#if YYCC_OS == YYCC_OS_WINDOWS
#pragma region WcharToChar
bool WcharToChar(const std::wstring_view& src, std::string& dst, UINT code_page) {
// if src is empty, direct output
if (src.empty()) {
dst.clear();
return true;
}
// init WideCharToMultiByte used variables
// setup src pointer
LPCWCH lpWideCharStr = reinterpret_cast<LPCWCH>(src.data());
// check whether source string is too large.
size_t cSrcSize = src.size();
if (cSrcSize > std::numeric_limits<int>::max()) return false;
int cchWideChar = static_cast<int>(src.size());
// do convertion
// do a dry-run first to fetch desired size.
int desired_size = WideCharToMultiByte(code_page, 0, lpWideCharStr, cchWideChar, NULL, 0, NULL, NULL);
if (desired_size <= 0) return false;
// resize dest for receiving result
dst.resize(static_cast<size_t>(desired_size));
// do real convertion
int write_result = WideCharToMultiByte(code_page, 0, lpWideCharStr, cchWideChar, reinterpret_cast<LPSTR>(dst.data()), desired_size, NULL, NULL);
if (write_result <= 0) return false;
return true;
}
bool WcharToChar(const wchar_t* src, std::string& dst, UINT code_page) {
CONVFCT_TYPE2(WcharToChar, wchar_t, char, code_page);
}
std::string WcharToChar(const std::wstring_view& src, UINT code_page) {
CONVFCT_TYPE3(WcharToChar, wchar_t, char, code_page);
}
std::string WcharToChar(const wchar_t* src, UINT code_page) {
CONVFCT_TYPE4(WcharToChar, wchar_t, char, code_page);
}
#pragma endregion
#pragma region CharToWchar
bool CharToWchar(const std::string_view& src, std::wstring& dst, UINT code_page) {
// if src is empty, direct output
if (src.empty()) {
dst.clear();
return true;
}
// init WideCharToMultiByte used variables
// setup src pointer
LPCCH lpMultiByteStr = reinterpret_cast<LPCCH>(src.data());
// check whether source string is too large.
size_t cSrcSize = src.size();
if (cSrcSize > std::numeric_limits<int>::max()) return false;
int cbMultiByte = static_cast<int>(src.size());
// do convertion
// do a dry-run first to fetch desired size.
int desired_size = MultiByteToWideChar(code_page, 0, lpMultiByteStr, cbMultiByte, NULL, 0);
if (desired_size <= 0) return false;
// resize dest for receiving result
dst.resize(static_cast<size_t>(desired_size));
// do real convertion
int write_result = MultiByteToWideChar(code_page, 0, lpMultiByteStr, cbMultiByte, reinterpret_cast<LPWSTR>(dst.data()), desired_size);
if (write_result <= 0) return false;
return true;
}
bool CharToWchar(const char* src, std::wstring& dst, UINT code_page) {
CONVFCT_TYPE2(CharToWchar, char, wchar_t, code_page);
}
std::wstring CharToWchar(const std::string_view& src, UINT code_page) {
CONVFCT_TYPE3(CharToWchar, char, wchar_t, code_page);
}
std::wstring CharToWchar(const char* src, UINT code_page) {
CONVFCT_TYPE4(CharToWchar, char, wchar_t, code_page);
}
#pragma endregion
#pragma region CharToChar
bool CharToChar(const std::string_view& src, std::string& dst, UINT src_code_page, UINT dst_code_page) {
std::wstring intermediary;
if (!CharToWchar(src, intermediary, src_code_page)) return false;
if (!WcharToChar(intermediary, dst, dst_code_page)) return false;
return true;
}
bool CharToChar(const char* src, std::string& dst, UINT src_code_page, UINT dst_code_page) {
CONVFCT_TYPE2(CharToChar, char, char, src_code_page, dst_code_page);
}
std::string CharToChar(const std::string_view& src, UINT src_code_page, UINT dst_code_page) {
CONVFCT_TYPE3(CharToChar, char, char, src_code_page, dst_code_page);
}
std::string CharToChar(const char* src, UINT src_code_page, UINT dst_code_page) {
CONVFCT_TYPE4(CharToChar, char, char, src_code_page, dst_code_page);
}
#pragma endregion
#pragma region WcharToUTF8
bool WcharToUTF8(const std::wstring_view& src, yycc_u8string& dst) {
std::string adapted_dst;
bool ret = WcharToChar(src, adapted_dst, CP_UTF8);
if (ret) dst = ToUTF8(adapted_dst);
return ret;
}
bool WcharToUTF8(const wchar_t* src, yycc_u8string& dst) {
CONVFCT_TYPE2(WcharToUTF8, wchar_t, yycc_char8_t);
}
yycc_u8string WcharToUTF8(const std::wstring_view& src) {
CONVFCT_TYPE3(WcharToUTF8, wchar_t, yycc_char8_t);
}
yycc_u8string WcharToUTF8(const wchar_t* src) {
CONVFCT_TYPE4(WcharToUTF8, wchar_t, yycc_char8_t);
}
#pragma endregion
#pragma region UTF8ToWchar
bool UTF8ToWchar(const yycc_u8string_view& src, std::wstring& dst) {
std::string_view adapted_src(ToOrdinaryView(src));
return CharToWchar(adapted_src, dst, CP_UTF8);
}
bool UTF8ToWchar(const yycc_char8_t* src, std::wstring& dst) {
CONVFCT_TYPE2(UTF8ToWchar, yycc_char8_t, wchar_t);
}
std::wstring UTF8ToWchar(const yycc_u8string_view& src) {
CONVFCT_TYPE3(UTF8ToWchar, yycc_char8_t, wchar_t);
}
std::wstring UTF8ToWchar(const yycc_char8_t* src) {
CONVFCT_TYPE4(UTF8ToWchar, yycc_char8_t, wchar_t);
}
#pragma endregion
#pragma region CharToUTF8
bool CharToUTF8(const std::string_view& src, yycc_u8string& dst, UINT code_page) {
std::string adapted_dst;
bool ret = CharToChar(src, adapted_dst, code_page, CP_UTF8);
if (ret) dst = ToUTF8(adapted_dst);
return ret;
}
bool CharToUTF8(const char* src, yycc_u8string& dst, UINT code_page) {
CONVFCT_TYPE2(CharToUTF8, char, yycc_char8_t, code_page);
}
yycc_u8string CharToUTF8(const std::string_view& src, UINT code_page) {
CONVFCT_TYPE3(CharToUTF8, char, yycc_char8_t, code_page);
}
yycc_u8string CharToUTF8(const char* src, UINT code_page) {
CONVFCT_TYPE4(CharToUTF8, char, yycc_char8_t, code_page);
}
#pragma endregion
#pragma region UTF8ToChar
bool UTF8ToChar(const yycc_u8string_view& src, std::string& dst, UINT code_page) {
std::string_view adapted_src(ToOrdinaryView(src));
return CharToChar(adapted_src, dst, CP_UTF8, code_page);
}
bool UTF8ToChar(const yycc_char8_t* src, std::string& dst, UINT code_page) {
CONVFCT_TYPE2(UTF8ToChar, yycc_char8_t, char, code_page);
}
std::string UTF8ToChar(const yycc_u8string_view& src, UINT code_page) {
CONVFCT_TYPE3(UTF8ToChar, yycc_char8_t, char, code_page);
}
std::string UTF8ToChar(const yycc_char8_t* src, UINT code_page) {
CONVFCT_TYPE4(UTF8ToChar, yycc_char8_t, char, code_page);
}
#pragma endregion
#endif
#pragma region UTF8 UTF16 UTF32 Help Funcs
/*
According to the documentation introduced in CppReference.
The standard library is guaranteed to provide several specific specializations of \c std::codecvt.
The UTF8 char type in UTF8 related specializations of \c std::codecvt is different.
It is also independend from we defined \c yycc_char8_t.
So it is essential define a type which can correctly trigger specific specializations of \c std::codecv in there.
*/
#if defined(__cpp_char8_t)
using CodecvtUTF8Char_t = char8_t;
#else
using CodecvtUTF8Char_t = char;
#endif
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
using CodecvtFacet_t = std::codecvt<_TChar, CodecvtUTF8Char_t, std::mbstate_t>;
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
static bool UTF8ToUTFOther(const yycc_u8string_view& src, std::basic_string<_TChar>& dst) {
// Reference:
// https://zh.cppreference.com/w/cpp/locale/codecvt/in
// if src is empty, return directly
if (src.empty()) {
dst.clear();
return true;
}
// init locale and get codecvt facet
// same reason in UTFOtherToUTF8 to keeping reference to locale
const auto& this_locale = std::locale::classic();
const auto& this_codecvt = std::use_facet<CodecvtFacet_t<_TChar>>(this_locale);
// convertion preparation
std::mbstate_t mb{};
dst.resize(src.size());
const CodecvtUTF8Char_t* intern_from = reinterpret_cast<const CodecvtUTF8Char_t*>(src.data()),
*intern_from_end = reinterpret_cast<const CodecvtUTF8Char_t*>(src.data() + src.size()),
*intern_from_next = nullptr;
_TChar* extern_to = dst.data(),
*extern_to_end = dst.data() + dst.size(),
*extern_to_next = nullptr;
// do convertion
auto result = this_codecvt.in(
mb,
intern_from, intern_from_end, intern_from_next,
extern_to, extern_to_end, extern_to_next
);
// check result
if (result != CodecvtFacet_t<_TChar>::ok)
return false;
// resize result and return
dst.resize(extern_to_next - dst.data());
return true;
}
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
static bool UTFOtherToUTF8(const std::basic_string_view<_TChar>& src, yycc_u8string& dst) {
// Reference:
// https://zh.cppreference.com/w/cpp/locale/codecvt/out
// if src is empty, return directly
if (src.empty()) {
dst.clear();
return true;
}
// init locale and get codecvt facet
// the reference to locale must be preserved until convertion done.
// because the life time of codecvt facet is equal to the reference to locale.
const auto& this_locale = std::locale::classic();
const auto& this_codecvt = std::use_facet<CodecvtFacet_t<_TChar>>(this_locale);
// do convertion preparation
std::mbstate_t mb{};
dst.resize(src.size() * this_codecvt.max_length());
const _TChar* intern_from = src.data(),
*intern_from_end = src.data() + src.size(),
*intern_from_next = nullptr;
CodecvtUTF8Char_t* extern_to = reinterpret_cast<CodecvtUTF8Char_t*>(dst.data()),
*extern_to_end = reinterpret_cast<CodecvtUTF8Char_t*>(dst.data() + dst.size()),
*extern_to_next = nullptr;
// do convertion
auto result = this_codecvt.out(
mb,
intern_from, intern_from_end, intern_from_next,
extern_to, extern_to_end, extern_to_next
);
// check result
if (result != CodecvtFacet_t<_TChar>::ok)
return false;
// resize result and retuen
dst.resize(extern_to_next - reinterpret_cast<CodecvtUTF8Char_t*>(dst.data()));
return true;
}
#pragma endregion
#pragma region UTF8ToUTF16
bool UTF8ToUTF16(const yycc_u8string_view& src, std::u16string& dst) {
return UTF8ToUTFOther<char16_t>(src, dst);
}
bool UTF8ToUTF16(const yycc_char8_t* src, std::u16string& dst) {
CONVFCT_TYPE2(UTF8ToUTF16, yycc_char8_t, char16_t);
}
std::u16string UTF8ToUTF16(const yycc_u8string_view& src) {
CONVFCT_TYPE3(UTF8ToUTF16, yycc_char8_t, char16_t);
}
std::u16string UTF8ToUTF16(const yycc_char8_t* src) {
CONVFCT_TYPE4(UTF8ToUTF16, yycc_char8_t, char16_t);
}
#pragma endregion
#pragma region UTF16ToUTF8
bool UTF16ToUTF8(const std::u16string_view& src, yycc_u8string& dst) {
return UTFOtherToUTF8<char16_t>(src, dst);
}
bool UTF16ToUTF8(const char16_t* src, yycc_u8string& dst) {
CONVFCT_TYPE2(UTF16ToUTF8, char16_t, yycc_char8_t);
}
yycc_u8string UTF16ToUTF8(const std::u16string_view& src) {
CONVFCT_TYPE3(UTF16ToUTF8, char16_t, yycc_char8_t);
}
yycc_u8string UTF16ToUTF8(const char16_t* src) {
CONVFCT_TYPE4(UTF16ToUTF8, char16_t, yycc_char8_t);
}
#pragma endregion
#pragma region UTF8ToUTF32
bool UTF8ToUTF32(const yycc_u8string_view& src, std::u32string& dst) {
return UTF8ToUTFOther<char32_t>(src, dst);
}
bool UTF8ToUTF32(const yycc_char8_t* src, std::u32string& dst) {
CONVFCT_TYPE2(UTF8ToUTF32, yycc_char8_t, char32_t);
}
std::u32string UTF8ToUTF32(const yycc_u8string_view& src) {
CONVFCT_TYPE3(UTF8ToUTF32, yycc_char8_t, char32_t);
}
std::u32string UTF8ToUTF32(const yycc_char8_t* src) {
CONVFCT_TYPE4(UTF8ToUTF32, yycc_char8_t, char32_t);
}
#pragma endregion
#pragma region UTF32ToUTF8
bool UTF32ToUTF8(const std::u32string_view& src, yycc_u8string& dst) {
return UTFOtherToUTF8<char32_t>(src, dst);
}
bool UTF32ToUTF8(const char32_t* src, yycc_u8string& dst) {
CONVFCT_TYPE2(UTF32ToUTF8, char32_t, yycc_char8_t);
}
yycc_u8string UTF32ToUTF8(const std::u32string_view& src) {
CONVFCT_TYPE3(UTF32ToUTF8, char32_t, yycc_char8_t);
}
yycc_u8string UTF32ToUTF8(const char32_t* src) {
CONVFCT_TYPE4(UTF32ToUTF8, char32_t, yycc_char8_t);
}
#pragma endregion
#undef CONVFCT_TYPE2
#undef CONVFCT_TYPE3
#undef CONVFCT_TYPE4
}

View File

@ -1,95 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <string>
#if YYCC_OS == YYCC_OS_WINDOWS
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include "WinImportSuffix.hpp"
#endif
/**
* @brief The helper for all encoding stuff.
* @details
* For more infomations about how to use the functions provided by this namespace,
* please see \ref library_encoding and \ref encoding_helper.
*/
namespace YYCC::EncodingHelper {
#define _YYCC_U8(strl) u8 ## strl ///< The assistant macro for YYCC_U8.
#define YYCC_U8(strl) (reinterpret_cast<const ::YYCC::yycc_char8_t*>(_YYCC_U8(strl))) ///< The macro for creating UTF8 string literal. See \ref library_encoding.
#define YYCC_U8_CHAR(chr) (static_cast<YYCC::yycc_char8_t>(chr)) ///< The macro for casting ordinary char type into YYCC UTF8 char type.
const yycc_char8_t* ToUTF8(const char* src);
yycc_char8_t* ToUTF8(char* src);
yycc_u8string ToUTF8(const std::string_view& src);
yycc_u8string_view ToUTF8View(const std::string_view& src);
const char* ToOrdinary(const yycc_char8_t* src);
char* ToOrdinary(yycc_char8_t* src);
std::string ToOrdinary(const yycc_u8string_view& src);
std::string_view ToOrdinaryView(const yycc_u8string_view& src);
#if YYCC_OS == YYCC_OS_WINDOWS
bool WcharToChar(const std::wstring_view& src, std::string& dst, UINT code_page);
bool WcharToChar(const wchar_t* src, std::string& dst, UINT code_page);
std::string WcharToChar(const std::wstring_view& src, UINT code_page);
std::string WcharToChar(const wchar_t* src, UINT code_page);
bool CharToWchar(const std::string_view& src, std::wstring& dst, UINT code_page);
bool CharToWchar(const char* src, std::wstring& dst, UINT code_page);
std::wstring CharToWchar(const std::string_view& src, UINT code_page);
std::wstring CharToWchar(const char* src, UINT code_page);
bool CharToChar(const std::string_view& src, std::string& dst, UINT src_code_page, UINT dst_code_page);
bool CharToChar(const char* src, std::string& dst, UINT src_code_page, UINT dst_code_page);
std::string CharToChar(const std::string_view& src, UINT src_code_page, UINT dst_code_page);
std::string CharToChar(const char* src, UINT src_code_page, UINT dst_code_page);
bool WcharToUTF8(const std::wstring_view& src, yycc_u8string& dst);
bool WcharToUTF8(const wchar_t* src, yycc_u8string& dst);
yycc_u8string WcharToUTF8(const std::wstring_view& src);
yycc_u8string WcharToUTF8(const wchar_t* src);
bool UTF8ToWchar(const yycc_u8string_view& src, std::wstring& dst);
bool UTF8ToWchar(const yycc_char8_t* src, std::wstring& dst);
std::wstring UTF8ToWchar(const yycc_u8string_view& src);
std::wstring UTF8ToWchar(const yycc_char8_t* src);
bool CharToUTF8(const std::string_view& src, yycc_u8string& dst, UINT code_page);
bool CharToUTF8(const char* src, yycc_u8string& dst, UINT code_page);
yycc_u8string CharToUTF8(const std::string_view& src, UINT code_page);
yycc_u8string CharToUTF8(const char* src, UINT code_page);
bool UTF8ToChar(const yycc_u8string_view& src, std::string& dst, UINT code_page);
bool UTF8ToChar(const yycc_char8_t* src, std::string& dst, UINT code_page);
std::string UTF8ToChar(const yycc_u8string_view& src, UINT code_page);
std::string UTF8ToChar(const yycc_char8_t* src, UINT code_page);
#endif
bool UTF8ToUTF16(const yycc_u8string_view& src, std::u16string& dst);
bool UTF8ToUTF16(const yycc_char8_t* src, std::u16string& dst);
std::u16string UTF8ToUTF16(const yycc_u8string_view& src);
std::u16string UTF8ToUTF16(const yycc_char8_t* src);
bool UTF16ToUTF8(const std::u16string_view& src, yycc_u8string& dst);
bool UTF16ToUTF8(const char16_t* src, yycc_u8string& dst);
yycc_u8string UTF16ToUTF8(const std::u16string_view& src);
yycc_u8string UTF16ToUTF8(const char16_t* src);
bool UTF8ToUTF32(const yycc_u8string_view& src, std::u32string& dst);
bool UTF8ToUTF32(const yycc_char8_t* src, std::u32string& dst);
std::u32string UTF8ToUTF32(const yycc_u8string_view& src);
std::u32string UTF8ToUTF32(const yycc_char8_t* src);
bool UTF32ToUTF8(const std::u32string_view& src, yycc_u8string& dst);
bool UTF32ToUTF8(const char32_t* src, yycc_u8string& dst);
yycc_u8string UTF32ToUTF8(const std::u32string_view& src);
yycc_u8string UTF32ToUTF8(const char32_t* src);
}

View File

@ -1,182 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <initializer_list>
#include <type_traits>
/**
* @brief The namespace for convenient C++ enum class logic operations.
* @details
* C++ enum class statement is a modern way to declare enum in C++.
* But it lack essential logic operations which is commonly used by programmer.
* So we create this helper to resolve this issue.
*/
namespace YYCC::EnumHelper {
//// Reference:
//// Enum operator overload: https://stackoverflow.com/a/71107019
//// Constexpr operator overload: https://stackoverflow.com/a/17746099
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
//inline constexpr TEnum operator|(TEnum lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(static_cast<ut>(lhs) | static_cast<ut>(rhs));
//}
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
//inline constexpr TEnum operator|=(TEnum& lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// lhs = lhs | rhs;
// return lhs;
//}
//
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
//inline constexpr TEnum operator&(TEnum lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(static_cast<ut>(lhs) & static_cast<ut>(rhs));
//}
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
//inline constexpr TEnum operator&=(TEnum& lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// lhs = lhs & rhs;
// return lhs;
//}
//
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
//inline constexpr TEnum operator^(TEnum lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(static_cast<ut>(lhs) ^ static_cast<ut>(rhs));
//}
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
//inline constexpr TEnum operator^=(TEnum& lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// lhs = lhs ^ rhs;
// return lhs;
//}
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
//inline constexpr TEnum operator~(TEnum lhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(~(static_cast<ut>(lhs)));
//}
//
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
//inline constexpr bool operator bool(TEnum lhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<bool>(static_cast<ut>(lhs));
//}
/**
* @brief The helper struct to check all given template argument are the same enum type.
* @tparam TEnum The template parameter to be checked (first one).
* @tparam Ts The template parameter to be checked.
*/
template<typename TEnum, typename... Ts>
struct all_enum_values {
public:
// Please note it is std::is_same, not std::is_same_v!
// That's std::conjunction_v required.
static constexpr bool value = std::is_enum_v<std::remove_cv_t<TEnum>> && std::conjunction_v<std::is_same<std::remove_cv_t<TEnum>, std::remove_cv_t<Ts>>...>;
};
/**
* @brief The convenient calling to all_enum_values::value to check enum template parameter.
* @tparam TEnum The template parameter to be checked (first one).
* @tparam Ts The template parameter to be checked.
*/
template<typename TEnum, typename... Ts>
inline constexpr bool all_enum_values_v = all_enum_values<TEnum, Ts...>::value;
/**
* @brief Merge given enum flags like performing <TT>e1 | e2 | ... | en</TT>
* @tparam TEnum Enum type for processing.
* @param[in] val The first enum flag to be merged.
* @param[in] val_left Left enum flags to be merged.
* @return The merged enum flag.
* @remarks
* This function use recursive expansion to get final merge result.
* So there is no difference of each arguments.
* We independ first argument just served for expansion.
*/
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum, Ts...>, int> = 0>
constexpr TEnum Merge(TEnum val, Ts... val_left) {
using ut = std::underlying_type_t<TEnum>;
ut result = static_cast<ut>(val);
if constexpr (sizeof...(val_left) > 0) {
result |= static_cast<ut>(Merge(val_left...));
}
return static_cast<TEnum>(result);
}
/**
* @brief Reverse given enum flags like performing <TT>~(e)</TT>
* @tparam TEnum Enum type for processing.
* @param[in] e The list of enum flags to be inversed.
* @return The inversed enum flag.
*/
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr TEnum Invert(TEnum e) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<TEnum>(~(static_cast<ut>(e)));
}
/**
* @brief Use specified enum flag to mask given enum flag like performing <TT>e1 &= e2</TT>
* @tparam TEnum Enum type for processing.
* @param[in,out] e1 The enum flags to be masked.
* @param[in] e2 The mask enum flag.
*/
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr void Mask(TEnum& e1, TEnum e2) {
using ut = std::underlying_type_t<TEnum>;
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(e2));
}
/**
* @brief Add multiple enum flags to given enum flag like performing <TT>e1 |= (e2 | e3 | ... | en)</TT>
* @tparam TEnum Enum type for processing.
* @param[in,out] e1 The enum flag which flags add on.
* @param[in] vals The enum flag to be added.
*/
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum, Ts...>, int> = 0>
constexpr void Add(TEnum& e1, Ts... vals) {
using ut = std::underlying_type_t<TEnum>;
e1 = static_cast<TEnum>(static_cast<ut>(e1) | static_cast<ut>(Merge(vals...)));
}
/**
* @brief Remove multiple enum flags from given enum flag like performing <TT>e1 &= ~(e2 | e3 | ... | en)</TT>
* @tparam TEnum Enum type for processing.
* @param[in,out] e1 The enum flag which flags removed from.
* @param[in] vals The enum flag to be removed.
*/
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum>, int> = 0>
constexpr void Remove(TEnum& e1, Ts... vals) {
using ut = std::underlying_type_t<TEnum>;
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(Invert(Merge(vals...))));
}
/**
* @brief Check whether given enum flag has any of specified multiple enum flags (OR) like performing <TT>bool(e1 & (e2 | e3 | ... | en))</TT>
* @tparam TEnum Enum type for processing.
* @param[in] e1 The enum flag where we check.
* @param[in] vals The enum flags for checking.
* @return True if it has any of given flags (OR), otherwise false.
*/
template<typename TEnum, typename... Ts, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr bool Has(TEnum e1, Ts... vals) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<bool>(static_cast<ut>(e1) & static_cast<ut>(Merge(vals...)));
}
/**
* @brief Cast given enum flags to its equvalent boolean value like performing <TT>bool(e)</TT>
* @tparam TEnum Enum type for processing.
* @param e The enum flags to be cast.
* @return The equvalent bool value of given enum flag.
*/
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr bool Bool(TEnum e) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<bool>(static_cast<ut>(e));
}
}

View File

@ -1,221 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include "EncodingHelper.hpp"
#include "StringHelper.hpp"
#include <string>
#include <cinttypes>
#include <type_traits>
#include <stdexcept>
#include <charconv>
#include <array>
/**
* @brief The helper involving convertion between arithmetic types (integral, floating point and bool) and string
* @details
* See also \ref parser_helper.
*/
namespace YYCC::ParserHelper {
// Reference: https://zh.cppreference.com/w/cpp/utility/from_chars
/**
* @brief Try parsing given string to floating point types.
* @tparam _Ty The type derived from floating point type.
* @param[in] strl The string need to be parsed.
* @param[out] num
* The variable receiving result.
* There is no guarantee that the content is not modified when parsing failed.
* @param[in] fmt The floating point format used when try parsing.
* @return True if success, otherwise false.
*/
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num, std::chars_format fmt = std::chars_format::general) {
auto [ptr, ec] = std::from_chars(
EncodingHelper::ToOrdinary(strl.data()),
EncodingHelper::ToOrdinary(strl.data() + strl.size()),
num, fmt
);
if (ec == std::errc()) {
// check whether the full string is matched
return ptr == EncodingHelper::ToOrdinary(strl.data() + strl.size());
} else if (ec == std::errc::invalid_argument) {
// given string is invalid
return false;
} else if (ec == std::errc::result_out_of_range) {
// given string is out of range
return false;
} else {
// unreachable
throw std::runtime_error("unreachable code.");
}
}
/**
* @brief Try parsing given string to integral types.
* @tparam _Ty The type derived from integral type except bool type.
* @param[in] strl The string need to be parsed.
* @param[out] num
* The variable receiving result.
* There is no guarantee that the content is not modified when parsing failed.
* @param[in] base Integer base to use: a value between 2 and 36 (inclusive).
* @return True if success, otherwise false.
*/
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num, int base = 10) {
auto [ptr, ec] = std::from_chars(
EncodingHelper::ToOrdinary(strl.data()),
EncodingHelper::ToOrdinary(strl.data() + strl.size()),
num, base
);
if (ec == std::errc()) {
// check whether the full string is matched
return ptr == EncodingHelper::ToOrdinary(strl.data() + strl.size());
} else if (ec == std::errc::invalid_argument) {
// given string is invalid
return false;
} else if (ec == std::errc::result_out_of_range) {
// given string is out of range
return false;
} else {
// unreachable
throw std::runtime_error("unreachable code.");
}
}
/**
* @brief Try parsing given string to bool types.
* @tparam _Ty The type derived from bool type.
* @param[in] strl The string need to be parsed ("true" or "false", case insensitive).
* @param[out] num
* The variable receiving result.
* There is no guarantee that the content is not modified when parsing failed.
* @return True if success, otherwise false.
*/
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num) {
// get lower case
yycc_u8string lower_case(strl);
YYCC::StringHelper::Lower(lower_case);
// compare result
if (strl == YYCC_U8("true")) num = true;
else if (strl == YYCC_U8("false")) num = false;
else return false;
return true;
}
/**
* @brief Parse given string to floating point types.
* @tparam _Ty The type derived from floating point type.
* @param[in] strl The string need to be parsed.
* @param[in] fmt The floating point format used when try parsing.
* @return
* The parsing result.
* There is no guarantee about the content of this return value when parsing failed.
* It may be any possible value but usually is its default value.
*/
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
_Ty Parse(const yycc_u8string_view& strl, std::chars_format fmt = std::chars_format::general) {
_Ty ret;
TryParse(strl, ret, fmt);
return ret;
}
/**
* @brief Parse given string to integral type types.
* @tparam _Ty The type derived from integral type except bool type.
* @param[in] strl The string need to be parsed.
* @param[in] base Integer base to use: a value between 2 and 36 (inclusive).
* @return
* The parsing result.
* There is no guarantee about the content of this return value when parsing failed.
* It may be any possible value but usually is its default value.
*/
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
_Ty Parse(const yycc_u8string_view& strl, int base = 10) {
_Ty ret;
TryParse(strl, ret, base);
return ret;
}
/**
* @brief Parse given string to bool types.
* @tparam _Ty The type derived from bool type.
* @param[in] strl The string need to be parsed ("true" or "false", case insensitive).
* @return
* The parsing result.
* There is no guarantee about the content of this return value when parsing failed.
* It may be any possible value but usually is its default value.
*/
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
_Ty Parse(const yycc_u8string_view& strl) {
_Ty ret;
TryParse(strl, ret);
return ret;
}
// Reference: https://en.cppreference.com/w/cpp/utility/to_chars
/**
* @brief Return the string representation of given floating point value.
* @tparam _Ty The type derived from floating point type.
* @param[in] num The value need to get string representation.
* @param[in] fmt The floating point format used when getting string representation.
* @param[in] precision The floating point precision used when getting string representation.
* @return The string representation of given value.
*/
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
yycc_u8string ToString(_Ty num, std::chars_format fmt = std::chars_format::general, int precision = 6) {
// default precision = 6 is gotten from: https://en.cppreference.com/w/c/io/fprintf
std::array<yycc_char8_t, 64> buffer;
auto [ptr, ec] = std::to_chars(
EncodingHelper::ToOrdinary(buffer.data()),
EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),
num, fmt, precision
);
if (ec == std::errc()) {
return yycc_u8string(buffer.data(), EncodingHelper::ToUTF8(ptr) - buffer.data());
} else if (ec == std::errc::value_too_large) {
// too short buffer
// this should not happened
throw std::out_of_range("ToString() buffer is not sufficient.");
} else {
// unreachable
throw std::runtime_error("unreachable code.");
}
}
/**
* @brief Return the string representation of given integral value.
* @tparam _Ty The type derived from integral type except bool type.
* @param[in] num The value need to get string representation.
* @param[in] base Integer base used when getting string representation: a value between 2 and 36 (inclusive).
* @return The string representation of given value.
*/
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
yycc_u8string ToString(_Ty num, int base = 10) {
std::array<yycc_char8_t, 64> buffer;
auto [ptr, ec] = std::to_chars(
EncodingHelper::ToOrdinary(buffer.data()),
EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),
num, base
);
if (ec == std::errc()) {
return yycc_u8string(buffer.data(), EncodingHelper::ToUTF8(ptr) - buffer.data());
} else if (ec == std::errc::value_too_large) {
// too short buffer
// this should not happened
throw std::out_of_range("ToString() buffer is not sufficient.");
} else {
// unreachable
throw std::runtime_error("unreachable code.");
}
}
/**
* @brief Return the string representation of given bool value.
* @tparam _Ty The type derived from bool type.
* @param[in] num The value need to get string representation.
* @return The string representation of given value ("true" or "false").
*/
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
yycc_u8string ToString(_Ty num) {
if (num) return yycc_u8string(YYCC_U8("true"));
else return yycc_u8string(YYCC_U8("false"));
}
}

View File

@ -1,41 +0,0 @@
#include "StdPatch.hpp"
#include "EncodingHelper.hpp"
#include <string>
#include <stdexcept>
namespace YYCC::StdPatch {
std::filesystem::path ToStdPath(const yycc_u8string_view& u8_path) {
#if YYCC_OS == YYCC_OS_WINDOWS
// convert path to wchar
std::wstring wpath;
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_path, wpath))
throw std::invalid_argument("Fail to convert given UTF8 string.");
// return path with wchar_t ctor
return std::filesystem::path(wpath);
#else
std::string cache = YYCC::EncodingHelper::ToOrdinary(u8_path);
return std::filesystem::path(cache.c_str());
#endif
}
yycc_u8string ToUTF8Path(const std::filesystem::path& path) {
#if YYCC_OS == YYCC_OS_WINDOWS
// get and convert to utf8
yycc_u8string u8_path;
if (!YYCC::EncodingHelper::WcharToUTF8(path.c_str(), u8_path))
throw std::invalid_argument("Fail to convert to UTF8 string.");
// return utf8 path
return u8_path;
#else
return EncodingHelper::ToUTF8(path.string());
#endif
}
}

View File

@ -1,218 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <filesystem>
#include <string>
#include <string_view>
/**
* @brief \c Standard library related patches for UTF8 compatibility and the limitation of C++ standard version.
* @details
* See also \ref std_patch.
*/
namespace YYCC::StdPatch {
/**
* @brief Constructs \c std::filesystem::path from UTF8 path.
* @param[in] u8_path UTF8 path string for building.
* @return \c std::filesystem::path instance.
* @exception std::invalid_argument Fail to parse given UTF8 string (maybe invalid?).
*/
std::filesystem::path ToStdPath(const yycc_u8string_view& u8_path);
/**
* @brief Returns the UTF8 representation of given \c std::filesystem::path.
* @param[in] path The \c std::filesystem::path instance converting to UTF8 path.
* @return The UTF8 representation of given \c std::filesystem::path.
* @exception std::invalid_argument Fail to convert to UTF8 string.
*/
yycc_u8string ToUTF8Path(const std::filesystem::path& path);
#pragma region StartsWith EndsWith
// Reference:
// https://en.cppreference.com/w/cpp/string/basic_string_view/starts_with
// https://en.cppreference.com/w/cpp/string/basic_string_view/ends_with
// https://en.cppreference.com/w/cpp/string/basic_string/starts_with
// https://en.cppreference.com/w/cpp/string/basic_string/ends_with
#pragma region String View
/**
* @brief Checks if the string view begins with the given prefix
* @param[in] that The string view to find.
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string_view<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
return std::basic_string_view<CharT, Traits>(that.data(), std::min(that.size(), sv.size())) == sv;
}
/**
* @brief Checks if the string view begins with the given prefix
* @param[in] that The string view to find.
* @param[in] ch A single character.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string_view<CharT, Traits>& that, CharT ch) noexcept {
return !that.empty() && Traits::eq(that.front(), ch);
}
/**
* @brief Checks if the string view begins with the given prefix
* @param[in] that The string view to find.
* @param[in] s A null-terminated character string.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string_view<CharT, Traits>& that, const CharT* s) noexcept {
return StartsWith(that, std::basic_string_view(s));
}
/**
* @brief Checks if the string view ends with the given suffix
* @param[in] that The string view to find.
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string_view<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
return that.size() >= sv.size() && that.compare(that.size() - sv.size(), std::basic_string_view<CharT, Traits>::npos, sv) == 0;
}
/**
* @brief Checks if the string view ends with the given suffix
* @param[in] that The string view to find.
* @param[in] ch A single character.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string_view<CharT, Traits>& that, CharT ch) noexcept {
return !that.empty() && Traits::eq(that.back(), ch);
}
/**
* @brief Checks if the string view ends with the given suffix
* @param[in] that The string view to find.
* @param[in] s A null-terminated character string.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string_view<CharT, Traits>& that, const CharT* s) noexcept {
return EndsWith(that, std::basic_string_view(s));
}
#pragma endregion
#pragma region String
/**
* @brief Checks if the string begins with the given prefix
* @param[in] that The string to find.
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
return StartsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), sv);
}
/**
* @brief Checks if the string begins with the given prefix
* @param[in] that The string to find.
* @param[in] ch A single character.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string<CharT, Traits>& that, CharT ch) noexcept {
return StartsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), ch);
}
/**
* @brief Checks if the string begins with the given prefix
* @param[in] that The string to find.
* @param[in] s A null-terminated character string.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string<CharT, Traits>& that, const CharT* s) noexcept {
return StartsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), s);
}
/**
* @brief Checks if the string ends with the given suffix
* @param[in] that The string to find.
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
return EndsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), sv);
}
/**
* @brief Checks if the string ends with the given suffix
* @param[in] that The string to find.
* @param[in] ch A single character.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string<CharT, Traits>& that, CharT ch) noexcept {
return EndsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), ch);
}
/**
* @brief Checks if the string ends with the given suffix
* @param[in] that The string to find.
* @param[in] s A null-terminated character string.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string<CharT, Traits>& that, const CharT* s) noexcept {
return EndsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), s);
}
#pragma endregion
#pragma endregion
#pragma region Contain
/**
* @brief Checks if there is an element with key equivalent to key in the container.
* @details
* The polyfill to \c Contains function of unordered and ordered associative container.
* Because this function only present after C++ 20.
* This function will use our custom polyfill if the version of C++ standard you are using lower than C++ 20.
* Otherwise it will fallback to vanilla standard library function.
* @tparam _TContainer
* The type of container. This container must have \c find() and \c end() member functions.
* @tparam _TKey
* The type of key of container.
* If the container is a set, this type is the type of item in set.
* If the container is a map, this type is the key type of map.
* @param[in] container The reference to container to find.
* @param[in] key Key value of the element to search for
* @return True if there is such an element, otherwise false.
* @remarks
* This template function do not have constraint check.
* If container type has \c find() and \c end() member functions, this template function will be created without any error.
* However, this function should be used for standard library associative container according to its original purpose.
* It means that the type of container usually and should be one of following types:
* \li \c std::set
* \li \c std::multiset
* \li \c std::map
* \li \c std::multimap
* \li \c std::unordered_set
* \li \c std::unordered_multiset
* \li \c std::unordered_map
* \li \c std::unordered_multimap
*/
template<class _TContainer, class _TKey>
bool Contains(const _TContainer& container, const _TKey& key) {
// __cplusplus macro need special compiler switch enabled when compiling.
// So we use _MSVC_LANG check it instead.
#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
return container.contains(key);
#else
return container.find(key) != container.end();
#endif
}
#pragma endregion
}

View File

@ -1,223 +0,0 @@
#include "StringHelper.hpp"
#include "EncodingHelper.hpp"
#include <algorithm>
namespace YYCC::StringHelper {
#pragma region Printf VPrintf
bool Printf(yycc_u8string& strl, const yycc_char8_t* format, ...) {
va_list argptr;
va_start(argptr, format);
bool ret = VPrintf(strl, format, argptr);
va_end(argptr);
return ret;
}
bool VPrintf(yycc_u8string& strl, const yycc_char8_t* format, va_list argptr) {
va_list args1;
va_copy(args1, argptr);
va_list args2;
va_copy(args2, argptr);
// the return value is desired char count without NULL terminal.
// minus number means error
int count = std::vsnprintf(
nullptr,
0,
EncodingHelper::ToOrdinary(format),
args1
);
if (count < 0) {
// invalid length returned by vsnprintf.
return false;
}
va_end(args1);
// resize std::string to desired count.
// and pass its length + 1 to std::vsnprintf,
// because std::vsnprintf only can write "buf_size - 1" chars with a trailing NULL.
// however std::vsnprintf already have a trailing NULL, so we plus 1 for it.
strl.resize(count);
int write_result = std::vsnprintf(
EncodingHelper::ToOrdinary(strl.data()),
strl.size() + 1,
EncodingHelper::ToOrdinary(format),
args2
);
va_end(args2);
if (write_result < 0 || write_result > count) {
// invalid write result in vsnprintf.
return false;
}
return true;
}
yycc_u8string Printf(const yycc_char8_t* format, ...) {
yycc_u8string ret;
va_list argptr;
va_start(argptr, format);
VPrintf(ret, format, argptr);
va_end(argptr);
return ret;
}
yycc_u8string VPrintf(const yycc_char8_t* format, va_list argptr) {
yycc_u8string ret;
va_list argcpy;
va_copy(argcpy, argptr);
VPrintf(ret, format, argcpy);
va_end(argcpy);
return ret;
}
#pragma endregion
#pragma region Replace
void Replace(yycc_u8string& strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl) {
// Reference: https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
// check requirements
// from string should not be empty
yycc_u8string from_strl(_from_strl);
yycc_u8string to_strl(_to_strl);
if (from_strl.empty()) return;
// start replace one by one
size_t start_pos = 0;
while ((start_pos = strl.find(from_strl, start_pos)) != yycc_u8string::npos) {
strl.replace(start_pos, from_strl.size(), to_strl);
start_pos += to_strl.size(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
}
}
yycc_u8string Replace(const yycc_u8string_view& _strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl) {
// prepare result
yycc_u8string strl(_strl);
Replace(strl, _from_strl, _to_strl);
// return value
return strl;
}
#pragma endregion
#pragma region Join
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer) {
yycc_u8string ret;
bool is_first = true;
yycc_u8string_view element;
// fetch element
while (fct_data(element)) {
// insert decilmer
if (is_first) is_first = false;
else {
// append decilmer.
ret.append(decilmer);
}
// insert element if it is not empty
if (!element.empty())
ret.append(element);
}
return ret;
}
#pragma endregion
#pragma region Upper Lower
template<bool bIsToLower>
static void GeneralStringLowerUpper(yycc_u8string& strl) {
// References:
// https://en.cppreference.com/w/cpp/algorithm/transform
// https://en.cppreference.com/w/cpp/string/byte/tolower
std::transform(
strl.cbegin(), strl.cend(), strl.begin(),
[](unsigned char c) -> char {
if constexpr (bIsToLower) return std::tolower(c);
else return std::toupper(c);
}
);
}
void Lower(yycc_u8string& strl) {
GeneralStringLowerUpper<true>(strl);
}
yycc_u8string Lower(const yycc_u8string_view& strl) {
yycc_u8string ret(strl);
Lower(ret);
return ret;
}
void Upper(yycc_u8string& strl) {
GeneralStringLowerUpper<false>(strl);
}
yycc_u8string Upper(const yycc_u8string_view& strl) {
// same as Lower, just replace char transform function.
yycc_u8string ret(strl);
Upper(ret);
return ret;
}
#pragma endregion
#pragma region Split
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
// call split view
auto view_result = SplitView(strl, _decilmer);
// copy string view result to string
std::vector<yycc_u8string> elems;
elems.reserve(view_result.size());
for (const auto& strl_view : view_result) {
elems.emplace_back(yycc_u8string(strl_view));
}
// return copied result
return elems;
}
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
// Reference:
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
// prepare return value
std::vector<yycc_u8string_view> elems;
// if string need to be splitted is empty, return original string (empty string).
// if decilmer is empty, return original string.
yycc_u8string decilmer(_decilmer);
if (strl.empty() || decilmer.empty()) {
elems.emplace_back(strl);
return elems;
}
// start spliting
std::size_t previous = 0, current;
while ((current = strl.find(decilmer.c_str(), previous)) != yycc_u8string::npos) {
elems.emplace_back(strl.substr(previous, current - previous));
previous = current + decilmer.size();
}
// try insert last part but prevent possible out of range exception
if (previous <= strl.size()) {
elems.emplace_back(strl.substr(previous));
}
return elems;
}
#pragma endregion
}

View File

@ -1,159 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <string>
#include <cstdarg>
#include <functional>
#include <vector>
/**
* @brief The helper containing string operations
* @details
* See also \ref string_helper.
*/
namespace YYCC::StringHelper {
/**
* @brief Perform a string formatting operation.
* @param[out] strl
* The string container receiving the result.
* There is no guarantee that the content is not modified when function failed.
* @param[in] format The format string.
* @param[in] ... Argument list of format string.
* @return True if success, otherwise false.
*/
bool Printf(yycc_u8string& strl, const yycc_char8_t* format, ...);
/**
* @brief Perform a string formatting operation.
* @param[out] strl
* The string container receiving the result.
* There is no guarantee that the content is not modified when function failed.
* @param[in] format The format string.
* @param[in] argptr Argument list of format string.
* @return True if success, otherwise false.
*/
bool VPrintf(yycc_u8string& strl, const yycc_char8_t* format, va_list argptr);
/**
* @brief Perform a string formatting operation.
* @param[in] format The format string.
* @param[in] ... Argument list of format string.
* @return The formatting result. Empty string if error happened.
*/
yycc_u8string Printf(const yycc_char8_t* format, ...);
/**
* @brief Perform a string formatting operation.
* @param[in] format The format string.
* @param[in] argptr Argument list of format string.
* @return The formatting result. Empty string if error happened.
*/
yycc_u8string VPrintf(const yycc_char8_t* format, va_list argptr);
/**
* @brief Modify given string with all occurrences of substring \e old replaced by \e new.
* @param[in,out] strl The string for replacing
* @param[in] _from_strl The \e old string.
* @param[in] _to_strl The \e new string.
*/
void Replace(yycc_u8string& strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl);
/**
* @brief Return a copy with all occurrences of substring \e old replaced by \e new.
* @param[in] _strl The string for replacing
* @param[in] _from_strl The \e old string.
* @param[in] _to_strl The \e new string.
* @return The result of replacement.
*/
yycc_u8string Replace(const yycc_u8string_view& _strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl);
/**
* @brief The data provider of general join function.
* @details
* For programmer using lambda to implement this function pointer:
* \li During calling, implementation should assign the reference of string view passed in argument
* to the string which need to be joined.
* \li Function return true to continue joining. otherwise return false to stop joining.
* The argument content assigned in the calling returning false is not included in join process.
*/
using JoinDataProvider = std::function<bool(yycc_u8string_view&)>;
/**
* @brief Universal join function.
* @details
* This function use function pointer as a general data provider interface,
* so this function suit for all types container.
* You can use this universal join function for any custom container by
* using C++ lambda syntax to create a code block adapted to this function pointer.
* @param[in] fct_data The function pointer in JoinDataProvider type prividing the data to be joined.
* @param[in] decilmer The decilmer used for joining.
* @return The result string of joining.
*/
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer);
/**
* @brief Specialized join function for standard library container.
* @tparam InputIt
* Must meet the requirements of LegacyInputIterator.
* It also can be dereferenced and then implicitly converted to yycc_u8string_view.
* @param[in] first The beginning of the range of elements to join.
* @param[in] last The terminal of the range of elements to join (exclusive).
* @param[in] decilmer The decilmer used for joining.
* @return The result string of joining.
*/
template<class InputIt>
yycc_u8string Join(InputIt first, InputIt last, const yycc_u8string_view& decilmer) {
return Join([&first, &last](yycc_u8string_view& view) -> bool {
// if we reach tail, return false to stop join process
if (first == last) return false;
// otherwise fetch data, inc iterator and return.
view = *first;
++first;
return true;
}, decilmer);
}
/**
* @brief Convert given string to lowercase.
* @param[in,out] strl The string to be lowercase.
*/
void Lower(yycc_u8string& strl);
/**
* @brief Return a copy of the string converted to lowercase.
* @param[in] strl The string to be lowercase.
* @return The copy of the string converted to lowercase.
*/
yycc_u8string Lower(const yycc_u8string_view& strl);
/**
* @brief Convert given string to uppercase.
* @param[in,out] strl The string to be uppercase.
*/
void Upper(yycc_u8string& strl);
/**
* @brief Return a copy of the string converted to uppercase.
* @param[in] strl The string to be uppercase.
* @return The copy of the string converted to uppercase.
*/
yycc_u8string Upper(const yycc_u8string_view& strl);
/**
* @brief Split given string with specified decilmer.
* @param[in] strl The string need to be splitting.
* @param[in] _decilmer The decilmer for splitting.
* @return
* The split result.
* \par
* If given string or decilmer are empty,
* the result container will only contain 1 entry which is equal to given string.
*/
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
/**
* @brief Split given string with specified decilmer as string view.
* @param[in] strl The string need to be splitting.
* @param[in] _decilmer The decilmer for splitting.
* @return
* The split result with string view format.
* This will not produce any copy of original string.
* \par
* If given string or decilmer are empty,
* the result container will only contain 1 entry which is equal to given string.
* @see Split(const yycc_u8string_view&, const yycc_char8_t*)
*/
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
}

View File

@ -1,19 +0,0 @@
// It is by design that no pragma once or #if to prevent deplicated including.
// Because this header is the part of wrapper, not a real header.
// #pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
// Define 2 macros to disallow Windows generate MIN and MAX macros
// which cause std::min and std::max can not function as normal.
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(NOMINMAX)
#define NOMINMAX
#endif
#endif

View File

@ -1,23 +0,0 @@
// It is by design that no pragma once or #if to prevent deplicated including.
// Because this header is the part of wrapper, not a real header.
// #pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
// Windows also will generate following macros
// which may cause the function sign is different in Windows and other platforms.
// So we simply remove them.
// Because #undef will not throw error if there are no matched macro,
// so we simply #undef them directly.
#undef GetObject
#undef GetClassName
#undef LoadImage
#undef GetTempPath
#undef GetModuleFileName
#undef CopyFile
#undef MoveFile
#undef DeleteFile
#endif

View File

@ -3,7 +3,7 @@
#include "EncodingHelper.hpp"
#include "ConsoleHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <shellapi.h>
@ -24,7 +24,7 @@ namespace YYCC::ArgParser {
return ArgumentList(std::move(args));
}
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
ArgumentList ArgumentList::CreateFromWin32() {
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw

View File

@ -37,7 +37,7 @@ namespace YYCC::ArgParser {
* and should not be seen as a part of arguments.
*/
static ArgumentList CreateFromStd(int argc, char* argv[]);
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
/**
* @brief Create argument list from Win32 function.
* @details

View File

@ -1,5 +1,5 @@
#include "COMHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
namespace YYCC::COMHelper {

View File

@ -1,6 +1,6 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include <memory>

View File

@ -5,7 +5,7 @@
#include <iostream>
// Include Windows used headers in Windows.
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <io.h>
@ -16,7 +16,7 @@
namespace YYCC::ConsoleHelper {
#pragma region Windows Specific Functions
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
static bool RawEnableColorfulConsole(FILE* fs) {
if (!_isatty(_fileno(fs))) return false;
@ -161,7 +161,7 @@ namespace YYCC::ConsoleHelper {
#pragma endregion
bool EnableColorfulConsole() {
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
bool ret = true;
ret &= RawEnableColorfulConsole(stdout);
@ -177,7 +177,7 @@ namespace YYCC::ConsoleHelper {
}
yycc_u8string ReadLine() {
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
// get stdin mode
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
@ -221,7 +221,7 @@ namespace YYCC::ConsoleHelper {
strl += YYCC_U8("\n");
}
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
// call Windows specific writer
WinConsoleWrite(strl, bIsErr);
#else

View File

@ -1,5 +1,5 @@
#include "DialogHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include "EncodingHelper.hpp"
#include "StringHelper.hpp"

View File

@ -1,6 +1,6 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include "COMHelper.hpp"
#include <string>

View File

@ -1,5 +1,5 @@
#include "ExceptionHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include "WinFctHelper.hpp"
#include "ConsoleHelper.hpp"

View File

@ -1,6 +1,6 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
/**
* @brief Windows specific unhandled exception processor.

View File

@ -7,7 +7,7 @@
#include <stdexcept>
#include <memory>
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include "WinImportSuffix.hpp"
@ -16,7 +16,7 @@
namespace YYCC::IOHelper {
std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode) {
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
// convert mode and file path to wchar
std::wstring wmode, wpath;

View File

@ -10,24 +10,7 @@
* See also \ref io_helper.
*/
namespace YYCC::IOHelper {
#if UINTPTR_MAX == UINT32_MAX
#define PRI_XPTR_LEFT_PADDING "08"
#elif UINTPTR_MAX == UINT64_MAX
/**
* @brief The left-padding zero format string of HEX-printed pointer type.
* @details
* When printing a pointer with HEX style, we always hope it can be left-padded with some zero for easy reading.
* In different architecture, the size of this padding is differnet too so we create this macro.
*
* In 32-bit environment, it will be "08" meaning left pad zero until 8 number position.
* In 64-bit environment, it will be "016" meaning left pad zero until 16 number position.
*/
#define PRI_XPTR_LEFT_PADDING "016"
#else
#error "Not supported pointer size."
#endif
/// @brief C++ standard deleter for std::FILE*
class StdFileDeleter {
public:

View File

@ -1,5 +1,5 @@
#include "WinFctHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include "EncodingHelper.hpp"
#include "COMHelper.hpp"

View File

@ -1,6 +1,6 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include <string>

View File

@ -50,7 +50,7 @@
// If we are in Windows,
// we need add 2 macros to disable Windows shitty warnings and errors of
// depracted functions and not secure functions.
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS

26
src/yycc.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
/**
* @file
* When you use YYCCommonplace, please make sure that you include this header first,
* before including any other headers of YYCC.
* This header contain essential check macros and version infos.
* They are crucial before using YYCC.
*/
// Library version
#include "yycc/version.hpp"
// Detect essential macros
// Operating System macros
#include "yycc/macro/os_detector.hpp"
// Compiler macros
#include "yycc/macro/compiler_detector.hpp"
// Endian macros
#include "yycc/macro/endian_detector.hpp"
// Pointer size macros
#include "yycc/macro/ptr_size_detector.hpp"
// STL macros
#include "yycc/macro/stl_detector.hpp"
namespace yycc {}

View File

0
src/yycc/clap/kernel.hpp Normal file
View File

78
src/yycc/constraint.hpp Normal file
View File

@ -0,0 +1,78 @@
#pragma once
#include "macro/class_copy_move.hpp"
#include <functional>
#include <stdexcept>
#include <algorithm>
/// @brief The namespace containing generic constraint concept used varied in other modules.
namespace yycc::constraint {
/// @brief Function prototype used in Constraint for checking whether given value is valid.
/// @details Analyze given value, and return true if value is legal, otherwise false.
template<typename T>
using FnCheck = std::function<bool(const T&)>;
/// @brief Function prototype used in Constraint for clamping given value into a valid value.
/// @details Analyze given value, return clamped value.
template<typename T>
using FnClamp = std::function<T(const T&)>;
/**
* @brief The constraint applied to settings to limit its stored value.
* @tparam T The data type this constraint need to be processed with.
* @details
* Constraint class contains various features:
* \li Check: Check whether given value is in range.
* \li Clamp: Clamp given value into valid value.
* Every instances of Constraint can have some, or none of these features.
* So it is essential to check whether instance has corresponding features before using it.
*/
template<typename T>
class Constraint {
public:
Constraint(FnCheck<T>&& fn_check, FnClamp<T>&& fn_clamp) :
fn_check(std::move(fn_check)), fn_clamp(std::move(fn_clamp)) {}
YYCC_DELETE_COPY(Constraint)
YYCC_DEFAULT_MOVE(Constraint)
/**
* @brief Perform Check feature.
* @param[in] value The valid for checking.
* @return True if valid is okey, otherwise false.
* @exception std::logic_error Raised if this feature is not supported.
*/
bool check(const T& value) const {
if (!support_check()) {
throw std::logic_error("this Constraint do not support check operation");
} else {
return fn_check(value);
}
}
/**
* @brief Perform Clamp feature.
* @param[in] value The valid for clamping.
* @return The result after clamping.
* @exception std::logic_error Raised if this feature is not supported.
*/
T clamp(const T& value) const {
if (!support_clamp()) {
throw std::logic_error("this Constraint do not support clamp operation");
} else {
return fn_clamp(value);
}
}
/// @brief Check whether this Constraint support Check feature.
/// @return True if it support, otherwise false.
bool support_check() const noexcept { return this->fn_check != nullptr; }
/// @brief Check whether this Constraint support Clamp feature.
/// @return True if it support, otherwise false.
bool support_clamp() const noexcept { return this->fn_clamp != nullptr; }
private:
/// @brief Pointer to Check feature function.
FnCheck<T> fn_check;
/// @brief Pointer to Clamp feature function.
FnClamp<T> fn_clamp;
};
} // namespace yycc::core::constraint

View File

@ -0,0 +1,75 @@
#pragma once
#include "../constraint.hpp"
#include <set>
/// @brief The namespace containing convenient function building common used Constraint instance.
namespace yycc::constraint::builder {
/**
* @brief Build Constraint for arithmetic values by minimum and maximum value range.
* @tparam T An arithmetic or enum type (except bool) of underlying stored value.
* @param[in] min_value The minimum value of range (inclusive).
* @param[in] max_value The maximum value of range (inclusive).
* @return The generated constraint instance which can be directly applied.
*/
template<typename T, std::enable_if_t<std::is_arithmetic_v<T> && !std::is_same_v<T, bool>, int> = 0>
Constraint<T> min_max_constraint(T min_value, T max_value) {
if (min_value > max_value) throw std::invalid_argument("the max value must be equal or greater than min value");
auto fn_check = [min_value, max_value](const T& val) -> bool { return (val <= max_value) && (val >= min_value); };
auto fn_clamp = [min_value, max_value](const T& val) -> T { return std::clamp(val, min_value, max_value); };
return Constraint<T>(std::move(fn_check), std::move(fn_clamp));
}
/**
* @brief Get constraint for enum values by enumerating all possible values.
* @tparam T An enum type (except bool) of underlying stored value.
* @param[in] il An initializer list storing all possible values.
* @param[in] default_index The index of default value in given list.
* @return The generated constraint instance which can be directly applied.
*/
template<typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
Constraint<T> enum_constraint(const std::initializer_list<T>& il, size_t default_index = 0u) {
if (default_index >= il.size()) throw std::invalid_argument("the default index must be a valid index in given list");
T default_entry = il.begin()[default_index];
std::set<T> entries(il);
// TODO: modify it as `contain` once we finish patch namespace.
auto fn_check = [entries](const T& val) -> bool { return entries.find(val) != entries.end(); };
auto fn_clamp = [entries, default_entry](const T& val) -> T {
if (entries.find(val) != entries.end()) {
return val;
} else {
return default_entry;
}
};
return Constraint<T>(std::move(fn_check), fn_clamp);
}
/**
* @brief Get constraint for string values by enumerating all possible values.
* @param[in] il An initializer list storing all possible values.
* @param[in] default_index The index of default value in given list.
* @return The generated constraint instance which can be directly applied.
*/
inline Constraint<std::u8string> strenum_constraint(const std::initializer_list<std::u8string_view>& il, size_t default_index = 0u) {
if (default_index >= il.size()) throw std::invalid_argument("the default index must be a valid index in given list");
std::u8string default_entry = std::u8string(il.begin()[default_index]);
std::set<std::u8string> entries;
for (const auto& i : il) {
entries.emplace(i);
}
auto fn_check = [entries](const std::u8string& val) -> bool { return entries.contains(val); };
auto fn_clamp = [entries, default_entry](const std::u8string& val) -> std::u8string {
if (entries.contains(val)) return val;
else return default_entry;
};
return Constraint<std::u8string>(std::move(fn_check), fn_clamp);
}
} // namespace yycc::constraint::builder
#undef NS_YYCC_STRING

324
src/yycc/encoding/iconv.cpp Normal file
View File

@ -0,0 +1,324 @@
#include "iconv.hpp"
#if defined(YYCC_FEAT_ICONV)
#include "../macro/endian_detector.hpp"
#include <cerrno>
#include <stdexcept>
#include <cstdint>
#include <cstdlib>
#include <vector>
#pragma region Iconv Shit Fix
// YYC MARK:
// I don't know what Iconv is for, Iconv put an huge pieces of shit into its header file "iconv.h" (at least for me).
// Especially a macro called iconv, which pollutes my namespace name while also can not be disabled because I need to rely on it to access essential functions.
// I can't simply redefine it, because I can't make sure that this "iconv" is defined in that way on all platforms.
// So I can only write some definitions of functions and types here, and extract the functions and types I need before I declare the namespace.
// And at the same time remove those annoying macro definitions. Hopefully, the compiler will optimize these wrapper functions.
#include <iconv.h>
typedef iconv_t that_iconv_t;
static iconv_t that_iconv_open(const char* tocode, const char* fromcode) {
return iconv_open(tocode, fromcode);
}
static int that_iconv_close(iconv_t cd) {
return iconv_close(cd);
}
static size_t that_iconv(iconv_t cd, const char** inbuf, size_t* inbytesleft, char** outbuf, size_t* outbytesleft) {
// YYC MARK:
// This is also bullshit. I don't know why the real signature of this function differ with its document written by GNU.
// I have to make a "const" cast in there.
return iconv(cd, const_cast<char**>(inbuf), inbytesleft, outbuf, outbytesleft);
}
#undef iconv_t
#undef iconv_open
#undef iconv_close
#undef iconv
#pragma endregion
namespace yycc::encoding::iconv {
static const that_iconv_t INVALID_ICONV_TOKEN = reinterpret_cast<that_iconv_t>(-1);
#pragma region PrivToken
class PrivToken {
public:
PrivToken(const CodeName& from_code, const CodeName& to_code) : inner(INVALID_ICONV_TOKEN) {
// We must cast them into string container, not string view,
// because they may not have NULL terminator.
std::string iconv_from_code(from_code);
std::string iconv_to_code(to_code);
// Call iconv_t creator
that_iconv_t descriptor = that_iconv_open(iconv_to_code.c_str(), iconv_from_code.c_str());
if (descriptor == INVALID_ICONV_TOKEN) {
if (errno == EINVAL) {
return;
} else {
throw std::runtime_error("impossible errno when calling iconv_open()");
}
}
// Setup value
this->inner = descriptor;
}
~PrivToken() {
if (this->inner != INVALID_ICONV_TOKEN) {
that_iconv_close(this->inner);
}
}
PrivToken(PrivToken&& rhs) : inner(rhs.inner) {
// Reset rhs inner
rhs.inner = INVALID_ICONV_TOKEN;
}
PrivToken& operator=(PrivToken&& rhs) {
// Free self first
if (this->inner != INVALID_ICONV_TOKEN) {
that_iconv_close(this->inner);
}
// Copy rhs inner and reset it.
this->inner = rhs.inner;
rhs.inner = INVALID_ICONV_TOKEN;
// Return self
return *this;
}
YYCC_DELETE_COPY(PrivToken)
bool is_valid() const { return this->inner != INVALID_ICONV_TOKEN; }
that_iconv_t get_inner() const { return this->inner; }
private:
that_iconv_t inner;
};
#pragma endregion
#pragma region Token
Token::Token(const CodeName& from_code, const CodeName& to_code) : inner(std::make_unique<PrivToken>(from_code, to_code)) {}
Token::~Token() {}
bool Token::is_valid() const {
return this->inner->is_valid();
}
PrivToken* Token::get_inner() const {
return this->inner.get();
}
#pragma endregion
#pragma region Kernel
constexpr const size_t ICONV_INC_LEN = 16u;
constexpr size_t ICONV_ERR_RV = static_cast<size_t>(-1);
// Reference: https://stackoverflow.com/questions/13297458/simple-utf8-utf16-string-conversion-with-iconv
static ConvResult<std::vector<uint8_t>> iconv_kernel(const Token& token, const uint8_t* str_from_buf, size_t str_from_len) {
// ===== Check Requirements =====
// Prepare return value
std::vector<uint8_t> str_to;
// Unwrap and check iconv_t
that_iconv_t cd = token.get_inner()->get_inner();
if (cd == INVALID_ICONV_TOKEN) return std::unexpected(ConvError::InvalidCd);
// Check empty input
if (str_from_len == 0u) return str_to;
// Check nullptr input variables
if (str_from_buf == nullptr) return std::unexpected(ConvError::NullPointer);
// ===== Do Iconv =====
// setup input variables
size_t inbytesleft = str_from_len;
const char* inbuf = reinterpret_cast<const char*>(str_from_buf);
// pre-allocation output variables
str_to.resize(str_from_len + ICONV_INC_LEN);
size_t outbytesleft = str_to.size();
char* outbuf = reinterpret_cast<char*>(str_to.data());
// conv core
size_t nchars = that_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
while (nchars == ICONV_ERR_RV && errno == E2BIG) {
// record the length has been converted
size_t len = outbuf - reinterpret_cast<char*>(str_to.data());
// resize for container and its variables
str_to.resize(str_to.size() + ICONV_INC_LEN);
outbytesleft = str_to.size();
// assign new outbuf from failed position
outbuf = reinterpret_cast<char*>(str_to.data()) + len;
nchars = that_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
}
// restore descriptor initial state
that_iconv(cd, nullptr, nullptr, nullptr, nullptr);
// check error
if (nchars == ICONV_ERR_RV) {
if (errno == EILSEQ) {
return std::unexpected(ConvError::InvalidMbSeq);
} else if (errno == EINVAL) {
return std::unexpected(ConvError::IncompleteMbSeq);
} else {
throw std::runtime_error("impossible errno when calling iconv_open()");
}
} else {
// success
// compute result data
str_to.resize(str_to.size() - outbytesleft);
return str_to;
}
}
#pragma endregion
#pragma region Convertion Class Helper
// YYC MARK:
// If we use UTF16 or UTF32 code name directly, it will produce a BOM at data head.
// That's not what we expected.
// So we need manually check runtime endian and explicitly specify endian in code name.
using namespace std::literals::string_view_literals;
constexpr auto UTF8_CODENAME_LITERAL = "UTF-8"sv;
constexpr auto WCHAR_CODENAME_LITERAL = "WCHAR_T"sv;
constexpr auto UTF16_CODENAME_LITERAL =
#if defined(YYCC_ENDIAN_LITTLE)
"UTF16LE"sv;
#else
"UTF16BE"sv;
#endif
constexpr auto UTF32_CODENAME_LITERAL =
#if defined(YYCC_ENDIAN_LITTLE)
"UTF32LE"sv;
#else
"UTF32BE"sv;
#endif
// TODO:
// There is a memory copy in this function. Consider optimizing it in future.
// A possible solution is that create a std::vector-like wrapper for std::basic_string and std::basic_string_view.
// We call them VecString and VecStringView, and use them in "iconv_kernel" instead of real std::vector.
// They exposed interface are std::vector-like but its inner is std::basic_string and std::basic_string_view.
#define USER_CONVFN(src_char_type, dst_char_type) \
auto rv = iconv_kernel(this->token, reinterpret_cast<const uint8_t*>(src.data()), src.size()); \
if (rv.has_value()) { \
const auto& dst = rv.value(); \
if constexpr (sizeof(dst_char_type) > 1u) { \
if (dst.size() % sizeof(dst_char_type) != 0u) return std::unexpected(ConvError::BadRv); \
} \
return std::basic_string<dst_char_type>(reinterpret_cast<const dst_char_type*>(dst.data()), dst.size() / sizeof(dst_char_type)); \
} else { \
return std::unexpected(rv.error()); \
}
#pragma endregion
#pragma region Char -> UTF8
CharToUtf8::CharToUtf8(const CodeName& code_name) : token(code_name, UTF8_CODENAME_LITERAL) {}
CharToUtf8::~CharToUtf8() {}
ConvResult<std::u8string> CharToUtf8::to_utf8(const std::string_view& src) {
USER_CONVFN(char, char8_t);
}
#pragma endregion
#pragma region UTF8 -> Char
Utf8ToChar::Utf8ToChar(const CodeName& code_name) : token(UTF8_CODENAME_LITERAL, code_name) {}
Utf8ToChar::~Utf8ToChar() {}
ConvResult<std::string> Utf8ToChar::to_char(const std::u8string_view& src) {
USER_CONVFN(char8_t, char);
}
#pragma endregion
#pragma region WChar -> Char
WcharToUtf8::WcharToUtf8() : token(WCHAR_CODENAME_LITERAL, UTF8_CODENAME_LITERAL) {}
WcharToUtf8::~WcharToUtf8() {}
ConvResult<std::u8string> WcharToUtf8::to_utf8(const std::wstring_view& src) {
USER_CONVFN(wchar_t, char8_t);
}
#pragma endregion
#pragma region Char -> WChar
Utf8ToWchar::Utf8ToWchar() : token(UTF8_CODENAME_LITERAL, WCHAR_CODENAME_LITERAL) {}
Utf8ToWchar::~Utf8ToWchar() {}
ConvResult<std::wstring> Utf8ToWchar::to_wchar(const std::u8string_view& src) {
USER_CONVFN(char8_t, wchar_t);
}
#pragma endregion
#pragma region UTF8 -> UTF16
Utf8ToUtf16::Utf8ToUtf16() : token(UTF8_CODENAME_LITERAL, UTF16_CODENAME_LITERAL) {}
Utf8ToUtf16::~Utf8ToUtf16() {}
ConvResult<std::u16string> Utf8ToUtf16::to_utf16(const std::u8string_view& src) {
USER_CONVFN(char8_t, char16_t);
}
#pragma endregion
#pragma region UTF16 -> UTF8
Utf16ToUtf8::Utf16ToUtf8() : token(UTF16_CODENAME_LITERAL, UTF8_CODENAME_LITERAL) {}
Utf16ToUtf8::~Utf16ToUtf8() {}
ConvResult<std::u8string> Utf16ToUtf8::to_utf8(const std::u16string_view& src) {
USER_CONVFN(char16_t, char8_t);
}
#pragma endregion
#pragma region UTF8 -> UTF32
Utf8ToUtf32::Utf8ToUtf32() : token(UTF8_CODENAME_LITERAL, UTF32_CODENAME_LITERAL) {}
Utf8ToUtf32::~Utf8ToUtf32() {}
ConvResult<std::u32string> Utf8ToUtf32::to_utf32(const std::u8string_view& src) {
USER_CONVFN(char8_t, char32_t);
}
#pragma endregion
#pragma region UTF32 -> UTF8
Utf32ToUtf8::Utf32ToUtf8() : token(UTF32_CODENAME_LITERAL, UTF8_CODENAME_LITERAL) {}
Utf32ToUtf8::~Utf32ToUtf8() {}
ConvResult<std::u8string> Utf32ToUtf8::to_utf8(const std::u32string_view& src) {
USER_CONVFN(char32_t, char8_t);
}
#pragma endregion
} // namespace yycc::encoding::iconv
#endif

175
src/yycc/encoding/iconv.hpp Normal file
View File

@ -0,0 +1,175 @@
#pragma once
#include "../macro/os_detector.hpp"
#include "../macro/class_copy_move.hpp"
#include <string>
#include <string_view>
#include <expected>
#include <memory>
namespace yycc::encoding::iconv {
// YYC MARK:
// I don't want to include "iconv.h" in there.
// One of reasons is that I want to hide all implementation of Iconv.
// Another reason is that "iconv.h" defines some annoying macros which intervene some names in this files.
// So I introduce PIMPL design mode. Use a pointer to hide all details in class PrivToken.
/// @brief The code name type used by Iconv.
using CodeName = std::string_view;
/// @private
class PrivToken;
/// @private
class Token {
public:
Token(const CodeName& from_code, const CodeName& to_code);
~Token();
YYCC_DELETE_COPY(Token)
YYCC_DEFAULT_MOVE(Token)
bool is_valid() const;
PrivToken* get_inner() const;
private:
std::unique_ptr<PrivToken> inner;
};
/// @brief The possible error occurs in this module.
enum class ConvError {
InvalidCd, ///< Given token is invalid.
NullPointer, ///< Some of essential pointer in argument is nullptr.
InvalidMbSeq, ///< An invalid multibyte sequence has been encountered in the input.
IncompleteMbSeq, ///< An incomplete multibyte sequence has been encountered in the input.
BadRv, ///< The size of encoding convertion is not matched with expected char type.
};
/// @brief The result type in this module.
template<typename T>
using ConvResult = std::expected<T, ConvError>;
#if defined(YYCC_FEAT_ICONV)
/// @brief Char -> UTF8
class CharToUtf8 {
public:
CharToUtf8(const CodeName& code_name);
~CharToUtf8();
YYCC_DELETE_COPY(CharToUtf8)
YYCC_DEFAULT_MOVE(CharToUtf8)
public:
ConvResult<std::u8string> to_utf8(const std::string_view& src);
private:
Token token;
};
/// @brief UTF8 -> Char
class Utf8ToChar {
public:
Utf8ToChar(const CodeName& code_name);
~Utf8ToChar();
YYCC_DELETE_COPY(Utf8ToChar)
YYCC_DEFAULT_MOVE(Utf8ToChar)
public:
ConvResult<std::string> to_char(const std::u8string_view& src);
private:
Token token;
};
/// @brief WChar -> UTF8
class WcharToUtf8 {
public:
WcharToUtf8();
~WcharToUtf8();
YYCC_DELETE_COPY(WcharToUtf8)
YYCC_DEFAULT_MOVE(WcharToUtf8)
public:
ConvResult<std::u8string> to_utf8(const std::wstring_view& src);
private:
Token token;
};
/// @brief UTF8 -> WChar
class Utf8ToWchar {
public:
Utf8ToWchar();
~Utf8ToWchar();
YYCC_DELETE_COPY(Utf8ToWchar)
YYCC_DEFAULT_MOVE(Utf8ToWchar)
public:
ConvResult<std::wstring> to_wchar(const std::u8string_view& src);
private:
Token token;
};
/// @brief UTF8 -> UTF16
class Utf8ToUtf16 {
public:
Utf8ToUtf16();
~Utf8ToUtf16();
YYCC_DELETE_COPY(Utf8ToUtf16)
YYCC_DEFAULT_MOVE(Utf8ToUtf16)
public:
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
private:
Token token;
};
/// @brief UTF16 -> UTF8
class Utf16ToUtf8 {
public:
Utf16ToUtf8();
~Utf16ToUtf8();
YYCC_DELETE_COPY(Utf16ToUtf8)
YYCC_DEFAULT_MOVE(Utf16ToUtf8)
public:
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
private:
Token token;
};
/// @brief UTF8 -> UTF32
class Utf8ToUtf32 {
public:
Utf8ToUtf32();
~Utf8ToUtf32();
YYCC_DELETE_COPY(Utf8ToUtf32)
YYCC_DEFAULT_MOVE(Utf8ToUtf32)
public:
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
private:
Token token;
};
/// @brief UTF32 -> UTF8
class Utf32ToUtf8 {
public:
Utf32ToUtf8();
~Utf32ToUtf8();
YYCC_DELETE_COPY(Utf32ToUtf8)
YYCC_DEFAULT_MOVE(Utf32ToUtf8)
public:
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
private:
Token token;
};
#endif
} // namespace yycc::encoding::iconv

View File

@ -0,0 +1,472 @@
#include "pycodec.hpp"
#include "../string/op.hpp"
#include <map>
using namespace std::literals::string_view_literals;
namespace op = ::yycc::string::op;
namespace yycc::encoding::pycodec {
#pragma region Encoding Name
static const std::map<std::u8string_view, std::u8string_view> ALIAS_MAP{
{u8"646"sv, u8"ascii"sv},
{u8"us-ascii"sv, u8"ascii"sv},
{u8"big5-tw"sv, u8"big5"sv},
{u8"csbig5"sv, u8"big5"sv},
{u8"big5-hkscs"sv, u8"big5hkscs"sv},
{u8"hkscs"sv, u8"big5hkscs"sv},
{u8"ibm037"sv, u8"cp037"sv},
{u8"ibm039"sv, u8"cp037"sv},
{u8"273"sv, u8"cp273"sv},
{u8"ibm273"sv, u8"cp273"sv},
{u8"csibm273"sv, u8"cp273"sv},
{u8"ebcdic-cp-he"sv, u8"cp424"sv},
{u8"ibm424"sv, u8"cp424"sv},
{u8"437"sv, u8"cp437"sv},
{u8"ibm437"sv, u8"cp437"sv},
{u8"ebcdic-cp-be"sv, u8"cp500"sv},
{u8"ebcdic-cp-ch"sv, u8"cp500"sv},
{u8"ibm500"sv, u8"cp500"sv},
{u8"ibm775"sv, u8"cp775"sv},
{u8"850"sv, u8"cp850"sv},
{u8"ibm850"sv, u8"cp850"sv},
{u8"852"sv, u8"cp852"sv},
{u8"ibm852"sv, u8"cp852"sv},
{u8"855"sv, u8"cp855"sv},
{u8"ibm855"sv, u8"cp855"sv},
{u8"857"sv, u8"cp857"sv},
{u8"ibm857"sv, u8"cp857"sv},
{u8"858"sv, u8"cp858"sv},
{u8"ibm858"sv, u8"cp858"sv},
{u8"860"sv, u8"cp860"sv},
{u8"ibm860"sv, u8"cp860"sv},
{u8"861"sv, u8"cp861"sv},
{u8"cp-is"sv, u8"cp861"sv},
{u8"ibm861"sv, u8"cp861"sv},
{u8"862"sv, u8"cp862"sv},
{u8"ibm862"sv, u8"cp862"sv},
{u8"863"sv, u8"cp863"sv},
{u8"ibm863"sv, u8"cp863"sv},
{u8"ibm864"sv, u8"cp864"sv},
{u8"865"sv, u8"cp865"sv},
{u8"ibm865"sv, u8"cp865"sv},
{u8"866"sv, u8"cp866"sv},
{u8"ibm866"sv, u8"cp866"sv},
{u8"869"sv, u8"cp869"sv},
{u8"cp-gr"sv, u8"cp869"sv},
{u8"ibm869"sv, u8"cp869"sv},
{u8"932"sv, u8"cp932"sv},
{u8"ms932"sv, u8"cp932"sv},
{u8"mskanji"sv, u8"cp932"sv},
{u8"ms-kanji"sv, u8"cp932"sv},
{u8"windows-31j"sv, u8"cp932"sv},
{u8"949"sv, u8"cp949"sv},
{u8"ms949"sv, u8"cp949"sv},
{u8"uhc"sv, u8"cp949"sv},
{u8"950"sv, u8"cp950"sv},
{u8"ms950"sv, u8"cp950"sv},
{u8"ibm1026"sv, u8"cp1026"sv},
{u8"1125"sv, u8"cp1125"sv},
{u8"ibm1125"sv, u8"cp1125"sv},
{u8"cp866u"sv, u8"cp1125"sv},
{u8"ruscii"sv, u8"cp1125"sv},
{u8"ibm1140"sv, u8"cp1140"sv},
{u8"windows-1250"sv, u8"cp1250"sv},
{u8"windows-1251"sv, u8"cp1251"sv},
{u8"windows-1252"sv, u8"cp1252"sv},
{u8"windows-1253"sv, u8"cp1253"sv},
{u8"windows-1254"sv, u8"cp1254"sv},
{u8"windows-1255"sv, u8"cp1255"sv},
{u8"windows-1256"sv, u8"cp1256"sv},
{u8"windows-1257"sv, u8"cp1257"sv},
{u8"windows-1258"sv, u8"cp1258"sv},
{u8"eucjp"sv, u8"euc_jp"sv},
{u8"ujis"sv, u8"euc_jp"sv},
{u8"u-jis"sv, u8"euc_jp"sv},
{u8"jisx0213"sv, u8"euc_jis_2004"sv},
{u8"eucjis2004"sv, u8"euc_jis_2004"sv},
{u8"eucjisx0213"sv, u8"euc_jisx0213"sv},
{u8"euckr"sv, u8"euc_kr"sv},
{u8"korean"sv, u8"euc_kr"sv},
{u8"ksc5601"sv, u8"euc_kr"sv},
{u8"ks_c-5601"sv, u8"euc_kr"sv},
{u8"ks_c-5601-1987"sv, u8"euc_kr"sv},
{u8"ksx1001"sv, u8"euc_kr"sv},
{u8"ks_x-1001"sv, u8"euc_kr"sv},
{u8"chinese"sv, u8"gb2312"sv},
{u8"csiso58gb231280"sv, u8"gb2312"sv},
{u8"euc-cn"sv, u8"gb2312"sv},
{u8"euccn"sv, u8"gb2312"sv},
{u8"eucgb2312-cn"sv, u8"gb2312"sv},
{u8"gb2312-1980"sv, u8"gb2312"sv},
{u8"gb2312-80"sv, u8"gb2312"sv},
{u8"iso-ir-58"sv, u8"gb2312"sv},
{u8"936"sv, u8"gbk"sv},
{u8"cp936"sv, u8"gbk"sv},
{u8"ms936"sv, u8"gbk"sv},
{u8"gb18030-2000"sv, u8"gb18030"sv},
{u8"hzgb"sv, u8"hz"sv},
{u8"hz-gb"sv, u8"hz"sv},
{u8"hz-gb-2312"sv, u8"hz"sv},
{u8"csiso2022jp"sv, u8"iso2022_jp"sv},
{u8"iso2022jp"sv, u8"iso2022_jp"sv},
{u8"iso-2022-jp"sv, u8"iso2022_jp"sv},
{u8"iso2022jp-1"sv, u8"iso2022_jp_1"sv},
{u8"iso-2022-jp-1"sv, u8"iso2022_jp_1"sv},
{u8"iso2022jp-2"sv, u8"iso2022_jp_2"sv},
{u8"iso-2022-jp-2"sv, u8"iso2022_jp_2"sv},
{u8"iso2022jp-2004"sv, u8"iso2022_jp_2004"sv},
{u8"iso-2022-jp-2004"sv, u8"iso2022_jp_2004"sv},
{u8"iso2022jp-3"sv, u8"iso2022_jp_3"sv},
{u8"iso-2022-jp-3"sv, u8"iso2022_jp_3"sv},
{u8"iso2022jp-ext"sv, u8"iso2022_jp_ext"sv},
{u8"iso-2022-jp-ext"sv, u8"iso2022_jp_ext"sv},
{u8"csiso2022kr"sv, u8"iso2022_kr"sv},
{u8"iso2022kr"sv, u8"iso2022_kr"sv},
{u8"iso-2022-kr"sv, u8"iso2022_kr"sv},
{u8"iso-8859-1"sv, u8"latin_1"sv},
{u8"iso8859-1"sv, u8"latin_1"sv},
{u8"8859"sv, u8"latin_1"sv},
{u8"cp819"sv, u8"latin_1"sv},
{u8"latin"sv, u8"latin_1"sv},
{u8"latin1"sv, u8"latin_1"sv},
{u8"l1"sv, u8"latin_1"sv},
{u8"iso-8859-2"sv, u8"iso8859_2"sv},
{u8"latin2"sv, u8"iso8859_2"sv},
{u8"l2"sv, u8"iso8859_2"sv},
{u8"iso-8859-3"sv, u8"iso8859_3"sv},
{u8"latin3"sv, u8"iso8859_3"sv},
{u8"l3"sv, u8"iso8859_3"sv},
{u8"iso-8859-4"sv, u8"iso8859_4"sv},
{u8"latin4"sv, u8"iso8859_4"sv},
{u8"l4"sv, u8"iso8859_4"sv},
{u8"iso-8859-5"sv, u8"iso8859_5"sv},
{u8"cyrillic"sv, u8"iso8859_5"sv},
{u8"iso-8859-6"sv, u8"iso8859_6"sv},
{u8"arabic"sv, u8"iso8859_6"sv},
{u8"iso-8859-7"sv, u8"iso8859_7"sv},
{u8"greek"sv, u8"iso8859_7"sv},
{u8"greek8"sv, u8"iso8859_7"sv},
{u8"iso-8859-8"sv, u8"iso8859_8"sv},
{u8"hebrew"sv, u8"iso8859_8"sv},
{u8"iso-8859-9"sv, u8"iso8859_9"sv},
{u8"latin5"sv, u8"iso8859_9"sv},
{u8"l5"sv, u8"iso8859_9"sv},
{u8"iso-8859-10"sv, u8"iso8859_10"sv},
{u8"latin6"sv, u8"iso8859_10"sv},
{u8"l6"sv, u8"iso8859_10"sv},
{u8"iso-8859-11"sv, u8"iso8859_11"sv},
{u8"thai"sv, u8"iso8859_11"sv},
{u8"iso-8859-13"sv, u8"iso8859_13"sv},
{u8"latin7"sv, u8"iso8859_13"sv},
{u8"l7"sv, u8"iso8859_13"sv},
{u8"iso-8859-14"sv, u8"iso8859_14"sv},
{u8"latin8"sv, u8"iso8859_14"sv},
{u8"l8"sv, u8"iso8859_14"sv},
{u8"iso-8859-15"sv, u8"iso8859_15"sv},
{u8"latin9"sv, u8"iso8859_15"sv},
{u8"l9"sv, u8"iso8859_15"sv},
{u8"iso-8859-16"sv, u8"iso8859_16"sv},
{u8"latin10"sv, u8"iso8859_16"sv},
{u8"l10"sv, u8"iso8859_16"sv},
{u8"cp1361"sv, u8"johab"sv},
{u8"ms1361"sv, u8"johab"sv},
{u8"kz_1048"sv, u8"kz1048"sv},
{u8"strk1048_2002"sv, u8"kz1048"sv},
{u8"rk1048"sv, u8"kz1048"sv},
{u8"maccyrillic"sv, u8"mac_cyrillic"sv},
{u8"macgreek"sv, u8"mac_greek"sv},
{u8"maciceland"sv, u8"mac_iceland"sv},
{u8"maclatin2"sv, u8"mac_latin2"sv},
{u8"maccentraleurope"sv, u8"mac_latin2"sv},
{u8"mac_centeuro"sv, u8"mac_latin2"sv},
{u8"macroman"sv, u8"mac_roman"sv},
{u8"macintosh"sv, u8"mac_roman"sv},
{u8"macturkish"sv, u8"mac_turkish"sv},
{u8"csptcp154"sv, u8"ptcp154"sv},
{u8"pt154"sv, u8"ptcp154"sv},
{u8"cp154"sv, u8"ptcp154"sv},
{u8"cyrillic-asian"sv, u8"ptcp154"sv},
{u8"csshiftjis"sv, u8"shift_jis"sv},
{u8"shiftjis"sv, u8"shift_jis"sv},
{u8"sjis"sv, u8"shift_jis"sv},
{u8"s_jis"sv, u8"shift_jis"sv},
{u8"shiftjis2004"sv, u8"shift_jis_2004"sv},
{u8"sjis_2004"sv, u8"shift_jis_2004"sv},
{u8"sjis2004"sv, u8"shift_jis_2004"sv},
{u8"shiftjisx0213"sv, u8"shift_jisx0213"sv},
{u8"sjisx0213"sv, u8"shift_jisx0213"sv},
{u8"s_jisx0213"sv, u8"shift_jisx0213"sv},
{u8"u32"sv, u8"utf_32"sv},
{u8"utf32"sv, u8"utf_32"sv},
{u8"utf-32be"sv, u8"utf_32_be"sv},
{u8"utf-32le"sv, u8"utf_32_le"sv},
{u8"u16"sv, u8"utf_16"sv},
{u8"utf16"sv, u8"utf_16"sv},
{u8"utf-16be"sv, u8"utf_16_be"sv},
{u8"utf-16le"sv, u8"utf_16_le"sv},
{u8"u7"sv, u8"utf_7"sv},
{u8"unicode-1-1-utf-7"sv, u8"utf_7"sv},
{u8"u8"sv, u8"utf_8"sv},
{u8"utf"sv, u8"utf_8"sv},
{u8"utf8"sv, u8"utf_8"sv},
{u8"utf-8"sv, u8"utf_8"sv},
{u8"cp65001"sv, u8"utf_8"sv},
};
/**
* @brief Resolve encoding name alias and fetch real encoding name.
* @param[in] lang The encoding name for finding.
* @return
* The given encoding name if given name not present in alias map.
* Otherwise the found encoding name by given name.
*/
static std::u8string resolve_encoding_alias(const std::u8string_view& enc_name) {
auto name = op::to_lower(enc_name);
auto finder = ALIAS_MAP.find(name);
if (finder == ALIAS_MAP.end()) {
// not found, use original encoding name.
return std::u8string(enc_name);
} else {
// found, use found encoding name.
return std::u8string(finder->second);
}
}
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
using CodePage = YYCC_PYCODEC_BACKEND_NS::CodePage;
static const std::map<std::u8string_view, CodePage> WINCP_MAP{
{u8"ascii"sv, static_cast<CodePage>(437u)}, {u8"big5"sv, static_cast<CodePage>(950u)},
{u8"cp037"sv, static_cast<CodePage>(037u)}, {u8"cp437"sv, static_cast<CodePage>(437u)},
{u8"cp500"sv, static_cast<CodePage>(500u)}, {u8"cp720"sv, static_cast<CodePage>(720u)},
{u8"cp737"sv, static_cast<CodePage>(737u)}, {u8"cp775"sv, static_cast<CodePage>(775u)},
{u8"cp850"sv, static_cast<CodePage>(850u)}, {u8"cp852"sv, static_cast<CodePage>(852u)},
{u8"cp855"sv, static_cast<CodePage>(855u)}, {u8"cp857"sv, static_cast<CodePage>(857u)},
{u8"cp858"sv, static_cast<CodePage>(858u)}, {u8"cp860"sv, static_cast<CodePage>(860u)},
{u8"cp861"sv, static_cast<CodePage>(861u)}, {u8"cp862"sv, static_cast<CodePage>(862u)},
{u8"cp863"sv, static_cast<CodePage>(863u)}, {u8"cp864"sv, static_cast<CodePage>(864u)},
{u8"cp865"sv, static_cast<CodePage>(865u)}, {u8"cp866"sv, static_cast<CodePage>(866u)},
{u8"cp869"sv, static_cast<CodePage>(869u)}, {u8"cp874"sv, static_cast<CodePage>(874u)},
{u8"cp875"sv, static_cast<CodePage>(875u)}, {u8"cp932"sv, static_cast<CodePage>(932u)},
{u8"cp949"sv, static_cast<CodePage>(949u)}, {u8"cp950"sv, static_cast<CodePage>(950u)},
{u8"cp1026"sv, static_cast<CodePage>(1026u)}, {u8"cp1140"sv, static_cast<CodePage>(1140u)},
{u8"cp1250"sv, static_cast<CodePage>(1250u)}, {u8"cp1251"sv, static_cast<CodePage>(1251u)},
{u8"cp1252"sv, static_cast<CodePage>(1252u)}, {u8"cp1253"sv, static_cast<CodePage>(1253u)},
{u8"cp1254"sv, static_cast<CodePage>(1254u)}, {u8"cp1255"sv, static_cast<CodePage>(1255u)},
{u8"cp1256"sv, static_cast<CodePage>(1256u)}, {u8"cp1257"sv, static_cast<CodePage>(1257u)},
{u8"cp1258"sv, static_cast<CodePage>(1258u)}, {u8"euc_jp"sv, static_cast<CodePage>(20932u)},
{u8"euc_kr"sv, static_cast<CodePage>(51949u)}, {u8"gb2312"sv, static_cast<CodePage>(936u)},
{u8"gbk"sv, static_cast<CodePage>(936u)}, {u8"gb18030"sv, static_cast<CodePage>(54936u)},
{u8"hz"sv, static_cast<CodePage>(52936u)}, {u8"iso2022_jp"sv, static_cast<CodePage>(50220u)},
{u8"iso2022_kr"sv, static_cast<CodePage>(50225u)}, {u8"latin_1"sv, static_cast<CodePage>(28591u)},
{u8"iso8859_2"sv, static_cast<CodePage>(28592u)}, {u8"iso8859_3"sv, static_cast<CodePage>(28593u)},
{u8"iso8859_4"sv, static_cast<CodePage>(28594u)}, {u8"iso8859_5"sv, static_cast<CodePage>(28595u)},
{u8"iso8859_6"sv, static_cast<CodePage>(28596u)}, {u8"iso8859_7"sv, static_cast<CodePage>(28597u)},
{u8"iso8859_8"sv, static_cast<CodePage>(28598u)}, {u8"iso8859_9"sv, static_cast<CodePage>(28599u)},
{u8"iso8859_13"sv, static_cast<CodePage>(28603u)}, {u8"iso8859_15"sv, static_cast<CodePage>(28605u)},
{u8"johab"sv, static_cast<CodePage>(1361u)}, {u8"mac_cyrillic"sv, static_cast<CodePage>(10007u)},
{u8"mac_greek"sv, static_cast<CodePage>(10006u)}, {u8"mac_iceland"sv, static_cast<CodePage>(10079u)},
{u8"mac_turkish"sv, static_cast<CodePage>(10081u)}, {u8"shift_jis"sv, static_cast<CodePage>(932u)},
{u8"utf_7"sv, static_cast<CodePage>(65000u)}, {u8"utf_8"sv, static_cast<CodePage>(65001u)},
};
static bool fetch_code_page(const std::u8string_view& enc_name, CodePage& out_cp) {
// resolve alias
std::u8string resolved_name = resolve_encoding_alias(enc_name);
// find code page
op::lower(resolved_name);
auto finder = WINCP_MAP.find(resolved_name);
if (finder == WINCP_MAP.end()) return false;
// okey, we found it.
out_cp = finder->second;
return true;
}
#else
static const std::map<std::u8string_view, std::string_view> ICONV_MAP{
{u8"ascii"sv, "ASCII"sv},
{u8"big5"sv, "BIG5"sv},
{u8"big5hkscs"sv, "BIG5-HKSCS"sv},
{u8"cp850"sv, "CP850"sv},
{u8"cp862"sv, "CP862"sv},
{u8"cp866"sv, "CP866"sv},
{u8"cp874"sv, "CP874"sv},
{u8"cp932"sv, "CP932"sv},
{u8"cp949"sv, "CP949"sv},
{u8"cp950"sv, "CP950"sv},
{u8"cp1250"sv, "CP1250"sv},
{u8"cp1251"sv, "CP1251"sv},
{u8"cp1252"sv, "CP1252"sv},
{u8"cp1253"sv, "CP1253"sv},
{u8"cp1254"sv, "CP1254"sv},
{u8"cp1255"sv, "CP1255"sv},
{u8"cp1256"sv, "CP1256"sv},
{u8"cp1257"sv, "CP1257"sv},
{u8"cp1258"sv, "CP1258"sv},
{u8"euc_jp"sv, "EUC-JP"sv},
{u8"euc_kr"sv, "EUC-KR"sv},
{u8"gb2312"sv, "CP936"sv},
{u8"gbk"sv, "GBK"sv},
{u8"gb18030"sv, "GB18030"sv},
{u8"hz"sv, "HZ"sv},
{u8"iso2022_jp"sv, "ISO-2022-JP"sv},
{u8"iso2022_jp_1"sv, "ISO-2022-JP-1"sv},
{u8"iso2022_jp_2"sv, "ISO-2022-JP-2"sv},
{u8"iso2022_kr"sv, "ISO-2022-KR"sv},
{u8"latin_1"sv, "ISO-8859-1"sv},
{u8"iso8859_2"sv, "ISO-8859-2"sv},
{u8"iso8859_3"sv, "ISO-8859-3"sv},
{u8"iso8859_4"sv, "ISO-8859-4"sv},
{u8"iso8859_5"sv, "ISO-8859-5"sv},
{u8"iso8859_6"sv, "ISO-8859-6"sv},
{u8"iso8859_7"sv, "ISO-8859-7"sv},
{u8"iso8859_8"sv, "ISO-8859-8"sv},
{u8"iso8859_9"sv, "ISO-8859-9"sv},
{u8"iso8859_10"sv, "ISO-8859-10"sv},
{u8"iso8859_11"sv, "ISO-8859-11"sv},
{u8"iso8859_13"sv, "ISO-8859-13"sv},
{u8"iso8859_14"sv, "ISO-8859-14"sv},
{u8"iso8859_15"sv, "ISO-8859-15"sv},
{u8"iso8859_16"sv, "ISO-8859-16"sv},
{u8"johab"sv, "JOHAB"sv},
{u8"koi8_t"sv, "KOI8-T"sv},
{u8"mac_cyrillic"sv, "MacCyrillic"sv},
{u8"mac_greek"sv, "MacGreek"sv},
{u8"mac_iceland"sv, "MacIceland"sv},
{u8"mac_roman"sv, "MacRoman"sv},
{u8"mac_turkish"sv, "MacTurkish"sv},
{u8"ptcp154"sv, "PT154"sv},
{u8"shift_jis"sv, "SHIFT_JIS"sv},
{u8"utf_32"sv, "UTF-32"sv},
{u8"utf_32_be"sv, "UTF-32BE"sv},
{u8"utf_32_le"sv, "UTF-32LE"sv},
{u8"utf_16"sv, "UTF16"sv},
{u8"utf_16_be"sv, "UTF-16BE"sv},
{u8"utf_16_le"sv, "UTF-16LE"sv},
{u8"utf_7"sv, "UTF-7"sv},
{u8"utf_8"sv, "UTF-8"sv},
};
static bool fetch_iconv_name(const std::u8string_view& enc_name, std::string& out_code) {
// resolve alias
std::u8string resolved_name = resolve_encoding_alias(enc_name);
// find code page
op::lower(resolved_name);
auto finder = ICONV_MAP.find(resolved_name);
if (finder == ICONV_MAP.end()) return false;
// okey, we found it.
out_code = finder->second;
return true;
}
#endif
#pragma endregion
#pragma region Misc
ConvError::ConvError(const ConvError::Error& err) : inner(err) {}
bool is_valid_encoding_name(const EncodingName& name) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
CodePage unused;
return fetch_code_page(name, unused);
#else
std::string unused;
return fetch_iconv_name(name, unused);
#endif
}
#pragma endregion
#pragma region Char -> UTF8
CharToUtf8::CharToUtf8(const EncodingName& name) :
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
code_page(fetch)
#else
inner(fetch_iconv_name())
#endif
{}
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
#pragma region
#pragma endregion
} // namespace yycc::encoding::pycodec

View File

@ -0,0 +1,192 @@
#pragma once
#include "../macro/os_detector.hpp"
#include "../macro/stl_detector.hpp"
#include "../macro/class_copy_move.hpp"
#include <string>
#include <string_view>
#include <expected>
// Choose the backend of PyCodec module
#if defined(YYCC_FEAT_ICONV)
// We try Iconv first in any cases.
#include "iconv.hpp"
#define YYCC_PYCODEC_ICONV_BACKEND
#define YYCC_PYCODEC_BACKEND_NS ::yycc::encoding::iconv
#elif defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
// If we can not use Iconv, we try to fallback to Windows implementation.
#include "windows.hpp"
#define YYCC_PYCODEC_WIN32_BACKEND
#define YYCC_PYCODEC_BACKEND_NS ::yycc::encoding::windows
#else
// No viable implementation.
#error "Can not find viable encoding convertion solution in current environment for PyCodec module."
#endif
namespace yycc::encoding::pycodec {
/// @brief The universal name of encoding.
using EncodingName = std::u8string_view;
/// @brief The possible error occurs in this module.
class ConvError {
public:
using Error = YYCC_PYCODEC_BACKEND_NS::ConvError;
ConvError(const Error& err);
YYCC_DEFAULT_COPY_MOVE(ConvError)
private:
Error inner;
};
/// @brief The result type of this module.
template<typename T>
using ConvResult = std::expected<T, ConvError>;
/**
* @brief Check whether given name is a valid encoding name in PyCodec.
* @param[in] name The name to be checked.
* @return True if it is valid, otherwise false.
*/
bool is_valid_encoding_name(const EncodingName& name);
/// @brief Char -> UTF8
class CharToUtf8 {
public:
CharToUtf8(const EncodingName& name);
~CharToUtf8();
YYCC_DELETE_COPY(CharToUtf8)
YYCC_DEFAULT_MOVE(CharToUtf8)
public:
ConvResult<std::u8string> to_utf8(const std::string_view& src);
private:
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
YYCC_PYCODEC_BACKEND_NS::CodePage code_page;
#else
YYCC_PYCODEC_BACKEND_NS::CharToUtf8 inner;
#endif
};
/// @brief UTF8 -> Char
class Utf8ToChar {
public:
Utf8ToChar(const EncodingName& name);
~Utf8ToChar();
YYCC_DELETE_COPY(Utf8ToChar)
YYCC_DEFAULT_MOVE(Utf8ToChar)
public:
ConvResult<std::string> to_char(const std::u8string_view& src);
private:
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
YYCC_PYCODEC_BACKEND_NS::CodePage code_page;
#else
YYCC_PYCODEC_BACKEND_NS::Utf8ToChar inner;
#endif
};
/// @brief WChar -> UTF8
class WcharToUtf8 {
public:
WcharToUtf8();
~WcharToUtf8();
YYCC_DELETE_COPY(WcharToUtf8)
YYCC_DEFAULT_MOVE(WcharToUtf8)
public:
ConvResult<std::u8string> to_utf8(const std::wstring_view& src);
private:
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
YYCC_PYCODEC_BACKEND_NS::WcharToUtf8 inner;
#endif
};
/// @brief UTF8 -> WChar
class Utf8ToWchar {
public:
Utf8ToWchar();
~Utf8ToWchar();
YYCC_DELETE_COPY(Utf8ToWchar)
YYCC_DEFAULT_MOVE(Utf8ToWchar)
public:
ConvResult<std::wstring> to_wchar(const std::u8string_view& src);
private:
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
YYCC_PYCODEC_BACKEND_NS::Utf8ToWchar inner;
#endif
};
/// @brief UTF8 -> UTF16
class Utf8ToUtf16 {
public:
Utf8ToUtf16();
~Utf8ToUtf16();
YYCC_DELETE_COPY(Utf8ToUtf16)
YYCC_DEFAULT_MOVE(Utf8ToUtf16)
public:
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
private:
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
YYCC_PYCODEC_BACKEND_NS::Utf8ToUtf16 inner;
#endif
};
/// @brief UTF16 -> UTF8
class Utf16ToUtf8 {
public:
Utf16ToUtf8();
~Utf16ToUtf8();
YYCC_DELETE_COPY(Utf16ToUtf8)
YYCC_DEFAULT_MOVE(Utf16ToUtf8)
public:
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
private:
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
YYCC_PYCODEC_BACKEND_NS::Utf16ToUtf8 inner;
#endif
};
/// @brief UTF8 -> UTF32
class Utf8ToUtf32 {
public:
Utf8ToUtf32();
~Utf8ToUtf32();
YYCC_DELETE_COPY(Utf8ToUtf32)
YYCC_DEFAULT_MOVE(Utf8ToUtf32)
public:
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
private:
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
YYCC_PYCODEC_BACKEND_NS::Utf8ToUtf32 inner;
#endif
};
/// @brief UTF32 -> UTF8
class Utf32ToUtf8 {
public:
Utf32ToUtf8();
~Utf32ToUtf8();
YYCC_DELETE_COPY(Utf32ToUtf8)
YYCC_DEFAULT_MOVE(Utf32ToUtf8)
public:
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
private:
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
YYCC_PYCODEC_BACKEND_NS::Utf32ToUtf8 inner;
#endif
};
} // namespace yycc::encoding::pycodec

118
src/yycc/encoding/stl.cpp Normal file
View File

@ -0,0 +1,118 @@
#include "stl.hpp"
#include <locale>
namespace yycc::encoding::stl {
#pragma region Generic Converter
/*
* YYC MARK:
* According to the documentation introduced in CppReference.
* The standard library is guaranteed to provide several specific specializations of \c std::codecvt.
* The UTF8 char type in UTF8 related specializations of \c std::codecvt is different in different C++ standard.
* But the oldest C++ version YYCC supported is C++ 23, char8_t is the only viable UTF8 char type for \c std::codecvt.
* So we can simply and safely use it to correctly trigger specific specializations of \c std::codecv in there.
*/
template<typename TChar>
requires(std::is_same_v<TChar, char16_t> || std::is_same_v<TChar, char32_t>)
using CodecvtFacet = std::codecvt<TChar, char8_t, std::mbstate_t>;
template<typename TChar>
requires(std::is_same_v<TChar, char16_t> || std::is_same_v<TChar, char32_t>)
static ConvResult<std::basic_string<TChar>> generic_to_utf_other(const std::u8string_view& src) {
// Reference:
// https://en.cppreference.com/w/cpp/locale/codecvt/in
// prepare return value
std::basic_string<TChar> dst;
// if src is empty, return directly
if (src.empty()) {
return dst;
}
// init locale and get codecvt facet
// same reason in UTFOtherToUTF8 to keeping reference to locale
const auto& this_locale = std::locale::classic();
const auto& this_codecvt = std::use_facet<CodecvtFacet<TChar>>(this_locale);
// convertion preparation
std::mbstate_t mb{};
dst.resize(src.size());
const char8_t *intern_from = reinterpret_cast<const char8_t*>(src.data()),
*intern_from_end = reinterpret_cast<const char8_t*>(src.data() + src.size()), *intern_from_next = nullptr;
TChar *extern_to = dst.data(), *extern_to_end = dst.data() + dst.size(), *extern_to_next = nullptr;
// do convertion
auto result = this_codecvt.in(mb, intern_from, intern_from_end, intern_from_next, extern_to, extern_to_end, extern_to_next);
// check result
if (result != CodecvtFacet<TChar>::ok) return std::unexpected(ConvError{});
// resize result and return
dst.resize(extern_to_next - dst.data());
return dst;
}
template<typename TChar>
requires(std::is_same_v<TChar, char16_t> || std::is_same_v<TChar, char32_t>)
static ConvResult<std::u8string> generic_to_utf8(const std::basic_string_view<TChar>& src) {
// Reference:
// https://en.cppreference.com/w/cpp/locale/codecvt/out
// prepare return value
std::u8string dst;
// if src is empty, return directly
if (src.empty()) {
return dst;
}
// init locale and get codecvt facet
// the reference to locale must be preserved until convertion done.
// because the life time of codecvt facet is equal to the reference to locale.
const auto& this_locale = std::locale::classic();
const auto& this_codecvt = std::use_facet<CodecvtFacet<TChar>>(this_locale);
// do convertion preparation
std::mbstate_t mb{};
dst.resize(src.size() * this_codecvt.max_length());
const TChar *intern_from = src.data(), *intern_from_end = src.data() + src.size(), *intern_from_next = nullptr;
char8_t *extern_to = reinterpret_cast<char8_t*>(dst.data()), *extern_to_end = reinterpret_cast<char8_t*>(dst.data() + dst.size()),
*extern_to_next = nullptr;
// do convertion
auto result = this_codecvt.out(mb, intern_from, intern_from_end, intern_from_next, extern_to, extern_to_end, extern_to_next);
// check result
if (result != CodecvtFacet<TChar>::ok) return std::unexpected(ConvError{});
// resize result and retuen
dst.resize(extern_to_next - reinterpret_cast<char8_t*>(dst.data()));
return dst;
}
#pragma endregion Converter
#pragma region
ConvResult<std::u16string> to_utf16(const std::u8string_view& src) {
// UTF8 -> UTF16
return generic_to_utf_other<char16_t>(src);
}
ConvResult<std::u8string> to_utf8(const std::u16string_view& src) {
// UTF16 -> UTF8
return generic_to_utf8<char16_t>(src);
}
ConvResult<std::u32string> to_utf32(const std::u8string_view& src) {
// UTF8 -> UTF32
return generic_to_utf_other<char32_t>(src);
}
ConvResult<std::u8string> to_utf8(const std::u32string_view& src) {
// UTF32 -> UTF8
return generic_to_utf8<char32_t>(src);
}
#pragma endregion
} // namespace yycc::encoding::stlcvt

43
src/yycc/encoding/stl.hpp Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <string>
#include <string_view>
#include <expected>
namespace yycc::encoding::stl {
/// @brief Possible convertion error occurs in this module.
struct ConvError {};
/// @brief The result type of this module.
template<typename T>
using ConvResult = std::expected<T, ConvError>;
/**
* @brief UTF8 -> UTF16
* @param src
* @return
*/
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
/**
* @brief UTF16 -> UTF8
* @param src
* @return
*/
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
/**
* @brief UTF8 -> UTF32
* @param src
* @return
*/
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
/**
* @brief UTF32 -> UTF8
* @param src
* @return
*/
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
}

View File

@ -0,0 +1,217 @@
#include "windows.hpp"
#if defined(YYCC_OS_WINDOWS)
#include "../string/reinterpret.hpp"
#include <limits>
#include <stdexcept>
#include <cuchar>
#include "../windows/import_guard_head.hpp"
#include <Windows.h>
#include "../windows/import_guard_tail.hpp"
#define NS_YYCC_STRING_REINTERPRET ::yycc::string::reinterpret
namespace yycc::encoding::windows {
#pragma region WideCharToMultiByte and MultiByteToWideChar stuff
// WChar -> Char
ConvResult<std::string> to_char(const std::wstring_view& src, CodePage code_page) {
// prepare result
std::string dst;
// if src is empty, direct output
if (src.empty()) {
return dst;
}
// init WideCharToMultiByte used variables
// setup src pointer
LPCWCH lpWideCharStr = reinterpret_cast<LPCWCH>(src.data());
// check whether source string is too large.
size_t cSrcSize = src.size();
if (cSrcSize > std::numeric_limits<int>::max()) return std::unexpected(ConvError::TooLargeLength);
int cchWideChar = static_cast<int>(src.size());
// do convertion
// do a dry-run first to fetch desired size.
int desired_size = WideCharToMultiByte(code_page, 0, lpWideCharStr, cchWideChar, NULL, 0, NULL, NULL);
if (desired_size <= 0) return std::unexpected(ConvError::NoDesiredSize);
// resize dest for receiving result
dst.resize(static_cast<size_t>(desired_size));
// do real convertion
int write_result
= WideCharToMultiByte(code_page, 0, lpWideCharStr, cchWideChar, reinterpret_cast<LPSTR>(dst.data()), desired_size, NULL, NULL);
if (write_result <= 0) return std::unexpected(ConvError::BadWrittenSize);
return dst;
}
// Char -> WChar
ConvResult<std::wstring> to_wchar(const std::string_view& src, CodePage code_page) {
// prepare result
std::wstring dst;
// if src is empty, direct output
if (src.empty()) {
return dst;
}
// init WideCharToMultiByte used variables
// setup src pointer
LPCCH lpMultiByteStr = reinterpret_cast<LPCCH>(src.data());
// check whether source string is too large.
size_t cSrcSize = src.size();
if (cSrcSize > std::numeric_limits<int>::max()) return std::unexpected(ConvError::TooLargeLength);
int cbMultiByte = static_cast<int>(src.size());
// do convertion
// do a dry-run first to fetch desired size.
int desired_size = MultiByteToWideChar(code_page, 0, lpMultiByteStr, cbMultiByte, NULL, 0);
if (desired_size <= 0) return std::unexpected(ConvError::NoDesiredSize);
// resize dest for receiving result
dst.resize(static_cast<size_t>(desired_size));
// do real convertion
int write_result = MultiByteToWideChar(code_page, 0, lpMultiByteStr, cbMultiByte, reinterpret_cast<LPWSTR>(dst.data()), desired_size);
if (write_result <= 0) return std::unexpected(ConvError::BadWrittenSize);
return dst;
}
// Char -> Char
ConvResult<std::string> to_char(const std::string_view& src, CodePage src_code_page, CodePage dst_code_page) {
auto first_rv = to_wchar(src, src_code_page);
return first_rv.and_then([dst_code_page](const auto& src) { return to_char(src, dst_code_page); });
}
// WChar -> UTF8
ConvResult<std::u8string> to_utf8(const std::wstring_view& src) {
auto rv = to_char(src, CP_UTF8);
return rv.transform([](const auto& dst) { return NS_YYCC_STRING_REINTERPRET::as_utf8(dst); });
}
// UTF8 -> WChar
ConvResult<std::wstring> to_wchar(const std::u8string_view& src) {
return to_wchar(NS_YYCC_STRING_REINTERPRET::as_ordinary_view(src), CP_UTF8);
}
// Char -> UTF8
ConvResult<std::u8string> to_utf8(const std::string_view& src, CodePage code_page) {
auto rv = to_char(src, code_page, CP_UTF8);
return rv.transform([](const auto& dst) { return NS_YYCC_STRING_REINTERPRET::as_utf8(dst); });
}
// UTF8 -> Char
ConvResult<std::string> to_char(const std::u8string_view& src, CodePage code_page) {
return to_char(NS_YYCC_STRING_REINTERPRET::as_ordinary_view(src), CP_UTF8, code_page);
}
#pragma endregion
#pragma region UTF stuff
// YYC MARK:
// The convertion between UTF is implemented by c16rtomb, c32rtomb, mbrtoc16 and mbrtoc32.
// These function is locale related in C++ standard, but in Microsoft STL, it's only for UTF8.
// So we can use them safely in Win32 environment.
// Reference:
// * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/c16rtomb-c32rtomb1?view=msvc-170
// * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/mbrtoc16-mbrtoc323?view=msvc-170
#if defined(YYCC_STL_MSSTL)
// 1 UTF32 unit can produe 4 UTF8 units or 2 UTF16 units in theory.
// So we pre-allocate memory for the result to prevent allocating memory multiple times.
constexpr size_t MULTIPLE_UTF8_TO_UTF16 = 1u;
constexpr size_t MULTIPLE_UTF16_TO_UTF8 = 2u;
constexpr size_t MULTIPLE_UTF8_TO_UTF32 = 1u;
constexpr size_t MULTIPLE_UTF32_TO_UTF8 = 4u;
// UTF8 -> UTF16
ConvResult<std::u16string> to_utf16(const std::u8string_view& src) {
std::u16string dst;
dst.reserve(src.size() * MULTIPLE_UTF8_TO_UTF16);
std::mbstate_t state{}; // zero-initialized to initial state
char16_t c16;
const char* ptr = reinterpret_cast<const char*>(src.data());
const char* end = ptr + src.size();
while (ptr < end) {
size_t rc = std::mbrtoc16(&c16, ptr, end - ptr, &state);
if (rc == (size_t) -1) return std::unexpected(ConvError::EncodeUtf8);
else if (rc == (size_t) -2) return std::unexpected(ConvError::IncompleteUtf8);
else if (rc != (size_t) -3) dst.push_back(c16); // from earlier surrogate pair
else {
dst.push_back(c16);
ptr += rc;
}
}
return dst;
}
// UTF16 -> UTF8
ConvResult<std::u8string> to_utf8(const std::u16string_view& src) {
std::u8string dst;
dst.reserve(src.size() * MULTIPLE_UTF16_TO_UTF8);
std::mbstate_t state{};
char mbout[MB_LEN_MAX]{};
for (char16_t c : src) {
size_t rc = std::c16rtomb(mbout, c, &state);
if (rc != (size_t) -1) dst.append(reinterpret_cast<char8_t*>(mbout), rc);
else return std::unexpected(ConvError::InvalidUtf16);
}
return dst;
}
// UTF8 -> UTF32
ConvResult<std::u32string> to_utf32(const std::u8string_view& src) {
std::u32string dst;
dst.reserve(src.size() * MULTIPLE_UTF8_TO_UTF32);
std::mbstate_t state{};
char32_t c32;
const char* ptr = reinterpret_cast<const char*>(src.data());
const char* end = ptr + src.size();
while (ptr < end) {
size_t rc = std::mbrtoc32(&c32, ptr, end - ptr, &state);
if (rc == (size_t) -1) return std::unexpected(ConvError::EncodeUtf8);
else if (rc == (size_t) -2) return std::unexpected(ConvError::IncompleteUtf8);
else if (rc != (size_t) -3) throw std::runtime_error("no surrogates in UTF-32");
else dst.push_back(c32);
ptr += rc;
}
return dst;
}
// UTF32 -> UTF8
ConvResult<std::u8string> to_utf8(const std::u32string_view& src) {
std::u8string dst;
dst.reserve(src.size() * MULTIPLE_UTF32_TO_UTF8);
std::mbstate_t state{};
char mbout[MB_LEN_MAX]{};
for (char32_t c : src) {
size_t rc = std::c32rtomb(mbout, c, &state);
if (rc != (size_t) -1) dst.append(reinterpret_cast<char8_t*>(mbout), rc);
else return std::unexpected(ConvError::InvalidUtf32);
}
return dst;
}
#endif
#pragma endregion
} // namespace yycc::encoding::windows
#endif

View File

@ -0,0 +1,129 @@
#pragma once
#include "../macro/os_detector.hpp"
#include "../macro/stl_detector.hpp"
#include <string>
#include <string_view>
#include <expected>
#include <cstdint>
namespace yycc::encoding::windows {
/// @brief The type of Windows code page.
using CodePage = uint32_t;
/// @brief The possible error kind occurs in this module.
enum class ConvError {
TooLargeLength, ///< The length of given string is too large exceeding the maximum capacity of Win32 function.
NoDesiredSize, ///< Can not compute the desired size of result string.
BadWrittenSize, ///< The size of written data is not matched with expected size.
InvalidUtf32, ///< Given char is invalid in UTF32.
InvalidUtf16, ///< Given char is invalid in UTF16.
EncodeUtf8, ///< Error occurs when encoding UTF8.
IncompleteUtf8, ///< Given UTF8 string is incomplete.
};
/// @brief The result type in this module.
template<typename T>
using ConvResult = std::expected<T, ConvError>;
#if defined(YYCC_OS_WINDOWS)
/**
* @brief WChar -> Char
* @param src
* @param code_page
* @return
*/
ConvResult<std::string> to_char(const std::wstring_view& src, CodePage code_page);
/**
* @brief Char -> WChar
* @param src
* @param code_page
* @return
*/
ConvResult<std::wstring> to_wchar(const std::string_view& src, CodePage code_page);
/**
* @brief Char -> Char
* @details This is the combination of "WChar -> Char" and "Char -> WChar"
* @param src
* @param src_code_page
* @param dst_code_page
* @return
*/
ConvResult<std::string> to_char(const std::string_view& src, CodePage src_code_page, CodePage dst_code_page);
/**
* @brief WChar -> UTF8
* @details This is the specialization of "WChar -> Char"
* @param src
* @return
*/
ConvResult<std::u8string> to_utf8(const std::wstring_view& src);
/**
* @brief UTF8 -> WChar
* @details This is the specialization of "Char -> WChar"
* @param src
* @return
*/
ConvResult<std::wstring> to_wchar(const std::u8string_view& src);
/**
* @brief Char -> UTF8
* @details This is the specialization of "Char -> Char"
* @param src
* @param code_page
* @return
*/
ConvResult<std::u8string> to_utf8(const std::string_view& src, CodePage code_page);
/**
* @brief UTF8 -> Char
* @details This is the specialization of "Char -> Char"
* @param src
* @param code_page
* @return
*/
ConvResult<std::string> to_char(const std::u8string_view& src, CodePage code_page);
// YYC MARK:
// UTF convertion only works on Microsoft STL.
// See implementation for more details
#if defined(YYCC_STL_MSSTL)
/**
* @brief UTF8 -> UTF16
* @param src
* @return
*/
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
/**
* @brief UTF16 -> UTF8
* @param src
* @return
*/
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
/**
* @brief UTF8 -> UTF32
* @param src
* @return
*/
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
/**
* @brief UTF32 -> UTF8
* @param src
* @return
*/
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
#endif
#endif
} // namespace yycc::encoding::windows

194
src/yycc/flag_enum.hpp Normal file
View File

@ -0,0 +1,194 @@
#pragma once
#include <type_traits>
/**
* @brief The namespace for convenient C++ enum class logic operations.
* @details
* C++ enum class statement is a modern way to declare enum in C++.
* But it lack essential logic operations which is commonly used by programmer.
* So we create this helper to resolve this issue.
*/
namespace yycc::flag_enum {
// Reference:
// Enum operator overload: https://stackoverflow.com/a/71107019
// Constexpr operator overload: https://stackoverflow.com/a/17746099
// YYC MARK:
// Currently, the solution of "Constexpr operator overload" is not used.
// We use explicit way, "Enum operator overload".
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator|(TEnum lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(static_cast<ut>(lhs) | static_cast<ut>(rhs));
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator|=(TEnum& lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// lhs = lhs | rhs;
// return lhs;
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator&(TEnum lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(static_cast<ut>(lhs) & static_cast<ut>(rhs));
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator&=(TEnum& lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// lhs = lhs & rhs;
// return lhs;
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator^(TEnum lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(static_cast<ut>(lhs) ^ static_cast<ut>(rhs));
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator^=(TEnum& lhs, TEnum rhs) {
// using ut = std::underlying_type_t<TEnum>;
// lhs = lhs ^ rhs;
// return lhs;
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr TEnum operator~(TEnum lhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<TEnum>(~(static_cast<ut>(lhs)));
// }
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
// inline constexpr bool operator bool(TEnum lhs) {
// using ut = std::underlying_type_t<TEnum>;
// return static_cast<bool>(static_cast<ut>(lhs));
// }
/**
* @private
* @brief The helper struct to check all given template argument are the same enum type.
* @tparam TEnum The template parameter to be checked (first one).
* @tparam Ts The template parameter to be checked.
*/
template<typename TEnum, typename... Ts>
struct AllSameEnum {
public:
// YYC MARK:
// Please note that we must use std::is_same, not std::is_same_v!
// That's std::conjunction_v required.
static constexpr bool value = std::is_enum_v<std::remove_cv_t<TEnum>>
&& std::conjunction_v<std::is_same<std::remove_cv_t<TEnum>, std::remove_cv_t<Ts>>...>;
};
/**
* @private
* @brief The convenient calling to all_enum_values::value to check enum template parameter.
* @tparam TEnum The template parameter to be checked (first one).
* @tparam Ts The template parameter to be checked.
*/
template<typename TEnum, typename... Ts>
inline constexpr bool ALL_SAME_ENUM = AllSameEnum<TEnum, Ts...>::value;
/**
* @brief Merge given enum flags like performing <TT>e1 | e2 | ... | en</TT>
* @tparam TEnum Enum type for processing.
* @param[in] val The first enum flag to be merged.
* @param[in] val_left Left enum flags to be merged.
* @return The merged enum flag.
* @remarks
* This function use recursive expansion to get final merge result.
* So there is no difference of each arguments.
* We independ first argument just served for expansion.
*/
template<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr TEnum merge(TEnum val, Ts... val_left) {
using ut = std::underlying_type_t<TEnum>;
ut result = static_cast<ut>(val);
if constexpr (sizeof...(val_left) > 0) {
result |= static_cast<ut>(merge(val_left...));
}
return static_cast<TEnum>(result);
}
/**
* @brief Reverse given enum flags like performing <TT>~(e)</TT>
* @tparam TEnum Enum type for processing.
* @param[in] e The list of enum flags to be inversed.
* @return The inversed enum flag.
*/
template<typename TEnum>
requires(std::is_enum_v<TEnum>)
constexpr TEnum invert(TEnum e) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<TEnum>(~(static_cast<ut>(e)));
}
/**
* @brief Use specified enum flag to mask given enum flag like performing <TT>e1 &= e2</TT>
* @tparam TEnum Enum type for processing.
* @param[in,out] e1 The enum flags to be masked.
* @param[in] e2 The mask enum flag.
*/
template<typename TEnum>
requires(std::is_enum_v<TEnum>)
constexpr void mask(TEnum& e1, TEnum e2) {
using ut = std::underlying_type_t<TEnum>;
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(e2));
}
/**
* @brief Add multiple enum flags to given enum flag like performing <TT>e1 |= (e2 | e3 | ... | en)</TT>
* @tparam TEnum Enum type for processing.
* @param[in,out] e1 The enum flag which flags add on.
* @param[in] vals The enum flag to be added.
*/
template<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr void add(TEnum& e1, Ts... vals) {
using ut = std::underlying_type_t<TEnum>;
e1 = static_cast<TEnum>(static_cast<ut>(e1) | static_cast<ut>(merge(vals...)));
}
/**
* @brief Remove multiple enum flags from given enum flag like performing <TT>e1 &= ~(e2 | e3 | ... | en)</TT>
* @tparam TEnum Enum type for processing.
* @param[in,out] e1 The enum flag which flags removed from.
* @param[in] vals The enum flag to be removed.
*/
template<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr void remove(TEnum& e1, Ts... vals) {
using ut = std::underlying_type_t<TEnum>;
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(invert(merge(vals...))));
}
/**
* @brief Check whether given enum flag has any of specified multiple enum flags (OR) like performing <TT>bool(e1 & (e2 | e3 | ... | en))</TT>
* @tparam TEnum Enum type for processing.
* @param[in] e1 The enum flag where we check.
* @param[in] vals The enum flags for checking.
* @return True if it has any of given flags (OR), otherwise false.
*/
template<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr bool has(TEnum e1, Ts... vals) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<bool>(static_cast<ut>(e1) & static_cast<ut>(merge(vals...)));
}
/**
* @brief Cast given enum flags to its equvalent boolean value like performing <TT>bool(e)</TT>
* @tparam TEnum Enum type for processing.
* @param e The enum flags to be cast.
* @return The equvalent bool value of given enum flag.
*/
template<typename TEnum, typename... Ts>
requires(ALL_SAME_ENUM<TEnum, Ts...>)
constexpr bool boolean(TEnum e) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<bool>(static_cast<ut>(e));
}
} // namespace yycc::flag_enum

View File

@ -0,0 +1,31 @@
#pragma once
/// @brief Explicitly remove copy (\c constructor and \c operator\=) for given class.
#define YYCC_DELETE_COPY(CLSNAME) \
CLSNAME(const CLSNAME&) = delete; \
CLSNAME& operator=(const CLSNAME&) = delete;
/// @brief Explicitly remove move (\c constructor and \c operator\=) for given class.
#define YYCC_DELETE_MOVE(CLSNAME) \
CLSNAME(CLSNAME&&) = delete; \
CLSNAME& operator=(CLSNAME&&) = delete;
/// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class.
#define YYCC_DELETE_COPY_MOVE(CLSNAME) \
YYCC_DELETE_COPY(CLSNAME) \
YYCC_DELETE_MOVE(CLSNAME)
/// @brief Explicitly set default copy (\c constructor and \c operator\=) for given class.
#define YYCC_DEFAULT_COPY(CLSNAME) \
CLSNAME(const CLSNAME&) = default; \
CLSNAME& operator=(const CLSNAME&) = default;
/// @brief Explicitly set default move (\c constructor and \c operator\=) for given class.
#define YYCC_DEFAULT_MOVE(CLSNAME) \
CLSNAME(CLSNAME&&) = default; \
CLSNAME& operator=(CLSNAME&&) = default;
/// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class.
#define YYCC_DEFAULT_COPY_MOVE(CLSNAME) \
YYCC_DEFAULT_COPY(CLSNAME) \
YYCC_DEFAULT_MOVE(CLSNAME)

View File

@ -0,0 +1,30 @@
#pragma once
#if (defined(YYCC_CC_MSVC) + defined(YYCC_CC_GCC) + defined(YYCC_CC_CLANG)) != 1
#error "Current compiler is not supported!"
#endif
namespace yycc::macro::compiler {
/// @brief The kind of compiler.
enum class CompilerKind {
Msvc, ///< MSVC
Gcc, ///< GCC
Clang, ///< Clang
};
/**
* @brief Fetch the compiler type.
* @return The kind of compiler.
*/
inline constexpr CompilerKind get_compiler() {
#if defined(YYCC_CC_MSVC)
return CompilerKind::Msvc;
#elif defined(YYCC_CC_GCC)
return CompilerKind::Gcc;
#else
return CompilerKind::Clang;
#endif
}
} // namespace yycc::macro::compiler

View File

@ -0,0 +1,28 @@
#pragma once
// Check endian
#if (defined(YYCC_ENDIAN_LITTLE) + defined(YYCC_ENDIAN_BIG)) != 1
#error "Current system endian (byte order) is not supported!"
#endif
namespace yycc::macro::endian {
/// @brief The endian kind of OS.
enum class EndianKind {
Little, ///< Little endian.
Big, ///< Big endian.
};
/**
* @brief Fetch the endian of OS.
* @return The endian of OS.
*/
inline constexpr EndianKind get_endian() {
#if defined(YYCC_ENDIAN_LITTLE)
return EndianKind::Little;
#else
return EndianKind::Big;
#endif
}
} // namespace yycc::macro::endian

View File

@ -0,0 +1,31 @@
#pragma once
// Check OS macro
#if (defined(YYCC_OS_WINDOWS) + defined(YYCC_OS_LINUX) + defined(YYCC_OS_MACOS)) != 1
#error "Current operating system is not supported!"
#endif
namespace yycc::macro::os {
/// @brief The operating system kind.
enum class OsKind {
Windows, ///< Microsoft Windows
Linux, ///< GNU/Linux
MacOs, ///< Apple macOS
};
/**
* @brief Fetch the operating system
* @return The kind of operating system.
*/
inline constexpr OsKind get_os() {
#if defined(YYCC_OS_WINDOWS)
return OsKind::Windows;
#elif defined(YYCC_OS_LINUX)
return OsKind::Linux;
#else
return OsKind::MacOs;
#endif
}
} // namespace yycc::macro::os

View File

@ -0,0 +1,28 @@
#pragma once
// Check pointer size macro
#if (defined(YYCC_PTRSIZE_32) + defined(YYCC_PTRSIZE_64)) != 1
#error "Current environment used pointer size is not supported!"
#endif
namespace yycc::macro::ptr_size {
/// @brief The pointer size kind.
enum class PtrSizeKind {
Bits32, ///< 32-bit environment
Bits64 ///< 64-bit environment
};
/**
* @brief Fetch the pointer size
* @return The kind of pointer size.
*/
inline constexpr PtrSizeKind get_ptr_size() {
#if defined(YYCC_PTRSIZE_32)
return PtrSizeKind::Bits32;
#else
return PtrSizeKind::Bits64;
#endif
}
} // namespace yycc::macro::ptr_size

View File

@ -0,0 +1,39 @@
#pragma once
// Include a common used STL header for convenient test.
#include <cinttypes>
#if defined(_MSVC_STL_VERSION)
#define YYCC_STL_MSSTL
#elif defined(__GLIBCXX__) || defined(__GLIBCPP__)
#define YYCC_STL_GNUSTL
#elif defined(_LIBCPP_VERSION)
#define YYCC_STL_CLANGSTL
#else
#error "Current STL is not supported!"
#endif
namespace yycc::macro::stl {
/// @brief The STL implementation kind.
enum class StlKind {
MSSTL, ///< Microsoft STL
GNUSTL, ///< GNU STL
CLANGSTL ///< Clang STL
};
/**
* @brief Fetch the STL implementation
* @return The kind of STL implementation.
*/
inline constexpr StlKind get_stl() {
#if defined(YYCC_STL_MSSTL)
return StlKind::MSSTL;
#elif defined(YYCC_STL_GNUSTL)
return StlKind::GNUSTL;
#else
return StlKind::CLANGSTL;
#endif
}
} // namespace yycc::macro::stl

View File

@ -0,0 +1,26 @@
#pragma once
/// @brief Return true if left version number is equal to right version number, otherwise false.
#define YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3) ((av1) == (bv1) && (av2) == (bv2) && (av3) == (bv3))
/// @brief Return true if left version number is not equal to right version number, otherwise false.
#define YYCC_VERCMP_NE(av1, av2, av3, bv1, bv2, bv3) (!YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
/// @brief Return true if left version number is greater than right version number, otherwise false.
#define YYCC_VERCMP_G(av1, av2, av3, bv1, bv2, bv3) ( \
((av1) > (bv1)) || \
((av1) == (bv1) && (av2) > (bv2)) || \
((av1) == (bv1) && (av2) == (bv2) && (av3) > (bv3)) \
)
/// @brief Return true if left version number is greater than or equal to right version number, otherwise false.
#define YYCC_VERCMP_GE(av1, av2, av3, bv1, bv2, bv3) (YYCC_VERCMP_G(av1, av2, av3, bv1, bv2, bv3) || YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
/// @brief Return true if left version number is not lower than right version number, otherwise false.
#define YYCC_VERCMP_NL(av1, av2, av3, bv1, bv2, bv3) YYCC_VERCMP_GE(av1, av2, av3, bv1, bv2, bv3)
/// @brief Return true if left version number is lower than right version number, otherwise false.
#define YYCC_VERCMP_L(av1, av2, av3, bv1, bv2, bv3) ( \
((av1) < (bv1)) || \
((av1) == (bv1) && (av2) < (bv2)) || \
((av1) == (bv1) && (av2) == (bv2) && (av3) < (bv3)) \
)
/// @brief Return true if left version number is lower than or equal to right version number, otherwise false.
#define YYCC_VERCMP_LE(av1, av2, av3, bv1, bv2, bv3) (YYCC_VERCMP_L(av1, av2, av3, bv1, bv2, bv3) || YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
/// @brief Return true if left version number is not greater than right version number, otherwise false.
#define YYCC_VERCMP_NG(av1, av2, av3, bv1, bv2, bv3) YYCC_VERCMP_LE(av1, av2, av3, bv1, bv2, bv3)

38
src/yycc/num/op.hpp Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <stdexcept>
#include <concepts>
/**
* @brief The namespace providing functions for robust numeric operations.
* @details
* After writing some programs in Rust, I deeply appreciated the richness of operators
* for primitive types in Rust, which provides convenient operations like ceiling integral division.
* Therefore, I replicate these convenient features from Rust in this namespace.
*
* Currently unimplemented features due to lack of demand:
* \li Only supports unsigned integer ceiling division
*/
namespace yycc::num::op {
/**
* @brief Unsigned integer ceiling division
* @details
* Performs division between two unsigned integers and rounds up the result.
* @exception std::logic_error If the divisor is zero
* @tparam T The unsigned integer type for division operation
* @param[in] lhs Left operand
* @param[in] rhs Right operand
* @return Ceiling division result
*/
template<typename T>
requires std::unsigned_integral<T>
T div_ceil(T lhs, T rhs) {
// Check divisor first
if (rhs == 0) throw std::logic_error("div with 0");
// YYC MARK:
// We use this algorithm, instead of traditional `(lhs + rhs - 1) / rhs`,
// which may have unsafe overflow case.
return (lhs % rhs == 0) ? (lhs / rhs) : (lhs / rhs) + 1u;
}
}

124
src/yycc/num/parse.hpp Normal file
View File

@ -0,0 +1,124 @@
#pragma once
#include "../string/op.hpp"
#include "../string/reinterpret.hpp"
#include <string_view>
#include <type_traits>
#include <charconv>
#include <stdexcept>
#include <expected>
#define NS_YYCC_STRING_REINTERPRET ::yycc::string::reinterpret
#define NS_YYCC_STRING_OP ::yycc::string::op
/**
* @brief Provides string parsing utilities for converting strings to numeric and boolean values.
* @details
* This namespace contains functions for parsing strings into various numeric types (integer, floating point)
* and boolean values. It uses \c std::from_chars internally for efficient parsing.
* @remarks See https://zh.cppreference.com/w/cpp/utility/from_chars for underlying called functions.
*/
namespace yycc::num::parse {
/// @brief The error kind when parsing string into number.
enum class ParseError {
PartiallyParsed, ///< Only a part of given string was parsed. The whole string may be invalid.
InvalidString, ///< Given string is a invalid number string.
OutOfRange, ///< Given string is valid but its value out of the range of given number type.
};
/// @brief The return value of internal parse function which ape `std::expected`.
template<typename T>
using ParseResult = std::expected<T, ParseError>;
/**
* @private
* @brief Internal parsing function for floating point types
* @tparam T Floating point type (float, double, etc)
* @param strl The UTF-8 string view to parse
* @param fmt The floating point format to use
* @return ParseResult<T> containing either the parsed value or a ParseError
*/
template<typename T>
requires(std::is_floating_point_v<T>)
ParseResult<T> parse(const std::u8string_view& strl, std::chars_format fmt = std::chars_format::general) {
namespace reinterpret = NS_YYCC_STRING_REINTERPRET;
T rv;
const auto* head = reinterpret::as_ordinary(strl.data());
const auto* tail = reinterpret::as_ordinary(strl.data() + strl.size());
auto [ptr, ec] = std::from_chars(head, tail, rv, fmt);
if (ec == std::errc()) {
// Parse completely.
// But we need to check whether the whole string was parsed.
if (ptr == tail) return rv;
else return std::unexpected(ParseError::PartiallyParsed);
} else if (ec == std::errc::invalid_argument) {
// Given string is invalid
return std::unexpected(ParseError::InvalidString);
} else if (ec == std::errc::result_out_of_range) {
// Given string is out of range
return std::unexpected(ParseError::OutOfRange);
} else {
// Unreachable
throw std::runtime_error("invalid ec.");
}
}
/**
* @private
* @brief Internal parsing function for integral types (except bool)
* @tparam T Integral type (int, long, etc)
* @param strl The UTF-8 string view to parse
* @param base Numeric base (2-36)
* @return ParseResult<T> containing either the parsed value or a ParseError
*/
template<typename T>
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
ParseResult<T> parse(const std::u8string_view& strl, int base = 10) {
namespace reinterpret = NS_YYCC_STRING_REINTERPRET;
T rv;
const auto* head = reinterpret::as_ordinary(strl.data());
const auto* tail = reinterpret::as_ordinary(strl.data() + strl.size());
auto [ptr, ec] = std::from_chars(head, tail, rv, base);
if (ec == std::errc()) {
// Parse completely.
// But we need to check whether the whole string was parsed.
if (ptr == tail) return rv;
else return std::unexpected(ParseError::PartiallyParsed);
} else if (ec == std::errc::invalid_argument) {
// Given string is invalid
return std::unexpected(ParseError::InvalidString);
} else if (ec == std::errc::result_out_of_range) {
// Given string is out of range
return std::unexpected(ParseError::OutOfRange);
} else {
// Unreachable
throw std::runtime_error("invalid ec.");
}
}
/**
* @private
* @brief Internal parsing function for boolean type
* @tparam T Must be bool type
* @param strl The UTF-8 string view to parse ("true" or "false", case insensitive)
* @return ParseResult<bool> containing either the parsed value or a ParseError
*/
template<typename T>
requires(std::is_same_v<T, bool>)
ParseResult<T> parse(const std::u8string_view& strl) {
// Get lower case
auto lower_case = NS_YYCC_STRING_OP::to_lower(strl);
// Compare result
if (lower_case == u8"true") return true;
else if (lower_case == u8"false") return false;
else return std::unexpected(ParseError::InvalidString);
}
} // namespace yycc::num::parse
#undef NS_YYCC_STRING_OP
#undef NS_YYCC_STRING_REINTERPRET

165
src/yycc/num/safe_cast.hpp Normal file
View File

@ -0,0 +1,165 @@
#pragma once
#include <expected>
#include <concepts>
#include <limits>
#include <type_traits>
/**
* @brief The namespace providing functions which safely cast numeric value from one type to another.
* @details
* When writing Rust code, I deeply realized that casting between types with different ranges is very important,
* but it is greatly easy to make mistake which finally cause fatal error.
* For widening conversions, we can safely perform them directly without consideration.
* For narrowing conversions, we need to introduce a Result mechanism to determine if errors occur.
* These features are implemented for all primitive types in Rust,
* and managed uniformly through the \c From and \c TryFrom trait, which is perfect.
* But in C++, we need to manually replicate them.
*
* In this namespace, we divide conversion functions into two categories:
* \li \c to for definitely safe conversions,
* \li \c try_to for potentially risky conversions.
* There is a metaprogramming concept <TT>CAN_SAFE_TO\<T, U\></TT> to determine if a conversion is safe,
* which applied for these functions as the constraint.
*
* However, directly using \c CAN_SAFE_TO to determine whether the convertion is safe is incorrect.
* In Rust, whether these conversions are safe is manually determined by different traits ( \c From and \c TryFrom).
* But in C++, we brutally use the size of data types in compile-time to determine safe conversion,
* which causes variable-length data types produces different \c CAN_SAFE_TO results on different platforms,
* which affecting our code's portability.
* For example, we can use 'to' directly for conversion between \c uint64_t and \c size_t: on 64-bit platforms,
* but on 32-bit platforms it causes compile-time errors, resulting in portability issues.
*
* Rust's solution is to define the minimum size of \c usize (32-bit),
* allowing safe conversion only for data smaller than this (e.g. \c u16 ).
* But in C++ we can't do this because we can't know the minimum size of every variable-length primitive data type.
* So we use another solution.
*
* Our solution is to enforce \c CAN_SAFE_TO rules for \c to functions but not for \c try_to functions.
* Inside \c try_to, we use \c CAN_SAFE_TO to determine if safe conversion is possible.
* If yes, convert directly, otherwise perform essential checks before convertion.
* So under this solution, programmers need manually determine if conversion between two types is definitely safe before using \c to .
* But at least, the compiler will throw errors when \c to is inviable, and the only thing you should do is switching to \c try_to.
* Also, using \c try_to everywhere won't impact performance as unnecessary checks are removed via <TT>if constexpr</TT> statement in function.
*
* Currently unsupported conversions due to lack of demand:
* \li Floating-point to floating-point conversions
* \li Floating-point to integer conversions
*/
namespace yycc::num::safe_cast {
/// @brief All possible error raised in this module.
enum class CastError {
Overflow, ///< Overflow error occurred during conversion.
Underflow, ///< Underflow error occurred during conversion.
};
/// @brief The result type in this module.
template<typename T>
using Result = std::expected<T, CastError>;
/**
* @private
* @brief Check if an integer type can be safely converted to another integer type
* @return True if it can be safely converted, false otherwise.
*/
template<typename TDst, typename TSrc>
requires std::integral<TDst> && std::integral<TSrc>
constexpr bool can_safe_to() {
// Fetch the sign info of TSrc and TDst.
constexpr bool is_src_signed = std::is_signed_v<TSrc>;
constexpr bool is_dst_signed = std::is_signed_v<TDst>;
// Get the range of TSrc and TDst.
constexpr TSrc src_min = std::numeric_limits<TSrc>::min();
constexpr TSrc src_max = std::numeric_limits<TSrc>::max();
constexpr TDst dst_min = std::numeric_limits<TDst>::min();
constexpr TDst dst_max = std::numeric_limits<TDst>::max();
if constexpr (is_src_signed) {
if constexpr (is_dst_signed) {
// Signed to signed conversion, both upper and lower bound need to be checked.
// If completely within the range, it is definitely safe.
return dst_min <= src_min && dst_max >= src_max;
} else {
// Signed to unsigned conversion, always unsafe.
// Because negative numbers exist.
return false;
}
} else {
if constexpr (is_dst_signed) {
// Unsigned to signed conversion, only check the upper bound.
// If the upper bound is small enough, it is definitely safe.
return dst_max >= src_max;
} else {
// Unsigned to unsigned conversion, only check the upper bound,
// because the lower bound is 0.
return dst_max >= src_max;
}
}
}
/**
* @private
* @brief Variable version of can_safe_to()
* @details Convenience variable for subsequent constraints
*/
template<typename TDst, typename TSrc>
requires std::integral<TDst> && std::integral<TSrc>
inline constexpr bool CAN_SAFE_TO = can_safe_to<TDst, TSrc>();
/**
* @brief Convert an integer type to another integer type.
* @details Similar to Rust's \c From trait, but with reversed direction (from "from" to "to").
* @return The converted result.
*/
template<typename TDst, typename TSrc>
requires std::integral<TDst> && std::integral<TSrc> && CAN_SAFE_TO<TDst, TSrc>
TDst to(const TSrc& lhs) {
return static_cast<TDst>(lhs);
}
/**
* @brief Attempt to convert an integer type to another integer type.
* @details Similar to Rust's \c TryFrom trait, but with reversed direction (from "from" to "to").
* @return A result containing the conversion result or convertion error.
*/
template<typename TDst, typename TSrc>
requires std::integral<TDst> && std::integral<TSrc>
Result<TDst> try_to(const TSrc& lhs) {
// Check whether we can convert directly.
if constexpr (CAN_SAFE_TO<TDst, TSrc>) {
return static_cast<TDst>(lhs);
} else {
// Fetch the sign info of TSrc and TDst.
constexpr bool is_src_signed = std::is_signed_v<TSrc>;
constexpr bool is_dst_signed = std::is_signed_v<TDst>;
// Fetch the range of TSrc and TDst.
constexpr TSrc src_min = std::numeric_limits<TSrc>::min();
constexpr TSrc src_max = std::numeric_limits<TSrc>::max();
constexpr TDst dst_min = std::numeric_limits<TDst>::min();
constexpr TDst dst_max = std::numeric_limits<TDst>::max();
// Check whether we can convert safely.
if constexpr (is_src_signed == is_dst_signed) {
// If both are signed or unsigned, compare ranges directly.
if (lhs < dst_min) return std::unexpected(CastError::Underflow);
if (lhs > dst_max) return std::unexpected(CastError::Overflow);
return static_cast<TDst>(lhs);
} else {
// If signs are different, we need to check the value range.
if constexpr (is_src_signed) {
// If TSrc is signed, TDst is unsigned, we need to ensure lhs is not negative.
if (lhs < 0) return std::unexpected(CastError::Underflow);
if (lhs > dst_max) return std::unexpected(CastError::Overflow);
return static_cast<TDst>(lhs);
} else {
// If TSrc is unsigned, TDst is signed, we need to ensure lhs is not greater than dst_max.
if (lhs > dst_max) return std::unexpected(CastError::Overflow);
return static_cast<TDst>(lhs);
}
}
}
}
} // namespace yycc::num::safe_cast

740
src/yycc/num/safe_op.hpp Normal file
View File

@ -0,0 +1,740 @@
#pragma once
#include "../macro/os_detector.hpp"
#include "../macro/compiler_detector.hpp"
#include <stdexcept>
#include <optional>
#include <concepts>
#include <limits>
// Choose the function family for hardware based overflow.
#if defined(YYCC_CC_GCC) || defined(YYCC_CC_CLANG)
#define YYCC_HARDWARE_OVERFLOW_GCC_FNS
#elif defined(YYCC_OS_WINDOWS)
#define YYCC_HARDWARE_OVERFLOW_WIN32_FNS
#else
#error "Not supported platform or compiler for integral overflow function family."
#endif
// Import essential header if we are using Windows function family.
#if defined(YYCC_HARDWARE_OVERFLOW_WIN32_FNS)
#include "../windows/import_guard_head.hpp"
#include <intsafe.h>
#include "../windows/import_guard_tail.hpp"
#endif
/**
* @brief The namespace providing Rust-like safe arithmetic operations.
* @details
* After writing some programs in Rust, I've deeply realized the richness of operators for primitive types in Rust.
* You can explicitly specify the behavior of arithmetic overflow
* (choose one of wrapping, checked, overflowing, and saturating).
* Therefore, I'm replicating these convenient features from Rust in this namespace.
*
* Additionally, I provide a bunch of extra operations, called ordinary operation.
* These functions are just an alias to wrapping operator, indicating wrapping is the normal case.
* These normal operators will not have any undefined behavior which C++ will make.
* It basically like \c Add, \c Sub, \c Mul, and \c Div traits in Rust,
* providing safe operations for primitive types.
*/
namespace yycc::num::safe_op {
/*
Implementation notes:
- Wrapping operation:
- Unsigned integer use default overflow behavior.
- Signed integer will be casted into unsigned integer before operation to simulate wrapping overflow.
- Checked operation:
- Use compiler built-in function to detect overflow, return std::optional<T>.
- Return std::nullopt when division by zero or overflow.
- Overflowing operation:
- Return std::pair<T, bool> where bool indicates whether overflow occurs.
- Explicitly handle 2 division undefined behavior:
- Division by zero.
- Signed minimum value divided by -1.
- Saturating operation:
- Return maximum or minimum value when overflow or underflow occurs.
- Determine saturation direction (overflow or underflow) based on operand sign.
*/
#pragma region Undefined Behaviors
/*
YYC MARK:
Following undefined behaviors should be noticed:
- Signed integer overflow and underflow (e.g. INT_MAX + 1).
- Perform `INT_MIN / -1` division.
- The right operand in division is zero.
*/
/**
* @private
* @brief Adds two numbers while considering the undefined behavior of signed integer overflow.
* @tparam T Integer type
* @param[in] a The left operand of the addition
* @param[in] b The right operand of the addition
* @return The result of the addition that takes into account the undefined behavior of signed integers.
*/
template<typename T>
requires std::integral<T>
T ub_signed_int_add(T a, T b) {
if constexpr (std::is_unsigned_v<T>) {
// Add, Sub, Mul and Div for unsigned integer is natural wrapping.
// So we can use operator simply and directly.
return a + b;
} else {
// In C++, it is undefined behavior that signed integer overflow.
// So we need cast them into unsigned integer forcely before operation,
// do operation for them, and cast the result back to simulate wrapping overflow.
using UT = std::make_unsigned_t<T>;
return static_cast<T>(static_cast<UT>(a) + static_cast<UT>(b));
}
}
/**
* @private
* @brief Subtracts two numbers while considering the undefined behavior of signed integer overflow.
* @tparam T Integer type
* @param[in] a The left operand of the subtraction
* @param[in] b The right operand of the subtraction
* @return The result of the subtraction that takes into account the undefined behavior of signed integers.
*/
template<typename T>
requires std::integral<T>
T ub_signed_int_sub(T a, T b) {
if constexpr (std::is_unsigned_v<T>) {
return a - b;
} else {
using UT = std::make_unsigned_t<T>;
return static_cast<T>(static_cast<UT>(a) - static_cast<UT>(b));
}
}
/**
* @private
* @brief Multiplies two numbers while considering the undefined behavior of signed integer overflow.
* @tparam T Integer type
* @param[in] a The left operand of the multiplication
* @param[in] b The right operand of the multiplication
* @return The result of the multiplication that takes into account the undefined behavior of signed integers.
*/
template<typename T>
requires std::integral<T>
T ub_signed_int_mul(T a, T b) {
if constexpr (std::is_unsigned_v<T>) {
return a * b;
} else {
using UT = std::make_unsigned_t<T>;
return static_cast<T>(static_cast<UT>(a) * static_cast<UT>(b));
}
}
/**
* @private
* @brief Checks for undefined behavior when dividing the minimum signed integer value by -1.
* @tparam T Integer type
* @param[in] a The left operand of the division
* @param[in] b The right operand of the division
* @return Returns true if undefined behavior will occur, false otherwise.
*/
template<typename T>
requires std::integral<T>
bool ub_signed_int_min_div_minus_one(T a, T b) {
if constexpr (std::is_signed_v<T>) {
// For signed value, `INT_MIN / -1` may cause overflow,
// which finally cause the undefined behavior,
// due to the truth that `INT_MIN == -INT_MIN - 1`.
if (b == -1 && a == std::numeric_limits<T>::min()) {
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* @private
* @brief Checks for the undefined behavior of division by zero.
* @tparam T Integer type
* @param[in] a The left operand of the division
* @param[in] b The right operand of the division
* @return Returns true if undefined behavior will occur, false otherwise.
*/
template<typename T>
requires std::integral<T>
bool ub_div_zero([[maybe_unused]] T a, T b) {
return b == 0;
}
#pragma endregion
#pragma region Hardware Operation Overflow
// YYC MARK:
// If we are using Windows function family,
// we define a convenient macro assisting overflow calculation.
#if defined(YYCC_HARDWARE_OVERFLOW_WIN32_FNS)
#define WIN_EASY_OPER(fn, ty, a, b, c) FAILED(fn(static_cast<ty>(a), static_cast<ty>(b), reinterpret_cast<ty*>(c)))
#endif
/**
* @private
* @brief Addition with overflow detection based on hardware instructions.
* @param[in] a The left operand of the addition.
* @param[in] b The right operand of the addition.
* @param[out] c The pointer to the variable storing result.
* @return Returns true if an overflow occurs, false otherwise.
*/
template<typename T>
requires std::integral<T>
bool hardware_add_overflow(T a, T b, T* c) {
if (c == nullptr) [[unlikely]]
throw std::logic_error("invalid nullptr");
#if defined(YYCC_HARDWARE_OVERFLOW_GCC_FNS)
return __builtin_add_overflow(a, b, c);
#else
bool overflow = false;
constexpr size_t T_SIZE = sizeof(T);
if constexpr (std::is_signed_v<T>) {
if constexpr (T_SIZE == 8) {
overflow = WIN_EASY_OPER(LongLongAdd, LONGLONG, a, b, c);
} else if constexpr (T_SIZE == 4) {
overflow = WIN_EASY_OPER(LongAdd, LONG, a, b, c);
} else if constexpr (T_SIZE == 2) {
overflow = WIN_EASY_OPER(ShortAdd, SHORT, a, b, c);
} else if constexpr (T_SIZE == 1) {
overflow = WIN_EASY_OPER(Int8Add, INT8, a, b, c);
} else {
static_assert(std::false_type::value, "not supported integral type.");
}
} else {
if constexpr (T_SIZE == 8) {
overflow = WIN_EASY_OPER(ULongLongAdd, ULONGLONG, a, b, c);
} else if constexpr (T_SIZE == 4) {
overflow = WIN_EASY_OPER(ULongAdd, ULONG, a, b, c);
} else if constexpr (T_SIZE == 2) {
overflow = WIN_EASY_OPER(UShortAdd, USHORT, a, b, c);
} else if constexpr (T_SIZE == 1) {
overflow = WIN_EASY_OPER(UInt8Add, UINT8, a, b, c);
} else {
static_assert(std::false_type::value, "not supported integral type.");
}
}
// Due to the limitation of Windows function family,
// if overflow or underflow occurs, there is no calculation result.
// So we need fill the wrapping value manually.
if (overflow) *c = ub_signed_int_add<T>(a, b);
return overflow;
#endif
}
/**
* @private
* @brief Subtraction with overflow detection based on hardware instructions.
* @param[in] a The left operand of the subtraction.
* @param[in] b The right operand of the subtraction.
* @param[out] c The pointer to the variable storing the result.
* @return Returns true if an overflow occurs, false otherwise.
*/
template<typename T>
requires std::integral<T>
bool hardware_sub_overflow(T a, T b, T* c) {
if (c == nullptr) [[unlikely]]
throw std::logic_error("invalid nullptr");
#if defined(YYCC_HARDWARE_OVERFLOW_GCC_FNS)
return __builtin_sub_overflow(a, b, c);
#else
bool overflow = false;
constexpr size_t T_SIZE = sizeof(T);
if constexpr (std::is_signed_v<T>) {
if constexpr (T_SIZE == 8) {
overflow = WIN_EASY_OPER(LongLongSub, LONGLONG, a, b, c);
} else if constexpr (T_SIZE == 4) {
overflow = WIN_EASY_OPER(LongSub, LONG, a, b, c);
} else if constexpr (T_SIZE == 2) {
overflow = WIN_EASY_OPER(ShortSub, SHORT, a, b, c);
} else if constexpr (T_SIZE == 1) {
overflow = WIN_EASY_OPER(Int8Sub, INT8, a, b, c);
} else {
static_assert(std::false_type::value, "not supported integral type.");
}
} else {
if constexpr (T_SIZE == 8) {
overflow = WIN_EASY_OPER(ULongLongSub, ULONGLONG, a, b, c);
} else if constexpr (T_SIZE == 4) {
overflow = WIN_EASY_OPER(ULongSub, ULONG, a, b, c);
} else if constexpr (T_SIZE == 2) {
overflow = WIN_EASY_OPER(UShortSub, USHORT, a, b, c);
} else if constexpr (T_SIZE == 1) {
overflow = WIN_EASY_OPER(UInt8Sub, UINT8, a, b, c);
} else {
static_assert(std::false_type::value, "not supported integral type.");
}
}
// Similarly, manually calculate wrapping value.
if (overflow) *c = ub_signed_int_sub<T>(a, b);
return overflow;
#endif
}
/**
* @private
* @brief Multiplication with overflow detection based on hardware instructions.
* @param[in] a The left operand of the multiplication.
* @param[in] b The right operand of the multiplication.
* @param[out] c The reference to the variable storing the result.
* @return Returns true if an overflow occurs, false otherwise.
*/
template<typename T>
requires std::integral<T>
bool hardware_mul_overflow(T a, T b, T* c) {
if (c == nullptr) [[unlikely]]
throw std::logic_error("invalid nullptr");
#if defined(YYCC_HARDWARE_OVERFLOW_GCC_FNS)
return __builtin_mul_overflow(a, b, c);
#else
bool overflow = false;
constexpr size_t T_SIZE = sizeof(T);
if constexpr (std::is_signed_v<T>) {
if constexpr (T_SIZE == 8) {
overflow = WIN_EASY_OPER(LongLongMult, LONGLONG, a, b, c);
} else if constexpr (T_SIZE == 4) {
overflow = WIN_EASY_OPER(LongMult, LONG, a, b, c);
} else if constexpr (T_SIZE == 2) {
overflow = WIN_EASY_OPER(ShortMult, SHORT, a, b, c);
} else if constexpr (T_SIZE == 1) {
overflow = WIN_EASY_OPER(Int8Mult, INT8, a, b, c);
} else {
static_assert(std::false_type::value, "not supported integral type.");
}
} else {
if constexpr (T_SIZE == 8) {
overflow = WIN_EASY_OPER(ULongLongMult, ULONGLONG, a, b, c);
} else if constexpr (T_SIZE == 4) {
overflow = WIN_EASY_OPER(ULongMult, ULONG, a, b, c);
} else if constexpr (T_SIZE == 2) {
overflow = WIN_EASY_OPER(UShortMult, USHORT, a, b, c);
} else if constexpr (T_SIZE == 1) {
overflow = WIN_EASY_OPER(UInt8Mult, UINT8, a, b, c);
} else {
static_assert(std::false_type::value, "not supported integral type.");
}
}
// Similarly, manually calculate wrapping value.
if (overflow) *c = ub_signed_int_mul<T>(a, b);
return overflow;
#endif
}
// YYC MARK:
// Delete the defined macro to prevent polluting the content later.
#if defined(YYCC_HARDWARE_OVERFLOW_WIN32_FNS)
#undef WIN_EASY_OPER
#endif
#pragma endregion
#pragma region Wrapping operations
// YYC MARK:
// wrapping_* function family will wrap the result in any scenario.
/**
* @brief Performs a wrapping addition operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the addition.
* @param[in] b The right operand of the addition.
* @return The wrapping result of the addition operation.
*/
template<typename T>
requires std::integral<T>
T wrapping_add(T a, T b) {
return ub_signed_int_add(a, b);
}
/**
* @brief Performs a wrapping subtraction operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the subtraction.
* @param[in] b The right operand of the subtraction.
* @return The wrapping result of the subtraction operation.
*/
template<typename T>
requires std::integral<T>
T wrapping_sub(T a, T b) {
return ub_signed_int_sub(a, b);
}
/**
* @brief Performs a wrapping multiplication operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the multiplication.
* @param[in] b The right operand of the multiplication.
* @return The wrapping result of the multiplication operation.
*/
template<typename T>
requires std::integral<T>
T wrapping_mul(T a, T b) {
return ub_signed_int_mul(a, b);
}
/**
* @brief Performs a wrapping division operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the division.
* @param[in] b The right operand of the division.
* @return The wrapping result of the division operation.
* @exception std::logic_error If division by zero occurs.
*/
template<typename T>
requires std::integral<T>
T wrapping_div(T a, T b) {
// Division by zero is undefined behavior.
if (ub_div_zero(a, b)) throw std::logic_error("div with 0");
// `INT_MIN / -1` overflow undefined behavior.
if (ub_signed_int_min_div_minus_one(a, b))
// "a" self is the minimum value of signed integer, return it directly.
// There is no need to re-fetch it by std::numeric_limits.
return a;
return a / b;
}
#pragma endregion
#pragma region Checked operations
// YYC MARK:
// If overflow occurs when using checked_* function family,
// these functions will return std::nullopt, otherwise the computed result.
/**
* @brief Performs a checked addition operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the addition.
* @param[in] b The right operand of the addition.
* @return An std::optional containing the result if no overflow occurs, otherwise std::nullopt.
*/
template<typename T>
requires std::integral<T>
std::optional<T> checked_add(T a, T b) {
T result;
if (hardware_add_overflow(a, b, &result)) return std::nullopt;
return result;
}
/**
* @brief Performs a checked subtraction operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the subtraction.
* @param[in] b The right operand of the subtraction.
* @return An std::optional containing the result if no overflow occurs, otherwise std::nullopt.
*/
template<typename T>
requires std::integral<T>
std::optional<T> checked_sub(T a, T b) {
T result;
if (hardware_sub_overflow(a, b, &result)) return std::nullopt;
return result;
}
/**
* @brief Performs a checked multiplication operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the multiplication.
* @param[in] b The right operand of the multiplication.
* @return An std::optional containing the result if no overflow occurs, otherwise std::nullopt.
*/
template<typename T>
requires std::integral<T>
std::optional<T> checked_mul(T a, T b) {
T result;
if (hardware_mul_overflow(a, b, &result)) return std::nullopt;
return result;
}
/**
* @brief Performs a checked division operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the division.
* @param[in] b The right operand of the division.
* @return
* An std::optional containing the result,
* if no undefined behavior (division by zero or overflow) occurs,
* otherwise std::nullopt.
*/
template<typename T>
requires std::integral<T>
std::optional<T> checked_div(T a, T b) {
// Division by zero is undefined behavior.
if (ub_div_zero(a, b)) return std::nullopt;
// `INT_MIN / -1` overflow undefined behavior.
if (ub_signed_int_min_div_minus_one(a, b)) return std::nullopt;
return a / b;
}
#pragma endregion
#pragma region Overflowing operations
// YYC MARK:
// overflowing_* function family return a tuple with 2 items as the result.
// First is the wrapping value and second is a boolean indicates whether overflow occurs.
/**
* @brief The result returned by the overflow function family.
* @details
* The first item is the operation result.
* The second item indicates whether an overflow has occurred. true means overflow, otherwise false.
*/
template<typename T>
requires std::integral<T>
using OverflowingPair = std::pair<T, bool>;
template<typename T>
requires std::integral<T>
OverflowingPair<T> overflowing_add(T a, T b) {
T result;
bool overflow = hardware_add_overflow(a, b, &result);
return std::make_pair(result, overflow);
}
template<typename T>
requires std::integral<T>
OverflowingPair<T> overflowing_sub(T a, T b) {
T result;
bool overflow = hardware_sub_overflow(a, b, &result);
return std::make_pair(result, overflow);
}
template<typename T>
requires std::integral<T>
OverflowingPair<T> overflowing_mul(T a, T b) {
T result;
bool overflow = hardware_mul_overflow(a, b, &result);
return std::make_pair(result, overflow);
}
template<typename T>
requires std::integral<T>
OverflowingPair<T> overflowing_div(T a, T b) {
// Division by zero is undefined behavior.
if (ub_div_zero(a, b)) throw std::logic_error("div with 0");
// `INT_MIN / -1` overflow undefined behavior.
if (ub_signed_int_min_div_minus_one(a, b)) {
// "a" self is minimum value, no need to get it again.
return std::make_pair(a, true);
} else {
return std::make_pair(a / b, false);
}
}
#pragma endregion
#pragma region Saturating operations
/*
YYC MARK:
The direction of saturation is pretty simple for unsigned integer.
However, it is slightly complex for signed integer.
In detail, it follow following rules:
Acknowledge the truth for signed integer: abs(MIN) = abs(MAX) + 1
- ADD operation:
- Range operation rule: [a, b] + [c, d] = [a+c, b+d]
- Pos+Pos -> [0, MAX] + [0, MAX] -> [0, 2 * MAX]. May overflow -> Saturating to MAX.
- Neg+Neg -> [MIN, -1] + [MIN, -1] -> [2 * MIN, -2]. May underflow -> Saturating to MIN.
- Pos+Neg -> [0, MAX] + [MIN, -1] -> [MIN, MAX - 1]. Impossible overflow or underflow.
- SUB Operation:
- Range operation rule: [a, b] - [c, d] = [a-d, b-c]
- Pos-Neg -> [0, MAX] - [MIN, -1] -> [1, MAX - MIN]. Maybe overflow -> Saturating to MAX.
- Neg-Pos -> [MIN, -1] - [0, MAX] -> [MIN - MAX, -1]. Maybe underflow -> Saturating to MIN.
- Pos-Pos -> [0, MAX] - [0, MAX] -> [-MAX, MAX]. Impossible overflow or underflow.
- Neg-Neg -> [MIN, -1] - [MIN, -1] -> [MIN + 1, -(MIN + 1)]. Impossible overflow or underflow.
- MUL Operation:
- Pos*Pos -> Maybe overflow -> Saturating to MAX.
- Pos*Neg -> Maybe underflow -> Saturating to MIN.
- Neg*Neg -> Maybe overflow -> Saturating to MAX.
*/
/**
* @brief Performs a saturating addition operation on two integers.
* @details
* If an overflow occurs during the addition,
* the result will be saturated to the maximum/minimum value,
* according to the the direction of overflow (overflow or underflow).
* @tparam T Integer type.
* @param[in] a The left operand of the addition.
* @param[in] b The right operand of the addition.
* @return The result of the saturating addition operation.
*/
template<typename T>
requires std::integral<T>
T saturating_add(T a, T b) {
T result;
if (hardware_add_overflow(a, b, &result)) {
using Limits = std::numeric_limits<T>;
if constexpr (std::is_unsigned_v<T>) {
return Limits::max();
} else {
// Overflow only occurs when 2 operand have same sign.
// So we simply check the sign of one of them.
return (a > 0) ? Limits::max() : Limits::min();
}
}
return result;
}
/**
* @brief Performs a saturating subtraction operation on two integers.
* @details
* If an overflow occurs during the subtraction,
* the result will be saturated to the maximum/minimum value,
* according to the the direction of overflow (overflow or underflow).
* @tparam T Integer type.
* @param[in] a The left operand of the subtraction.
* @param[in] b The right operand of the subtraction.
* @return The result of the saturating subtraction operation.
*/
template<typename T>
requires std::integral<T>
T saturating_sub(T a, T b) {
T result;
if (hardware_sub_overflow(a, b, &result)) {
using Limits = std::numeric_limits<T>;
if constexpr (std::is_unsigned_v<T>) {
return 0;
} else {
// Overflow only occurs when 2 operand have different sign.
// So we simply compare these 2 operand.
// If a < b, then "a" is negative, otherwise positive.
return (a < b) ? Limits::min() : Limits::max();
}
}
return result;
}
/**
* @brief Performs a saturating multiplication operation on two integers.
* @details
* If an overflow occurs during the multiplication,
* the result will be saturated to the maximum/minimum value,
* according to the the direction of overflow (overflow or underflow).
* @tparam T Integer type.
* @param[in] a The left operand of the multiplication.
* @param[in] b The right operand of the multiplication.
* @return The result of the saturating multiplication operation.
*/
template<typename T>
requires std::integral<T>
T saturating_mul(T a, T b) {
T result;
if (hardware_mul_overflow(a, b, &result)) {
using Limits = std::numeric_limits<T>;
if constexpr (std::is_unsigned_v<T>) {
return Limits::max();
} else {
// Check whether 2 operands have different sign.
// If the result of XOR is true, these 2 operands should have different sign.
return ((a ^ b) < 0) ? Limits::min() : Limits::max();
}
}
return result;
}
/**
* @brief Performs a saturating division operation on two integers.
* @details
* If an overflow occurs during the division,
* the result will be saturated to the maximum/minimum value,
* according to the the direction of overflow (overflow or underflow).
* @tparam T Integer type.
* @param[in] a The left operand of the division.
* @param[in] b The right operand of the division.
* @return The result of the saturating division operation.
* @exception std::logic_error If division by zero occurs.
*/
template<typename T>
requires std::integral<T>
T saturating_div(T a, T b) {
// Division by zero is undefined behavior.
if (ub_div_zero(a, b)) throw std::logic_error("div with zero");
// `INT_MIN / -1` overflow undefined behavior.
if (ub_signed_int_min_div_minus_one(a, b)) {
return std::numeric_limits<T>::max();
} else {
return a / b;
}
}
#pragma endregion
#pragma region Ordinary operations
// YYC MARK:
// Rust-way add, sub, mul and div operators.
// There is no any undefined behavior which may occurs in these functions.
// These normal operator is just an alias to wrapping_* function family.
/**
* @brief Performs an addition operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the addition.
* @param[in] b The right operand of the addition.
* @return The result of the addition operation.
*/
template<typename T>
requires std::integral<T>
T add(T a, T b) {
return wrapping_add(a, b);
}
/**
* @brief Performs a subtraction operation on two integers.
* @tparam T Integer type
* @param[in] a The left operand of the subtraction.
* @param[in] b The right operand of the subtraction.
* @return The result of the subtraction operation.
*/
template<typename T>
requires std::integral<T>
T sub(T a, T b) {
return wrapping_sub(a, b);
}
/**
* @brief Performs a multiplication operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the multiplication.
* @param[in] b The right operand of the multiplication.
* @return The result of the multiplication operation.
*/
template<typename T>
requires std::integral<T>
T mul(T a, T b) {
return wrapping_mul(a, b);
}
/**
* @brief Performs a division operation on two integers.
* @tparam T Integer type.
* @param[in] a The left operand of the division.
* @param[in] b The right operand of the division.
* @return The result of the division operation.
* @exception std::logic_error If division by zero or value overflow occurs.
*/
template<typename T>
requires std::integral<T>
T div(T a, T b) {
return wrapping_div(a, b);
}
#pragma endregion
} // namespace yycc::num::safe_op
// YYC MARK:
// Delete the macro definition to prevent polluting the content later.
#undef YYCC_HARDWARE_OVERFLOW_GCC_FNS
#undef YYCC_HARDWARE_OVERFLOW_WIN32_FNS

100
src/yycc/num/stringify.hpp Normal file
View File

@ -0,0 +1,100 @@
#pragma once
#include "../string/reinterpret.hpp"
#include <string>
#include <array>
#include <type_traits>
#include <charconv>
#include <stdexcept>
#define NS_YYCC_STRING_REINTERPRET ::yycc::string::reinterpret
/**
* @brief Provides stringify utilities for converting numeric and boolean values to strings.
* @details
* This namespace contains functions for stringifying various numeric types (integer, floating point)
* and boolean values into string. It uses \c std::to_chars internally for efficient stringify.
* @remarks
* See https://en.cppreference.com/w/cpp/utility/to_chars for underlying called functions.
* Default float precision = 6 is gotten from: https://en.cppreference.com/w/c/io/fprintf
*/
namespace yycc::num::stringify {
/// @private
/// @brief Size of the internal buffer used for string conversion.
inline constexpr size_t STRINGIFY_BUFFER_SIZE = 64u;
/// @private
/// @brief Type alias for the buffer used in string conversion.
using StringifyBuffer = std::array<char8_t, STRINGIFY_BUFFER_SIZE>;
/**
* @brief Return the string representation of given floating point value.
* @tparam T The type derived from floating point type.
* @param[in] num The value need to get string representation.
* @param[in] fmt The floating point format used when getting string representation.
* @param[in] precision The floating point precision used when getting string representation.
* @return The string representation of given value.
*/
template<typename T>
requires(std::is_floating_point_v<T>)
std::u8string stringify(T num, std::chars_format fmt = std::chars_format::general, int precision = 6) {
namespace reinterpret = NS_YYCC_STRING_REINTERPRET;
StringifyBuffer buffer;
auto [ptr, ec] = std::to_chars(reinterpret::as_ordinary(buffer.data()),
reinterpret::as_ordinary(buffer.data() + buffer.size()),
num,
fmt,
precision);
if (ec == std::errc()) {
return std::u8string(buffer.data(), reinterpret::as_utf8(ptr) - buffer.data());
} else if (ec == std::errc::value_too_large) {
// Too short buffer. This should not happen.
throw std::out_of_range("stringify() buffer is not sufficient.");
} else {
// Unreachable
throw std::runtime_error("unreachable code.");
}
}
/**
* @brief Return the string representation of given integral value.
* @tparam T The type derived from integral type except bool type.
* @param[in] num The value need to get string representation.
* @param[in] base Integer base used when getting string representation: a value between 2 and 36 (inclusive).
* @return The string representation of given value.
*/
template<typename T>
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
std::u8string stringify(T num, int base = 10) {
namespace reinterpret = NS_YYCC_STRING_REINTERPRET;
StringifyBuffer buffer;
auto [ptr, ec] = std::to_chars(reinterpret::as_ordinary(buffer.data()),
reinterpret::as_ordinary(buffer.data() + buffer.size()),
num,
base);
if (ec == std::errc()) {
return std::u8string(buffer.data(), reinterpret::as_utf8(ptr) - buffer.data());
} else if (ec == std::errc::value_too_large) {
// Too short buffer. This should not happen.
throw std::out_of_range("stringify() buffer is not sufficient.");
} else {
// Unreachable
throw std::runtime_error("unreachable code.");
}
}
/**
* @brief Return the string representation of given bool value.
* @tparam T The type derived from bool type.
* @param[in] num The value need to get string representation.
* @return The string representation of given value ("true" or "false").
*/
template<typename T>
requires(std::is_same_v<T, bool>)
std::u8string stringify(T num) {
if (num) return std::u8string(u8"true");
else return std::u8string(u8"false");
}
} // namespace yycc::num::stringify
#undef NS_YYCC_STRING_REINTERPRET

35
src/yycc/patch/fopen.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "fopen.hpp"
#include "../macro/os_detector.hpp"
#include "../string/reinterpret.hpp"
#if defined(YYCC_OS_WINDOWS)
#include "../windows/import_guard_head.hpp"
#include <Windows.h>
#include "../windows/import_guard_tail.hpp"
#endif
#define REINTERPRET ::yycc::string::reinterpret
namespace yycc::patch::fopen {
std::FILE* fopen(const char8_t* u8_filepath, const char8_t* u8_mode) {
// TODO: Fix this after finish Windows encoding
// #if defined(YYCC_OS_WINDOWS)
// // convert mode and file path to wchar
// std::wstring wmode, wpath;
// if (!YYCC::EncodingHelper::UTF8ToWchar(u8_mode, wmode))
// return nullptr;
// if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filepath, wpath))
// return nullptr;
// // call microsoft specified fopen which support wchar as argument.
// return _wfopen(wpath.c_str(), wmode.c_str());
// #else
// return std::fopen(REINTERPRET::as_ordinary(u8_filepath), REINTERPRET::as_ordinary(u8_mode));
// #endif
return std::fopen(REINTERPRET::as_ordinary(u8_filepath), REINTERPRET::as_ordinary(u8_mode));
}
}

30
src/yycc/patch/fopen.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <memory>
namespace yycc::patch::fopen {
/// @brief C++ standard deleter for std::FILE*
class StdFileDeleter {
public:
StdFileDeleter() {}
void operator() (std::FILE* ptr) {
if (ptr != nullptr) {
std::fclose(ptr);
}
}
};
/// @brief Smart unique pointer of \c std::FILE*
using SmartStdFile = std::unique_ptr<std::FILE, StdFileDeleter>;
/**
* @brief The UTF8 version of \c std::fopen.
* @param[in] u8_filepath The UTF8 encoded path to the file to be opened.
* @param[in] u8_mode UTF8 encoded mode string of the file to be opened.
* @remarks
* This function is suit for Windows because std::fopen do not support UTF8 on Windows.
* On other platforms, this function will delegate request directly to std::fopen.
* @return \c FILE* of the file to be opened, or nullptr if failed.
*/
std::FILE* fopen(const char8_t* u8_filepath, const char8_t* u8_mode);
}

View File

@ -0,0 +1,20 @@
#pragma once
#include "../macro/ptr_size_detector.hpp"
#include <cstdint>
/**
* @def PRIXPTR_LPAD
* @brief The left-padding zero format string of HEX-printed pointer type.
* @details
* When printing a pointer with HEX style, we usually hope it can be left-padded with some zero for easy reading.
* In different architecture, the size of this padding is differnet too so we create this macro.
*
* In 32-bit environment, it will be "08" meaning left pad zero until 8 number position.
* In 64-bit environment, it will be "016" meaning left pad zero until 16 number position.
*/
#if defined(YYCC_PTRSIZE_32)
#define PRIXPTR_LPAD "08"
#else
#define PRIXPTR_LPAD "016"
#endif

25
src/yycc/rust/option.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <optional>
/**
* @brief The reproduction of Rust Option type.
* @details
* This namespace reproduce Rust Option type, and its members Some and None in C++.
* However Option is not important than Result, so its implementation is very casual.
*/
namespace yycc::rust::option {
template<typename T>
using Option = std::optional<T>;
template<typename OptionType, typename... Args>
OptionType Some(Args &&...args) {
return OptionType(std::in_place, std::forward<Args>(args)...);
}
template<typename OptionType>
OptionType None() {
return OptionType(std::nullopt);
}
} // namespace yycc::rust::option

36
src/yycc/rust/panic.cpp Normal file
View File

@ -0,0 +1,36 @@
#include "panic.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <stacktrace>
namespace yycc::rust::panic {
void panic(const char* file, int line, const std::string_view& msg) {
// Output message in stderr.
auto& dst = std::cerr;
// TODO: Fix colorful output when finishing `termcolor` lib.
// Print error message if we support it.
// // Setup color
// dst << FOREGROUND<Color::Red>;
// File name and line number message
dst << "program paniked at " << std::quoted(file) << ":Ln" << line << std::endl;
// User custom message
dst << "note: " << msg << std::endl;
// Stacktrace message if we support it.
dst << "stacktrace: " << std::endl;
dst << std::stacktrace::current() << std::endl;
// // Restore color
// dst << RESET;
// Make sure all messages are flushed into screen.
dst.flush();
// Force exit
std::abort();
}
} // namespace yycc::rust::panic

50
src/yycc/rust/panic.hpp Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include <string_view>
#include <format>
/**
* @brief Provides Rust-style panic functionality for immediate program termination on unrecoverable errors.
* @details
* This namespace provides macros and functions to handle unrecoverable errors in C++ code.
* It imitate Rust's \c panic! macro behavior, allowing the program to immediately exit with error information and stack traces.
*
* After writing programs in Rust, I deeply realized the necessity of handling errors immediately.
* When encountering unrecoverable errors, the program should exit immediately and report the error, which ensures program robustness.
* Therefore, I introduced this namespace and implemented macros and functions equivalent to Rust's \c panic! macro.
*
* Unfortunately, I cannot change the exception mechanism in the standard library.
* The standard library will still throw exceptions where it does, and I cannot prevent that.
* Therefore, I suggest a good practice that any C++ exception should be immediately treated as an error and cause the program to crash and exit.
* For this reason, registering any unhandled error callbacks which may resume the execution of program is prohibited to prevent unexpected continuation of execution.
* For code we write ourselves that we can control, we should use the macros provided in this file instead of throwing exceptions.
* In this way, unexpected behavior in our code will cause the program to exit immediately, outputting error information and stack traces.
* Standard library exceptions will also cause the program to exit, but without stack information.
*/
namespace yycc::rust::panic {
/**
* @brief Immediately crashes the entire program like Rust's \c panic! macro.
* @details The macro parameter is the additional message to display.
*/
#define RS_PANIC(msg) ::yycc::rust::panic::panic(__FILE__, __LINE__, (msg))
/**
* @brief Immediately crashes the entire program like Rust's \c panic! macro.
* @details
* The macro parameters are the message to format and its arguments, following \c std::format syntax.
* This macro essentially calls \c std::format internally.
*/
#define RS_PANICF(msg, ...) RS_PANIC(std::format(msg __VA_OPT__(, ) __VA_ARGS__))
/**
* @brief Immediately crashes the entire program like Rust's \c panic! macro.
* @details
* This is the actual crash output function called by the macros.
* The crash information will be written to \c stderr, including stack traces if your C++ runtime support it.
* @param[in] file Source file name where panic occurred. Usually filled by macros.
* @param[in] line Line number in source file where panic occurred. Usually filled by macros.
* @param[in] msg Message to display during panic.
*/
[[noreturn]] void panic(const char* file, int line, const std::string_view& msg);
} // namespace yycc::rust::panic

49
src/yycc/rust/prelude.hpp Normal file
View File

@ -0,0 +1,49 @@
#pragma once
// Rust prelude section
#include "primitive.hpp"
#include "result.hpp"
#include "option.hpp"
#include "panic.hpp"
#include <vector>
namespace yycc::rust::prelude {
// Include primitive types
#define NS_RUST_PRIMITIVE ::yycc::rust::primitive
using i8 = NS_RUST_PRIMITIVE::i8;
using i16 = NS_RUST_PRIMITIVE::i16;
using i32 = NS_RUST_PRIMITIVE::i32;
using i64 = NS_RUST_PRIMITIVE::i64;
using u8 = NS_RUST_PRIMITIVE::u8;
using u16 = NS_RUST_PRIMITIVE::u16;
using u32 = NS_RUST_PRIMITIVE::u32;
using u64 = NS_RUST_PRIMITIVE::u64;
using isize = NS_RUST_PRIMITIVE::isize;
using usize = NS_RUST_PRIMITIVE::usize;
using f32 = NS_RUST_PRIMITIVE::f32;
using f64 = NS_RUST_PRIMITIVE::f64;
using str = NS_RUST_PRIMITIVE::str;
#undef NS_RUST_PRIMITIVE
// Other types
using String = std::u8string;
template<typename T>
using Vec = std::vector<T>;
// Expose Result and Option
using namespace ::yycc::rust::option;
using namespace ::yycc::rust::result;
// Panic are introduced by including header file
// so we do not need re-expose it.
} // namespace yycc::prelude::rust
// Expose all members
using namespace ::yycc::rust::prelude;

View File

@ -0,0 +1,28 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <string_view>
namespace yycc::rust::primitive {
// `bool` is keyword so should not declare it anymore.
// `char` is keyword so should not declare it anymore.
using i8 = std::int8_t;
using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
using isize = std::ptrdiff_t;
using usize = std::size_t;
using f32 = float;
using f64 = double;
using str = std::u8string_view;
}

77
src/yycc/rust/result.hpp Normal file
View File

@ -0,0 +1,77 @@
#pragma once
#include <expected>
/**
* @brief The reproduction of Rust Option type.
* @details
* After writing programs in Rust, I deeply recognized the advantages of Rust and its indispensable infrastructure Result.
* Therefore, introducing Result into C++ to enhance coding safety is essential.
* I've done my best to simulate Rust's \c Result and its members \c Ok and \c Err (actually, I had DeepSeek simulate them).
*
* Why not write it in C++ style? Because C++'s way of using \c Result is too ugly and not explicit enough.
* In C++'s approach, the expected value is returned directly,
* and when encountering void specialization, you must write a pair of curly braces, which is very unclear.
* For unexpected values, you need to manually construct \c std::unexpected, which is even more painful.
* If you need in-place construction of unexpected values, you even need to put \c std::in_place as the first parameter of the constructor,
* otherwise \c std::unexpected 's constructor won't forward the subsequent parameters to the unexpected value's constructor.
*
* In the \c Result type, type \c E can be any value according to your needs.
* In Rust, an unexpected value type \c Ea can be converted to another unexpected value type \c Eb.
* This feature is implemented through the \c From trait, allowing you to safely wrap one type of unexpected value into another in a function.
* But in C++, we have C++ ways to do the same thing.
* Assuming for each type \c E, we define a separate struct to describe them,
* then we just need to add some extra constructors to the struct to convert them from one type to another.
*
* For example, type \c Ea is a struct named \c IoError.
* In this struct, there is a member of type \c IoErrorKind indicating the category of this IO error.
* At the same time, it has a constructor with its own type as the only parameter, used to construct (copy or move) itself.
* Now in a function, we want to convert it to another type \c Eb named \c SystemError .
* All you need to do is create a new struct named \c SystemError, then write all necessary constructors and other functions for it.
* Then, the key point is to add a constructor with parameter type <TT>const IoError&</TT>.
* This way, we can simply convert type \c Ea to type \c Eb through calls like: <TT>Err<Result<T, E>>(result.error());</TT>.
*
* In Rust, if you want to get human-readable descriptions of unexpected values, you must implement the \c Display trait.
* But you don't need to do this in C++, you must write your own conversion functions to adapt to various output requirements.
* For example, when using \c std::format, you need to write suitable formatting adapters for \c std::format.
* Similarly, when using \c std::cerr 's \c operator<< overload, you also need to write suitable adapters.
* @remarks This namespace only work with environment supporting `std::expected` (i.e. C++ 23).
*/
namespace yycc::rust::result {
/**
* @brief Equivalent Rust \c Result in C++
* @tparam T The type of the expected value.
* @tparam E The type of the unexpected value.
*/
template<typename T, typename E>
using Result = std::expected<T, E>;
/**
* @brief Equvialent Rust \c Result::Ok in C++.
* @tparam ResultType The type of the Result instance.
* @param[in] args The arguments for building expected value.
* @return An built Result instance with expected value.
*/
template<typename ResultType, typename... Args>
ResultType Ok(Args &&...args) {
using T = ResultType::value_type;
if constexpr (!std::is_void_v<T>) {
return ResultType(std::in_place, std::forward<Args>(args)...);
} else {
static_assert(sizeof...(Args) == 0, "Ok<void> cannot accept arguments");
return ResultType(std::in_place);
}
}
/**
* @brief Equvialent Rust \c Result::Err in C++.
* @tparam ResultType The type of the Result instance.
* @param[in] args The arguments for building unexpected value.
* @return An built Result instance with unexpected value.
*/
template<typename ResultType, typename... Args>
ResultType Err(Args &&...args) {
return ResultType(std::unexpect, std::forward<Args>(args)...);
}
} // namespace yycc::rust::result

211
src/yycc/string/op.cpp Normal file
View File

@ -0,0 +1,211 @@
#include "op.hpp"
#include <algorithm>
namespace yycc::string::op {
#pragma region Printf VPrintf
template<typename TChar>
requires(sizeof(TChar) == sizeof(char))
static FormatResult<std::basic_string<TChar>> generic_printf(const TChar* format, va_list argptr) {
// Prepare result
std::basic_string<TChar> rv;
// Check format
if (format == nullptr) return std::unexpected(FormatError::NullFormat);
// Prepare variable arguments
va_list args1;
va_copy(args1, argptr);
va_list args2;
va_copy(args2, argptr);
// The return value is desired char count without NULL terminal.
// Minus number means error.
int count = std::vsnprintf(nullptr, 0, reinterpret_cast<const char*>(format), args1);
// Check expected size.
if (count < 0) {
// Invalid length returned by vsnprintf.
return std::unexpected(FormatError::NoDesiredSize);
}
va_end(args1);
// Resize std::string to desired count, and pass its length + 1 to std::vsnprintf,
// Because std::vsnprintf only can write "buf_size - 1" chars with a trailing NULL.
// However std::vsnprintf already have a trailing NULL, so we plus 1 for it.
rv.resize(count);
int write_result = std::vsnprintf(reinterpret_cast<char*>(rv.data()), rv.size() + 1, reinterpret_cast<const char*>(format), args2);
va_end(args2);
// Check written size.
if (write_result < 0 || write_result > count) {
// Invalid write result in vsnprintf.
return std::unexpected(FormatError::BadWrittenSize);
}
// Return value
return rv;
}
FormatResult<std::u8string> printf(const char8_t* format, ...) {
va_list argptr;
va_start(argptr, format);
auto rv = vprintf(format, argptr);
va_end(argptr);
return rv;
}
FormatResult<std::u8string> vprintf(const char8_t* format, va_list argptr) {
return generic_printf(format, argptr);
}
FormatResult<std::string> printf(const char* format, ...) {
va_list argptr;
va_start(argptr, format);
auto rv = vprintf(format, argptr);
va_end(argptr);
return rv;
}
FormatResult<std::string> vprintf(const char* format, va_list argptr) {
return generic_printf(format, argptr);
}
#pragma endregion
#pragma region Replace
void replace(std::u8string& strl, const std::u8string_view& _from_strl, const std::u8string_view& _to_strl) {
// Reference: https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
// check requirements
// from string should not be empty
std::u8string from_strl(_from_strl);
std::u8string to_strl(_to_strl);
if (from_strl.empty()) return;
// start replace one by one
size_t start_pos = 0;
while ((start_pos = strl.find(from_strl, start_pos)) != std::u8string::npos) {
strl.replace(start_pos, from_strl.size(), to_strl);
start_pos += to_strl.size(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
}
}
std::u8string replace(const std::u8string_view& _strl, const std::u8string_view& _from_strl, const std::u8string_view& _to_strl) {
// prepare result
std::u8string strl(_strl);
replace(strl, _from_strl, _to_strl);
// return value
return strl;
}
#pragma endregion
#pragma region Join
std::u8string join(JoinDataProvider fct_data, const std::u8string_view& delimiter) {
std::u8string ret;
bool is_first = true;
std::u8string_view element;
// fetch element
while (fct_data(element)) {
// insert delimiter
if (is_first) is_first = false;
else {
// append delimiter.
ret.append(delimiter);
}
// insert element if it is not empty
if (!element.empty()) ret.append(element);
}
return ret;
}
#pragma endregion
#pragma region Upper Lower
template<bool BIsToLower>
static void generic_lower_upper(std::u8string& strl) {
// References:
// https://en.cppreference.com/w/cpp/algorithm/transform
// https://en.cppreference.com/w/cpp/string/byte/tolower
std::transform(strl.cbegin(), strl.cend(), strl.begin(), [](unsigned char c) -> char {
if constexpr (BIsToLower) return std::tolower(c);
else return std::toupper(c);
});
}
void lower(std::u8string& strl) {
generic_lower_upper<true>(strl);
}
std::u8string to_lower(const std::u8string_view& strl) {
std::u8string ret(strl);
lower(ret);
return ret;
}
void upper(std::u8string& strl) {
generic_lower_upper<false>(strl);
}
std::u8string to_upper(const std::u8string_view& strl) {
// same as Lower, just replace char transform function.
std::u8string ret(strl);
upper(ret);
return ret;
}
#pragma endregion
#pragma region Split
std::vector<std::u8string_view> split(const std::u8string_view& strl, const std::u8string_view& _delimiter) {
// Reference:
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
// prepare return value
std::vector<std::u8string_view> elems;
// if string need to be splitted is empty, return original string (empty string).
// if delimiter is empty, return original string.
std::u8string delimiter(_delimiter);
if (strl.empty() || delimiter.empty()) {
elems.emplace_back(strl);
return elems;
}
// start spliting
std::size_t previous = 0, current;
while ((current = strl.find(delimiter.c_str(), previous)) != std::u8string::npos) {
elems.emplace_back(strl.substr(previous, current - previous));
previous = current + delimiter.size();
}
// try insert last part but prevent possible out of range exception
if (previous <= strl.size()) {
elems.emplace_back(strl.substr(previous));
}
return elems;
}
std::vector<std::u8string> split_owned(const std::u8string_view& strl, const std::u8string_view& _delimiter) {
// call split view
auto view_result = split(strl, _delimiter);
// copy string view result to string
std::vector<std::u8string> elems;
elems.reserve(view_result.size());
for (const auto& strl_view : view_result) {
elems.emplace_back(std::u8string(strl_view));
}
// return copied result
return elems;
}
#pragma endregion
} // namespace yycc::string::op

166
src/yycc/string/op.hpp Normal file
View File

@ -0,0 +1,166 @@
#pragma once
#include <string>
#include <string_view>
#include <cstdarg>
#include <functional>
#include <vector>
#include <expected>
namespace yycc::string::op {
enum class FormatError {
NullFormat, ///< Given format string is nullptr.
NoDesiredSize, ///< Fail to fetch the expected size of result.
BadWrittenSize, ///< The written size is different with expected size.
};
template<typename T>
using FormatResult = std::expected<T, FormatError>;
/**
* @brief Perform an UTF8 string formatting operation.
* @param[in] format The format string.
* @param[in] ... Argument list of format string.
* @return The formatted result, or the fail reason.
*/
FormatResult<std::u8string> printf(const char8_t* format, ...);
/**
* @brief Perform an UTF8 string formatting operation.
* @param[in] format The format string.
* @param[in] argptr Argument list of format string.
* @return The formatted result, or the fail reason.
*/
FormatResult<std::u8string> vprintf(const char8_t* format, va_list argptr);
/**
* @brief Perform an ordinary string formatting operation.
* @param[in] format The format string.
* @param[in] ... Argument list of format string.
* @return The formatted result, or the fail reason.
*/
FormatResult<std::string> printf(const char* format, ...);
/**
* @brief Perform an ordinary string formatting operation.
* @param[in] format The format string.
* @param[in] argptr Argument list of format string.
* @return The formatted result, or the fail reason.
*/
FormatResult<std::string> vprintf(const char* format, va_list argptr);
/**
* @brief Modify given string with all occurrences of substring \e old replaced by \e new.
* @param[in,out] strl The string for replacing
* @param[in] _from_strl The \e old string.
* @param[in] _to_strl The \e new string.
*/
void replace(std::u8string& strl, const std::u8string_view& _from_strl, const std::u8string_view& _to_strl);
/**
* @brief Return a copy with all occurrences of substring \e old replaced by \e new.
* @param[in] _strl The string for replacing
* @param[in] _from_strl The \e old string.
* @param[in] _to_strl The \e new string.
* @return The result of replacement.
*/
std::u8string replace(const std::u8string_view& _strl, const std::u8string_view& _from_strl, const std::u8string_view& _to_strl);
/**
* @brief The data provider of general join function.
* @details
* For programmer using lambda to implement this function pointer:
* \li During calling, implementation should assign the reference of string view passed in argument
* to the string which need to be joined.
* \li Function return true to continue joining. otherwise return false to stop joining.
* The argument content assigned in the calling returning false is not included in join process.
*/
using JoinDataProvider = std::function<bool(std::u8string_view&)>;
/**
* @brief Universal join function.
* @details
* This function use function pointer as a general data provider interface,
* so this function suit for all types container.
* You can use this universal join function for any custom container by
* using C++ lambda syntax to create a code block adapted to this function pointer.
* @param[in] fct_data The function pointer in JoinDataProvider type prividing the data to be joined.
* @param[in] delimiter The delimiter used for joining.
* @return The result string of joining.
*/
std::u8string join(JoinDataProvider fct_data, const std::u8string_view& delimiter);
/**
* @brief Specialized join function for standard library container.
* @tparam InputIt
* Must meet the requirements of LegacyInputIterator.
* It also can be dereferenced and then implicitly converted to std::u8string_view.
* @param[in] first The beginning of the range of elements to join.
* @param[in] last The terminal of the range of elements to join (exclusive).
* @param[in] delimiter The delimiter used for joining.
* @return The result string of joining.
*/
template<class InputIt>
std::u8string join(InputIt first, InputIt last, const std::u8string_view& delimiter) {
return join(
[&first, &last](std::u8string_view& view) -> bool {
// if we reach tail, return false to stop join process
if (first == last) return false;
// otherwise fetch data, inc iterator and return.
view = *first;
++first;
return true;
},
delimiter);
}
/**
* @brief Convert given string to lowercase.
* @param[in,out] strl The string to be lowercase.
*/
void lower(std::u8string& strl);
/**
* @brief Return a copy of the string converted to lowercase.
* @param[in] strl The string to be lowercase.
* @return The copy of the string converted to lowercase.
*/
std::u8string to_lower(const std::u8string_view& strl);
/**
* @brief Convert given string to uppercase.
* @param[in,out] strl The string to be uppercase.
*/
void upper(std::u8string& strl);
/**
* @brief Return a copy of the string converted to uppercase.
* @param[in] strl The string to be uppercase.
* @return The copy of the string converted to uppercase.
*/
std::u8string to_upper(const std::u8string_view& strl);
// TODO:
// Add strip, lstrip and rstrip functions.
/**
* @brief Split given string with specified delimiter as string view.
* @param[in] strl The string need to be splitting.
* @param[in] _delimiter The delimiter for splitting.
* @return
* The split result with string view format.
* This will not produce any copy of original string.
* \par
* If given string or delimiter are empty,
* the result container will only contain 1 entry which is equal to given string.
* @see Split(const std::u8string_view&, const char8_t*)
*/
std::vector<std::u8string_view> split(const std::u8string_view& strl, const std::u8string_view& _delimiter);
/**
* @brief Split given string with specified delimiter.
* @param[in] strl The string need to be splitting.
* @param[in] _delimiter The delimiter for splitting.
* @return
* The split result.
* \par
* If given string or delimiter are empty,
* the result container will only contain 1 entry which is equal to given string.
*/
std::vector<std::u8string> split_owned(const std::u8string_view& strl, const std::u8string_view& _delimiter);
// TODO:
// Add lazy_split(const std::u8string_view& strl, const std::u8string_view& _delimiter);
// Once we add it, we need redirect all split function into it.
} // namespace yycc::string::op

View File

@ -0,0 +1,31 @@
#include "reinterpret.hpp"
namespace yycc::string::reinterpret {
const char8_t* as_utf8(const char* src) {
return reinterpret_cast<const char8_t*>(src);
}
char8_t* as_utf8(char* src) {
return reinterpret_cast<char8_t*>(src);
}
std::u8string as_utf8(const std::string_view& src) {
return std::u8string(reinterpret_cast<const char8_t*>(src.data()), src.size());
}
std::u8string_view as_utf8_view(const std::string_view& src) {
return std::u8string_view(reinterpret_cast<const char8_t*>(src.data()), src.size());
}
const char* as_ordinary(const char8_t* src) {
return reinterpret_cast<const char*>(src);
}
char* as_ordinary(char8_t* src) {
return reinterpret_cast<char*>(src);
}
std::string as_ordinary(const std::u8string_view& src) {
return std::string(reinterpret_cast<const char*>(src.data()), src.size());
}
std::string_view as_ordinary_view(const std::u8string_view& src) {
return std::string_view(reinterpret_cast<const char*>(src.data()), src.size());
}
} // namespace yycc::string::reinterpret

View File

@ -0,0 +1,64 @@
#pragma once
#include <string>
#include <string_view>
/**
* @brief Provides utilities for reinterpretation between UTF-8 and ordinary string types.
* @details
* Please note that there is no encoding convertion happended in this namespace provided functions.
* They just simply reinterpret one string to another string.
* The validation of UTF8 string is guaranteed by user self.
*/
namespace yycc::string::reinterpret {
/**
* @brief Reinterpret ordinary C-string to UTF-8 string (const version).
* @param src Source ordinary string
* @return Pointer to UTF-8 encoded string
*/
const char8_t* as_utf8(const char* src);
/**
* @brief Reinterpret ordinary C-string as an UTF-8 string (non-const version).
* @param src Source ordinary string
* @return Pointer to UTF-8 encoded string
*/
char8_t* as_utf8(char* src);
/**
* @brief Reinterpret ordinary string view to copied UTF-8 string.
* @param src Source ordinary string view
* @return UTF-8 encoded string
*/
std::u8string as_utf8(const std::string_view& src);
/**
* @brief Reinterpret ordinary string view to UTF-8 string view.
* @param src Source ordinary string view
* @return UTF-8 encoded string view
*/
std::u8string_view as_utf8_view(const std::string_view& src);
/**
* @brief Reinterpret UTF-8 C-string to ordinary string (const version).
* @param src Source UTF-8 string
* @return Pointer to ordinary string
*/
const char* as_ordinary(const char8_t* src);
/**
* @brief Reinterpret UTF-8 C-string to ordinary string (non-const version).
* @param src Source UTF-8 string
* @return Pointer to ordinary string
*/
char* as_ordinary(char8_t* src);
/**
* @brief Reinterpret UTF-8 string view to ordinary string.
* @param src Source UTF-8 string view
* @return Ordinary string
*/
std::string as_ordinary(const std::u8string_view& src);
/**
* @brief Reinterpret UTF-8 string view to ordinary string view
* @param src Source UTF-8 string view
* @return Ordinary string view
*/
std::string_view as_ordinary_view(const std::u8string_view& src);
}

5
src/yycc/version.hpp Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#define YYCC_VER_MAJOR 2
#define YYCC_VER_MINOR 0
#define YYCC_VER_PATCH 0

View File

@ -0,0 +1,9 @@
// It is by design that no pragma once or #if to prevent deplicated including.
// Because this header is the part of wrapper, not a real header.
// #pragma once
// YYC MARK:
// Since YYCC 2.0 version, we use CMake to handle Windows shitty macros,
// so we do not need declare WIN32_LEAN_AND_MEAN or NOMINMAX in there.
// But for keeping the pair of this guard, we still keep this header file,
// although it do nothing.

View File

@ -0,0 +1,23 @@
// It is by design that no pragma once or #if to prevent deplicated including.
// Because this header is the part of wrapper, not a real header.
// #pragma once
#include "../macro/os_detector.hpp"
#if defined(YYCC_OS_WINDOWS)
// Windows also will generate following macros which may cause
// the function sign is different in Windows and other platforms.
// So we simply remove them.
// At the same time, because `#undef` will not throw error if there are no matched macro,
// we can simply use `#undef` to remove them directly.
#undef GetObject
#undef GetClassName
#undef LoadImage
#undef GetTempPath
#undef GetModuleFileName
#undef CopyFile
#undef MoveFile
#undef DeleteFile
#endif

View File

@ -3,34 +3,43 @@ add_executable(YYCCTestbench "")
# Setup testbench sources
target_sources(YYCCTestbench
PRIVATE
shared/literals.cpp
main.cpp
yycc/macro/version_cmp.cpp
yycc/flag_enum.cpp
yycc/constraint.cpp
yycc/constraint/builder.cpp
yycc/patch/ptr_pad.cpp
yycc/string/op.cpp
yycc/string/reinterpret.cpp
yycc/num/parse.cpp
yycc/num/stringify.cpp
yycc/num/op.cpp
yycc/num/safe_cast.cpp
yycc/num/safe_op.cpp
yycc/encoding/stl.cpp
yycc/encoding/windows.cpp
yycc/encoding/iconv.cpp
)
# Add YYCC as its library
target_include_directories(YYCCTestbench
target_sources(YYCCTestbench
PRIVATE
YYCCommonplace
FILE_SET HEADERS
FILES
shared/literals.hpp
)
# Setup headers
target_include_directories(YYCCTestbench
PUBLIC
"${CMAKE_CURRENT_LIST_DIR}"
)
# Setup libraries
target_link_libraries(YYCCTestbench
PRIVATE
YYCCommonplace
)
# Setup C++ standard
target_compile_features(YYCCTestbench PUBLIC cxx_std_17)
set_target_properties(YYCCTestbench PROPERTIES CXX_EXTENSION OFF)
# Order Unicode charset for private using
target_compile_definitions(YYCCTestbench
PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:UNICODE>
$<$<CXX_COMPILER_ID:MSVC>:_UNICODE>
)
# Order build as UTF-8 in MSVC
target_compile_options(YYCCTestbench
PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
GTest::gtest_main
)
# Install testbench only on Release mode
install(TARGETS YYCCTestbench
CONFIGURATIONS Release RelWithDebInfo MinSizeRel
RUNTIME DESTINATION ${YYCC_INSTALL_BIN_PATH}
)
# Discover all test
include(GoogleTest)
gtest_discover_tests(YYCCTestbench)

View File

@ -1,735 +1,6 @@
#include <YYCCommonplace.hpp>
#include <cstdio>
#include <set>
#include <map>
namespace Console = YYCC::ConsoleHelper;
namespace YYCCTestbench {
#pragma region Unicode Test Data
// UNICODE Test Strings
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
#define TEST_UNICODE_STR_JAPAN "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"
#define TEST_UNICODE_STR_CHINA "\u7B80\u4F53\u4E2D\u6587"
#define TEST_UNICODE_STR_KOREA "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"
#define TEST_UNICODE_STR_ISRAEL "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"
#define TEST_UNICODE_STR_EGYPT "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"
#define TEST_UNICODE_STR_GREECE "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"
#define TEST_UNICODE_STR_RUSSIA "\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"
#define TEST_UNICODE_STR_THAILAND "\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"
#define TEST_UNICODE_STR_FRANCE "fran\u00E7ais langue \u00E9trang\u00E8re"
#define TEST_UNICODE_STR_SPAIN "ma\u00F1ana ol\u00E9"
#define TEST_UNICODE_STR_MATHMATICS "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"
#define TEST_UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
#define CONCAT(prefix, strl) prefix ## strl
#define CPP_U8_LITERAL(strl) YYCC_U8(strl)
#define CPP_U16_LITERAL(strl) CONCAT(u, strl)
#define CPP_U32_LITERAL(strl) CONCAT(U, strl)
#define CPP_WSTR_LITERAL(strl) CONCAT(L, strl)
static std::vector<YYCC::yycc_u8string> c_UTF8TestStrTable {
CPP_U8_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_U8_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_U8_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_U8_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_U8_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_U8_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_U8_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_U8_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_U8_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_U8_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_U8_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_U8_LITERAL(TEST_UNICODE_STR_EMOJI),
};
static std::vector<std::wstring> c_WStrTestStrTable {
CPP_WSTR_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EMOJI),
};
static std::vector<std::u16string> c_UTF16TestStrTable {
CPP_U16_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_U16_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_U16_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_U16_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_U16_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_U16_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_U16_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_U16_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_U16_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_U16_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_U16_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_U16_LITERAL(TEST_UNICODE_STR_EMOJI),
};
static std::vector<std::u32string> c_UTF32TestStrTable {
CPP_U32_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_U32_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_U32_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_U32_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_U32_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_U32_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_U32_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_U32_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_U32_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_U32_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_U32_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_U32_LITERAL(TEST_UNICODE_STR_EMOJI),
};
#undef CPP_WSTR_LITERAL
#undef CPP_U32_LITERAL
#undef CPP_U16_LITERAL
#undef CPP_U8_LITERAL
#undef CONCAT
#pragma endregion
static void Assert(bool condition, const YYCC::yycc_char8_t* description) {
if (condition) {
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("OK: %s")), description);
} else {
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("Failed: %s\n")), description);
std::abort();
}
}
static void ConsoleTestbench() {
// Color Test
Console::EnableColorfulConsole();
Console::WriteLine(YYCC_U8("Color Test:"));
#define TEST_MACRO(col) Console::WriteLine(YYCC_U8("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col ));
// U+2588 is full block
TEST_MACRO(BLACK);
TEST_MACRO(RED);
TEST_MACRO(GREEN);
TEST_MACRO(YELLOW);
TEST_MACRO(BLUE);
TEST_MACRO(MAGENTA);
TEST_MACRO(CYAN);
TEST_MACRO(WHITE);
#undef TEST_MACRO
// UTF8 Output Test
Console::WriteLine(YYCC_U8("UTF8 Output Test:"));
for (const auto& strl : c_UTF8TestStrTable) {
Console::FormatLine(YYCC_U8("\t%s"), strl.c_str());
}
// UTF8 Input Test
Console::WriteLine(YYCC_U8("UTF8 Input Test:"));
for (const auto& strl : c_UTF8TestStrTable) {
Console::FormatLine(YYCC_U8("\tPlease type: %s"), strl.c_str());
Console::Write(YYCC_U8("\t> "));
YYCC::yycc_u8string gotten(Console::ReadLine());
if (gotten == strl) Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("\tMatched! Got: %s")), gotten.c_str());
else Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("\tNOT Matched! Got: %s")), gotten.c_str());
}
}
static void EncodingTestbench() {
// get test tuple size
size_t count = c_UTF8TestStrTable.size();
// check the convertion between given string
for (size_t i = 0u; i < count; ++i) {
// get item
const auto& u8str = c_UTF8TestStrTable[i];
const auto& u16str = c_UTF16TestStrTable[i];
const auto& u32str = c_UTF32TestStrTable[i];
// create cache variables
YYCC::yycc_u8string u8cache;
std::u16string u16cache;
std::u32string u32cache;
// do convertion check
Assert(YYCC::EncodingHelper::UTF8ToUTF16(u8str, u16cache) && u16cache == u16str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF16"));
Assert(YYCC::EncodingHelper::UTF8ToUTF32(u8str, u32cache) && u32cache == u32str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF32"));
Assert(YYCC::EncodingHelper::UTF16ToUTF8(u16str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF16ToUTF8"));
Assert(YYCC::EncodingHelper::UTF32ToUTF8(u32str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF32ToUTF8"));
}
// check wstring convertion on windows
#if YYCC_OS == YYCC_OS_WINDOWS
for (size_t i = 0u; i < count; ++i) {
// get item
const auto& u8str = c_UTF8TestStrTable[i];
const auto& wstr = c_WStrTestStrTable[i];
// create cache variables
YYCC::yycc_u8string u8cache;
std::wstring wcache;
// do convertion check
Assert(YYCC::EncodingHelper::UTF8ToWchar(u8str.c_str(), wcache) && wcache == wstr, YYCC_U8("YYCC::EncodingHelper::UTF8ToWchar"));
Assert(YYCC::EncodingHelper::WcharToUTF8(wstr.c_str(), u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::WcharToUTF8"));
}
#endif
}
static void StringTestbench() {
// Test Printf
auto test_printf = YYCC::StringHelper::Printf(YYCC_U8("%s == %s"), YYCC_U8("Hello World"), YYCC_U8("Hello, world"));
Assert(test_printf == YYCC_U8("Hello World == Hello, world"), YYCC_U8("YYCC::StringHelper::Printf"));
// Test Replace
auto test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8("bb"), YYCC_U8("dd")); // normal case
Assert(test_replace == YYCC_U8("aaddcc"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8("zz"), YYCC_U8("yy")); // no replace
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC::yycc_u8string_view(), YYCC_U8("zz")); // empty finding
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaaabbaa"), YYCC_U8("aa"), YYCC_U8("")); // no replaced string
Assert(test_replace == YYCC_U8("bb"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaxcc"), YYCC_U8("x"), YYCC_U8("yx")); // nested replacing
Assert(test_replace == YYCC_U8("aayxcc"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC::yycc_u8string_view(), YYCC_U8(""), YYCC_U8("xy")); // empty source string
Assert(test_replace == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Replace"));
// Test Upper / Lower
auto test_lower = YYCC::StringHelper::Lower(YYCC_U8("LOWER"));
Assert(test_lower == YYCC_U8("lower"), YYCC_U8("YYCC::StringHelper::Lower"));
auto test_upper = YYCC::StringHelper::Upper(YYCC_U8("upper"));
Assert(test_upper == YYCC_U8("UPPER"), YYCC_U8("YYCC::StringHelper::Upper"));
// Test Join
std::vector<YYCC::yycc_u8string> test_join_container {
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
};
auto test_join = YYCC::StringHelper::Join(test_join_container.begin(), test_join_container.end(), YYCC_U8(", "));
Assert(test_join == YYCC_U8(", 1, 2, "), YYCC_U8("YYCC::StringHelper::Join"));
// Test Split
auto test_split = YYCC::StringHelper::Split(YYCC_U8(", 1, 2, "), YYCC_U8(", ")); // normal
Assert(test_split.size() == 4u, YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[0] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[1] == YYCC_U8("1"), YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[2] == YYCC_U8("2"), YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[3] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split"));
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched decilmer
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC::yycc_u8string_view()); // empty decilmer
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
test_split = YYCC::StringHelper::Split(YYCC::yycc_u8string_view(), YYCC_U8("")); // empty source string
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[0].empty(), YYCC_U8("YYCC::StringHelper::Split"));
}
static void ParserTestbench() {
// Test success TryParse
#define TEST_MACRO(type_t, value, string_value, ...) { \
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
type_t cache; \
Assert(YYCC::ParserHelper::TryParse<type_t>(cache_string, cache, ##__VA_ARGS__) && cache == value, YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \
}
TEST_MACRO(int8_t, INT8_C(-61), "-61");
TEST_MACRO(uint8_t, UINT8_C(200), "200");
TEST_MACRO(int16_t, INT16_C(6161), "6161");
TEST_MACRO(uint16_t, UINT16_C(32800), "32800");
TEST_MACRO(int32_t, INT32_C(61616161), "61616161");
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807");
TEST_MACRO(uint32_t, UINT32_C(0xffff), "ffff", 16);
TEST_MACRO(float, 1.0f, "1.0");
TEST_MACRO(double, 1.0, "1.0");
TEST_MACRO(bool, true, "true");
#undef TEST_MACRO
// Test failed TryParse
#define TEST_MACRO(type_t, string_value, ...) { \
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
type_t cache; \
Assert(!YYCC::ParserHelper::TryParse<type_t>(cache_string, cache, ##__VA_ARGS__), YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \
}
TEST_MACRO(int8_t, "6161");
TEST_MACRO(uint8_t, "32800");
TEST_MACRO(int16_t, "61616161");
TEST_MACRO(uint16_t, "4294967293");
TEST_MACRO(int32_t, "616161616161");
TEST_MACRO(uint32_t, "9223372036854775807");
TEST_MACRO(int64_t, "616161616161616161616161");
TEST_MACRO(uint64_t, "92233720368547758079223372036854775807");
TEST_MACRO(float, "1e40");
TEST_MACRO(double, "1e114514");
TEST_MACRO(bool, "hello, world!");
#undef TEST_MACRO
// Test ToString
#define TEST_MACRO(type_t, value, string_value, ...) { \
type_t cache = value; \
YYCC::yycc_u8string ret(YYCC::ParserHelper::ToString<type_t>(cache, ##__VA_ARGS__)); \
Assert(ret == YYCC_U8(string_value), YYCC_U8("YYCC::StringHelper::ToString<" #type_t ">")); \
}
TEST_MACRO(int8_t, INT8_C(-61), "-61");
TEST_MACRO(uint8_t, UINT8_C(200), "200");
TEST_MACRO(int16_t, INT16_C(6161), "6161");
TEST_MACRO(uint16_t, UINT16_C(32800), "32800");
TEST_MACRO(int32_t, INT32_C(61616161), "61616161");
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807");
TEST_MACRO(uint32_t, UINT32_C(0xffff), "ffff", 16);
TEST_MACRO(float, 1.0f, "1.0", std::chars_format::fixed, 1);
TEST_MACRO(double, 1.0, "1.0", std::chars_format::fixed, 1);
TEST_MACRO(bool, true, "true");
#undef TEST_MACRO
}
static void DialogTestbench() {
#if YYCC_OS == YYCC_OS_WINDOWS
YYCC::yycc_u8string ret;
std::vector<YYCC::yycc_u8string> rets;
YYCC::DialogHelper::FileDialog params;
auto& filters = params.ConfigreFileTypes();
filters.Add(YYCC_U8("Microsoft Word (*.docx; *.doc)"), { YYCC_U8("*.docx"), YYCC_U8("*.doc") });
filters.Add(YYCC_U8("Microsoft Excel (*.xlsx; *.xls)"), { YYCC_U8("*.xlsx"), YYCC_U8("*.xls") });
filters.Add(YYCC_U8("Microsoft PowerPoint (*.pptx; *.ppt)"), { YYCC_U8("*.pptx"), YYCC_U8("*.ppt") });
filters.Add(YYCC_U8("Text File (*.txt)"), { YYCC_U8("*.txt") });
filters.Add(YYCC_U8("All Files (*.*)"), { YYCC_U8("*.*") });
params.SetDefaultFileTypeIndex(0u);
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
Console::FormatLine(YYCC_U8("Open File: %s"), ret.c_str());
}
if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) {
Console::WriteLine(YYCC_U8("Open Multiple Files:"));
for (const auto& item : rets) {
Console::FormatLine(YYCC_U8("\t%s"), item.c_str());
}
}
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
Console::FormatLine(YYCC_U8("Save File: %s"), ret.c_str());
}
params.Clear();
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
Console::FormatLine(YYCC_U8("Open Folder: %s"), ret.c_str());
}
#endif
}
static void ExceptionTestbench() {
#if YYCC_OS == YYCC_OS_WINDOWS
YYCC::ExceptionHelper::Register([](const YYCC::yycc_u8string& log_path, const YYCC::yycc_u8string& coredump_path) -> void {
MessageBoxW(
NULL,
YYCC::EncodingHelper::UTF8ToWchar(
YYCC::StringHelper::Printf(YYCC_U8("Log generated:\nLog path: %s\nCore dump path: %s"), log_path.c_str(), coredump_path.c_str())
).c_str(),
L"Fatal Error", MB_OK + MB_ICONERROR
);
}
);
// Perform a div zero exception.
#if defined (YYCC_DEBUG_UE_FILTER)
// Reference: https://stackoverflow.com/questions/20981982/is-it-possible-to-debug-unhandledexceptionfilters-with-a-debugger
__try {
// all of code normally inside of main or WinMain here...
int i = 1, j = 0;
int k = i / j;
} __except (YYCC::ExceptionHelper::DebugCallUExceptionImpl(GetExceptionInformation())) {
OutputDebugStringW(L"executed filter function\n");
}
#else
int i = 1, j = 0;
int k = i / j;
#endif
YYCC::ExceptionHelper::Unregister();
#endif
}
static void WinFctTestbench() {
#if YYCC_OS == YYCC_OS_WINDOWS
HMODULE test_current_module;
Assert((test_current_module = YYCC::WinFctHelper::GetCurrentModule()) != nullptr, YYCC_U8("YYCC::WinFctHelper::GetCurrentModule"));
Console::FormatLine(YYCC_U8("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR), test_current_module);
YYCC::yycc_u8string test_temp;
Assert(YYCC::WinFctHelper::GetTempDirectory(test_temp), YYCC_U8("YYCC::WinFctHelper::GetTempDirectory"));
Console::FormatLine(YYCC_U8("Temp Directory: %s"), test_temp.c_str());
YYCC::yycc_u8string test_module_name;
Assert(YYCC::WinFctHelper::GetModuleFileName(YYCC::WinFctHelper::GetCurrentModule(), test_module_name), YYCC_U8("YYCC::WinFctHelper::GetModuleFileName"));
Console::FormatLine(YYCC_U8("Current Module File Name: %s"), test_module_name.c_str());
YYCC::yycc_u8string test_localappdata_path;
Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), YYCC_U8("YYCC::WinFctHelper::GetLocalAppData"));
Console::FormatLine(YYCC_U8("Local AppData: %s"), test_localappdata_path.c_str());
Assert(YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(1252)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
Assert(!YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(114514)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
// MARK: There is no testbench for MoveFile, CopyFile DeleteFile.
// Because they can operate file system files.
// And may cause test environment entering unstable status.
#endif
}
static void StdPatchTestbench() {
// Std Path
std::filesystem::path test_path;
for (const auto& strl : c_UTF8TestStrTable) {
test_path /= YYCC::StdPatch::ToStdPath(strl);
}
YYCC::yycc_u8string test_slashed_path(YYCC::StdPatch::ToUTF8Path(test_path));
#if YYCC_OS == YYCC_OS_WINDOWS
std::wstring wdecilmer(1u, std::filesystem::path::preferred_separator);
YYCC::yycc_u8string decilmer(YYCC::EncodingHelper::WcharToUTF8(wdecilmer));
#else
YYCC::yycc_u8string decilmer(1u, std::filesystem::path::preferred_separator);
#endif
YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), decilmer));
Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::StdPatch::ToStdPath, YYCC::StdPatch::ToUTF8Path"));
// StartsWith, EndsWith
YYCC::yycc_u8string test_starts_ends_with(YYCC_U8("aaabbbccc"));
Assert(YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::StartsWith"));
Assert(!YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::StartsWith"));
Assert(!YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::EndsWith"));
Assert(YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::EndsWith"));
// Contains
std::set<int> test_set { 1, 2, 3, 4, 6, 7 };
Assert(YYCC::StdPatch::Contains(test_set, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
Assert(!YYCC::StdPatch::Contains(test_set, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
std::map<int, float> test_map { { 1, 1.0f }, { 4, 4.0f } };
Assert(YYCC::StdPatch::Contains(test_map, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
Assert(!YYCC::StdPatch::Contains(test_map, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
}
enum class TestFlagEnum : uint8_t {
Test1 = 0b00000000,
Test2 = 0b00000001,
Test3 = 0b00000010,
Test4 = 0b00000100,
Test5 = 0b00001000,
Test6 = 0b00010000,
Test7 = 0b00100000,
Test8 = 0b01000000,
Test9 = 0b10000000,
Inverted = 0b01111111,
Merged = Test3 + Test5,
};
static void EnumHelperTestbench() {
TestFlagEnum val;
Assert(YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5) == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Merge"));
Assert(YYCC::EnumHelper::Invert(TestFlagEnum::Test9) == TestFlagEnum::Inverted, YYCC_U8("YYCC::EnumHelper::Invert"));
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
YYCC::EnumHelper::Mask(val, TestFlagEnum::Test3);
Assert(YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
YYCC::EnumHelper::Mask(val, TestFlagEnum::Test4);
Assert(!YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
val = TestFlagEnum::Test3;
YYCC::EnumHelper::Add(val, TestFlagEnum::Test5);
Assert(val == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Add"));
val = TestFlagEnum::Merged;
YYCC::EnumHelper::Remove(val, TestFlagEnum::Test5);
Assert(val == TestFlagEnum::Test3, YYCC_U8("YYCC::EnumHelper::Remove"));
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
Assert(YYCC::EnumHelper::Has(val, TestFlagEnum::Test3), YYCC_U8("YYCC::EnumHelper::Has"));
Assert(!YYCC::EnumHelper::Has(val, TestFlagEnum::Test4), YYCC_U8("YYCC::EnumHelper::Has"));
Assert(!YYCC::EnumHelper::Bool(TestFlagEnum::Test1), YYCC_U8("YYCC::EnumHelper::Bool"));
Assert(YYCC::EnumHelper::Bool(TestFlagEnum::Test2), YYCC_U8("YYCC::EnumHelper::Bool"));
}
static void VersionMacroTestbench() {
Assert(YYCC_VERCMP_E(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_E"));
Assert(!YYCC_VERCMP_NE(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_NE"));
Assert(YYCC_VERCMP_G(1, 2, 3, 0, 2, 5), YYCC_U8("YYCC_VERCMP_G"));
Assert(YYCC_VERCMP_GE(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_GE"));
Assert(YYCC_VERCMP_NL(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_NL"));
Assert(YYCC_VERCMP_L(0, 2, 5, 1, 2, 3), YYCC_U8("YYCC_VERCMP_L"));
Assert(YYCC_VERCMP_LE(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_LE"));
Assert(YYCC_VERCMP_NG(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_NG"));
}
enum class TestEnum : int8_t {
Test1, Test2, Test3
};
class TestConfigManager {
public:
TestConfigManager() :
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
m_FloatSetting(YYCC_U8("float-setting"), 0.0f),
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
m_BoolSetting(YYCC_U8("bool-setting"), false),
m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
&m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting
}) {}
~TestConfigManager() {}
void PrintSettings() {
Console::WriteLine(YYCC_U8("Config Manager Settings:"));
Console::FormatLine(YYCC_U8("\tint-setting: %" PRIi32), m_IntSetting.Get());
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_FloatSetting.Get());
Console::FormatLine(YYCC_U8("\tstring-setting: %s"), m_StringSetting.Get().c_str());
Console::FormatLine(YYCC_U8("\tbool-setting: %s"), m_BoolSetting.Get() ? YYCC_U8("true") : YYCC_U8("false"));
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_ClampedFloatSetting.Get());
Console::FormatLine(YYCC_U8("\tenum-setting: %" PRIi8), static_cast<std::underlying_type_t<TestEnum>>(m_EnumSetting.Get()));
}
YYCC::ConfigManager::NumberSetting<int32_t> m_IntSetting;
YYCC::ConfigManager::NumberSetting<float> m_FloatSetting;
YYCC::ConfigManager::StringSetting m_StringSetting;
YYCC::ConfigManager::NumberSetting<bool> m_BoolSetting;
YYCC::ConfigManager::NumberSetting<float> m_ClampedFloatSetting;
YYCC::ConfigManager::NumberSetting<TestEnum> m_EnumSetting;
YYCC::ConfigManager::CoreManager m_CoreManager;
};
static void ConfigManagerTestbench() {
// init cfg manager
TestConfigManager test;
// test constraint works
Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::Constraints::Constraint"));
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::Constraints::Constraint"));
// test modify settings
#define TEST_MACRO(member_name, set_val) { \
Assert(test.member_name.Set(set_val), YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
Assert(test.member_name.Get() == set_val, YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
}
TEST_MACRO(m_IntSetting, INT32_C(114));
TEST_MACRO(m_FloatSetting, 2.0f);
TEST_MACRO(m_StringSetting, YYCC_U8("fuck"));
TEST_MACRO(m_BoolSetting, true);
TEST_MACRO(m_ClampedFloatSetting, 0.5f);
TEST_MACRO(m_EnumSetting, TestEnum::Test2);
#undef TEST_MACRO
// test save
test.PrintSettings();
Assert(test.m_CoreManager.Save(), YYCC_U8("YYCC::ConfigManager::CoreManager::Save"));
// test reset
test.m_CoreManager.Reset();
test.PrintSettings();
Assert(test.m_IntSetting.Get() == INT32_C(0), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_FloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_StringSetting.Get() == YYCC_U8(""), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_BoolSetting.Get() == false, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_EnumSetting.Get() == TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
// test load
YYCC::ConfigManager::ConfigLoadResult wrong_result = YYCC::EnumHelper::Merge(
YYCC::ConfigManager::ConfigLoadResult::ItemError,
YYCC::ConfigManager::ConfigLoadResult::BrokenFile
);
Assert(!YYCC::EnumHelper::Has(test.m_CoreManager.Load(), wrong_result), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
test.PrintSettings();
Assert(test.m_IntSetting.Get() == INT32_C(114), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_FloatSetting.Get() == 2.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_StringSetting.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_BoolSetting.Get() == true, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_ClampedFloatSetting.Get() == 0.5f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_EnumSetting.Get() == TestEnum::Test2, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
}
class TestArgParser {
public:
TestArgParser() :
m_IntArgument(YYCC_U8("int"), YYCC_U8_CHAR('i'), YYCC_U8("integral argument"), YYCC_U8("114514")),
m_FloatArgument(nullptr, YYCC_U8_CHAR('f'), nullptr, nullptr, true),
m_StringArgument(YYCC_U8("string"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true),
m_BoolArgument(nullptr, YYCC_U8_CHAR('b'), nullptr),
m_ClampedFloatArgument(YYCC_U8("clamped-float"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
m_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the testbench of argument parser."), {
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
&m_BoolArgument, &m_ClampedFloatArgument
}) {}
~TestArgParser() {}
YYCC::ArgParser::NumberArgument<int32_t> m_IntArgument;
YYCC::ArgParser::NumberArgument<float> m_FloatArgument;
YYCC::ArgParser::StringArgument m_StringArgument;
YYCC::ArgParser::SwitchArgument m_BoolArgument;
YYCC::ArgParser::NumberArgument<float> m_ClampedFloatArgument;
YYCC::ArgParser::OptionContext m_OptionContext;
};
static void ArgParserTestbench(int argc, char* argv[]) {
// test command line getter
{
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromStd"));
auto result = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
for (result.Reset(); !result.IsEOF(); result.Next()) {
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
}
}
#if YYCC_OS == YYCC_OS_WINDOWS
{
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromWin32"));
auto result = YYCC::ArgParser::ArgumentList::CreateFromWin32();
for (result.Reset(); !result.IsEOF(); result.Next()) {
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
}
}
#endif
// test option context
// init option context
TestArgParser test;
#define PREPARE_DATA(...) const char* test_argv[] = { __VA_ARGS__ }; \
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(sizeof(test_argv) / sizeof(char*), const_cast<char**>(test_argv));
// normal test
{
PREPARE_DATA("exec", "-i", "114514");
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_FloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_StringArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_ClampedFloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// no argument
{
PREPARE_DATA("exec");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// error argument
{
PREPARE_DATA("exec", "-?", "114514");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// lost argument
{
PREPARE_DATA("exec", "-i");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// dplicated assign
{
PREPARE_DATA("exec", "-i", "114514" "--int", "114514");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// extra useless argument
{
PREPARE_DATA("exec", "-i", "114514" "1919810");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// invalid clamp argument
{
PREPARE_DATA("exec", "-i", "114514", "--clamped-float", "114.0");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// full argument
{
PREPARE_DATA("exec", "-i", "114514", "-f", "2.0", "--string", "fuck", "-b", "--clamped-float", "0.5");
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_FloatArgument.IsCaptured() && test.m_FloatArgument.Get() == 2.0f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_StringArgument.IsCaptured() && test.m_StringArgument.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_ClampedFloatArgument.IsCaptured() && test.m_ClampedFloatArgument.Get() == 0.5f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// Help text
test.m_OptionContext.Help();
#undef PREPARE_DATA
}
}
#include <gtest/gtest.h>
int main(int argc, char* argv[]) {
#if YYCC_VERCMP_NE(YYCC_VER_MAJOR, YYCC_VER_MINOR, YYCC_VER_PATCH, 1, 3 ,0)
#error "The YYCC library used when compiling is not match code expected, this may cause build error."
#error "If you trust it, please annotate these preprocessor statement, otherwise please contact developer."
#endif
// common testbench
// normal
YYCCTestbench::EncodingTestbench();
YYCCTestbench::StringTestbench();
YYCCTestbench::ParserTestbench();
YYCCTestbench::WinFctTestbench();
YYCCTestbench::StdPatchTestbench();
YYCCTestbench::EnumHelperTestbench();
YYCCTestbench::VersionMacroTestbench();
// advanced
YYCCTestbench::ConfigManagerTestbench();
YYCCTestbench::ArgParserTestbench(argc, argv);
// testbench which may terminal app or ordering input
YYCCTestbench::ConsoleTestbench();
YYCCTestbench::DialogTestbench();
YYCCTestbench::ExceptionTestbench();
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

559
testbench/main_legacy.cpp Normal file
View File

@ -0,0 +1,559 @@
#include <YYCCommonplace.hpp>
#include <cstdio>
#include <set>
#include <map>
namespace Console = YYCC::ConsoleHelper;
namespace YYCCTestbench {
#pragma region Unicode Test Data
// UNICODE Test Strings
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
#define TEST_UNICODE_STR_JAPAN "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"
#define TEST_UNICODE_STR_CHINA "\u7B80\u4F53\u4E2D\u6587"
#define TEST_UNICODE_STR_KOREA "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"
#define TEST_UNICODE_STR_ISRAEL "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"
#define TEST_UNICODE_STR_EGYPT "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"
#define TEST_UNICODE_STR_GREECE "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"
#define TEST_UNICODE_STR_RUSSIA "\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"
#define TEST_UNICODE_STR_THAILAND "\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"
#define TEST_UNICODE_STR_FRANCE "fran\u00E7ais langue \u00E9trang\u00E8re"
#define TEST_UNICODE_STR_SPAIN "ma\u00F1ana ol\u00E9"
#define TEST_UNICODE_STR_MATHMATICS "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"
#define TEST_UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
#define CONCAT(prefix, strl) prefix ## strl
#define CPP_U8_LITERAL(strl) YYCC_U8(strl)
#define CPP_U16_LITERAL(strl) CONCAT(u, strl)
#define CPP_U32_LITERAL(strl) CONCAT(U, strl)
#define CPP_WSTR_LITERAL(strl) CONCAT(L, strl)
static std::vector<YYCC::yycc_u8string> c_UTF8TestStrTable {
CPP_U8_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_U8_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_U8_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_U8_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_U8_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_U8_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_U8_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_U8_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_U8_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_U8_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_U8_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_U8_LITERAL(TEST_UNICODE_STR_EMOJI),
};
static std::vector<std::wstring> c_WStrTestStrTable {
CPP_WSTR_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EMOJI),
};
static std::vector<std::u16string> c_UTF16TestStrTable {
CPP_U16_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_U16_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_U16_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_U16_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_U16_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_U16_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_U16_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_U16_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_U16_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_U16_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_U16_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_U16_LITERAL(TEST_UNICODE_STR_EMOJI),
};
static std::vector<std::u32string> c_UTF32TestStrTable {
CPP_U32_LITERAL(TEST_UNICODE_STR_JAPAN),
CPP_U32_LITERAL(TEST_UNICODE_STR_CHINA),
CPP_U32_LITERAL(TEST_UNICODE_STR_KOREA),
CPP_U32_LITERAL(TEST_UNICODE_STR_ISRAEL),
CPP_U32_LITERAL(TEST_UNICODE_STR_EGYPT),
CPP_U32_LITERAL(TEST_UNICODE_STR_GREECE),
CPP_U32_LITERAL(TEST_UNICODE_STR_RUSSIA),
CPP_U32_LITERAL(TEST_UNICODE_STR_THAILAND),
CPP_U32_LITERAL(TEST_UNICODE_STR_FRANCE),
CPP_U32_LITERAL(TEST_UNICODE_STR_SPAIN),
CPP_U32_LITERAL(TEST_UNICODE_STR_MATHMATICS),
CPP_U32_LITERAL(TEST_UNICODE_STR_EMOJI),
};
#undef CPP_WSTR_LITERAL
#undef CPP_U32_LITERAL
#undef CPP_U16_LITERAL
#undef CPP_U8_LITERAL
#undef CONCAT
#pragma endregion
static void Assert(bool condition, const YYCC::yycc_char8_t* description) {
if (condition) {
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("OK: %s")), description);
} else {
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("Failed: %s\n")), description);
std::abort();
}
}
static void ConsoleTestbench() {
// Color Test
Console::EnableColorfulConsole();
Console::WriteLine(YYCC_U8("Color Test:"));
#define TEST_MACRO(col) Console::WriteLine(YYCC_U8("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col ));
// U+2588 is full block
TEST_MACRO(BLACK);
TEST_MACRO(RED);
TEST_MACRO(GREEN);
TEST_MACRO(YELLOW);
TEST_MACRO(BLUE);
TEST_MACRO(MAGENTA);
TEST_MACRO(CYAN);
TEST_MACRO(WHITE);
#undef TEST_MACRO
// UTF8 Output Test
Console::WriteLine(YYCC_U8("UTF8 Output Test:"));
for (const auto& strl : c_UTF8TestStrTable) {
Console::FormatLine(YYCC_U8("\t%s"), strl.c_str());
}
// UTF8 Input Test
Console::WriteLine(YYCC_U8("UTF8 Input Test:"));
for (const auto& strl : c_UTF8TestStrTable) {
Console::FormatLine(YYCC_U8("\tPlease type: %s"), strl.c_str());
Console::Write(YYCC_U8("\t> "));
YYCC::yycc_u8string gotten(Console::ReadLine());
if (gotten == strl) Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("\tMatched! Got: %s")), gotten.c_str());
else Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("\tNOT Matched! Got: %s")), gotten.c_str());
}
}
static void EncodingTestbench() {
// get test tuple size
size_t count = c_UTF8TestStrTable.size();
// check the convertion between given string
for (size_t i = 0u; i < count; ++i) {
// get item
const auto& u8str = c_UTF8TestStrTable[i];
const auto& u16str = c_UTF16TestStrTable[i];
const auto& u32str = c_UTF32TestStrTable[i];
// create cache variables
YYCC::yycc_u8string u8cache;
std::u16string u16cache;
std::u32string u32cache;
// do convertion check
Assert(YYCC::EncodingHelper::UTF8ToUTF16(u8str, u16cache) && u16cache == u16str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF16"));
Assert(YYCC::EncodingHelper::UTF8ToUTF32(u8str, u32cache) && u32cache == u32str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF32"));
Assert(YYCC::EncodingHelper::UTF16ToUTF8(u16str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF16ToUTF8"));
Assert(YYCC::EncodingHelper::UTF32ToUTF8(u32str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF32ToUTF8"));
}
// check wstring convertion on windows
#if defined(YYCC_OS_WINDOWS)
for (size_t i = 0u; i < count; ++i) {
// get item
const auto& u8str = c_UTF8TestStrTable[i];
const auto& wstr = c_WStrTestStrTable[i];
// create cache variables
YYCC::yycc_u8string u8cache;
std::wstring wcache;
// do convertion check
Assert(YYCC::EncodingHelper::UTF8ToWchar(u8str.c_str(), wcache) && wcache == wstr, YYCC_U8("YYCC::EncodingHelper::UTF8ToWchar"));
Assert(YYCC::EncodingHelper::WcharToUTF8(wstr.c_str(), u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::WcharToUTF8"));
}
#endif
}
static void DialogTestbench() {
#if defined(YYCC_OS_WINDOWS)
YYCC::yycc_u8string ret;
std::vector<YYCC::yycc_u8string> rets;
YYCC::DialogHelper::FileDialog params;
auto& filters = params.ConfigreFileTypes();
filters.Add(YYCC_U8("Microsoft Word (*.docx; *.doc)"), { YYCC_U8("*.docx"), YYCC_U8("*.doc") });
filters.Add(YYCC_U8("Microsoft Excel (*.xlsx; *.xls)"), { YYCC_U8("*.xlsx"), YYCC_U8("*.xls") });
filters.Add(YYCC_U8("Microsoft PowerPoint (*.pptx; *.ppt)"), { YYCC_U8("*.pptx"), YYCC_U8("*.ppt") });
filters.Add(YYCC_U8("Text File (*.txt)"), { YYCC_U8("*.txt") });
filters.Add(YYCC_U8("All Files (*.*)"), { YYCC_U8("*.*") });
params.SetDefaultFileTypeIndex(0u);
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
Console::FormatLine(YYCC_U8("Open File: %s"), ret.c_str());
}
if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) {
Console::WriteLine(YYCC_U8("Open Multiple Files:"));
for (const auto& item : rets) {
Console::FormatLine(YYCC_U8("\t%s"), item.c_str());
}
}
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
Console::FormatLine(YYCC_U8("Save File: %s"), ret.c_str());
}
params.Clear();
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
Console::FormatLine(YYCC_U8("Open Folder: %s"), ret.c_str());
}
#endif
}
static void ExceptionTestbench() {
#if defined(YYCC_OS_WINDOWS)
YYCC::ExceptionHelper::Register([](const YYCC::yycc_u8string& log_path, const YYCC::yycc_u8string& coredump_path) -> void {
MessageBoxW(
NULL,
YYCC::EncodingHelper::UTF8ToWchar(
YYCC::StringHelper::Printf(YYCC_U8("Log generated:\nLog path: %s\nCore dump path: %s"), log_path.c_str(), coredump_path.c_str())
).c_str(),
L"Fatal Error", MB_OK + MB_ICONERROR
);
}
);
// Perform a div zero exception.
#if defined (YYCC_DEBUG_UE_FILTER)
// Reference: https://stackoverflow.com/questions/20981982/is-it-possible-to-debug-unhandledexceptionfilters-with-a-debugger
__try {
// all of code normally inside of main or WinMain here...
int i = 1, j = 0;
int k = i / j;
} __except (YYCC::ExceptionHelper::DebugCallUExceptionImpl(GetExceptionInformation())) {
OutputDebugStringW(L"executed filter function\n");
}
#else
int i = 1, j = 0;
int k = i / j;
#endif
YYCC::ExceptionHelper::Unregister();
#endif
}
static void WinFctTestbench() {
#if defined(YYCC_OS_WINDOWS)
HMODULE test_current_module;
Assert((test_current_module = YYCC::WinFctHelper::GetCurrentModule()) != nullptr, YYCC_U8("YYCC::WinFctHelper::GetCurrentModule"));
Console::FormatLine(YYCC_U8("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR), test_current_module);
YYCC::yycc_u8string test_temp;
Assert(YYCC::WinFctHelper::GetTempDirectory(test_temp), YYCC_U8("YYCC::WinFctHelper::GetTempDirectory"));
Console::FormatLine(YYCC_U8("Temp Directory: %s"), test_temp.c_str());
YYCC::yycc_u8string test_module_name;
Assert(YYCC::WinFctHelper::GetModuleFileName(YYCC::WinFctHelper::GetCurrentModule(), test_module_name), YYCC_U8("YYCC::WinFctHelper::GetModuleFileName"));
Console::FormatLine(YYCC_U8("Current Module File Name: %s"), test_module_name.c_str());
YYCC::yycc_u8string test_localappdata_path;
Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), YYCC_U8("YYCC::WinFctHelper::GetLocalAppData"));
Console::FormatLine(YYCC_U8("Local AppData: %s"), test_localappdata_path.c_str());
Assert(YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(1252)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
Assert(!YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(114514)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
// MARK: There is no testbench for MoveFile, CopyFile DeleteFile.
// Because they can operate file system files.
// And may cause test environment entering unstable status.
#endif
}
static void StdPatchTestbench() {
// Std Path
std::filesystem::path test_path;
for (const auto& strl : c_UTF8TestStrTable) {
test_path /= YYCC::StdPatch::ToStdPath(strl);
}
YYCC::yycc_u8string test_slashed_path(YYCC::StdPatch::ToUTF8Path(test_path));
#if defined(YYCC_OS_WINDOWS)
std::wstring wdelimiter(1u, std::filesystem::path::preferred_separator);
YYCC::yycc_u8string delimiter(YYCC::EncodingHelper::WcharToUTF8(wdelimiter));
#else
YYCC::yycc_u8string delimiter(1u, std::filesystem::path::preferred_separator);
#endif
YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), delimiter));
Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::StdPatch::ToStdPath, YYCC::StdPatch::ToUTF8Path"));
// StartsWith, EndsWith
YYCC::yycc_u8string test_starts_ends_with(YYCC_U8("aaabbbccc"));
Assert(YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::StartsWith"));
Assert(!YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::StartsWith"));
Assert(!YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::EndsWith"));
Assert(YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::EndsWith"));
// Contains
std::set<int> test_set { 1, 2, 3, 4, 6, 7 };
Assert(YYCC::StdPatch::Contains(test_set, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
Assert(!YYCC::StdPatch::Contains(test_set, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
std::map<int, float> test_map { { 1, 1.0f }, { 4, 4.0f } };
Assert(YYCC::StdPatch::Contains(test_map, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
Assert(!YYCC::StdPatch::Contains(test_map, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
}
enum class TestEnum : int8_t {
Test1, Test2, Test3
};
class TestConfigManager {
public:
TestConfigManager() :
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
m_FloatSetting(YYCC_U8("float-setting"), 0.0f),
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
m_BoolSetting(YYCC_U8("bool-setting"), false),
m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
&m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting
}) {}
~TestConfigManager() {}
void PrintSettings() {
Console::WriteLine(YYCC_U8("Config Manager Settings:"));
Console::FormatLine(YYCC_U8("\tint-setting: %" PRIi32), m_IntSetting.Get());
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_FloatSetting.Get());
Console::FormatLine(YYCC_U8("\tstring-setting: %s"), m_StringSetting.Get().c_str());
Console::FormatLine(YYCC_U8("\tbool-setting: %s"), m_BoolSetting.Get() ? YYCC_U8("true") : YYCC_U8("false"));
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_ClampedFloatSetting.Get());
Console::FormatLine(YYCC_U8("\tenum-setting: %" PRIi8), static_cast<std::underlying_type_t<TestEnum>>(m_EnumSetting.Get()));
}
YYCC::ConfigManager::NumberSetting<int32_t> m_IntSetting;
YYCC::ConfigManager::NumberSetting<float> m_FloatSetting;
YYCC::ConfigManager::StringSetting m_StringSetting;
YYCC::ConfigManager::NumberSetting<bool> m_BoolSetting;
YYCC::ConfigManager::NumberSetting<float> m_ClampedFloatSetting;
YYCC::ConfigManager::NumberSetting<TestEnum> m_EnumSetting;
YYCC::ConfigManager::CoreManager m_CoreManager;
};
static void ConfigManagerTestbench() {
// init cfg manager
TestConfigManager test;
// test constraint works
Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::Constraints::Constraint"));
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::Constraints::Constraint"));
// test modify settings
#define TEST_MACRO(member_name, set_val) { \
Assert(test.member_name.Set(set_val), YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
Assert(test.member_name.Get() == set_val, YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
}
TEST_MACRO(m_IntSetting, INT32_C(114));
TEST_MACRO(m_FloatSetting, 2.0f);
TEST_MACRO(m_StringSetting, YYCC_U8("fuck"));
TEST_MACRO(m_BoolSetting, true);
TEST_MACRO(m_ClampedFloatSetting, 0.5f);
TEST_MACRO(m_EnumSetting, TestEnum::Test2);
#undef TEST_MACRO
// test save
test.PrintSettings();
Assert(test.m_CoreManager.Save(), YYCC_U8("YYCC::ConfigManager::CoreManager::Save"));
// test reset
test.m_CoreManager.Reset();
test.PrintSettings();
Assert(test.m_IntSetting.Get() == INT32_C(0), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_FloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_StringSetting.Get() == YYCC_U8(""), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_BoolSetting.Get() == false, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_EnumSetting.Get() == TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
// test load
YYCC::ConfigManager::ConfigLoadResult wrong_result = YYCC::EnumHelper::Merge(
YYCC::ConfigManager::ConfigLoadResult::ItemError,
YYCC::ConfigManager::ConfigLoadResult::BrokenFile
);
Assert(!YYCC::EnumHelper::Has(test.m_CoreManager.Load(), wrong_result), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
test.PrintSettings();
Assert(test.m_IntSetting.Get() == INT32_C(114), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_FloatSetting.Get() == 2.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_StringSetting.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_BoolSetting.Get() == true, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_ClampedFloatSetting.Get() == 0.5f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_EnumSetting.Get() == TestEnum::Test2, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
}
class TestArgParser {
public:
TestArgParser() :
m_IntArgument(YYCC_U8("int"), YYCC_U8_CHAR('i'), YYCC_U8("integral argument"), YYCC_U8("114514")),
m_FloatArgument(nullptr, YYCC_U8_CHAR('f'), nullptr, nullptr, true),
m_StringArgument(YYCC_U8("string"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true),
m_BoolArgument(nullptr, YYCC_U8_CHAR('b'), nullptr),
m_ClampedFloatArgument(YYCC_U8("clamped-float"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
m_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the testbench of argument parser."), {
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
&m_BoolArgument, &m_ClampedFloatArgument
}) {}
~TestArgParser() {}
YYCC::ArgParser::NumberArgument<int32_t> m_IntArgument;
YYCC::ArgParser::NumberArgument<float> m_FloatArgument;
YYCC::ArgParser::StringArgument m_StringArgument;
YYCC::ArgParser::SwitchArgument m_BoolArgument;
YYCC::ArgParser::NumberArgument<float> m_ClampedFloatArgument;
YYCC::ArgParser::OptionContext m_OptionContext;
};
static void ArgParserTestbench(int argc, char* argv[]) {
// test command line getter
{
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromStd"));
auto result = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
for (result.Reset(); !result.IsEOF(); result.Next()) {
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
}
}
#if defined(YYCC_OS_WINDOWS)
{
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromWin32"));
auto result = YYCC::ArgParser::ArgumentList::CreateFromWin32();
for (result.Reset(); !result.IsEOF(); result.Next()) {
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
}
}
#endif
// test option context
// init option context
TestArgParser test;
#define PREPARE_DATA(...) const char* test_argv[] = { __VA_ARGS__ }; \
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(sizeof(test_argv) / sizeof(char*), const_cast<char**>(test_argv));
// normal test
{
PREPARE_DATA("exec", "-i", "114514");
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_FloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_StringArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_ClampedFloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// no argument
{
PREPARE_DATA("exec");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// error argument
{
PREPARE_DATA("exec", "-?", "114514");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// lost argument
{
PREPARE_DATA("exec", "-i");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// dplicated assign
{
PREPARE_DATA("exec", "-i", "114514" "--int", "114514");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// extra useless argument
{
PREPARE_DATA("exec", "-i", "114514" "1919810");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// invalid clamp argument
{
PREPARE_DATA("exec", "-i", "114514", "--clamped-float", "114.0");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// full argument
{
PREPARE_DATA("exec", "-i", "114514", "-f", "2.0", "--string", "fuck", "-b", "--clamped-float", "0.5");
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_FloatArgument.IsCaptured() && test.m_FloatArgument.Get() == 2.0f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_StringArgument.IsCaptured() && test.m_StringArgument.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_ClampedFloatArgument.IsCaptured() && test.m_ClampedFloatArgument.Get() == 0.5f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// Help text
test.m_OptionContext.Help();
#undef PREPARE_DATA
}
}
int main(int argc, char* argv[]) {
#if YYCC_VERCMP_NE(YYCC_VER_MAJOR, YYCC_VER_MINOR, YYCC_VER_PATCH, 1, 3 ,0)
#error "The YYCC library used when compiling is not match code expected, this may cause build error."
#error "If you trust it, please annotate these preprocessor statement, otherwise please contact developer."
#endif
// common testbench
// normal
YYCCTestbench::EncodingTestbench();
YYCCTestbench::StringTestbench();
YYCCTestbench::ParserTestbench();
YYCCTestbench::WinFctTestbench();
YYCCTestbench::StdPatchTestbench();
YYCCTestbench::EnumHelperTestbench();
YYCCTestbench::VersionMacroTestbench();
// advanced
YYCCTestbench::ConfigManagerTestbench();
YYCCTestbench::ArgParserTestbench(argc, argv);
// testbench which may terminal app or ordering input
YYCCTestbench::ConsoleTestbench();
YYCCTestbench::DialogTestbench();
YYCCTestbench::ExceptionTestbench();
}

View File

@ -0,0 +1,203 @@
#include "literals.hpp"
#include <stdexcept>
namespace yyccshared::literals {
#define CONCAT(prefix, strl) prefix##strl
#define U8_LITERAL(strl) CONCAT(u8, strl)
#define U16_LITERAL(strl) CONCAT(u, strl)
#define U32_LITERAL(strl) CONCAT(U, strl)
#define WSTR_LITERAL(strl) CONCAT(L, strl)
#pragma region UtfLiterals Data
// UNICODE Test Strings
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
#define UNICODE_STR_JAPAN "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"
#define UNICODE_STR_CHINA "\u7B80\u4F53\u4E2D\u6587"
#define UNICODE_STR_KOREA "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"
#define UNICODE_STR_ISRAEL "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"
#define UNICODE_STR_EGYPT "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"
#define UNICODE_STR_GREECE "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"
#define UNICODE_STR_RUSSIA \
"\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"
#define UNICODE_STR_THAILAND \
"\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"
#define UNICODE_STR_FRANCE "fran\u00E7ais langue \u00E9trang\u00E8re"
#define UNICODE_STR_SPAIN "ma\u00F1ana ol\u00E9"
#define UNICODE_STR_MATHMATICS "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"
#define UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
static std::vector<std::u8string> UTFLIT_U8STR_VEC{
U8_LITERAL(UNICODE_STR_JAPAN),
U8_LITERAL(UNICODE_STR_CHINA),
U8_LITERAL(UNICODE_STR_KOREA),
U8_LITERAL(UNICODE_STR_ISRAEL),
U8_LITERAL(UNICODE_STR_EGYPT),
U8_LITERAL(UNICODE_STR_GREECE),
U8_LITERAL(UNICODE_STR_RUSSIA),
U8_LITERAL(UNICODE_STR_THAILAND),
U8_LITERAL(UNICODE_STR_FRANCE),
U8_LITERAL(UNICODE_STR_SPAIN),
U8_LITERAL(UNICODE_STR_MATHMATICS),
U8_LITERAL(UNICODE_STR_EMOJI),
};
static std::vector<std::wstring> UTFLIT_WSTR_VEC{
WSTR_LITERAL(UNICODE_STR_JAPAN),
WSTR_LITERAL(UNICODE_STR_CHINA),
WSTR_LITERAL(UNICODE_STR_KOREA),
WSTR_LITERAL(UNICODE_STR_ISRAEL),
WSTR_LITERAL(UNICODE_STR_EGYPT),
WSTR_LITERAL(UNICODE_STR_GREECE),
WSTR_LITERAL(UNICODE_STR_RUSSIA),
WSTR_LITERAL(UNICODE_STR_THAILAND),
WSTR_LITERAL(UNICODE_STR_FRANCE),
WSTR_LITERAL(UNICODE_STR_SPAIN),
WSTR_LITERAL(UNICODE_STR_MATHMATICS),
WSTR_LITERAL(UNICODE_STR_EMOJI),
};
static std::vector<std::u16string> UTFLIT_U16STR_VEC{
U16_LITERAL(UNICODE_STR_JAPAN),
U16_LITERAL(UNICODE_STR_CHINA),
U16_LITERAL(UNICODE_STR_KOREA),
U16_LITERAL(UNICODE_STR_ISRAEL),
U16_LITERAL(UNICODE_STR_EGYPT),
U16_LITERAL(UNICODE_STR_GREECE),
U16_LITERAL(UNICODE_STR_RUSSIA),
U16_LITERAL(UNICODE_STR_THAILAND),
U16_LITERAL(UNICODE_STR_FRANCE),
U16_LITERAL(UNICODE_STR_SPAIN),
U16_LITERAL(UNICODE_STR_MATHMATICS),
U16_LITERAL(UNICODE_STR_EMOJI),
};
static std::vector<std::u32string> UTFLIT_U32STR_VEC{
U32_LITERAL(UNICODE_STR_JAPAN),
U32_LITERAL(UNICODE_STR_CHINA),
U32_LITERAL(UNICODE_STR_KOREA),
U32_LITERAL(UNICODE_STR_ISRAEL),
U32_LITERAL(UNICODE_STR_EGYPT),
U32_LITERAL(UNICODE_STR_GREECE),
U32_LITERAL(UNICODE_STR_RUSSIA),
U32_LITERAL(UNICODE_STR_THAILAND),
U32_LITERAL(UNICODE_STR_FRANCE),
U32_LITERAL(UNICODE_STR_SPAIN),
U32_LITERAL(UNICODE_STR_MATHMATICS),
U32_LITERAL(UNICODE_STR_EMOJI),
};
#pragma endregion
#pragma region UtfLiterals
UtfLiterals::UtfLiterals() :
u8str_vec(UTFLIT_U8STR_VEC), u16str_vec(UTFLIT_U16STR_VEC), u32str_vec(UTFLIT_U32STR_VEC), wstr_vec(UTFLIT_WSTR_VEC) {
// Check whether each vector has same size.
bool okey = true;
size_t exp_size = this->u8str_vec.size();
if (this->u16str_vec.size() != exp_size) okey = false;
if (this->u32str_vec.size() != exp_size) okey = false;
if (this->wstr_vec.size() != exp_size) okey = false;
if (!okey) throw std::logic_error("utf literal vector have different size");
}
UtfLiterals::~UtfLiterals() {}
size_t UtfLiterals::get_size() const {
// We have checked the size of each vector in ctor.
// So we simply return the length of one of them.
return this->u8str_vec.size();
}
const std::vector<std::u8string> &UtfLiterals::get_u8str_vec() const {
return this->u8str_vec;
}
const std::vector<std::u16string> &UtfLiterals::get_u16str_vec() const {
return this->u16str_vec;
}
const std::vector<std::u32string> &UtfLiterals::get_u32str_vec() const {
return this->u32str_vec;
}
const std::vector<std::wstring> &UtfLiterals::get_wstr_vec() const {
return this->wstr_vec;
}
#pragma endregion
#pragma region OtherLiteral
OtherLiteral::OtherLiteral(std::string &&other_str, uint32_t windows_ident, std::string &&iconv_ident, std::u8string &&pycodec_ident) :
other_str(std::move(other_str)), windows_ident(windows_ident), iconv_ident(std::move(iconv_ident)),
pycodec_ident(std::move(pycodec_ident)) {}
OtherLiteral::~OtherLiteral() {}
const std::string &OtherLiteral::get_other_str() const {
return this->other_str;
}
uint32_t OtherLiteral::get_windows_ident() const {
return this->windows_ident;
}
const std::string &OtherLiteral::get_iconv_ident() const {
return this->iconv_ident;
}
const std::u8string &OtherLiteral::get_pycodec_ident() const {
return this->pycodec_ident;
}
#pragma endregion
#pragma region OtherLiterals Data
std::vector<OtherLiteral> OTHERLIT_OTHERSTR_VEC{{"\xC4\xE3\xBA\xC3\xD6\xD0\xB9\xFA", UINT32_C(936), "GBK", u8"gbk"}};
#define OTHER_STR_GBK "\u4f60\u597d\u4e2d\u56fd"
static std::vector<std::u8string> OTHERLIT_U8STR_VEC{U8_LITERAL(OTHER_STR_GBK)};
static std::vector<std::wstring> OTHERLIT_WSTR_VEC{WSTR_LITERAL(OTHER_STR_GBK)};
#pragma endregion
#pragma region OtherLiterals
OtherLiterals::OtherLiterals() : other_str_vec(OTHERLIT_OTHERSTR_VEC), u8str_vec(OTHERLIT_U8STR_VEC), wstr_vec(OTHERLIT_WSTR_VEC) {
// Check whether each vector has same size.
bool okey = true;
size_t exp_size = this->other_str_vec.size();
if (this->u8str_vec.size() != exp_size) okey = false;
if (this->wstr_vec.size() != exp_size) okey = false;
if (!okey) throw std::logic_error("utf literal vector have different size");
}
OtherLiterals::~OtherLiterals() {}
size_t OtherLiterals::get_size() const {
// We have checked the size, return size directly.
return this->other_str_vec.size();
}
const std::vector<OtherLiteral> &OtherLiterals::get_other_str_vec() const {
return this->other_str_vec;
}
const std::vector<std::u8string> &OtherLiterals::get_u8str_vec() const {
return this->u8str_vec;
}
const std::vector<std::wstring> &OtherLiterals::get_wstr_vec() const {
return this->wstr_vec;
}
#pragma endregion
} // namespace yyccshared::literals

View File

@ -0,0 +1,64 @@
#pragma once
#include <yycc.hpp>
#include <yycc/macro/class_copy_move.hpp>
#include <cstdint>
#include <string>
#include <vector>
namespace yyccshared::literals {
class UtfLiterals {
public:
UtfLiterals();
~UtfLiterals();
YYCC_DELETE_COPY_MOVE(UtfLiterals)
size_t get_size() const;
const std::vector<std::u8string>& get_u8str_vec() const;
const std::vector<std::u16string>& get_u16str_vec() const;
const std::vector<std::u32string>& get_u32str_vec() const;
const std::vector<std::wstring>& get_wstr_vec() const;
private:
const std::vector<std::u8string>& u8str_vec;
const std::vector<std::u16string>& u16str_vec;
const std::vector<std::u32string>& u32str_vec;
const std::vector<std::wstring>& wstr_vec;
};
class OtherLiteral {
public:
OtherLiteral(std::string&& other_str, uint32_t windows_ident, std::string&& iconv_ident, std::u8string&& pycodec_ident);
~OtherLiteral();
YYCC_DEFAULT_COPY_MOVE(OtherLiteral)
const std::string& get_other_str() const;
uint32_t get_windows_ident() const;
const std::string& get_iconv_ident() const;
const std::u8string& get_pycodec_ident() const;
private:
std::string other_str;
uint32_t windows_ident;
std::string iconv_ident;
std::u8string pycodec_ident;
};
class OtherLiterals {
public:
OtherLiterals();
~OtherLiterals();
YYCC_DELETE_COPY_MOVE(OtherLiterals)
size_t get_size() const;
const std::vector<OtherLiteral>& get_other_str_vec() const;
const std::vector<std::u8string>& get_u8str_vec() const;
const std::vector<std::wstring>& get_wstr_vec() const;
private:
const std::vector<OtherLiteral>& other_str_vec;
const std::vector<std::u8string>& u8str_vec;
const std::vector<std::wstring>& wstr_vec;
};
} // namespace yyccshared::literals

View File

@ -0,0 +1,50 @@
#include <gtest/gtest.h>
#include <yycc.hpp>
#include <yycc/constraint.hpp>
#include <yycc/rust/prelude.hpp>
#define CONSTRAINT ::yycc::constraint::Constraint
namespace yycctest::constraint {
template<typename T>
bool check(const T& value) {
return false;
}
template<typename T>
T clamp(const T& value) {
return value;
}
TEST(Constraint, Normal) {
CONSTRAINT<u32> instance(check<u32>, clamp<u32>);
EXPECT_TRUE(instance.support_check());
EXPECT_TRUE(instance.support_clamp());
EXPECT_FALSE(instance.check(0));
EXPECT_EQ(instance.clamp(0), 0);
}
TEST(Constraint, SomeNone) {
{
CONSTRAINT<u32> instance(check<u32>, nullptr);
EXPECT_TRUE(instance.support_check());
EXPECT_FALSE(instance.support_clamp());
EXPECT_FALSE(instance.check(0));
}
{
CONSTRAINT<u32> instance(nullptr, clamp<u32>);
EXPECT_FALSE(instance.support_check());
EXPECT_TRUE(instance.support_clamp());
EXPECT_EQ(instance.clamp(0), 0);
}
}
TEST(Constraint, AllNone) {
CONSTRAINT<u32> instance(nullptr, nullptr);
EXPECT_FALSE(instance.support_check());
EXPECT_FALSE(instance.support_clamp());
}
}

View File

@ -0,0 +1,79 @@
#include <gtest/gtest.h>
#include <yycc.hpp>
#include <yycc/constraint/builder.hpp>
#include <yycc/rust/prelude.hpp>
#define BUILDER ::yycc::constraint::builder
using namespace std::literals::string_view_literals;
namespace yycctest::constraint::builder {
#define TEST_SUCCESS(constraint, value) \
EXPECT_TRUE(constraint.check(value)); \
EXPECT_EQ(constraint.clamp(value), value);
#define TEST_FAIL(constraint, value, clamped_value) \
EXPECT_FALSE(constraint.check(value)); \
EXPECT_EQ(constraint.clamp(value), clamped_value);
TEST(ConstraintBuilder, MinMaxConstraint) {
// Integral type
{
auto c = BUILDER::min_max_constraint<i32>(5, 61);
ASSERT_TRUE(c.support_check());
ASSERT_TRUE(c.support_clamp());
TEST_SUCCESS(c, 5);
TEST_SUCCESS(c, 6);
TEST_SUCCESS(c, 61);
TEST_FAIL(c, -2, 5);
TEST_FAIL(c, 0, 5);
TEST_FAIL(c, 66, 61);
}
// Unisgned integral type
{
auto c = BUILDER::min_max_constraint<u32>(5, 61);
ASSERT_TRUE(c.support_check());
ASSERT_TRUE(c.support_clamp());
TEST_SUCCESS(c, 5);
TEST_SUCCESS(c, 6);
TEST_SUCCESS(c, 61);
TEST_FAIL(c, 0, 5);
TEST_FAIL(c, 66, 61);
}
// Float point type
{
auto c = BUILDER::min_max_constraint<f32>(5.0f, 61.0f);
ASSERT_TRUE(c.support_check());
ASSERT_TRUE(c.support_clamp());
TEST_SUCCESS(c, 5.0f);
TEST_SUCCESS(c, 6.0f);
TEST_SUCCESS(c, 61.0f);
TEST_FAIL(c, 0.0f, 5.0f);
TEST_FAIL(c, 66.0f, 61.0f);
}
}
enum class TestEnum : u8 { Entry1 = 0, Entry2 = 1, Entry3 = 2 };
TEST(ConstraintBuilder, EnumConstraint) {
auto c = BUILDER::enum_constraint({TestEnum::Entry1, TestEnum::Entry2, TestEnum::Entry3}, 1u);
ASSERT_TRUE(c.support_check());
ASSERT_TRUE(c.support_clamp());
TEST_SUCCESS(c, TestEnum::Entry1);
TEST_SUCCESS(c, TestEnum::Entry2);
TEST_SUCCESS(c, TestEnum::Entry3);
TEST_FAIL(c, static_cast<TestEnum>(UINT8_C(61)), TestEnum::Entry2);
}
TEST(ConstraintBuilder, StrEnumConstraint) {
auto c = BUILDER::strenum_constraint({u8"first-entry"sv, u8"second-entry"sv, u8"third-entry"sv}, 1u);
ASSERT_TRUE(c.support_check());
ASSERT_TRUE(c.support_clamp());
TEST_SUCCESS(c, u8"first-entry");
TEST_SUCCESS(c, u8"second-entry");
TEST_SUCCESS(c, u8"third-entry");
TEST_FAIL(c, u8"wtf?", u8"second-entry");
}
} // namespace yycctest::constraint::builder

Some files were not shown because too many files have changed in this diff Show More