62 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
0b7e58c8e8 feat: use CMake to generate library version info.
- use CMake to produce YYCC version header when configuring.
2024-11-03 18:52:02 +08:00
831fa130bc feat: improve EnumHelper
- allow multiple enum flags in EnumHelper::Has to check whether given flags contains specified flags.
- update version in CMake script.
2024-11-03 18:06:36 +08:00
7adac00035 doc: fix document about recent changes
- fix build commandline introduction in documentation.
- update build script template.
- only install document in any Release-like build type.
- now testbench will be installed in any Release-like build type, not only Release.
2024-11-03 17:29:34 +08:00
0cd9582757 refactor: refactor project layout
- move header files into an individual directory to prevent the possibility that file name is conflict in Linux include directory.
- update build script generator. use jinja2 template engine to get better view, rather than python code written by hand.
- add version number and version comparation macros in core library.
2024-11-03 14:51:18 +08:00
2206825223 doc: add document for the change of loading function of ConfigManager. 2024-11-02 17:31:19 +08:00
21f7e7f786 feat: add new helper for scoped enum type.
- Add EnumHelper for bitwise operation of scoped enum type (copied from libcmo21)
- Enrich the return value of ConfigManager load function to present more infomation of loading.
- update testbench for new added feature and modification.
- add document for new added feature.
2024-11-02 17:10:55 +08:00
50dd086b53 fix: fix linux build issue 2024-08-27 17:35:57 +08:00
c91df3a74f fix: fix issues.
- add chars format argument for floating point overload of ParserHelper::TryParse.
- add overload for ParserHelper::Parse to match with ParserHelper::TryParse.
- fix the issue that we can not specify c++ standard in command line when configuring project.
- update documentation for changes.
- change following function's argument from const yycc_char8_t* to const yycc_u8string_view&.
	- StringHelper::Split, StringHelper::SplitView
	- StringHelper::Lower, StringHelper::Upper
	- StringHelper::Join
	- StringHelper::Replace
- use iterator type, not std::vector<yycc_u8string> for specialized StringHelper::Join to have more wide usage.
2024-08-26 11:58:20 +08:00
3858b4f3ec fix: use new way to detect c++ version in MSVC.
- use new macro to check C++ version in MSVC, instead of use compiler switch and __cplusplus macro.
2024-08-15 16:50:15 +08:00
f3a88e951c fix: add testbench for new added code. fix issues.
- add testbench for new added code in StdPatch.
- add documentation for new added code.
- fix the old usage of StdPatch::ToStdPath in ExceptionHelper.
2024-08-15 10:38:58 +08:00
59c185a424 doc: update build script and documentation.
- update build script.
- update documentation about build script changes.
2024-08-14 17:26:38 +08:00
dc98486fff chore: add new script for building
- add a python script for generating windows build script but not tested now.
2024-08-14 11:05:36 +08:00
72a48b703f feat: add various functions
- Add Win32 CopyFile, MoveFile, DeleteFile functions in WinFctHelper.
- rename FsPathPatch to StdPatch because this namespace will hold all standard library patches in future.
- add polyfill for std:basic_string::starts_with, std::basic_string::ends_with std::basic_string_view::starts_with, std::basic_string_view::ends_with in StdPatch.
- add polyfill for unordered and ordered associative standard library container's contains function in StdPatch.
- documentation and testbench will be fixed in later commits.
2024-08-13 09:38:12 +08:00
33cb284eb7 feat: add switch for build script to disable documentation build.
- add a new switch to disable documentation build which cost much time and disk space during building.
- add corresponding codumentation for this feature.
2024-08-05 14:46:59 +08:00
e6c24b8b61 feat: add helper macro and new Win32 function.
- add IsValidCodePage in WinFctHelper to check whether code page number is valid.
- add 6 macros to batchly (add / set default) (move / copy) (constructor / assign operator).
- add default or delete (copy / move) (constructor / assign operator) for some classes.
2024-08-04 11:57:56 +08:00
6da990876e feat: add smart FILE pointer.
- use std::unique_ptr and custom deleter to implement smart FILE pointer for convenient auto free.
2024-08-02 09:50:15 +08:00
0ac6b477f9 fix: fix fatal error of ExceptionHelper in x86 environemnt.
- fix a wrong placeholder of printf in ExceptionHelper which cause crash in unhandled exception handler.
- improve format function in ExceptionHelper.
- add a new debugging option and macro in CMake script and code for the convenience of debugging unhandled exception handler.
- add docuementation about previous term.
2024-07-31 20:32:11 +08:00
1cfbcb3b18 doc: update documentation
- use namespace bracket all content in documentation to reduce useless namespace prefix.
- change the argument type of AbstractSetting and CoreManager to yycc_u8string_view instead of const yycc_char8_t*.
- throw exception if given setting name is invalid in ConfigManager, instead of slient fallback.
2024-07-31 14:14:38 +08:00
598aae69ae doc: update documentation 2024-07-31 12:08:30 +08:00
656495f22e doc: update documentation 2024-07-30 22:13:59 +08:00
e167479de3 doc: add documentation for new added features 2024-07-30 17:31:38 +08:00
19023cb949 doc: add callback documentation in ExceptionHelper.
- add callback documentation in ExceptionHelper.
- fix other misc documentation issue.
2024-07-30 10:35:41 +08:00
650fcd12ec feat: add callback for unhandled exception handler.
- add callback for unhandled exception handler to give programmer a chance to fetch log and coredump path, especially for GUI application because its stderr is invisible.
- fix fatal anto-recursive calling bug in unhandled exception handler.
2024-07-29 21:42:27 +08:00
e8a0299fbc feat: finish ArgParser help text output 2024-07-29 19:31:17 +08:00
d1c1743dc9 feat: basically finish ArgParser
- finish ArgParser and test it.
- Help output function will be added in next commit.
2024-07-29 16:58:52 +08:00
35318505e4 feat: add arg parser feature but not finished 2024-07-28 22:42:16 +08:00
136 changed files with 9695 additions and 2486 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

14
.gitignore vendored
View File

@ -1,11 +1,17 @@
# -------------------- Output --------------------
## ===== Personal =====
# Ignore build resources
out/
build/
install/
# Ignore CMake generated stuff
src/yycc/version.hpp
CMakeSettings.json
# -------------------- VSCode --------------------
## ===== VSCode =====
.vscode/
# -------------------- CMake --------------------
## ===== CMake =====
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
@ -18,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,12 +1,18 @@
cmake_minimum_required(VERSION 3.23)
project(YYCC
VERSION 1.1.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_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)
@ -19,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)
@ -45,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(
@ -53,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

@ -8,9 +8,15 @@ For more usage about this library, please build documentation of this project vi
And I also highly recommend that you read documentation first before writing with this library.
However, the documentation need CMake to build and you may don't know how to use CMake in this project. So as the alternative, you also can browse the raw Doxygen documentation file: `doc/src/intro.dox` for how to build this project (including documentation) first.
## Build
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.
For Windows builing, you can browse GitHub action script to have a preview. It actually is a simple calling to script file.
For other platforms building (e.g. Linux), you can following common builing way of CMake project.
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

@ -14,5 +14,6 @@ add_custom_target (YYCCDocumentation
# Install built documentation
install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
CONFIGURATIONS Release RelWithDebInfo MinSizeRel
DESTINATION ${YYCC_INSTALL_DOC_PATH}
)

200
doc/src/arg_parser.dox Normal file
View File

@ -0,0 +1,200 @@
namespace YYCC::ArgParser {
/**
\page arg_parser Universal Argument Parser
YYCC::ArgParser provides an universal way to parsing command line arguments.
Universal argument parser has similar design with universal config manager,
it is highly recommand that read \ref config_manager chapter first,
because you will have a clear understanding of this namespace after reading universal config manager chapter.
There is an example about how to use universal argument parser.
In following content, we will describe it in detail.
\code{.cpp}
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;
};
// Initialize argument parser.
TestArgParser test;
// Get argument list for parsing from standard C main function.
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
// Start parsing
test.Parse(al);
// Get captured string argument
if (test.m_StringArgument.IsCaptured())
auto val = test.m_StringArgument.Get();
\endcode
These code can resolve following command line:
\code{.sh}
exec -i 114514 -f 2.0 --string fuck -b --clamped-float 0.5
\endcode
For convenience, we define following terms used in this article.
\li Every items in command line: Argument.
\li \c -i, \c --clamped-float: \b Switch / \b Option. the argument starts with dash or double dash.
\li \c 114514: \b Value. the value of switch.
\section arg_parser__argument Argument
Argument is the leaf of argument parser.
It has the same position as setting in universal config manager.
\subsection arg_parser__argument__presets Argument Presets
Like setting in universal config manager,
we also provide various common used argument presets.
Current'y we support following argument presets:
\li NumberArgument: The argument storing arithmetic type (except \c bool) inside. Such as <TT>-i 114514</TT> in example.
\li StringArgument: The argument storing string inside. Such as <TT>--string fuck</TT> in example.
\li SwitchArgument: The argument storing nothing. It is just a simple switch. Such as <TT>-b</TT> in example.
When constructing these argument,
you need provide one from long name or short name, or both of them.
Short name is the argument starting with dash and long name starts with double dash.
You don't need add dash or double dash prefix when providing these names.
Please note only ASCII characters, which can be displayed on screen, can be used in these names.
Optionally, you can provide description when constructing,
which will tell user how this switch does and more infomation about this switch.
And, you can add an example to tell user which value is valid.
Next, you can specify an argument to be optional.
Optional argument can be absent in command line.
Oppositely, non-optional argument must be presented in command line,
otherwise parser will return false to indicate an error.
For checking whether an optional argument is specified,
please call AbstractArgument::IsCaptured().
Last, you can optionally assign a constraint to it,
to help argument limit its value.
However SwitchArgument must be optional argument.
Because it is true if user specify it explicit it,
and will be false if user do not give this flag.
SwitchArgument doesn't have constraint features,
because it doesn't store any value inside.
Thus no need to limit this.
\subsection arg_parser__argument__custom Custom Argument
In most cases, the combination use of argument presets and constraints is enough.
However, if you still are urge to create your personal argument,
please inherit AbstractArgument and implement essential class functions.
For the class functions you need to implement,
please refer to our argument presets.
\section arg_parser__argument_list Argument List
Argument list is a struct used by parser for parsing.
It is a higher wrapper of a simple list containing argument items.
We provide 2 ways to get argument list.
\li ArgumentList::CreateFromStd: Create argument list from standard C main function parameters.
\li ArgumentList::CreateFromWin32: Create argument list from Win32 functions in Windows.
You should use this function in Windows instead of ArgumentList::CreateFromStd.
Because the command line passed in standard C main function has encoding issue in Windows.
Use this function you will fetch correct argument list especially command including non-ASCII characters.
Please note the first argument in given command line will be stripped.
Because in most cases it point to the executable self,
and should not be seen as the part of argument list.
\section arg_parser__option_context Option Context
Please note any unknow argument will let the parser return false.
This is different with other argument parsers.
In other common argument parsers,
they will collect all unknow argument as positional argument,
or just simply ignore them.
OptionContext also will not add \c -h or \c --help switch automatically.
This is also differnent with other parsers.
You should manually add it.
However, OptionContext provide a universal help print function, OptionContext::Help.
You can directly call it to output help text if you needed (fail to parse or user order help).
\section arg_parser__limitation Limitation
This universal argument parser is a tiny parser.
It only just fulfill my personal requirements.
So it only accepts limited command line syntax.
In following content I will tell you some syntaxes which this parser \b not accept.
\subsection arg_parser__limitation__flag_combination Flag Combination
\code{.sh}
exec -l -s -h
exec -lsh
\endcode
Parser accept first line but not accept the second line.
You must write these flags independently.
\subsection arg_parser__limitation__equal_symbol Equal Symbol
\code{.sh}
exec --value 114514
exec --value=114514
exec --value:114514
\endcode
Parser only accept first line command.
You can not use equal symbol or any other symbol to assign value for specified argument.
You must write value after the argument immediately please.
\subsection arg_parser__limitation__variable_argument Variable Argument
\code{.sh}
exec -DSOME_VARABLE=SOME_VALUE
exec -D SOME_VARIABLE=SOME_VALUE
\endcode
Parser only accept second line.
However you nned to write a custom argument or constraint to holding this value.
\subsection arg_parser__limitation__switch_dependency Switch Dependency
\code{.sh}
exec --action-a --action-b
\endcode
For command line written above,
if you hope \c --action-a and \c --action-b is exclusive,
or \c --action-b only be valid if \c --action-a specified,
you should manually implement this.
Parser don't have such features to process this switch dependency.
The thing you need to do is set these switches are \b not optional.
And after parser do a success parsing,
manually calling AbstractArgument::IsCaptured to fetch whether corresponding switches are captured,
then do your personal dependency check.
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::COMHelper {
/**
\page com_helper COM Helper
@ -23,11 +24,12 @@ This namespace contain a COM Guard which make sure COM was initialized in curren
It is essential because all calling to COM functions should be under the premise that COM has been initialized.
This guard also will uninitialize COM when unloading this module.
There is only an exposed function called YYCC::COMHelper::IsInitialized for user calling.
There is only an exposed function called #IsInitialized for user calling.
This function will check whether COM environment is initialized.
If you want YYCC automatically initialize COM environment for you,
you must call this function in your program at least one time.
Otherwise COM Guard code may be unavailable,
because compiler may think they are not essential code and drop them.
*/
*/
}

View File

@ -1,8 +1,9 @@
namespace YYCC::ConfigManager {
/**
\page config_manager Universal Config Manager
Universal config manager give programmer an universal way to manage its program settings.
YYCC::ConfigManager give programmer an universal way to manage its program settings.
There is an example about how to use universal config manager.
In following content, we will describe it in detail.
@ -16,7 +17,7 @@ public:
TestConfigManager() :
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
m_FloatSetting(YYCC_U8("float-setting"), 0.0f, YYCC::ConfigManager::ConstraintPresets::GetNumberRangeConstraint<float>(-1.0f, 1.0f)),
m_FloatSetting(YYCC_U8("float-setting"), 0.0f, YYCC::Constraints::GetNumberRangeConstraint<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_StringSetting, &m_FloatSetting, &m_EnumSetting
@ -32,11 +33,11 @@ public:
YYCC::ConfigManager::CoreManager m_CoreManager;
};
// init cfg manager
// Initialize config manager
TestConfigManager test;
// load string
// Load settings.
test.m_CoreManager.Load()
// get string value
// Get string setting value.
auto val = test.m_StringSetting.Get();
\endcode
@ -49,56 +50,31 @@ Each setting describe a single configuration entry.
We currently provide 2 setting preset classes which you can directly use.
\li YYCC::ConfigManager::NumberSetting: The setting storing a number inside.
\li NumberSetting: The setting storing a number inside.
It is a template class. Support all arithmetic and enum types (integral, floating point, bool, enum).
\li YYCC::ConfigManager::StringSetting: The setting storing a string inside.
\li StringSetting: The setting storing a string inside.
When constructing these settings,
you need to provide its unique name which will be used when saving to file or reading from file.
Also you need to provide a default value for it.
It will be used when fail to read file or initializing itself.
Optionally, you can assign a constraint to it which we will introduce in following section.
Optionally, you also can provide a constraint to setting.
Constraint is the struct instructing library to limit value in specified range.
It usually is used for making sure the setting stored value is valid.
See \ref constraints chapters to know how we provide constraints.
\subsection config_manager__setting__custom Custom Setting
In most cases, the combination use of setting presets and constraints introduced in following is enough.
In most cases, the combination use of setting presets and constraints is enough.
However, if you still are urge to create your personal setting,
please inherit YYCC::ConfigManager::AbstractSetting and implement essential class functions.
please inherit AbstractSetting and implement essential class functions.
For the class functions you need to implement,
please refer to our setting presets, YYCC::ConfigManager::NumberSetting and YYCC::ConfigManager::StringSetting.
\section config_manager__constraint Constraint
Constraint can be applied to specific setting instance,
and limit its value to specific values,
such as minimum maximum value, specific string format and etc.
\subsection config_manager__constraint__presets Constraint Presets
YYCC::ConfigManager provide some constraint presets in YYCC::ConfigManager::Constraints namespace.
All functions inside this namespace will return a YYCC::ConfigManager::Constraint instance,
and you can directly assign it to the constructor of setting.
Currently there is only one constraint preset:
\li YYCC::ConfigManager::Constraints::GetNumberRangeConstraint: Constrain the number value in minimum maximum value range (inclusive).
\subsection config_manager__constraint__custom Custom Constraint
For creating your personal constraint,
you need to create YYCC::ConfigManager::Constraint instance manually.
First you need decide the template argument of YYCC::ConfigManager::Constraint.
The type you assigned to template argument always is
the same type which is accepted by the setting this constraint will be applied to.
Second, you need assign class member of YYCC::ConfigManager::Constraint.
Currently there is only one class member.
It is a function pointer called when correcting value.
See our constraint presets for more infomation about how to write it.
please refer to our setting presets, NumberSetting and StringSetting.
\section config_manager__core_manager Core Manager
YYCC::ConfigManager::CoreManager manage a collection of settings.
CoreManager manage a collection of settings.
And have responsibility to reading and writing config file.
We highly suggest that you create a personal config manager class like example does.
@ -110,11 +86,66 @@ so it must be initialized after initializing all settings.
When initializing core manager, you need assign config file path first.
Then you need specify a version number.
Version number will be used when reading config file.
If the version of config file is higher than your given number,
core manager will assume you are trying to read a config file created by a higher version program.
Core manager will reject reading and use default value for all settings.
Otherwise, core manager will try to read config file and do proper migration if possible.
Version number is important.
It will be used when reading config file and only can be increased if needed (version can not downgrade).
The last argument is an initializer list which contain the \b pointer to all settings this manager managed.
*/
When executing YYCC::ConfigManager::CoreManager::Load to load configs, it will perform following steps one by one:
<UL>
<LI>
Open given config file.
<UL>
<LI>
If given file is not existing, loading function will simply return and all configs will be reset to its default value.
</LI>
<LI>
Success to open file, go to next step.
</LI>
</UL>
</LI>
<LI>
Fetch version number from file.
<UL>
<LI>
If fail to read version number from file, loading function will simply return and all configs will be reset to its default value.
</LI>
<LI>
If the version of config file is higher than your specified version number when constructing this class,
core manager will assume you are trying to read a config file created by a higher version program,
and will reject reading and use default value for all settings.
</LI>
<LI>
If the version of config file is lower than your specified version number,
core manager will try to read config file and do proper migration (set default value for configs which do not existing) if possible.
</LI>
<LI>
If the version of config file is equal than your specified version number,
core manager will read config file normally.
</LI>
</UL>
</LI>
<LI>
Read config file body.
<UL>
<LI>
If any IO error occurs when reading, loading function will simply return.
All read config will keep their read value and all configs which has not been read will keep their default value.
</LI>
<LI>
If some config can not parse binary data to its type,
this config will be skipped and core manager will process next config.
This config will keep its default value.
</LI>
</UL>
</LI>
</UL>
All of these scenarios can be found by the return value of loading function.
The return type of loading function, ConfigLoadResult is a flag enum.
You can find whether loading process happend specified issue by using bitwise operation on it.
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::ConsoleHelper {
/**
\page console_helper Console Helper
@ -22,9 +23,9 @@ That's ASCII Escape Code.
As we introduced in above,
you may know Windows console does not support ASCII Escape Code color in default.
However YYCC::ConsoleHelper::EnableColorfulConsole can fix this issue.
However #EnableColorfulConsole can fix this issue.
YYCC::ConsoleHelper::EnableColorfulConsole will forcely enable ASCII Escape Code support in Windows console if possible.
#EnableColorfulConsole will forcely enable ASCII Escape Code support in Windows console if possible.
Thus you can write colorful text in Windows console freely.
We suggest you to call this function at the beginning of program.
@ -46,7 +47,7 @@ And for second line, it will make <TT>"Light Red"</TT> to be shown in light red
but <TT>"I am "</TT> will keep default console font color.
You also may notice this macro is used with YYCC_U8 macro.
Because YYCC::ConsoleHelper::WriteLine only accept UTF8 argument.
Because #WriteLine only accept UTF8 argument.
So please note if you use console color macro with YYCC_U8,
please make YYCC_U8 always is located the outside.
Otherwise, YYCC_U8 will fail to make the whole become UTF8 stirng as we introduced in \ref library_encoding.
@ -176,4 +177,5 @@ only write plain string like \c std::fputs().
This is commonly used, otherwise functions will only write the text provided by arguments,
without adding something.
*/
*/
}

49
doc/src/constraints.dox Normal file
View File

@ -0,0 +1,49 @@
namespace YYCC::Constraints {
/**
\page constraints Constraints
YYCC::Constraints namespace provide Constraint struct declaration
and various common constraint generator function.
This namespace is specifically used by YYCC::ConfigManager and YYCC::ArgParser namespaces.
See \ref config_manager chapter and \ref arg_parser chapter for how to utlize this namespace.
\section constraints__prototype Prototype
Constraint instruct library how check whether given value is in range,
and how to clamp it if it is invalid.
For example, you can use constraint to limit a number in given minimum maximum value,
or limit a string in specific format by using regex and etc.
Constraint is a template struct.
The argument of template is the underlying data type which need to be checked.
The struct with different template argument is not compatible.
Currently, this struct only contain 1 function pointer,
which is used for detecting whether given value is in range / valid.
\subsection constraints__presets Constraint Presets
YYCC::Constraints provides some constraint presets which are commonly used.
All functions inside this namespace will return a Constraint instance,
and you can directly use it.
There is a list of all provided functions:
\li GetMinMaxRangeConstraint(): Limit the number value in given minimum maximum value range (inclusive).
\li GetEnumEnumerationConstraint(): Limit the enum value by given all possible value set.
\li GetStringEnumerationConstraint(): Limit the string by given all possible value set.
\subsection config_manager__constraint__custom Custom Constraint
For creating your personal constraint,
you need to create Constraint instance manually.
You can browse all existing constraint preset functions code for know how to write it.
The things you need to do is simple.
First, you need decide the template argument of Constraint.
Second, you need assign class member of Constraint by C++ lambda syntax.
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::DialogHelper {
/**
\page dialog_helper Dialog Helper
@ -12,7 +13,7 @@ It will be totally invisible if you are in other platforms.
\section dialog_helper__file_dialog Configure File Dialog
The first thing is that we should initialize YYCC::DialogHelper::FileDialog,
The first thing is that we should initialize FileDialog,
and configure it according to your requirements.
This class is the data struct representing all aspects of file dialog.
@ -28,7 +29,7 @@ params.SetInitDirectory(initial_directory_getter());
\subsection dialog_helper__file_dialog__owner Owner
YYCC::DialogHelper::FileDialog::SetOwner will set owner of this dialog.
FileDialog::SetOwner will set owner of this dialog.
It accepts a Microsoft defined \c HWND as argument which should be familiar with Windows programmer.
If you pass \c NULL to it or skip calling this function, it indicate that there is no owner of this dialog.
<I>
@ -38,7 +39,7 @@ But it would be better to have an owner if possible.
\subsection dialog_helper__file_dialog__title Title
YYCC::DialogHelper::FileDialog::SetTitle will set dialog title of this dialog.
FileDialog::SetTitle will set dialog title of this dialog.
If you pass \c nullptr or skip calling it,
the title of dialog will be filled by system and the function type you calling.
For example, the title will be "Open..." if you call open file function,
@ -49,7 +50,7 @@ So I suggest you do not set title except you really want to modify title.
\subsection dialog_helper__file_dialog__init_file_name Initial File Name
YYCC::DialogHelper::FileDialog::SetInitFileName will set the initial file name presented in dialog file name input box.
FileDialog::SetInitFileName will set the initial file name presented in dialog file name input box.
If you pass \c nullptr or skip calling it, the text in dialog file name input box will be empty.
User can modify the name presented in input box later.
@ -59,7 +60,7 @@ However, if you specify this field, the dialog will always presented your specif
\subsection dialog_helper__file_dialog__init_directory Initial Directory
YYCC::DialogHelper::FileDialog::SetInitDirectory will set the initial directory (startup directory) when opening dialog.
FileDialog::SetInitDirectory will set the initial directory (startup directory) when opening dialog.
In following cases, initial directory will fall back to system behavior:
@ -80,7 +81,7 @@ It is beneficial to let user get the file which they want in a directory includi
Because the file dialog picking directory does not have file filter drop down box.
Directory can not be filtered.
YYCC::DialogHelper::FileFilters takes responsibility for this feature:
FileFilters takes responsibility for this feature:
\code
auto& filters = params.ConfigreFileTypes();
@ -94,9 +95,9 @@ params.SetDefaultFileTypeIndex(0u);
\subsection dialog_helper__file_filters__setup File Filters
We don't need to initialize YYCC::DialogHelper::FileFilters by ourselves.
Oppositely, we fetch it from YYCC::DialogHelper::FileDialog instance by calling YYCC::DialogHelper::FileDialog::ConfigreFileTypes.
After fetching, we can call YYCC::DialogHelper::FileFilters::Add to add a filter pair for file filters.
We don't need to initialize FileFilters by ourselves.
Oppositely, we fetch it from FileDialog instance by calling FileDialog::ConfigreFileTypes.
After fetching, we can call FileFilters::Add to add a filter pair for file filters.
The first argument is the display text which user will see in file filter drop down box.
@ -106,21 +107,21 @@ It is okey to use multiple wildcard string in list.
This is suit for those file types involving multiple file extensions, such as the old and new file types of Microsoft Office as we illustracted.
Empty list not allowed
YYCC::DialogHelper::FileFilters::Add also will return a bool to indicate the success of this adding.
FileFilters::Add also will return a bool to indicate the success of this adding.
It should at least has one file filter in file dialog.
I don't know the consequence if you don't provide any file filter.
\subsection dialog_helper__file_filters__default_filter Default File Type
YYCC::DialogHelper::FileDialog::SetDefaultFileTypeIndex will set the default selected file filter of this dialog.
FileDialog::SetDefaultFileTypeIndex will set the default selected file filter of this dialog.
It accepts an index pointing to the file filter which you want to show in default for this file dialog.
The index of file filters is the order where you call YYCC::DialogHelper::FileFilters::Add above.
The index of file filters is the order where you call FileFilters::Add above.
If you pass \c NULL to it or skip calling this function, the first one will be default.
\section dialog_helper__result Create Dialog and Get Result
Finally, we can call file dialog functions by we initialized YYCC::DialogHelper::FileDialog
Finally, we can call file dialog functions by we initialized FileDialog
\code
YYCC::yycc_u8string single_selection;
@ -134,14 +135,14 @@ YYCC::DialogHelper::OpenFolderDialog(params, single_selection);
There are 4 file dialogs you can choose:
\li YYCC::DialogHelper::OpenFileDialog: Open single file
\li YYCC::DialogHelper::OpenMultipleFileDialog: Open multiple files
\li YYCC::DialogHelper::SaveFileDialog: Save single file
\li YYCC::DialogHelper::OpenFolderDialog: Open single directory
\li #OpenFileDialog: Open single file
\li #OpenMultipleFileDialog: Open multiple files
\li #SaveFileDialog: Save single file
\li #OpenFolderDialog: Open single directory
\subsection dialog_helper__result__arguments Arguments
Among these 4 functions, the first argument always is the reference to YYCC::DialogHelper::FileDialog.
Among these 4 functions, the first argument always is the reference to FileDialog.
Function will use it to decide what should be shown in this file dialog.
The second argument always is the reference to the container receiving the result.
@ -160,8 +161,9 @@ You may notice there are various classes which we never introduce.
Because they are intermediate classes and should not be used by programmer.
For example:
\li YYCC::DialogHelper::WinFileDialog: The converted YYCC::DialogHelper::FileDialog passed to Windows.
\li YYCC::DialogHelper::WinFileFilters: Same as YYCC::DialogHelper::WinFileDialog. It will be passed to Windows functions.
\li WinFileDialog: The converted FileDialog passed to Windows.
\li WinFileFilters: Same as WinFileDialog. It will be passed to Windows functions.
\li etc...
*/
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::EncodingHelper {
/**
\page encoding_helper Encoding Helper
@ -15,10 +16,10 @@ See \ref library_encoding for more infomation.
YYCC supports following convertions:
\li YYCC::EncodingHelper::ToUTF8: Convert ordinary string to UTF8 string.
\li YYCC::EncodingHelper::ToUTF8View: Same as ToUTF8, but return string view instead.
\li YYCC::EncodingHelper::ToOrdinary: Convert UTF8 string to ordinary string.
\li YYCC::EncodingHelper::ToOrdinaryView: Same as ToOrdinary, but return string view instead.
\li #ToUTF8: Convert ordinary string to UTF8 string.
\li #ToUTF8View: Same as ToUTF8, but return string view instead.
\li #ToOrdinary: Convert UTF8 string to ordinary string.
\li #ToOrdinaryView: Same as ToOrdinary, but return string view instead.
\section encoding_helper__win_conv Windows Specific Convertion
@ -32,13 +33,13 @@ Please use them carefully (make sure that you are using them only in Windows env
YYCC supports following convertions:
\li YYCC::EncodingHelper::WcharToChar: Convert \c wchar_t string to code page specified string.
\li YYCC::EncodingHelper::CharToWchar: The reversed convertion of WcharToChar.
\li YYCC::EncodingHelper::CharToChar: Convert string between 2 different code pages. It's a shortcut of calling CharToWchar and WcharToChar successively.
\li YYCC::EncodingHelper::WcharToUTF8: Convert \c wchar_t string to UTF8 string.
\li YYCC::EncodingHelper::UTF8ToWchar: The reversed convertion of WcharToUTF8.
\li YYCC::EncodingHelper::CharToUTF8: Convert code page specified string to UTF8 string.
\li YYCC::EncodingHelper::UTF8ToChar: The reversed convertion of CharToUTF8.
\li #WcharToChar: Convert \c wchar_t string to code page specified string.
\li #CharToWchar: The reversed convertion of WcharToChar.
\li #CharToChar: Convert string between 2 different code pages. It's a shortcut of calling CharToWchar and WcharToChar successively.
\li #WcharToUTF8: Convert \c wchar_t string to UTF8 string.
\li #UTF8ToWchar: The reversed convertion of WcharToUTF8.
\li #CharToUTF8: Convert code page specified string to UTF8 string.
\li #UTF8ToChar: The reversed convertion of CharToUTF8.
Code Page is a Windows concept.
If you don't understand it, please view corresponding Microsoft documentation.
@ -55,15 +56,15 @@ They can be used in any platform, not confined in Windows platforms.
YYCC supports following convertions:
\li YYCC::EncodingHelper::UTF8ToUTF16: Convert UTF8 string to UTF16 string.
\li YYCC::EncodingHelper::UTF16ToUTF8: The reversed convertion of UTF8ToUTF16.
\li YYCC::EncodingHelper::UTF8ToUTF32: Convert UTF8 string to UTF32 string.
\li YYCC::EncodingHelper::UTF32ToUTF8: The reversed convertion of UTF8ToUTF32.
\li #UTF8ToUTF16: Convert UTF8 string to UTF16 string.
\li #UTF16ToUTF8: The reversed convertion of UTF8ToUTF16.
\li #UTF8ToUTF32: Convert UTF8 string to UTF32 string.
\li #UTF32ToUTF8: The reversed convertion of UTF8ToUTF32.
\section encoding_helper__overloads Function Overloads
Every encoding convertion functions (except the convertion between UTF8 and ordinary string) have 4 different overloads for different scenarios.
Take YYCC::EncodingHelper::WcharToChar for example.
Take #WcharToChar for example.
There are following 4 overloads:
\code
@ -98,7 +99,7 @@ For the first type, please note that there is \b NO guarantee that the argument
Even the convertion is failed, the argument holding return value may still be changed by function itself.
In this case, the type of result is \c std::string because this is function required.
In other functions, such as YYCC::EncodingHelper::WcharToUTF8, the type of result can be \c yycc_u8string or etc.
In other functions, such as #WcharToUTF8, the type of result can be \c yycc_u8string or etc.
So please note the type of result is decided by convertion function itself, not only \c std::string.
\subsection encoding_helper__overloads__source Source String
@ -106,7 +107,7 @@ So please note the type of result is decided by convertion function itself, not
According to the way providing source string,
these 4 overload also can be divided into 2 types.
The first type take a reference to constant \c std::wstring_view.
The second type take a pointer to constant wchar_t.
The second type take a pointer to constant \c wchar_t.
For first type, it will take the whole string for convertion, including \b embedded NUL terminal.
Please note we use string view as argument.
@ -120,12 +121,12 @@ If you want to process string with \b embedded NUL terminal, please choose first
Otherwise the second type overload is enough.
Same as destination string, the type of source is also decided by the convertion function itself.
For exmaple, the type of source in YYCC::EncodingHelper::UTF8ToWchar is \c yycc_u8string_view and \c yycc_char8_t,
For exmaple, the type of source in #UTF8ToWchar is \c yycc_u8string_view and \c yycc_char8_t,
not \c std::wstring and \c wchar_t.
\subsection encoding_helper__overloads__extra Extra Argument
There is an extra argument called \c code_page for YYCC::EncodingHelper::WcharToChar.
There is an extra argument called \c code_page for #WcharToChar.
It indicates the code page of destination string,
because this function will convert \c wchar_t string to the string with specified code page encoding.
@ -143,4 +144,5 @@ we have 4 different overload as we illustrated before.
Programmer can use them freely according to your requirements.
And don't forget to provide extra argument if function required.
*/
*/
}

35
doc/src/enum_helper.dox Normal file
View File

@ -0,0 +1,35 @@
namespace YYCC::EnumHelper {
/**
\page enum_helper Scoped Enum Helper
\section enum_helper__intro Intro
C++ introduce a new enum called scoped enum.
It is better than legacy C enum because it will not leak name into namespace where it locate,
and also can specify an underlying type to it to make sure it is stored as specified size.
However, the shortcoming of it is that it lack bitwise operator comparing with legacy C enum.
Programmer must implement them for scoped enum one by one.
It is a hardship and inconvenient.
This is the reason why I invent this class
\section enum_helper__Usage Usage
In this namespace, we provide all bitwise functions related to scoped enum type which may be used.
See YYCC::EnumHelper for more detail (It is more clear to read function annotation than I introduce in there repeatedly).
\section enum_helper__why Why not Operator Overload
I have try it (and you even can see the relic of it in source code).
But it need a extra statement written in following to include it, otherwise compiler can not see it.
\code
using namespace YYCC::EnumHelper;
\endcode
Another reason why I do not use this method is that
this overload strategy may be applied to some type which should not be applied by accient, such as non-scoped enum type.
So I gave up this solution.
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::ExceptionHelper {
/**
\page exception_helper Unhandled Exception Handler
@ -18,11 +19,24 @@ It will be invisible on other platforms.
\subsection exception_helper__usage__code Register Code
In most scenarios, programmer only need call YYCC::ExceptionHelper::Register() when program started or module loaded.
And call YYCC::ExceptionHelper::Unregister when program exited or module unloaded.
In most scenarios, programmer only need call #Register when program started or module loaded.
And call #Unregister when program exited or module unloaded.
All details are hidden by these 2 feature.
Programmer do not need worried about the implementation of unhandled exception handler.
Optionally, you can provide a function pointer during calling #Register as a callback.
The prototype of this function pointer is #ExceptionCallback.
This callback will be called if any unhandled exception happened.
It provides 2 pathes to log file and core dump file respectively.
So that you can use an explicit way, e.g. \c MessageBox, to tell user exception happened and where are the log files,
especially in GUI application because the default output stream, \c stderr, is invisible in GUI application.
However, please note the pathes provided by callback may be empty.
In this case, it means that handler fail to create corresponding log files.
Also, if you trying to register unhandled exception handler on the same process in different module with different callback,
only the callback provided in first success registering will be called when unhandled exception happened,
due to \ref exception_helper__notes__singleton design.
\subsection exception_helper__usage__location Location
When unhandled exception occurs,
@ -64,16 +78,16 @@ YYCC::ExceptionHelper also have a mechanism that make sure the same unhandled ex
For example, you have an executable program A.exe, and 2 dynamic libraries B.dll and C.dll.
A.exe and B.dll use YYCC unhandled exception handler feature but C.dll not.
A.exe will load B.dll and C.dll at runtime.
Although both A.exe and B.dll call YYCC::ExceptionHelper::Register(),
Although both A.exe and B.dll call #Register,
when unhandled exception occurs, there is only one error report output,
which may be generated by A.exe or B.dll accoridng to their order of loading.
The core purpose of this is making sure the program will not output too many error report for the same unhandled exception,
no matter how many modules calling YYCC::ExceptionHelper::Register() are loaded.
no matter how many modules calling #Register are loaded.
Only one error report is enough.
More precisely, we use \c CreateMutexW to create an unique mutex in Windows global scope,
to make sure YYCC::ExceptionHelper::Register() only run once in the same process.
to make sure #Register only run once in the same process.
It is very like the implementation of singleton application.
\subsection exception_helper__notes__recursive_calling Recursive Calling
@ -84,4 +98,13 @@ YYCC::ExceptionHelper has internal mechanism to prevent this bad case.
If this really happened, the handler will quit silent and will not cause any issue.
Programmer don't need to worry about this.
\subsection exception_helper__notes__user_callback The Timing of User Callback
The timing of calling user callback is the tail of unhandled exception handler.
It means that all log and coredump have been written if possible before calling callback.
Because user callback may still raise exception.
We want all essential log files has been written before calling it,
so that at least we can visit them on disk or console.
*/
}

View File

@ -29,7 +29,7 @@
\li \subpage intro
\li \subpage platform_checker
\li \subpage library_macros
\li \subpage library_encoding
@ -43,12 +43,18 @@
\li \subpage io_helper
\li \subpage fs_path_patch
\li \subpage std_patch
\li \subpage enum_helper
<B>Advanced Features</B>
\li \subpage constraints
\li \subpage config_manager
\li \subpage arg_parser
</TD>
<TD ALIGN="LEFT" VALIGN="TOP">

View File

@ -29,12 +29,12 @@ During programming, I found Windows is super lack in UTF8 supports.
Programmer loves UTF8, because it can handle all charcaters over the world in one encoding and is still compatible with C-Style string.
However, Windows use a weird way to achieve internationalization, 2 different function trailing, A and W for legacy code and modern code respectively.
The worst things is that the char type W trailing function used, \c WCHAR, is defined as 2 bytes long, not 4 bytes long as Linux does (\c wchar_t).
It mean that one emoji charcater will be torn into 2 \c WCHAR on Windows because emoji code unit is higher than the manimum value of \c WCHAR.
It mean that one emoji charcater will be torn into 2 \c WCHAR on Windows because emoji code unit is higher than the maximum value of \c WCHAR.
Also, there are various issues which should not be presented.
For example, Microsoft invents various \e safe standard library functions to prevent possible overflow issues raised by \c std::fgets and etc.
also, MSVC may throw weird error when you using some specific standard library functions.
You need to define some weird macro to disable this shitty behavior.
You need to define some weird macros to disable this shitty behavior.
There are various non-standard issue you may faced on Windows programming.
All in all, programming on Windows is a tough work.
@ -102,10 +102,36 @@ Another one is MSVC distribution, this distribution is served for other MSVC pro
These have different directory layout which is specifically designed for corresponding build tools.
See following section for more details.
\subsection intro__usage__win__execute Execute Build Script
For creating distribution on Windows, please execute script <TT>python3 script/gen_win_build.py</TT> first.
Then execute <TT>script/win_build.bat</TT> to generate final result.
\c script/gen_win_build.py is the generator of \c script/win_build.bat.
It will accept various arguments and generate a proper real build script for you.
Currently \c script/gen_win_build.py supports following arguments:
\li \c -c, \c --cpp \c [cpp_version]: Specify the version of C++ standard for building.
Due to the different defination of UTF8 char type,
C++ 20 program can not use this library built by C++ 17 environment.
So this switch give you a chance to decide the version of C++ standard used when building.
The lowest and defult version of C++ standard is 17.
\li \c -d, \c --build-doc: Specify this if you want to build documentation.
End user usually needs documentation,
however if you are the developer of this library, you may need this switch.
Because documentation take too much disk space and cost a bunch of time for building and copying.
In default, generator will produce script which do not build documentation automatically.
\li \c -p, \c --pic: Enable Position Independent Code flag on non-Windows platfotm.
This flag is crucial to linking this library to another dynamic library.
If you do not specify this flag, the linking process will fail.
After script done, you will find CMake distribution in directory <TT>bin/<I>cpp_ver</I>/install</TT>.
and you will also find your MSVC distribution in directory <TT>bin/<I>cpp_ver</I>/msvc_install</TT>.
\e cpp_ver in path will be replaced by the C++ version you specified.
\subsubsection intro__usage__win__cmake CMake Distribution
For creating CMake distribution, please execute script <TT>script/win_build.bat</TT>.
After script done, you will find CMake distribution in directory <TT>bin/install</TT> with following structure.
CMake distribution has following directory structure.
\verbatim
YYCC
@ -132,11 +158,7 @@ So that CMake will automatically utilize correct package when switching build ty
\subsubsection intro__usage__win__msvc MSVC Distribution
Before creating MSVC distribution, you should create CMake distribution first,
because MSVC distribution depend on CMake distribution.
After creating CMake distribution, you can simply create MSVC distribution by executing <TT>script/win_msvc_build.bat</TT>.
Then you will find your MSVC distribution in directory <TT>bin/msvc_install</TT> with following structure.
MSVC distribution has following directory structure.
\verbatim
YYCC
@ -159,4 +181,11 @@ that MSVC distribution places all static library under one director \c lib.
Thus in MSVC project user can simply spcify the install path of YYCC,
and use MSVC macros in path to choose correct static library for linking
\section intro__debug Debug Tips
YYCC CMake build script contains a special option called \c YYCC_DEBUG_UE_FILTER.
If you set it to true, it will add a public macro \c YYCC_DEBUG_UE_FILTER to YYCC project.
This macro will enable special code path for the convenience of debugging \ref exception_helper related features.
So in common use, user should not enable this option.
*/

View File

@ -1,8 +1,9 @@
namespace YYCC::IOHelper {
/**
\page io_helper IO Helper
YYCC::IOHelper currently only has one function and one macro.
Actually, YYCC::IOHelper includes functions which can not be placed in other place.
\section io_helper__ptr_pri_padding Pointer Print Padding
@ -24,6 +25,11 @@ std::printf(stdout, "Raw Pointer 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR, raw_ptr);
Note \c PRIXPTR is defined by standard library for formatting pointer as hexadecimal style.
\section io_helper__smart_file Smart FILE Pointer
#SmartStdFile use \c std::unique_ptr with custom deleter to implement smart \c FILE*.
It is useful in the cases that you want to automatically free opened file when leaving corresponding scope.
\section io_helper__utf8_fopen UTF8 fopen
In Windows, standard \c std::fopen can not handle UTF8 file name in common environment.
@ -40,4 +46,5 @@ There is a simple example:
FILE* fs = YYCC::IOHelper::FOpen(YYCC_U8("/path/to/file"), YYCC_U8("rb"));
\endcode
*/
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC {
/**
\page library_encoding Library Encoding
@ -224,3 +225,4 @@ Linux user do not need care this.
Because almost Linux distro use UTF8 in default.
*/
}

122
doc/src/library_macros.dox Normal file
View File

@ -0,0 +1,122 @@
namespace YYCC {
/**
\page library_macros Library Macros
In this page we will introduce the macros defined by this library
which can not be grouped in other topic.
\section library_macros__batch_class_copy_move Library Version and Version Comparison
Version is a important things in modern software development, especially for a library.
In YYCC, we use Semantic Versioning as our version standard.
For more infomations about it, please see: https://semver.org/
First, YYCC has its own version and it can be visited by
\c YYCC_VER_MAJOR, \c YYCC_VER_MINOR, and \c YYCC_VER_PATCH.
Each part of Semantic Versioning is provided individually.
YYCC also provide a bunch of macros to compare 2 versions.
It also provides a way to check YYCC version in program using YYCC,
because some of them rely on a specific version of YYCC.
There is a list of these comparison macros.
\li YYCC_VERCMP_E
\li YYCC_VERCMP_NE
\li YYCC_VERCMP_G
\li YYCC_VERCMP_GE
\li YYCC_VERCMP_NL
\li YYCC_VERCMP_L
\li YYCC_VERCMP_LE
\li YYCC_VERCMP_NG
You may notice all of these macros are starts with \c YYCC_VERCMP_,
and their tails are inspired from x86 ASM comparison jump code.
For example, \c E means "equal" and \c NE means "not equal",
\c G means "greater", \c GE means "greater or equal", and \c NG means "not gretaer".
All of these macros take 6 arguments,
for the first 3 arguments, we call them "left version".
From left to right they are the major part, minor part and patch part of semantic version.
And for the last 3 arguments, we call them "right version".
From left to right they are the major part, minor part and patch part of semantic version.
There is a example about checking whether YYCC library version is exactly what we wanted version.
\code
#if YYCC_VERCMP_NE(YYCC_VER_MAJOR, YYCC_VER_MINOR, YYCC_VER_PATCH, 1, 3 ,0)
#error "Not Matched YYCC Version"
#endif
\endcode
\section library_macros__platform_checker Platform Checker
In many cross platform applications,
programmer usually write code adapted to different platforms in one source file
and enable them respectively by macros representing the target platform.
As a cross platform library,
YYCC also has this feature and you can utilize it if you don't have other ways to so the same things.
\subsection library_macros__platform_checker__values Values
YYCC always define a macro called \c YYCC_OS to indicate the system of target platform.
In implementation, it will check following list from top to bottom to set matched value for it.
\li \c YYCC_OS_WINDOWS: Windows environment. It is done by checking whether environment define \c _WIN32 macro.
\li \c YYCC_OS_LINUX: In current implementation, this means target platform is \b NOT Windows.
\subsection library_macros__platform_checker__usage Usage
Now you know any possible value of \c YYCC_OS.
The next step is how to use it to enable specified code in specific target platform.
We take Windows platform for example.
Assume \c blabla() function is Windows specific.
We have following example code:
\code
#if defined(YYCC_OS_WINDOWS)
blabla();
#endif
\endcode
It's enough and simple that use \c \#if to bracket the Windows specified code.
\section library_macros__batch_class_copy_move Batch Class Copy / Move Functions
YYCC provides 6 macros to batchly remove class copy constructor and move constructor,
or set default class copy constructor and move constructor.
<UL>
<LI>
\c YYCC_DEL_CLS_COPY: Declare following 2 statements which delete copy constrcutor and copy assign operator.
<UL>
<LI><TT>CLSNAME(const CLSNAME&) = delete;</TT></LI>
<LI><TT>CLSNAME& operator=(const CLSNAME&) = delete;</TT></LI>
</UL>
</LI>
<LI>
\c YYCC_DEL_CLS_MOVE: Declare following 2 statements which delete move constrcutor and move assign operator.
<UL>
<LI><TT>CLSNAME(CLSNAME&&) = delete;</TT></LI>
<LI><TT>CLSNAME& operator=(CLSNAME&&) = delete;</TT></LI>
</UL>
</LI>
<LI>\c YYCC_DEL_CLS_COPY_MOVE: The combination of \c YYCC_DEL_CLS_COPY and \c YYCC_DEL_CLS_MOVE.</LI>
<LI>
\c YYCC_DEF_CLS_COPY: Declare following 2 statements which set default copy constrcutor and copy assign operator.
<UL>
<LI><TT>CLSNAME(const CLSNAME&) = default;</TT></LI>
<LI><TT>CLSNAME& operator=(const CLSNAME&) = default;</TT></LI>
</UL>
</LI>
<LI>
\c YYCC_DEF_CLS_MOVE: Declare following 2 statements which set default move constrcutor and move assign operator.
<UL>
<LI><TT>CLSNAME(CLSNAME&&) = default;</TT></LI>
<LI><TT>CLSNAME& operator=(CLSNAME&&) = default;</TT></LI>
</UL>
</LI>
<LI>\c YYCC_DEF_CLS_COPY_MOVE: The combination of \c YYCC_DEF_CLS_COPY and \c YYCC_DEF_CLS_MOVE.</LI>
</UL>
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::ParserHelper {
/**
\page parser_helper Parser Helper
@ -14,11 +15,11 @@ Functions located in this helper support the convertion between string and follo
Please note in C++, \c bool is integral type but we list it individually because parser will treat it specially.
For \c bool type, parser will try doing convertion between it and \c "true" \c "false" string.
(\b case-sensitive. It means that \c true will only be converted to \c "true" and \c "TRUE" can not be recognised.)
(\b case-insensitive. It means that \c true can be converted from \c "true", \c "True" or \c "TRUE".)
\section parser_helper__try_parse Try Parse
YYCC::ParserHelper::TryParse will try to parse string into caller specified type.
#TryParse will try to parse string into caller specified type.
All of them accept an UTF8 string view at first argument,
require that you provide a container receiving converted result in the second argument,
and return a bool value to indicate whether the convertion is successful.
@ -30,11 +31,12 @@ YYCC::ParserHelper::TryParse<uint32_t>(YYCC_U8("123"), val);
YYCC::ParserHelper::TryParse<uint32_t>(YYCC_U8("7fff"), val, 16);
\endcode
For floating point type, this function allows caller to specify extra argument providing the format of given number string (\c std::chars_format).
For integral type, this function allows caller to specify extra argument providing the base of given number string.
\section parser_helper__parse Parse
YYCC::ParserHelper::Parse is similar to YYCC::ParserHelper::TryParse.
#Parse is similar to #TryParse.
But it will not return bool value to indicate success and doesn't have the argument receiving result.
It only accepts an UTF8 string view as the only one argument, and return result directly.
If the convertion failed, the return value is \b undefined (but usually is the default value of given type).
@ -44,15 +46,15 @@ There is an example:
uint32_t val = YYCC::ParserHelper::Parse<uint32_t>(YYCC_U8("123"));
\endcode
Please note, for integral types, there is no base argument in YYCC::ParserHelper::Parse.
Please use YYCC::ParserHelper::TryParse instead.
For integral and floating point value,
it has same extra argument with #TryParse to provide more number infomation.
Using this function is dangerous if the validation of your input is important.
In this case, please use YYCC::ParserHelper::TryParse instead.
In this case, please use #TryParse instead.
\section parser_helper__to_string To String
YYCC::ParserHelper::ToString basically is the reversed operation of YYCC::ParserHelper::Parse.
#ToString basically is the reversed operation of #Parse.
It gets the string representation of given type.
The only argument of these functions is the type which need to be converted to its string representation.
And they will return yycc_u8string as result.
@ -62,6 +64,11 @@ There is an example:
auto result = YYCC::ParserHelper::ToString<uint32_t>(UINT32_C(114));
\endcode
For floating point type, this function allows caller to specify extra arguments
which provides the format (\c std::chars_format) and precision when getting string representation.
For integral type, this function allows caller to specify extra argument
providing the base of number when getting string representation.
\section parser_helper__notes Notes
All functions within this helper are implementated by standard library functions.
@ -77,4 +84,5 @@ The argument of template is the type these functions need to be processed.
Although C++ have \e smart template type deduction,
it would be better to specify template argument manually to explicitly specify your desired type.
*/
*/
}

View File

@ -1,35 +0,0 @@
/**
\page platform_checker Platform Checker
In many cross platform applications,
programmer usually write code adapted to different platforms in one source file
and enable them respectively by macros representing the target platform.
As a cross platform library,
YYCC also has this feature and you can utilize it if you don't have other ways to so the same things.
\section platform_checker__values Values
YYCC always define a macro called \c YYCC_OS to indicate the system of target platform.
In implementation, it will check following list from top to bottom to set matched value for it.
\li \c YYCC_OS_WINDOWS: Windows environment. It is done by checking whether environment define \c _WIN32 macro.
\li \c YYCC_OS_LINUX: In current implementation, this means target platform is \b NOT Windows.
\section platform_checker__usage Usage
Now you know any possible value of \c YYCC_OS.
The next step is how to use it to enable specified code in specific target platform.
We take Windows platform for example.
Assume \c blabla() function is Windows specific.
We have following example code:
\code
#if YYCC_OS == YYCC_OS_WINDOWS
blabla();
#endif
\endcode
It's enough and simple that use \c \#if to bracket the Windows specified code.
*/

View File

@ -1,6 +1,44 @@
namespace YYCC::StdPatch {
/**
\page fs_path_patch std::filesystem::path Patch
\page std_patch Standard Library Patch
\section std_patch__starts_with_ends_with Starts With & Ends With
\c std::basic_string::starts_with and \c std::basic_string::ends_with (also available in \c std::basic_string_view)
are functions introduced in C++ 20 and unavailable in C++ 17.
YYCC::StdPatch provides a patch for these function in C++ 17 environment.
Please note these implementations are following implementation instruction presented by CppReference website.
And it should have the same performance with vanilla functions because Microsoft STL use the same way to implement.
These implementations will not fallback to vanilla function even they are available.
Because their performance are good.
To use these functions, you just need to call them like corresponding vanilla functions.
Our implementations provide all necessary overloads.
The only thing you need to do is provide the string self as the first argument,
because our implementations can not be inserted as a class member of string.
There is an example:
\code
YYCC::StdPatch::StartsWith(YYCC_U8("aabbcc"), YYCC_U8("aa"));
YYCC::StdPatch::EndsWith(YYCC_U8("aabbcc"), YYCC_U8("cc"));
\endcode
\section std_patch__contains Contains
\c Contains function in standard library ordered and unordered successive container are also introduced in C++ 20.
YYCC::StdPatch provides a patch for this function in C++ 17 environment.
Please note this implementation will fallback to vanilla function if it is available.
Because our implementation is a remedy (there is no way to use public class member to have the same performance of vanilla function).
There is an example about how to use it:
\code
std::set<int> test { 1, 5 };
YYCC::StdPatch::Contains(test, static_cast<int>(5));
\endcode
\section std_patch__fs_path std::filesystem::path Patch
As you know, the underlying char type of \c std::filesystem::path is \c wchar_t on Windows,
and in other platforms, it is simple \c char.
@ -22,17 +60,17 @@ This patch is served for Windows but also works on other plaftoms.
If you are in Windows, this patch will perform extra operations to achieve goals,
and in other platforms, they just redirect request to corresponding vanilla C++ functions.
\section fs_path_patch__from_utf8_path Create Path from UTF8 String
\subsection std_patch__fs_path__from_utf8_path Create Path from UTF8 String
YYCC::FsPathPatch::FromUTF8Path provides this feature.
#ToStdPath provides this feature.
It accepts an string pointer to UTF8 string and try to create \c std::filesystem::path from it.
Function will throw exception if encoding convertion or constructor self failed.
There are some example:
\code
auto foobar_path = YYCC::FsPathPatch::FromUTF8Path(YYCC_U8("/foo/bar"));
auto slashed_path = foobar_path / YYCC::FsPathPatch::FromUTF8Path(YYCC_U8("test"));
auto replaced_ext = foobar_path.replace_extension(YYCC::FsPathPatch::FromUTF8Path(YYCC_U8(".txt")));
auto foobar_path = YYCC::StdPatch::ToStdPath(YYCC_U8("/foo/bar"));
auto slashed_path = foobar_path / YYCC::StdPatch::ToStdPath(YYCC_U8("test"));
auto replaced_ext = foobar_path.replace_extension(YYCC::StdPatch::ToStdPath(YYCC_U8(".txt")));
\endcode
For first line in example, it is obvious that you can create a \c std::filesystem::path from this function.
@ -57,17 +95,18 @@ However it is depracted since C++ 20,
because \c std::filesystem::path directly supports UTF8 by \c char8_t since C++ 20.
Because C++ standard is volatile, we create this function to have an uniform programming experience.
\section fs_path_patch__to_utf8_path Extract UTF8 Path String from Path
\subsection std_patch__fs_path__to_utf8_path Extract UTF8 Path String from Path
YYCC::FsPathPatch::ToUTF8Path provides this feature.
It basically is the reversed operation of YYCC::FsPathPatch::FromUTF8Path.
#ToUTF8Path provides this feature.
It basically is the reversed operation of #ToStdPath.
It is usually used when you have done all path work in \c std::filesystem::path
and want to get the result.
There is an example:
\code
auto foobar_path = YYCC::FsPathPatch::FromUTF8Path(YYCC_U8("/foo/bar"));
auto result = YYCC::FsPathPatch::ToUTF8Path(foobar_path / YYCC::FsPathPatch::FromUTF8Path(YYCC_U8("test")));
auto foobar_path = YYCC::StdPatch::ToStdPath(YYCC_U8("/foo/bar"));
auto result = YYCC::StdPatch::ToUTF8Path(foobar_path / YYCC::StdPatch::ToStdPath(YYCC_U8("test")));
\endcode
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::StringHelper {
/**
\page string_helper String Helper
@ -14,10 +15,10 @@ yycc_u8string Printf(const yycc_char8_t*, ...);
yycc_u8string VPrintf(const yycc_char8_t*, va_list argptr);
\endcode
YYCC::StringHelper::Printf and YYCC::StringHelper::VPrintf is similar to \c std::sprintf and \c std::vsprintf.
YYCC::StringHelper::Printf accepts UTF8 format string and variadic arguments specifying data to print.
#Printf and #VPrintf is similar to \c std::sprintf and \c std::vsprintf.
#Printf accepts UTF8 format string and variadic arguments specifying data to print.
This is commonly used by programmer.
However, YYCC::StringHelper::VPrintf also do the same work but its second argument is \c va_list,
However, #VPrintf also do the same work but its second argument is \c va_list,
the representation of variadic arguments.
It is mostly used by other function which has variadic arguments.
@ -26,7 +27,7 @@ that you don't need to worry about whether the space of given buffer is enough,
because these functions help you to calculate this internally.
There is the same design like we introduced in \ref encoding_helper.
There are 2 overloads for YYCC::StringHelper::Printf and YYCC::StringHelper::VPrintf respectively.
There are 2 overloads for #Printf and #VPrintf respectively.
First overload return bool value and require a string container as argument for storing result.
The second overload return result string directly.
As you expected, first overload will return false if fail to format string (this is barely happened).
@ -37,18 +38,18 @@ and second overload will return empty string when formatter failed.
YYCC::StringHelper provide 2 functions for programmer do string replacement:
\code
void Replace(yycc_u8string&, const yycc_char8_t*, const yycc_char8_t*);
yycc_u8string Replace(const yycc_char8_t*, const yycc_char8_t*, const yycc_char8_t*);
void Replace(yycc_u8string&, const yycc_u8string_view&, const yycc_u8string_view&);
yycc_u8string Replace(const yycc_u8string_view&, const yycc_u8string_view&, const yycc_u8string_view&);
\endcode
The first overload will do replacement in given string container directly.
The second overload will produce a copy of original string and do replacement on the copied string.
YYCC::StringHelper::Replace has special treatments for following scenarios:
#Replace has special treatments for following scenarios:
\li If given string is empty or nullptr, the return value will be empty.
\li If the character sequence to be replaced is nullptr or empty string, no replacement will happen.
\li If the character sequence will be replaced into string is nullptr or empty, it will simply delete found character sequence from given string.
\li If given string is empty, the return value will be empty.
\li If the character sequence to be replaced is empty string, no replacement will happen.
\li If the character sequence will be replaced into string is or empty, it will simply delete found character sequence from given string.
\section string_helper__join Join
@ -58,10 +59,10 @@ YYCC::StringHelper provide an universal way for joining string and various speci
Because C++ list types are various.
There is no unique and convenient way to create an universal join function.
So we create YYCC::StringHelper::JoinDataProvider to describe join context.
So we create #JoinDataProvider to describe join context.
Before using universal join function,
you should setup YYCC::StringHelper::JoinDataProvider first, the context of join function.
you should setup #JoinDataProvider first, the context of join function.
It actually is an \c std::function object which can be easily fetched by C++ lambda syntax.
This function pointer accept a reference to \c yycc_u8string_view,
programmer should set it to the string to be joined when at each calling.
@ -69,8 +70,8 @@ And this function pointer return a bool value to indicate the end of join.
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 YYCC::StringHelper::JoinDataProvider object to YYCC::StringHelper::Join function.
And specify decilmer at the same time.
Then, you can pass the created #JoinDataProvider object to #Join function.
And specify delimiter at the same time.
Then you can get the final joined string.
There is an example:
@ -87,17 +88,25 @@ auto joined_string = YYCC::StringHelper::Join(
++iter;
return true;
},
decilmer
delimiter
);
\endcode
\subsection string_helper__join__specialized Specialized Join Function
Despite universal join function,
YYCC::StringHelper also provide some specialized join functions for commonly used types.
Current we support following join function:
YYCC::StringHelper also provide a specialized join functions for standard library container.
For example, the code written above can be written in following code by using this specialized overload.
The first two argument is just the begin and end iterator.
However, you must make sure that we can dereference it and then implicitly convert it to yycc_u8string_view.
Otherwise this overload will throw template error.
\li \c std::vector<yycc_u8string>: With an extra option which allow join it with reversed order.
\code
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(), delimiter);
\endcode
\section string_helper__lower_upper Lower Upper
@ -105,11 +114,11 @@ String helper provides Python-like string lower and upper function.
Both lower and upper function have 2 overloads:
\code
yycc_u8string Lower(const yycc_char8_t*);
yycc_u8string Lower(const yycc_u8string_view&);
void Lower(yycc_u8string&);
\endcode
First overload accepts a NULL-terminated string as argument and return a \b copy whose content are all the lower case of original string.
First overload accepts a string view as argument and return a \b copy whose content are all the lower case of original string.
Second overload accepts a mutable string container as argument and will make all characters stored in it become their lower case.
You can choose on of them for your flavor and requirements.
Upper also has similar 2 overloads.
@ -120,20 +129,21 @@ String helper provides Python-like string split function.
It has 2 types for you:
\code
std::vector<yycc_u8string> Split(const yycc_u8string_view&, const yycc_char8_t*);
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view&, const yycc_char8_t*);
std::vector<yycc_u8string> Split(const yycc_u8string_view&, const yycc_u8string_view&);
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view&, const yycc_u8string_view&);
\endcode
All these overloads take a string view as the first argument representing the string need to be split.
The second argument is a raw string pointer 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 \c nullptr or 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

@ -1,3 +1,4 @@
namespace YYCC::WinFctHelper {
/**
\page win_fct_helper Windows Function Helper
@ -9,9 +10,14 @@ It will be entirely invisible in other platforms.
Currently this namespace has following functions:
\li YYCC::WinFctHelper::GetCurrentModule: Get the handle to current module.
\li YYCC::WinFctHelper::GetTempDirectory: Get temporary directory in Windows.
\li YYCC::WinFctHelper::GetModuleFileName: Get the path to module in file system by given handle.
\li YYCC::WinFctHelper::GetLocalAppData: Get the path inside \%LOCALAPPDATA\%
\li #GetCurrentModule: Get the handle to current module.
\li #GetTempDirectory: Get temporary directory in Windows.
\li #GetModuleFileName: Get the path to module in file system by given handle.
\li #GetLocalAppData: Get the path inside \%LOCALAPPDATA\%
\li #IsValidCodePage: Check whether given code page number is valid.
\li #CopyFile: The UTF8 version of Win32 \c CopyFile.
\li #MoveFile: The UTF8 version of Win32 \c MoveFile.
\li #DeleteFile: The UTF8 version of Win32 \c DeleteFile.
*/
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC {
/**
\page win_import Windows Import Guard
@ -10,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"
@ -66,4 +67,5 @@ because the headers use \c \#if to check environment out and will do nothing in
However, we still highly recommend you use this pair with platform checker bracket like example does,
if your program need to be run on multiple platforms.
*/
*/
}

216
script/.gitignore vendored Normal file
View File

@ -0,0 +1,216 @@
## ===== 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__/

105
script/gen_build_script.py Normal file
View File

@ -0,0 +1,105 @@
import argparse
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: typing.TextIO, val: str) -> None:
f.write(val)
f.write('\n')
# Reference: https://stackoverflow.com/questions/29213106/how-to-securely-escape-command-line-arguments-for-the-cmd-exe-shell-on-windows
def escape_for_cmd_exe(arg):
meta_re = re.compile(r'([()%!^"<>&|])')
return meta_re.sub('^\1', arg)
def escape_cmd_argument(arg):
if not arg or re.search(r'(["\s])', arg):
arg = '"' + arg.replace('"', r'\"') + '"'
return escape_for_cmd_exe(arg)
def escape_sh_argument(arg):
return shlex.quote(arg)
@dataclass(frozen=True)
class ScriptSettings:
cpp_version: str
build_doc: bool
pic: bool
class TemplateRender:
loader: jinja2.BaseLoader
environment: jinja2.Environment
win_template: jinja2.Template
linux_template: jinja2.Template
settings: ScriptSettings
def __init__(self, settings: ScriptSettings) -> None:
self.loader = jinja2.FileSystemLoader(self.__get_dir())
self.environment = jinja2.Environment(loader=self.loader)
self.win_template = self.environment.get_template('win_build.bat.jinja')
self.linux_template = self.environment.get_template('linux_build.sh.jinja')
self.settings = settings
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(self.__get_dir() / dest_file, 'w', encoding='utf-8') as f:
f.write(template.render(
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.win_template, 'win_build.bat', True)
def render_linux_script(self) -> None:
self.__render(self.linux_template, 'linux_build.sh', False)
if __name__ == '__main__':
# parse argument
parser = argparse.ArgumentParser(
prog='YYCC Windows Build Script Generator',
description='YYCC Windows Build Script Generator'
)
parser.add_argument(
'-c', '--cpp',
action='store', default='17', dest='cpp', type=validate_cpp_ver,
help='The version of C++ standard used when building.'
)
parser.add_argument(
'-d', '--build-doc',
action='store_true', dest='build_doc',
help='Build YYCC without documentation.'
)
parser.add_argument(
'-p', '--pic',
action='store_true', dest='pic',
help='Enable Position Independent Code flag on non-Windows platform. This is crucial for compiling dynamic library using this library.'
)
args = parser.parse_args()
# build settings
settings = ScriptSettings(args.cpp, args.build_doc, args.pic)
# build template render and render result
render = TemplateRender(settings)
render.render_win_script()
render.render_linux_script()

View File

@ -1,9 +1,6 @@
#!/bin/bash
README_PATH=$(pwd)/README.md
if [ ! -f "$README_PATH" ]; then
echo "Error: You must run this script at the root folder of this project!"
exit
fi
# Navigate to project root directory
cd {{ repo_root_dir }}
# Create main binary directory
mkdir bin
@ -19,10 +16,10 @@ cd ..
# Build current system debug and release version
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ../.. --fresh
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} ../.. --fresh
cmake --build .
cmake --install . --prefix ../install/Debug
cmake -DCMAKE_BUILD_TYPE=Release -DYYCC_BUILD_TESTBENCH=ON ../.. --fresh
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} -DYYCC_BUILD_TESTBENCH=ON ../.. --fresh
cmake --build .
cmake --install . --prefix ../install/Release
cd ..

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,54 +0,0 @@
@ECHO OFF
SET README_PATH=%CD%\README.md
IF EXIST %README_PATH% (
REM DO NOTHING
) ELSE (
ECHO Error: You must run this script at the root folder of this project!
EXIT /b
)
:: Create main binary directory
MKDIR bin
CD bin
:: Create build folder
MKDIR Win32
MKDIR x64
MKDIR documentation
:: Create install folder
MKDIR install
CD install
MKDIR Win32_Debug
MKDIR Win32_Release
MKDIR x64_Debug
MKDIR x64_Release
CD ..
:: Build for Win32
CD Win32
cmake -G "Visual Studio 16 2019" -A Win32 -DYYCC_BUILD_TESTBENCH=ON ../..
cmake --build . --config Debug
cmake --install . --prefix=../install/Win32_Debug --config Debug
cmake --build . --config Release
cmake --install . --prefix=../install/Win32_Release --config Release
CD ..
:: Build for x64
CD x64
cmake -G "Visual Studio 16 2019" -A x64 -DYYCC_BUILD_TESTBENCH=ON ../..
cmake --build . --config Debug
cmake --install . --prefix=../install/x64_Debug --config Debug
cmake --build . --config Release
cmake --install . --prefix=../install/x64_Release --config Release
CD ..
:: Build for documentation
CD documentation
cmake -G "Visual Studio 16 2019" -A x64 -DYYCC_BUILD_DOC=ON ../..
cmake --build . --config Release
cmake --build . --target YYCCDocumentation
cmake --install . --prefix=../install/x64_Release --config Release
CD ..
:: Exit to original path
CD ..
ECHO Windows CMake Build Done

View File

@ -0,0 +1,85 @@
@ECHO OFF
:: Navigate to project root directory
CD /d {{ repo_root_dir }}
:: Create build directory and enter it
MKDIR bin
CD bin
MKDIR cpp{{ cpp_version }}
CD cpp{{ cpp_version }}
:: Create internal build directory
MKDIR Win32
MKDIR x64
MKDIR documentation
:: Create internal install directory
MKDIR install
CD install
MKDIR Win32_Debug
MKDIR Win32_Release
MKDIR x64_Debug
MKDIR x64_Release
CD ..
:: Create internal MSVC specific install directory
MKDIR msvc_install
CD msvc_install
MKDIR bin
MKDIR include
MKDIR lib
MKDIR share
CD bin
MKDIR Win32
MKDIR x64
CD ..
CD lib
MKDIR Win32\Debug
MKDIR Win32\Release
MKDIR x64\Debug
MKDIR x64\Release
CD ..
CD ..
:: Build for Win32
CD Win32
cmake -A Win32 -DCMAKE_CXX_STANDARD={{ cpp_version }} -DYYCC_BUILD_TESTBENCH=ON ../../..
cmake --build . --config Debug
cmake --install . --prefix=../install/Win32_Debug --config Debug
cmake --build . --config RelWithDebInfo
cmake --install . --prefix=../install/Win32_Release --config RelWithDebInfo
CD ..
:: Build for x64
CD x64
cmake -A x64 -DCMAKE_CXX_STANDARD={{ cpp_version }} -DYYCC_BUILD_TESTBENCH=ON ../../..
cmake --build . --config Debug
cmake --install . --prefix=../install/x64_Debug --config Debug
cmake --build . --config RelWithDebInfo
cmake --install . --prefix=../install/x64_Release --config RelWithDebInfo
CD ..
{% if build_doc %}
:: Build for documentation
CD documentation
cmake -A x64 -DCMAKE_CXX_STANDARD={{ cpp_version }} -DYYCC_BUILD_DOC=ON ../../..
cmake --build . --config RelWithDebInfo
cmake --build . --target YYCCDocumentation
cmake --install . --prefix=../install/x64_Release --config RelWithDebInfo
CD ..
{% endif %}
:: Copy header files
XCOPY install\x64_Release\include msvc_install\include\ /E /Y
:: Copy binary files
COPY install\Win32_Release\bin\YYCCTestbench.exe msvc_install\bin\Win32\YYCCTestbench.exe /Y
COPY install\x64_Release\bin\YYCCTestbench.exe msvc_install\bin\x64\YYCCTestbench.exe /Y
:: Copy library files
COPY install\Win32_Debug\lib\YYCCommonplace.lib msvc_install\lib\Win32\Debug\YYCCommonplace.lib /Y
COPY install\Win32_Release\lib\YYCCommonplace.lib msvc_install\lib\Win32\Release\YYCCommonplace.lib /Y
COPY install\x64_Debug\lib\YYCCommonplace.lib msvc_install\lib\x64\Debug\YYCCommonplace.lib /Y
COPY install\x64_Release\lib\YYCCommonplace.lib msvc_install\lib\x64\Release\YYCCommonplace.lib /Y
{% if build_doc %}
:: Copy documentation files
XCOPY install\x64_Release\share msvc_install\share\ /E /Y
{% endif %}
:: Leave build directory and report
CD ..\..
ECHO Windows CMake Build Done

View File

@ -1,50 +0,0 @@
@ECHO OFF
SET README_PATH=%CD%\README.md
IF EXIST %README_PATH% (
REM DO NOTHING
) ELSE (
ECHO Error: You must run this script at the root folder of this project!
EXIT /b
)
:: Enter main binary directory
CD bin
:: Create MSVC binary directory
MKDIR msvc_install
CD msvc_install
:: Create direcotries tree
MKDIR bin
MKDIR include
MKDIR lib
MKDIR share
CD bin
MKDIR Win32
MKDIR x64
CD ..
CD lib
MKDIR Win32\Debug
MKDIR Win32\Release
MKDIR x64\Debug
MKDIR x64\Release
CD ..
:: Exit MSVC binary directory
CD ..
:: Copy result
:: Copy include from x64_Release build
XCOPY install\x64_Release\include msvc_install\include\ /E /Y
:: Copy document from x64_Release build
XCOPY install\x64_Release\share msvc_install\share\ /E /Y
:: Copy binary testbench
COPY install\Win32_Release\bin\YYCCTestbench.exe msvc_install\bin\Win32\YYCCTestbench.exe /Y
COPY install\x64_Release\bin\YYCCTestbench.exe msvc_install\bin\x64\YYCCTestbench.exe /Y
:: Copy static library
COPY install\Win32_Debug\lib\YYCCommonplace.lib msvc_install\lib\Win32\Debug\YYCCommonplace.lib /Y
COPY install\Win32_Release\lib\YYCCommonplace.lib msvc_install\lib\Win32\Release\YYCCommonplace.lib /Y
COPY install\x64_Debug\lib\YYCCommonplace.lib msvc_install\lib\x64\Debug\YYCCommonplace.lib /Y
COPY install\x64_Release\lib\YYCCommonplace.lib msvc_install\lib\x64\Release\YYCCommonplace.lib /Y
:: Exit to original path
CD ..
ECHO Windows MSVC Build Done

View File

@ -1,45 +1,62 @@
# Configure version file
configure_file(
${CMAKE_CURRENT_LIST_DIR}/yycc/version.hpp.in
${CMAKE_CURRENT_LIST_DIR}/yycc/version.hpp
@ONLY
)
# Create static library
add_library(YYCCommonplace STATIC "")
# Setup static library sources
target_sources(YYCCommonplace
PRIVATE
# Sources
COMHelper.cpp
ConfigManager.cpp
ConsoleHelper.cpp
DialogHelper.cpp
EncodingHelper.cpp
ExceptionHelper.cpp
FsPathPatch.cpp
IOHelper.cpp
StringHelper.cpp
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
COMHelper.hpp
ConfigManager.hpp
ConsoleHelper.hpp
DialogHelper.hpp
EncodingHelper.hpp
ExceptionHelper.hpp
FsPathPatch.hpp
IOHelper.hpp
ParserHelper.hpp
StringHelper.hpp
WinFctHelper.hpp
# Windows including guard pair
WinImportPrefix.hpp
WinImportSuffix.hpp
# Misc
YYCCInternal.hpp
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
@ -47,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
set_target_properties(YYCCommonplace
PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED 17
CXX_EXTENSION OFF
)
# Order Unicode charset for private using
# Setup macros
target_compile_definitions(YYCCommonplace
PRIVATE
PUBLIC
# 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>
)
# Order build as UTF-8 in MSVC
target_compile_options(YYCCommonplace
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,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,41 +0,0 @@
#include "FsPathPatch.hpp"
#include "EncodingHelper.hpp"
#include <string>
#include <stdexcept>
namespace YYCC::FsPathPatch {
std::filesystem::path FromUTF8Path(const yycc_char8_t* 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
return std::filesystem::path(EncodingHelper::ToOrdinary(u8_path));
#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,29 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <filesystem>
/**
* @brief \c std::filesystem::path related patches for UTF8 compatibility
* @details
* See also \ref fs_path_patch.
*/
namespace YYCC::FsPathPatch {
/**
* @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 FromUTF8Path(const yycc_char8_t* 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);
}

View File

@ -1,42 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <cstdio>
#include <filesystem>
/**
* @brief Some IO related stuff
* @details
* 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 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.
*/
FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode);
}

View File

@ -1,154 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include "EncodingHelper.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.
* @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) {
auto [ptr, ec] = std::from_chars(
EncodingHelper::ToOrdinary(strl.data()),
EncodingHelper::ToOrdinary(strl.data() + strl.size()),
num, std::chars_format::general
);
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.
* @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").
* @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) {
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 arithmetic types.
* @tparam _Ty The type derived from arithmetic type.
* @param[in] strl The string need to be parsed.
* @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_arithmetic_v<_Ty>, 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 a string version of given arithmetic value.
* @tparam _Ty The type derived from arithmetic type.
* @param[in] num The value getting string version.
* @return The string version of given value.
*/
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
yycc_u8string ToString(_Ty num) {
std::array<yycc_char8_t, 64> buffer;
auto [ptr, ec] = std::to_chars(
EncodingHelper::ToOrdinary(buffer.data()),
EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),
num
);
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 a string version of given bool value.
* @tparam _Ty The type derived from bool type.
* @param[in] num The value getting string version.
* @return The string version 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,259 +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_char8_t* _from_strl, const yycc_char8_t* _to_strl) {
// Reference: https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
// check requirements
// from string and to string should not be nullptr.
if (_from_strl == nullptr || _to_strl == nullptr) return;
// 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_char8_t* _strl, const yycc_char8_t* _from_strl, const yycc_char8_t* _to_strl) {
// prepare result
yycc_u8string strl;
// if given string is not nullptr, assign it and process it.
if (_strl != nullptr) {
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_char8_t* 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 {
// only insert non-nullptr decilmer.
if (decilmer != nullptr)
ret.append(decilmer);
}
// insert element if it is not empty
if (!element.empty())
ret.append(element);
}
return ret;
}
yycc_u8string Join(const std::vector<yycc_u8string>& data, const yycc_char8_t* decilmer, bool reversed) {
if (reversed) {
auto iter = data.crbegin();
auto stop = data.crend();
return Join([&iter, &stop](yycc_u8string_view& view) -> bool {
// if we reach tail, return false
if (iter == stop) return false;
// otherwise fetch data, inc iterator and return.
view = *iter;
++iter;
return true;
}, decilmer);
} else {
auto iter = data.cbegin();
auto stop = data.cend();
return Join([&iter, &stop](yycc_u8string_view& view) -> bool {
// if we reach tail, return nullptr
if (iter == stop) return false;
// otherwise fetch data, inc iterator and return.
view = *iter;
++iter;
return true;
}, decilmer);
}
}
#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);
}
);
}
yycc_u8string Lower(const yycc_char8_t* strl) {
yycc_u8string ret;
if (strl == nullptr) return ret;
else ret = strl;
Lower(ret);
return ret;
}
void Lower(yycc_u8string& strl) {
GeneralStringLowerUpper<true>(strl);
}
yycc_u8string Upper(const yycc_char8_t* strl) {
// same as Lower, just replace char transform function.
yycc_u8string ret;
if (strl == nullptr) return ret;
else ret = strl;
Upper(ret);
return ret;
}
void Upper(yycc_u8string& strl) {
GeneralStringLowerUpper<false>(strl);
}
#pragma endregion
#pragma region Split
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_char8_t* _decilmer) {
// call split view
auto view_result = SplitView(strl, _decilmer);
// copy string view result to string
std::vector<yycc_u8string> elems;
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_char8_t* _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 item).
// if decilmer is nullptr, or decilmer is zero length, return original string.
yycc_u8string decilmer;
if (strl.empty() || _decilmer == nullptr || (decilmer = _decilmer, 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,146 +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_char8_t* _from_strl, const yycc_char8_t* _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_char8_t* _strl, const yycc_char8_t* _from_strl, const yycc_char8_t* _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_char8_t* decilmer);
/**
* @brief Specialized join function for \c std::vector.
* @param[in] data The list to be joined.
* @param[in] decilmer The decilmer used for joining.
* @param[in] reversed True if this list should be joined in reversed order.
* @return The result string of joining.
*/
yycc_u8string Join(const std::vector<yycc_u8string>& data, const yycc_char8_t* decilmer, bool reversed = false);
/**
* @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_char8_t* strl);
/**
* @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 uppercase.
* @param[in] strl The string to be uppercase.
* @return The copy of the string converted to uppercase.
*/
yycc_u8string Upper(const yycc_char8_t* strl);
/**
* @brief Convert given string to uppercase.
* @param[in,out] strl The string to be uppercase.
*/
void Upper(yycc_u8string& 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 is empty, or decilmer is nullptr or 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_char8_t* _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 is empty, or decilmer is nullptr or 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_char8_t* _decilmer);
}

View File

@ -1,62 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <string>
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include "WinImportSuffix.hpp"
/**
* @brief The helper providing assistance of Win32 functions.
* @details
* This helper is Windows specific.
* If current environment is not Windows, the whole namespace will be unavailable.
* See also \ref win_fct_helper
*/
namespace YYCC::WinFctHelper {
/**
* @brief Get Windows used HANDLE for current module.
* @details
* If your target is EXE, the current module simply is your program self.
* However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL.
*
* This function is frequently used by DLL.
* Because some design need the HANDLE of current module, not the host EXE loading your DLL.
* For example, you may want to get the path of your built DLL, or fetch resources from your DLL at runtime,
* then you should pass current module HANDLE, not NULL or the HANDLE of EXE.
* @return A Windows HANDLE pointing to current module, NULL if failed.
*/
HMODULE GetCurrentModule();
/**
* @brief Get path to Windows temporary folder.
* @details Windows temporary folder usually is the target of \%TEMP\%.
* @param[out] ret The variable receiving UTF8 encoded path to Windows temp folder.
* @return True if success, otherwise false.
*/
bool GetTempDirectory(yycc_u8string& ret);
/**
* @brief Get the file name of given module HANDLE
* @param[in] hModule
* The HANDLE to the module where you want to get file name.
* It is same as the HANDLE parameter of Win32 \c GetModuleFileName.
* @param[out] ret The variable receiving UTF8 encoded file name of given module.
* @return True if success, otherwise false.
*/
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret);
/**
* @brief Get the path to \%LOCALAPPDATA\%.
* @details \%LOCALAPPDATA\% usually was used as putting local app data files
* @param[out] ret The variable receiving UTF8 encoded path to LOCALAPPDATA.
* @return True if success, otherwise false.
*/
bool GetLocalAppData(yycc_u8string& ret);
}
#endif

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,20 +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
#endif

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,70 +0,0 @@
#pragma once
// Define operating system macros
#define YYCC_OS_WINDOWS 2
#define YYCC_OS_LINUX 3
// Check current operating system.
#if defined(_WIN32)
#define YYCC_OS YYCC_OS_WINDOWS
#else
#define YYCC_OS YYCC_OS_LINUX
#endif
// 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(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#if !defined(_CRT_NONSTDC_NO_DEPRECATE)
#define _CRT_NONSTDC_NO_DEPRECATE
#endif
#endif
// Define the UTF8 char type we used.
// And do a polyfill if no embedded char8_t type.
#include <string>
#include <string_view>
/**
* @brief Library core namespace
* @details Almost library functions are located in this namespace.
*/
namespace YYCC {
#if defined(__cpp_char8_t)
using yycc_char8_t = char8_t;
using yycc_u8string = std::u8string;
using yycc_u8string_view = std::u8string_view;
#else
using yycc_char8_t = unsigned char;
using yycc_u8string = std::basic_string<yycc_char8_t>;
using yycc_u8string_view = std::basic_string_view<yycc_char8_t>;
#endif
}
/**
\typedef yycc_char8_t
\brief YYCC UTF8 char type.
\details
This char type is an alias to \c std::char8_t if your current C++ standard support it.
Otherwise it is defined as <TT>unsigned char</TT> as C++ 20 stdandard does.
*/
/**
\typedef yycc_u8string
\brief YYCC UTF8 string container type.
\details
This type is defined as \c std::basic_string<yycc_char8_t>.
It is equal to \c std::u8string if your current C++ standard support it.
*/
/**
\typedef yycc_u8string_view
\brief YYCC UTF8 string view type.
\details
This type is defined as \c std::basic_string_view<yycc_char8_t>.
It is equal to \c std::u8string_view if your current C++ standard support it.
*/

View File

@ -0,0 +1,348 @@
#include "ArgParser.hpp"
#include "EncodingHelper.hpp"
#include "ConsoleHelper.hpp"
#if defined(YYCC_OS_WINDOWS)
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <shellapi.h>
#include <processenv.h>
#include "WinImportSuffix.hpp"
#endif
namespace YYCC::ArgParser {
#pragma region Arguments List
ArgumentList ArgumentList::CreateFromStd(int argc, char* argv[]) {
std::vector<yycc_u8string> args;
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
if (argv[i] != nullptr)
args.emplace_back(yycc_u8string(YYCC::EncodingHelper::ToUTF8(argv[i])));
}
return ArgumentList(std::move(args));
}
#if defined(YYCC_OS_WINDOWS)
ArgumentList ArgumentList::CreateFromWin32() {
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw
// prepare list
std::vector<yycc_u8string> args;
// try fetching from Win32 functions
int argc;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argv != NULL) {
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
if (argv[i] != nullptr) {
yycc_u8string u8_argv;
if (YYCC::EncodingHelper::WcharToUTF8(argv[i], u8_argv))
args.emplace_back(std::move(u8_argv));
}
}
}
LocalFree(argv);
// return result
return ArgumentList(std::move(args));
}
#endif
ArgumentList::ArgumentList(std::vector<yycc_u8string>&& arguments) :
m_Arguments(arguments), m_ArgumentsCursor(0u) {}
void ArgumentList::Prev() {
if (m_ArgumentsCursor == 0u)
throw std::runtime_error("attempt to move on the head of iterator.");
--m_ArgumentsCursor;
}
void ArgumentList::Next() {
if (IsEOF()) throw std::runtime_error("attempt to move on the tail of iterator.");
++m_ArgumentsCursor;
}
const yycc_u8string& ArgumentList::Argument() const {
if (IsEOF()) throw std::runtime_error("attempt to get data on the tail of iterator.");
return m_Arguments[m_ArgumentsCursor];
}
bool ArgumentList::IsSwitch(bool* is_long_name, yycc_u8string* long_name, yycc_char8_t* short_name) const {
// check eof first
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
// check long name first, then check short name
if (IsLongNameSwitch(long_name)) {
if (is_long_name != nullptr) *is_long_name = true;
return true;
}
if (IsShortNameSwitch(short_name)) {
if (is_long_name != nullptr) *is_long_name = false;
return true;
}
// not matched
return false;
}
bool ArgumentList::IsLongNameSwitch(yycc_u8string* name_part) const {
// fetch current parameter
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
// find double slash
if (param.find(AbstractArgument::DOUBLE_DASH) != 0u) return false;
// check gotten long name
yycc_u8string_view long_name = yycc_u8string_view(param).substr(2u);
if (!AbstractArgument::IsLegalLongName(long_name)) return false;
// set checked long name if possible and return
if (name_part != nullptr)
*name_part = long_name;
return true;
}
bool ArgumentList::IsShortNameSwitch(yycc_char8_t* name_part) const {
// fetch current parameter
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
// if the length is not exactly equal to 2,
// or it not starts with dash,
// it is impossible a short name
if (param.size() != 2u || param[0] != AbstractArgument::DASH) return false;
// check gotten short name
yycc_char8_t short_name = param[1];
if (!AbstractArgument::IsLegalShortName(short_name)) return false;
// set checked short name if possible and return
if (name_part != nullptr)
*name_part = short_name;
return true;
}
bool ArgumentList::IsValue(yycc_u8string* val) const {
bool is_value = !IsSwitch();
if (is_value && val != nullptr)
*val = m_Arguments[m_ArgumentsCursor];
return is_value;
}
bool ArgumentList::IsEOF() const { return m_ArgumentsCursor >= m_Arguments.size(); }
void ArgumentList::Reset() { m_ArgumentsCursor = 0u; }
#pragma endregion
#pragma region Abstract Argument
const yycc_u8string AbstractArgument::DOUBLE_DASH = YYCC_U8("--");
const yycc_char8_t AbstractArgument::DASH = YYCC_U8_CHAR('-');
const yycc_char8_t AbstractArgument::NO_SHORT_NAME = YYCC_U8_CHAR(0);
const yycc_char8_t AbstractArgument::MIN_SHORT_NAME = YYCC_U8_CHAR('!');
const yycc_char8_t AbstractArgument::MAX_SHORT_NAME = YYCC_U8_CHAR('~');
bool AbstractArgument::IsLegalShortName(yycc_char8_t short_name) {
if (short_name == AbstractArgument::DASH || // dash is not allowed
short_name < AbstractArgument::MIN_SHORT_NAME || short_name > AbstractArgument::MAX_SHORT_NAME) { // non-display ASCII chars are not allowed
return false;
}
// okey
return true;
}
bool AbstractArgument::IsLegalLongName(const yycc_u8string_view& long_name) {
// empty is not allowed
if (long_name.empty()) return false;
// non-display ASCII chars are not allowed
for (const auto& val : long_name) {
if (val < AbstractArgument::MIN_SHORT_NAME || val > AbstractArgument::MAX_SHORT_NAME)
return false;
}
// okey
return true;
}
AbstractArgument::AbstractArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name,
const yycc_char8_t* description, const yycc_char8_t* argument_example,
bool is_optional) :
m_LongName(), m_ShortName(AbstractArgument::NO_SHORT_NAME), m_Description(), m_ArgumentExample(),
m_IsOptional(is_optional), m_IsCaptured(false) {
// try to assign long name and check it
if (long_name != nullptr) {
m_LongName = long_name;
if (!AbstractArgument::IsLegalLongName(m_LongName))
throw std::invalid_argument("Given long name is invalid.");
}
// try to assign short name and check it
if (short_name != AbstractArgument::NO_SHORT_NAME) {
m_ShortName = short_name;
if (!AbstractArgument::IsLegalShortName(m_ShortName))
throw std::invalid_argument("Given short name is invalid.");
}
// check short name and long name existence
if (!HasShortName() && !HasLongName())
throw std::invalid_argument("you must specify an one of long name or short name.");
// try to assign other string values
if (description != nullptr) m_Description = description;
if (argument_example != nullptr) m_ArgumentExample = argument_example;
}
AbstractArgument::~AbstractArgument() {}
bool AbstractArgument::HasLongName() const { return !m_LongName.empty(); }
const yycc_u8string& AbstractArgument::GetLongName() const { return m_LongName; }
bool AbstractArgument::HasShortName() const { return m_ShortName != NO_SHORT_NAME; }
yycc_char8_t AbstractArgument::GetShortName() const { return m_ShortName; }
bool AbstractArgument::HasDescription() const { return !m_Description.empty(); }
const yycc_u8string& AbstractArgument::GetDescription() const { return m_Description; }
bool AbstractArgument::HasArgumentExample() const { return !m_ArgumentExample.empty(); }
const yycc_u8string& AbstractArgument::GetArgumentExample() const { return m_ArgumentExample; }
bool AbstractArgument::IsOptional() const { return m_IsOptional; }
bool AbstractArgument::IsCaptured() const { return m_IsCaptured; }
void AbstractArgument::SetCaptured(bool is_captured) { m_IsCaptured = is_captured; }
#pragma endregion
#pragma region Option Context
OptionContext::OptionContext(
const yycc_char8_t* summary, const yycc_char8_t* description,
std::initializer_list<AbstractArgument*> arguments) :
m_Summary(), m_Description() {
// assign summary and description
if (summary != nullptr) m_Summary = summary;
if (description != nullptr) m_Description = description;
// insert argument list and check them
for (auto* arg : arguments) {
// insert into long name map if necessary
if (arg->HasLongName()) {
auto result = m_LongNameMap.try_emplace(arg->GetLongName(), arg);
if (!result.second) throw std::invalid_argument("duplicated long name!");
}
// insert into short name map if necessary
if (arg->HasShortName()) {
auto result = m_ShortNameMap.try_emplace(arg->GetShortName(), arg);
if (!result.second) throw std::invalid_argument("duplicated short name!");
}
// insert into argument list
m_Arguments.emplace_back(arg);
}
}
OptionContext::~OptionContext() {}
bool OptionContext::Parse(ArgumentList& al) {
// reset argument list first
al.Reset();
// prepare variables and start loop
yycc_u8string long_name;
yycc_char8_t short_name;
bool is_long_name;
while (!al.IsEOF()) {
// if we can not find any switches, return with error
if (!al.IsSwitch(&is_long_name, &long_name, &short_name)) return false;
// find corresponding argument by long name or short name.
// if we can not find it, return with error.
AbstractArgument* arg;
if (is_long_name) {
auto finder = m_LongNameMap.find(long_name);
if (finder == m_LongNameMap.end()) return false;
arg = finder->second;
} else {
auto finder = m_ShortNameMap.find(short_name);
if (finder == m_ShortNameMap.end()) return false;
arg = finder->second;
}
// if this argument has been captured, raise error
if (arg->IsCaptured()) return false;
// call user parse function of found argument
if (arg->Parse(al)) {
// success. mark it is captured
arg->SetCaptured(true);
} else {
// failed, return error
return false;
}
// move to next argument
al.Next();
}
// after processing all argument,
// we should check whether all non-optional argument are captured.
for (const auto* arg : m_Arguments) {
if (!arg->IsOptional() && !arg->IsCaptured())
return false;
}
// okey
return true;
}
void OptionContext::Reset() {
for (auto* arg : m_Arguments) {
// clear user data and unset captured
arg->Reset();
arg->SetCaptured(false);
}
}
void OptionContext::Help() const {
// print summary and description if necessary
if (!m_Summary.empty())
YYCC::ConsoleHelper::WriteLine(m_Summary.c_str());
if (!m_Description.empty())
YYCC::ConsoleHelper::WriteLine(m_Description.c_str());
// blank line
YYCC::ConsoleHelper::WriteLine(YYCC_U8(""));
// print argument list
for (const auto* arg : m_Arguments) {
yycc_u8string argstr;
// print indent
argstr += YYCC_U8("\t");
// print optional head
bool is_optional = arg->IsOptional();
if (is_optional) argstr += YYCC_U8("[");
// switch name
bool short_name = arg->HasShortName(), long_name = arg->HasLongName();
if (short_name) {
argstr += YYCC_U8("-");
argstr += arg->GetShortName();
}
if (long_name) {
if (short_name) argstr += YYCC_U8(", ");
argstr += YYCC_U8("--");
argstr += arg->GetLongName();
}
// argument example
if (arg->HasArgumentExample()) {
argstr += YYCC_U8(" ");
argstr += arg->GetArgumentExample();
}
// optional tail
if (is_optional) argstr += YYCC_U8("]");
// argument description
if (arg->HasDescription()) {
// eol and double indent
argstr += YYCC_U8("\n\t\t");
// description
argstr += arg->GetDescription();
}
// write into console
YYCC::ConsoleHelper::WriteLine(argstr.c_str());
}
}
#pragma endregion
}

View File

@ -0,0 +1,465 @@
#pragma once
#include "YYCCInternal.hpp"
#include "Constraints.hpp"
#include "EncodingHelper.hpp"
#include "ParserHelper.hpp"
#include <cstring>
#include <functional>
#include <vector>
#include <map>
#include <stdexcept>
/**
* @brief Universal argument parser.
* @details
* For how to use this namespace, please see \ref arg_parser.
*/
namespace YYCC::ArgParser {
/**
* @brief The advanced wrapper of the list containing command line arguments.
* @details
* This class is used by OptionContext and argument class internally for convenience.
* It should not be constrcuted directly.
* Programmer should choose proper static creation function to create instance of this class.
*/
class ArgumentList {
public:
/**
* @brief Create argument list from the parameters of standard C main function.
* @param[in] argc The argument count passed to standard C main function.
* @param[in] argv The argument value passed to standard C main function.
* @return Extracted argument list instance.
* @remarks
* First item in command line will be stripped,
* because in most cases it points to executable self
* and should not be seen as a part of arguments.
*/
static ArgumentList CreateFromStd(int argc, char* argv[]);
#if defined(YYCC_OS_WINDOWS)
/**
* @brief Create argument list from Win32 function.
* @details
* @return Extracted argument list instance.
* @remarks
* First item in command line will be stripped,
* because in most cases it points to executable self
* and should not be seen as a part of arguments.
* \par
* Programmer should use this function instead of CreateFromStd(),
* because that function involve encoding issue on Windows, especially command line including non-ASCII chars.
* Only this function guaranteen that return correct argument list on Windows.
*/
static ArgumentList CreateFromWin32();
#endif
private:
/**
* @brief Constructor of ArgumentList used internally.
* @param[in] arguments
* Underlying argument list.
* This argument list should remove first executable name before passing it to there.
*/
ArgumentList(std::vector<yycc_u8string>&& arguments);
public:
YYCC_DEF_CLS_COPY_MOVE(ArgumentList);
public:
/**
* @brief Move to previous argument.
* @exception std::runtime_error Try moving at the head of argument list.
*/
void Prev();
/**
* @brief Move to next argument.
* @exception std::runtime_error Try moving at the tail of argument list.
*/
void Next();
/**
* @brief Get the string of current argument.
* @exception std::runtime_error Try fetching data at the tail of argument list.
* @return The constant reference to the string of current argument.
*/
const yycc_u8string& Argument() const;
/**
* @brief Check whether current argument is a option / switch.
* @param[out] is_long_name
* It will be set true if this argument is long name, otherwise short name.
* nullptr if you don't want to receive this infomation.
* @param[out] long_name
* The container holding matched long name if it is (double dash stripped).
* nullptr if you don't want to receive this infomation.
* @param[out] short_name
* The variable holding matched short name if it is (dash stripped).
* nullptr if you don't want to receive this infomation.
* @exception std::runtime_error Try fetching data at the tail of argument list.
* @return
* True if it is, otherwise false.
* If this function return false, all given parameters are in undefined status.
*/
bool IsSwitch(
bool* is_long_name = nullptr,
yycc_u8string* long_name = nullptr,
yycc_char8_t* short_name = nullptr) const;
/**
* @brief Check whether current argument is a value.
* @param[out] val
* The variable holding value if it is.
* nullptr if you don't want to receive this infomation.
* @exception std::runtime_error Try fetching data at the tail of argument list.
* @return True if it is, otherwise false.
*/
bool IsValue(yycc_u8string* val = nullptr) const;
/**
* @brief Check whether we are at the tail of argument list.
* @details
* Please note EOF is a special state that you can not fetch data from it.
* EOF is the next element of the last element of argument list.
* It more like \c end() in most C++ container.
* @return True if it is, otherwise false.
*/
bool IsEOF() const;
/**
* @brief Reset cursor to the head of argument list for reuse.
*/
void Reset();
private:
/**
* @brief Check whether current argument is long name option / switch.
* @details This function is used by IsSwitch() internally.
* @param[out] name_part
* The container holding matched long name if it is (double dash stripped).
* nullptr if you don't want to receive this infomation.
* @return True if it is, otherwise false.
*/
bool IsLongNameSwitch(yycc_u8string* name_part = nullptr) const;
/**
* @brief Check whether current argument is short name option / switch.
* @details This function is used by IsSwitch() internally.
* @param[out] name_part
* The variable holding matched short name if it is (dash stripped).
* nullptr if you don't want to receive this infomation.
* @return True if it is, otherwise false.
*/
bool IsShortNameSwitch(yycc_char8_t* name_part = nullptr) const;
private:
std::vector<yycc_u8string> m_Arguments;
size_t m_ArgumentsCursor;
};
/**
* @brief The base class of every argument.
* @details Programmer can inherit this class and implement essential functions to create custom argument.
*/
class AbstractArgument {
friend class OptionContext;
// Long name and short name constants and checker.
public:
static const yycc_u8string DOUBLE_DASH; ///< The constant value representing double dash (\c --)
static const yycc_char8_t DASH; ///< The constant value representing dash (\c -)
static const yycc_char8_t NO_SHORT_NAME; ///< The constant value representing that there is not short value.
static const yycc_char8_t MIN_SHORT_NAME; ///< The constant value representing the minimum value of valid ASCII chars in short and long name.
static const yycc_char8_t MAX_SHORT_NAME; ///< The constant value representing the maximum value of valid ASCII chars in short and long name.
/**
* @brief Check whether given short name is valid.
* @details
* An ASCII code of valid short name
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
* It also can not be #DASH.
* @param[in] short_name Short name for checking.
* @return True if it is valid, otherwise false.
*/
static bool IsLegalShortName(yycc_char8_t short_name);
/**
* @brief Check whether given long name is valid.
* @details
* An ASCII code of every item in valid long name
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
* However it can be #DASH. This is different with short name.
* @param[in] long_name Long name for checking.
* @return True if it is valid, otherwise false.
*/
static bool IsLegalLongName(const yycc_u8string_view& long_name);
// Constructor & destructor
public:
/**
* @brief Constructor an argument
* @param[in] long_name The long name of this argument. nullptr if no long name.
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
* @param[in] is_optional
* True if this argument is optional argument.
* Optional argument can be absent in argument list.
* Non-optional argument must be presented in argument list,
* otherwise parser will fail.
* @exception std::invalid_argument Given short name or long name are invalid.
*/
AbstractArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name = AbstractArgument::NO_SHORT_NAME,
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
bool is_optional = false);
virtual ~AbstractArgument();
YYCC_DEL_CLS_COPY_MOVE(AbstractArgument);
// ===== User Implementation =====
protected:
/**
* @brief User implemented custom parse function
* @param[in] al The argument list for parsing.
* @return True if parse is success, otherwise false.
* @remarks
* When enter this function, argument list points to switch self.
* After success parsing, you should point it to the argument this function last accepted.
* For exmaple, for command line "-i 114514",
* when enter this function, this argument list point to "-i",
* and you should set it to "114514" when exiting this function.
*/
virtual bool Parse(ArgumentList& al) = 0;
/**
* @brief User implemented custom reset function
* @remarks
* In this function, user should claer its stored value if is has.
* You don't need clar capture state. That is done by library self.
*/
virtual void Reset() = 0;
// ===== Basic Infos =====
public:
/// @brief Check whether this argument specify long name.
/// @return True if it is, otherwise false.
bool HasLongName() const;
/// @brief Get specified long name.
/// @return Specified long name.
const yycc_u8string& GetLongName() const;
/// @brief Check whether this argument specify short name.
/// @return True if it is, otherwise false.
bool HasShortName() const;
/// @brief Get specified short name.
/// @return Specified short name.
yycc_char8_t GetShortName() const;
/// @brief Check whether this argument specify description.
/// @return True if it is, otherwise false.
bool HasDescription() const;
/// @brief Get specified description.
/// @return Specified description.
const yycc_u8string& GetDescription() const;
/// @brief Check whether this argument specify example.
/// @return True if it is, otherwise false.
bool HasArgumentExample() const;
/// @brief Get specified example.
/// @return Specified example.
const yycc_u8string& GetArgumentExample() const;
/// @brief Check whether this argument is optional.
/// @return True if it is, otherwise false.
bool IsOptional() const;
private:
yycc_u8string m_LongName;
yycc_char8_t m_ShortName;
yycc_u8string m_Description;
yycc_u8string m_ArgumentExample;
bool m_IsOptional;
// ===== Capture State =====
public:
/// @brief Check whether this argument has been captured.
/// @return True if it is, otherwise false.
bool IsCaptured() const;
private:
/**
* @brief Set capture state of this argument.
* @details This function is used internally by OptionContext.
* @param[in] is_captured New states of captured.
*/
void SetCaptured(bool is_captured);
bool m_IsCaptured;
};
/// @brief The core of argument parser, also manage all arguments.
class OptionContext {
public:
/**
* @brief Construct option context.
* @param[in] summary The summary of this application which will be printed in help text.
* @param[in] description The description of this application which will be printed in help text.
* @param[in] arguments The initializer list including pointers to all arguments.
*/
OptionContext(
const yycc_char8_t* summary, const yycc_char8_t* description,
std::initializer_list<AbstractArgument*> arguments);
~OptionContext();
YYCC_DEL_CLS_COPY_MOVE(OptionContext);
public:
/**
* @brief Start a parse.
* @param[in] al The reference to ArgumentList for parsing.
* @return
* True if success, otherwise false.
* If this function return false, you should not visit any arguments it managed.
*/
bool Parse(ArgumentList& al);
/**
* @brief Reset all managed argument to default state thus you can start another parsing.
*/
void Reset();
/**
* @brief Print help text in \c stdout.
*/
void Help() const;
private:
yycc_u8string m_Summary;
yycc_u8string m_Description;
std::vector<AbstractArgument*> m_Arguments;
std::map<yycc_u8string, AbstractArgument*> m_LongNameMap;
std::map<yycc_char8_t, AbstractArgument*> m_ShortNameMap;
};
#pragma region Argument Presets
/**
* @brief Arithmetic (integral, floating point. except bool) type argument
* @tparam _Ty The internal stored type belongs to arithmetic type.
*/
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
class NumberArgument : public AbstractArgument {
public:
/**
* @brief Constructor an arithmetic argument
* @param[in] long_name The long name of this argument. nullptr if no long name.
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
* @param[in] is_optional True if this argument is optional argument.
* @param[in] constraint The constraint applied to this argument.
* @exception std::invalid_argument Given short name or long name are invalid.
*/
NumberArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name,
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
bool is_optional = false,
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
virtual ~NumberArgument() {}
YYCC_DEL_CLS_COPY_MOVE(NumberArgument);
public:
/// @brief Get stored data in argument.
_Ty Get() const {
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
return m_Data;
}
protected:
virtual bool Parse(ArgumentList& al) override {
// try get corresponding value
yycc_u8string strval;
al.Next();
if (al.IsEOF() || !al.IsValue(&strval)) {
al.Prev();
return false;
}
// try parsing value
if (!YYCC::ParserHelper::TryParse<_Ty>(strval, m_Data)) return false;
// check constraint
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
return false;
// okey
return true;
}
virtual void Reset() override {
std::memset(&m_Data, 0, sizeof(m_Data));
}
protected:
_Ty m_Data;
Constraints::Constraint<_Ty> m_Constraint;
};
/**
* @brief A simple switch type argument which do not store any value.
*/
class SwitchArgument : public AbstractArgument {
public:
/**
* @brief Constructor an switch argument
* @param[in] long_name The long name of this argument. nullptr if no long name.
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
* @exception std::invalid_argument Given short name or long name are invalid.
*/
SwitchArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name,
const yycc_char8_t* description = nullptr) :
// bool switch must be optional, because it is false if no given switch.
// bool switch doesn't have argument, so it doesn't have example property.
AbstractArgument(long_name, short_name, description, nullptr, true) {}
virtual ~SwitchArgument() {}
YYCC_DEL_CLS_COPY_MOVE(SwitchArgument);
protected:
virtual bool Parse(ArgumentList& al) override { return true; } // simply return true because no value to store.
virtual void Reset() override {} // nothing need to be reset.
};
/// @brief String type argument
class StringArgument : public AbstractArgument {
public:
/**
* @brief Constructor a string argument
* @param[in] long_name The long name of this argument. nullptr if no long name.
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
* @param[in] is_optional True if this argument is optional argument.
* @param[in] constraint The constraint applied to this argument.
* @exception std::invalid_argument Given short name or long name are invalid.
*/
StringArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name,
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
bool is_optional = false,
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
virtual ~StringArgument() {}
YYCC_DEL_CLS_COPY_MOVE(StringArgument);
public:
/// @brief Get stored data in argument.
const yycc_u8string& Get() const {
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
return m_Data;
}
protected:
virtual bool Parse(ArgumentList& al) override {
// try get corresponding value
al.Next();
if (al.IsEOF() || !al.IsValue(&m_Data)) {
al.Prev();
return false;
}
// check constraint
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
return false;
// okey
return true;
}
virtual void Reset() override {
m_Data.clear();
}
protected:
yycc_u8string m_Data;
Constraints::Constraint<yycc_u8string> m_Constraint;
};
#pragma endregion
}

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

@ -2,20 +2,39 @@
#include "EncodingHelper.hpp"
#include "IOHelper.hpp"
#include "EnumHelper.hpp"
#include <stdexcept>
namespace YYCC::ConfigManager {
#pragma region Abstract Setting
AbstractSetting::AbstractSetting(const yycc_u8string_view& name) : m_Name(name), m_RawData() {
if (m_Name.empty())
throw std::invalid_argument("the name of setting should not be empty");
}
AbstractSetting::~AbstractSetting() {}
const yycc_u8string& AbstractSetting::GetName() const { return m_Name; }
void AbstractSetting::ResizeData(size_t new_size) { m_RawData.resize(new_size); }
const void* AbstractSetting::GetDataPtr() const { return m_RawData.data(); }
void* AbstractSetting::GetDataPtr() { return m_RawData.data(); }
size_t AbstractSetting::GetDataSize() const { return m_RawData.size(); }
#pragma endregion
#pragma region Core Manager
CoreManager::CoreManager(
const yycc_char8_t* cfg_file_path,
const yycc_u8string_view& cfg_file_path,
uint64_t version_identifier,
std::initializer_list<AbstractSetting*> settings) :
m_CfgFilePath(), m_VersionIdentifier(version_identifier), m_Settings() {
// assign cfg path
if (cfg_file_path != nullptr)
m_CfgFilePath = cfg_file_path;
m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() {
// Mark: no need to check cfg file path
// it will be checked at creating file handle
// assign settings
for (auto* setting : settings) {
auto result = m_Settings.try_emplace(setting->GetName(), setting);
@ -26,29 +45,39 @@ namespace YYCC::ConfigManager {
}
}
bool CoreManager::Load() {
ConfigLoadResult CoreManager::Load() {
// prepare result variables
ConfigLoadResult ret = ConfigLoadResult::OK;
// reset all settings first
Reset();
// get file handle
auto fs = this->GetFileHandle(YYCC_U8("rb"));
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("rb")));
if (fs.get() == nullptr) {
// if we fail to get, it means that we do not have corresponding cfg file.
// all settings should be reset to default value.
return true;
ret = ConfigLoadResult::Created;
return ret;
}
// fetch version info
uint64_t version_info;
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info))
return false;
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) {
ret = ConfigLoadResult::Created;
return ret;
}
// check version
// if read version is greater than we expected,
// it means that this cfg file is created by the program higer than this.
// we should not read anything from it.
// however, for compaitibility reason, we allow read old cfg data.
if (version_info > m_VersionIdentifier)
return true;
if (version_info > m_VersionIdentifier) {
ret = ConfigLoadResult::ForwardNew;
return ret;
} else if (version_info < m_VersionIdentifier) {
EnumHelper::Add(ret, ConfigLoadResult::Migrated);
}
// fetch setting item from file
yycc_u8string name_cache;
@ -59,42 +88,55 @@ namespace YYCC::ConfigManager {
if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) {
// we also check whether reach EOF at there.
if (std::feof(fs.get())) break;
else return false;
else {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
}
// fetch name body
name_cache.resize(name_length);
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length)
return false;
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get setting data length
size_t data_length;
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length))
return false;
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get matched setting first
const auto& found = m_Settings.find(name_cache);
if (found != m_Settings.end()) {
// found. read data for it
found->second->ResizeData(data_length);
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length)
return false;
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// call user defined load function
// if fail to parse, reset to default value
if (!found->second->UserLoad())
if (!found->second->UserLoad()) {
EnumHelper::Add(ret, ConfigLoadResult::ItemError);
found->second->UserReset();
}
} else {
// fail to find. skip this unknown setting
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0)
return false;
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
}
}
return true;
return ret;
}
bool CoreManager::Save() {
// get file handle
auto fs = this->GetFileHandle(YYCC_U8("wb"));
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("wb")));
// if we fail to get, return false.
if (fs == nullptr) return false;
@ -139,15 +181,6 @@ namespace YYCC::ConfigManager {
}
}
CoreManager::FileHandleGuard_t CoreManager::GetFileHandle(const yycc_char8_t* mode) const {
return CoreManager::FileHandleGuard_t(
IOHelper::UTF8FOpen(this->m_CfgFilePath.c_str(), mode),
[](FILE* fs) -> void {
if (fs != nullptr) std::fclose(fs);
}
);
}
#pragma endregion
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "YYCCInternal.hpp"
#include "Constraints.hpp"
#include <memory>
#include <vector>
#include <map>
@ -16,84 +17,57 @@
* @details For how to use this namespace, please see \ref config_manager.
*/
namespace YYCC::ConfigManager {
/**
* @brief The constraint applied to settings to limit its stored value.
* @tparam _Ty The internal data type stroed in corresponding setting.
* @brief The load result of loading config.
*/
template<typename _Ty>
struct Constraint {
using CheckFct_t = std::function<bool(const _Ty&)>;
//using CorrectFct_t = std::function<_Ty(const _Ty&)>;
CheckFct_t m_CheckFct;
//CorrectFct_t m_CorrectFct;
bool IsValid() const {
return m_CheckFct != nullptr/* && m_CorrectFct != nullptr*/;
}
enum class ConfigLoadResult {
OK = 0, ///< Success load configs.
Created = 1 << 0, ///< Given file is not existing, we create all configs in default values.
ForwardNew = 1 << 1, ///< Detect the config file created by higher version. We create all configs in default values.
Migrated = 1 << 2, ///< Detect the config file created by lower version. We try migrate configs written in it.
BrokenFile = 1 << 3, ///< Given file has bad format. Thus some configs are kept as its default values.
ItemError = 1 << 4 ///< Some config can not be recognized from the data read from file so they are reset to default value.
};
/**
* @brief The namespace containing functions generating common used constraint.
*/
namespace ConstraintPresets {
/**
* @brief Get constraint for arithmetic values by minimum and maximum value range.
* @tparam _Ty The underlying arithmetic type.
* @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 _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_enum_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
Constraint<_Ty> GetNumberRangeConstraint(_Ty min_value, _Ty max_value) {
if (min_value > max_value)
throw std::invalid_argument("invalid min max value for NumberRangeConstraint");
return Constraint<_Ty> {
[min_value, max_value](const _Ty& val) -> bool { return (val <= max_value) && (val >= min_value); }
/*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_value); }*/
};
}
}
using UnderlyingConfigLoadResult_t = std::underlying_type_t<ConfigLoadResult>;
/// @brief The base class of every setting.
/// @details Programmer can inherit this class and implement essential to create custom setting.
/// @details Programmer can inherit this class and implement essential functions to create custom setting.
class AbstractSetting {
friend class CoreManager;
public:
/**
* @brief Construct a setting
* @param[in] name The name of this setting.
* @exception std::invalid_argument Name of setting is empty.
*/
AbstractSetting(const yycc_char8_t* name) : m_Name(), m_RawData() {
if (name != nullptr) m_Name = name;
}
virtual ~AbstractSetting() {}
AbstractSetting(const yycc_u8string_view& name);
virtual ~AbstractSetting();
YYCC_DEL_CLS_COPY_MOVE(AbstractSetting);
// Name interface
public:
/// @brief Get name of this setting.
/// @details Name was used in storing setting in file.
const yycc_u8string& GetName() const { return m_Name; }
const yycc_u8string& GetName() const;
private:
yycc_u8string m_Name;
// User Implementations
protected:
/// @brief User implemented custom load functions
/// @brief User implemented custom load function
/// @remarks
/// In this function, programmer should read data from internal buffer
/// and store it to its own another internal variables.
/// @return True if success, otherwise false.
virtual bool UserLoad() = 0;
/// @brief User implemented custom save functions
/// @brief User implemented custom save function
/// @remarks
/// In this function, programmer should write data,
/// which is stored in another variavle by it own, to internal buffer.
/// @return True if success, otherwise false.
virtual bool UserSave() = 0;
/// @brief User implemented custom reset functions
/// @brief User implemented custom reset function
/// @remarks In this function, programmer should reset its internal variable to default value.
virtual void UserReset() = 0;
@ -102,15 +76,15 @@ namespace YYCC::ConfigManager {
/// @brief Resize internal buffer to given size.
/// @remarks It is usually used in UserSave.
/// @param[in] new_size The new size of internal buffer.
void ResizeData(size_t new_size) { m_RawData.resize(new_size); }
void ResizeData(size_t new_size);
/// @brief Get data pointer to internal buffer.
/// @remarks It is usually used in UserLoad.
const void* GetDataPtr() const { return m_RawData.data(); }
const void* GetDataPtr() const;
/// @brief Get mutable data pointer to internal buffer.
/// @remarks It is usually used in UserSave.
void* GetDataPtr() { return m_RawData.data(); }
void* GetDataPtr();
/// @brief Get the length of internal buffer.
size_t GetDataSize() const { return m_RawData.size(); }
size_t GetDataSize() const;
private:
std::vector<uint8_t> m_RawData;
};
@ -125,17 +99,18 @@ namespace YYCC::ConfigManager {
* @param[in] settings An initializer list containing pointers to all managed settings.
*/
CoreManager(
const yycc_char8_t* cfg_file_path,
const yycc_u8string_view& cfg_file_path,
uint64_t version_identifier,
std::initializer_list<AbstractSetting*> settings);
~CoreManager() {}
YYCC_DEL_CLS_COPY_MOVE(CoreManager);
// Core functions
public:
/// @brief Load settings from file.
/// @details Before loading, all settings will be reset to default value first.
/// @return True if success, otherwise false.
bool Load();
/// @return What happend when loading config. This function always success.
ConfigLoadResult Load();
/// @brief Save settings to file.
/// @return True if success, otherwise false.
bool Save();
@ -143,9 +118,6 @@ namespace YYCC::ConfigManager {
void Reset();
private:
using FileHandleGuard_t = std::unique_ptr<FILE, std::function<void(FILE*)>>;
FileHandleGuard_t GetFileHandle(const yycc_char8_t* mode) const;
yycc_u8string m_CfgFilePath;
uint64_t m_VersionIdentifier;
std::map<yycc_u8string, AbstractSetting*> m_Settings;
@ -165,11 +137,15 @@ namespace YYCC::ConfigManager {
* @param[in] name The name of this setting.
* @param[in] default_value The default value of this setting.
* @param[in] constraint The constraint applied to this setting.
* @exception std::invalid_argument Name of setting is empty.
*/
NumberSetting(const yycc_char8_t* name, _Ty default_value, Constraint<_Ty> constraint = Constraint<_Ty> {}) :
NumberSetting(
const yycc_u8string_view& name, _Ty default_value,
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constraint(constraint) {}
virtual ~NumberSetting() {}
YYCC_DEL_CLS_COPY_MOVE(NumberSetting);
/// @brief Get stored data in setting.
_Ty Get() const { return m_Data; }
/**
@ -208,7 +184,7 @@ namespace YYCC::ConfigManager {
}
_Ty m_Data, m_DefaultData;
Constraint<_Ty> m_Constraint;
Constraints::Constraint<_Ty> m_Constraint;
};
/// @brief String type setting
@ -219,14 +195,18 @@ namespace YYCC::ConfigManager {
* @param[in] name The name of this setting.
* @param[in] default_value The default value of this setting.
* @param[in] constraint The constraint applied to this setting.
* @exception std::invalid_argument Name of setting is empty.
*/
StringSetting(const yycc_char8_t* name, const yycc_u8string_view& default_value, Constraint<yycc_u8string_view> constraint = Constraint<yycc_u8string_view> {}) :
StringSetting(
const yycc_u8string_view& name, const yycc_u8string_view& default_value,
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) {
m_Data = default_value;
m_DefaultData = default_value;
}
virtual ~StringSetting() {}
YYCC_DEL_CLS_COPY_MOVE(StringSetting);
/// @brief Get reference to stored string.
const yycc_u8string& Get() const { return m_Data; }
/**
@ -236,10 +216,11 @@ namespace YYCC::ConfigManager {
*/
bool Set(const yycc_u8string_view& new_data) {
// check data validation
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data))
yycc_u8string new_data_cache(new_data);
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data_cache))
return false;
// assign data
m_Data = new_data;
m_Data = std::move(new_data_cache);
return true;
}
@ -279,7 +260,7 @@ namespace YYCC::ConfigManager {
}
yycc_u8string m_Data, m_DefaultData;
Constraint<yycc_u8string_view> m_Constraint;
Constraints::Constraint<yycc_u8string> m_Constraint;
};
#pragma endregion

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

@ -0,0 +1,85 @@
#pragma once
#include "YYCCInternal.hpp"
#include <functional>
#include <stdexcept>
#include <set>
#include <initializer_list>
/**
* @brief The namespace containing constraint declaration
* and functions generating common used constraint.
*/
namespace YYCC::Constraints {
/**
* @brief The constraint applied to settings to limit its stored value.
* @tparam _Ty The data type this constraint need to be processed with.
*/
template<typename _Ty>
struct Constraint {
/// @brief Return true if value is legal, otherwise false.
using CheckFct_t = std::function<bool(const _Ty&)>;
/// @brief The function pointer used for checking whether given value is valid.
CheckFct_t m_CheckFct;
/**
* @brief Check whether this constraint is valid for using.
* @return
* True if this constraint is valid, otherwise false.
* If this function return false, it means that there is no contraint.
* And you should not use this constraint provided any function pointer.
*/
bool IsValid() const {
return m_CheckFct != nullptr;
}
};
/**
* @brief Get constraint for arithmetic or enum values by minimum and maximum value range.
* @tparam _Ty 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 _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
Constraint<_Ty> GetMinMaxRangeConstraint(_Ty min_value, _Ty max_value) {
if (min_value > max_value)
throw std::invalid_argument("invalid min max value for NumberRangeConstraint");
return Constraint<_Ty> {
[min_value, max_value](const _Ty& val) -> bool { return (val <= max_value) && (val >= min_value); }
/*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_value); }*/
};
}
/**
* @brief Get constraint for enum values by enumerating all possible values.
* @tparam _Ty An enum type (except bool) of underlying stored value.
* @param[in] il An initializer list storing all possible values.
* @return The generated constraint instance which can be directly applied.
*/
template<typename _Ty, std::enable_if_t<std::is_enum_v<_Ty>, int> = 0>
Constraint<_Ty> GetEnumEnumerationConstraint(const std::initializer_list<_Ty>& il) {
std::set<_Ty> data(il);
return Constraint<_Ty> {
[data](const _Ty& val) -> bool { return data.find(val) != data.end(); }
};
}
/**
* @brief Get constraint for string values by enumerating all possible values.
* @param[in] il An initializer list storing all possible values.
* @return The generated constraint instance which can be directly applied.
* @remarks
* Caller must make sure that the string view passed in initializer list is valid until this Constraint life time gone.
* Becasue this generator will not copy your given string view into string.
*/
inline Constraint<yycc_u8string> GetStringEnumerationConstraint(const std::initializer_list<yycc_u8string_view>& il) {
std::set<yycc_u8string_view> data(il);
return Constraint<yycc_u8string> {
[data](const yycc_u8string& val) -> bool { return data.find(yycc_u8string_view(val)) != data.end(); }
};
}
}

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"
@ -40,7 +40,8 @@ namespace YYCC::DialogHelper {
return false;
// convert pattern and join them
yycc_u8string joined_modes(YYCC::StringHelper::Join(it.second, YYCC_U8(";")));
const auto& filter_modes = it.second;
yycc_u8string joined_modes(YYCC::StringHelper::Join(filter_modes.begin(), filter_modes.end(), YYCC_U8(";")));
WinFileFilters::WinFilterModes modes;
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes))
return false;

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>
@ -31,6 +31,7 @@ namespace YYCC::DialogHelper {
friend class WinFileDialog;
public:
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
YYCC_DEL_CLS_COPY_MOVE(WinFileFilters);
/// @brief Get the count of available file filters
UINT GetFilterCount() const {
@ -67,6 +68,7 @@ namespace YYCC::DialogHelper {
class FileFilters {
public:
FileFilters() : m_Filters() {}
YYCC_DEL_CLS_COPY_MOVE(FileFilters);
/**
* @brief Add a filter pair in file types list.
@ -123,6 +125,7 @@ namespace YYCC::DialogHelper {
m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u),
m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(),
m_WinInitDirectory(nullptr) {}
YYCC_DEL_CLS_COPY_MOVE(WinFileDialog);
/// @brief Get whether this dialog has owner.
bool HasOwner() const { return m_WinOwner != NULL; }
@ -189,6 +192,7 @@ namespace YYCC::DialogHelper {
m_DefaultFileTypeIndex(0u),
m_Title(), m_InitFileName(), m_InitDirectory(),
m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {}
YYCC_DEL_CLS_COPY_MOVE(FileDialog);
/**
* @brief Set the owner of dialog.

View File

@ -1,12 +1,12 @@
#include "ExceptionHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
#include "WinFctHelper.hpp"
#include "ConsoleHelper.hpp"
#include "StringHelper.hpp"
#include "IOHelper.hpp"
#include "EncodingHelper.hpp"
#include "FsPathPatch.hpp"
#include "StdPatch.hpp"
#include <filesystem>
#include <cstdarg>
#include <cstdio>
@ -26,6 +26,7 @@ namespace YYCC::ExceptionHelper {
ExceptionRegister() :
m_CoreMutex(),
m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr),
m_UserCallback(nullptr),
m_SingletonMutex(NULL) {}
~ExceptionRegister() {
Unregister();
@ -35,7 +36,7 @@ namespace YYCC::ExceptionHelper {
/**
* @brief Try to register unhandled exception handler.
*/
void Register() {
void Register(ExceptionCallback callback) {
std::lock_guard<std::mutex> locker(m_CoreMutex);
// if we have registered, return
if (m_IsRegistered) return;
@ -63,6 +64,8 @@ namespace YYCC::ExceptionHelper {
// okey, we can register it.
// backup old handler
m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl);
// set user callback
m_UserCallback = callback;
// mark registered
m_IsRegistered = true;
}
@ -75,6 +78,8 @@ namespace YYCC::ExceptionHelper {
if (!m_IsRegistered) return;
// unregister handler
// reset user callback
m_UserCallback = nullptr;
// restore old handler
SetUnhandledExceptionFilter(m_PrevProcHandler);
m_PrevProcHandler = nullptr;
@ -115,6 +120,14 @@ namespace YYCC::ExceptionHelper {
std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_PrevProcHandler;
}
/**
* @brief Get user specified callback.
* @return The function pointer to user callback. nullptr if no associated callback.
*/
ExceptionCallback GetUserCallback() const {
std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_UserCallback;
}
/**
* @brief Try to start process unhandled exception.
@ -154,6 +167,12 @@ namespace YYCC::ExceptionHelper {
* True if it is, otherwise false.
*/
bool m_IsProcessing;
/**
* @brief User defined callback.
* @details It will be called at the tail of unhandled exception handler, because it may raise exception.
* We must make sure all log and coredump have been done before calling it.
*/
ExceptionCallback m_UserCallback;
/**
* @brief The backup of old unhandled exception handler.
*/
@ -166,6 +185,7 @@ namespace YYCC::ExceptionHelper {
HANDLE m_SingletonMutex;
};
/// @brief Core register singleton.
static ExceptionRegister g_ExceptionRegister;
#pragma region Exception Handler Implementation
@ -222,33 +242,6 @@ namespace YYCC::ExceptionHelper {
}
}
/**
* @brief Error log (including backtrace) used output function with format feature
* @details
* This function will format message first.
* And write them into given file stream and stderr.
* @param[in] fs
* The file stream where we write.
* If it is nullptr, function will skip writing for file stream.
* @param[in] fmt The format string.
* @param[in] ... The argument to be formatted.
*/
static void UExceptionErrLogFormatLine(std::FILE* fs, const yycc_char8_t* fmt, ...) {
// write to file
if (fs != nullptr) {
va_list arg1;
va_start(arg1, fmt);
std::vfprintf(fs, EncodingHelper::ToOrdinary(fmt), arg1);
std::fputs("\n", fs);
va_end(arg1);
}
// write to stderr
va_list arg2;
va_start(arg2, fmt);
ConsoleHelper::ErrWriteLine(YYCC::StringHelper::VPrintf(fmt, arg2).c_str());
va_end(arg2);
}
/**
* @brief Error log (including backtrace) used output function
* @details
@ -268,6 +261,27 @@ namespace YYCC::ExceptionHelper {
ConsoleHelper::ErrWriteLine(strl);
}
/**
* @brief Error log (including backtrace) used output function with format feature
* @details
* This function will format message first.
* And write them into given file stream and stderr.
* @param[in] fs
* The file stream where we write.
* If it is nullptr, function will skip writing for file stream.
* @param[in] fmt The format string.
* @param[in] ... The argument to be formatted.
*/
static void UExceptionErrLogFormatLine(std::FILE* fs, const yycc_char8_t* fmt, ...) {
// do format first
va_list arg;
va_start(arg, fmt);
auto fmt_result = YYCC::StringHelper::VPrintf(fmt, arg);
va_end(arg);
// write to file and console
UExceptionErrLogWriteLine(fs, fmt_result.c_str());
}
static void UExceptionBacktrace(FILE* fs, LPCONTEXT context, int maxdepth) {
// setup loading symbol options
SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); // lazy load symbol, and load line number.
@ -323,7 +337,7 @@ namespace YYCC::ExceptionHelper {
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrStack.Mode = AddrModeFlat;
frame.AddrFrame.Mode = AddrModeFlat;
// stack walker
while (StackWalk64(machine_type, process, thread, &frame, context,
0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
@ -336,12 +350,12 @@ namespace YYCC::ExceptionHelper {
}
// get module name
const yycc_char8_t* module_name = YYCC_U8("<unknown module>");
yycc_u8string module_name_raw;
const yycc_char8_t* no_module_name = YYCC_U8("<unknown module>");
yycc_u8string module_name(no_module_name);
DWORD64 module_base;
if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) {
if (WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name_raw)) {
module_name = module_name_raw.c_str();
if (!WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name)) {
module_name = no_module_name;
}
}
@ -357,9 +371,12 @@ namespace YYCC::ExceptionHelper {
}
// write to file
UExceptionErrLogFormatLine(fs, YYCC_U8("0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "]\t%s#L%" PRIu64),
// MARK: should not use PRIXPTR to print adddress.
// because Windows always use DWORD64 as the type of address.
// use PRIX64 instead.
UExceptionErrLogFormatLine(fs, YYCC_U8("0x%" PRI_XPTR_LEFT_PADDING PRIX64 "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIX64 "]\t%s#L%" PRIu64),
frame.AddrPC.Offset, // memory adress
module_name, frame.AddrPC.Offset - module_base, // module name + relative address
module_name.c_str(), frame.AddrPC.Offset - module_base, // module name + relative address
source_file, source_file_line // source file + source line
);
@ -442,8 +459,8 @@ namespace YYCC::ExceptionHelper {
if (!YYCC::WinFctHelper::GetModuleFileName(NULL, u8_process_path))
return false;
// extract file name from full path by std::filesystem::path
std::filesystem::path process_path(FsPathPatch::FromUTF8Path(u8_process_path.c_str()));
u8_process_name = FsPathPatch::ToUTF8Path(process_path.filename());
std::filesystem::path process_path(StdPatch::ToStdPath(u8_process_path));
u8_process_name = StdPatch::ToUTF8Path(process_path.filename());
}
// then get process id
DWORD process_id = GetCurrentProcessId();
@ -461,19 +478,19 @@ namespace YYCC::ExceptionHelper {
if (!WinFctHelper::GetLocalAppData(u8_localappdata_path))
return false;
// convert to std::filesystem::path
std::filesystem::path crash_report_path(FsPathPatch::FromUTF8Path(u8_localappdata_path.c_str()));
std::filesystem::path crash_report_path(StdPatch::ToStdPath(u8_localappdata_path));
// slash into crash report folder
crash_report_path /= FsPathPatch::FromUTF8Path(YYCC_U8("CrashDumps"));
crash_report_path /= StdPatch::ToStdPath(YYCC_U8("CrashDumps"));
// use create function to make sure it is existing
std::filesystem::create_directories(crash_report_path);
// build log path and coredump path
// build std::filesystem::path first
std::filesystem::path log_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_log_filename.c_str());
std::filesystem::path coredump_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_coredump_filename.c_str());
std::filesystem::path log_filepath = crash_report_path / StdPatch::ToStdPath(u8_log_filename);
std::filesystem::path coredump_filepath = crash_report_path / StdPatch::ToStdPath(u8_coredump_filename);
// output to result
log_path = FsPathPatch::ToUTF8Path(log_filepath);
coredump_path = FsPathPatch::ToUTF8Path(coredump_filepath);
log_path = StdPatch::ToUTF8Path(log_filepath);
coredump_path = StdPatch::ToUTF8Path(coredump_filepath);
return true;
}
@ -505,10 +522,14 @@ namespace YYCC::ExceptionHelper {
// write crash coredump
UExceptionCoreDump(coredump_path, info);
// call user callback
ExceptionCallback user_callback = g_ExceptionRegister.GetUserCallback();
if (user_callback != nullptr)
user_callback(log_path, coredump_path);
}
// stop process
g_ExceptionRegister.StartProcessing();
g_ExceptionRegister.StopProcessing();
end_proc:
// if backup proc can be run, run it
@ -523,14 +544,20 @@ namespace YYCC::ExceptionHelper {
#pragma endregion
void Register() {
g_ExceptionRegister.Register();
void Register(ExceptionCallback callback) {
g_ExceptionRegister.Register(callback);
}
void Unregister() {
g_ExceptionRegister.Unregister();
}
#if defined(YYCC_DEBUG_UE_FILTER)
long __stdcall DebugCallUExceptionImpl(void* data) {
return UExceptionImpl(static_cast<LPEXCEPTION_POINTERS>(data));
}
#endif
}
#endif

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.
@ -11,6 +11,24 @@
*/
namespace YYCC::ExceptionHelper {
/**
* @brief The callback function prototype which will be called when unhandled exception happened after registering.
* @details
* During registering unhandled exception handler,
* caller can optionally provide a function pointer matching this prorotype to register.
* Then it will be called if unhandled exception hanppened.
*
* This callback will provide 2 readonly arguments.
* First is the path to error log file.
* Second is the path to core dump file.
* These pathes may be empty if internal handler fail to create them.
*
* This callback is convenient for programmer using an explicit way to tell user an exception happened.
* Because in default, handler will only write error log to \c stderr and file.
* It will be totally invisible on a GUI application.
*/
using ExceptionCallback = void(*)(const yycc_u8string& log_path, const yycc_u8string& coredump_path);
/**
* @brief Register unhandled exception handler
* @details
@ -22,8 +40,9 @@ namespace YYCC::ExceptionHelper {
* (for convenient debugging of developer when reporting bugs.)
*
* This function usually is called at the start of program.
* @param[in] callback User defined callback called when unhandled exception happened. nullptr if no callback.
*/
void Register();
void Register(ExceptionCallback callback = nullptr);
/**
* @brief Unregister unhandled exception handler
* @details
@ -36,6 +55,10 @@ namespace YYCC::ExceptionHelper {
*/
void Unregister();
#if defined(YYCC_DEBUG_UE_FILTER)
long __stdcall DebugCallUExceptionImpl(void*);
#endif
}
#endif

View File

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

View File

@ -0,0 +1,38 @@
#pragma once
#include "YYCCInternal.hpp"
#include <cstdio>
#include <filesystem>
/**
* @brief Some IO related stuff
* @details
* See also \ref io_helper.
*/
namespace YYCC::IOHelper {
/// @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* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode);
}

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"
@ -9,7 +9,7 @@ namespace YYCC::WinFctHelper {
HMODULE GetCurrentModule() {
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
HMODULE hModule = NULL;
GetModuleHandleExW(
::GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter.
(LPCWSTR)GetCurrentModule,
&hModule);
@ -24,7 +24,7 @@ namespace YYCC::WinFctHelper {
// fetch temp folder
while (true) {
if ((expected_size = GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
if ((expected_size = ::GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
// failed, set to empty
return false;
}
@ -50,13 +50,13 @@ namespace YYCC::WinFctHelper {
DWORD copied_size;
while (true) {
if ((copied_size = GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
if ((copied_size = ::GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
// failed, return
return false;
}
// check insufficient buffer
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
// buffer is not enough, enlarge it and try again.
wpath.resize(wpath.size() + MAX_PATH);
} else {
@ -85,6 +85,31 @@ namespace YYCC::WinFctHelper {
return YYCC::EncodingHelper::WcharToUTF8(known_path.get(), ret);
}
bool IsValidCodePage(UINT code_page) {
CPINFOEXW cpinfo;
return ::GetCPInfoExW(code_page, 0, &cpinfo);
}
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists) {
std::wstring wExistingFileName, wNewFileName;
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
return ::CopyFileW(wExistingFileName.c_str(), wNewFileName.c_str(), bFailIfExists);
}
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName) {
std::wstring wExistingFileName, wNewFileName;
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
return ::MoveFileW(wExistingFileName.c_str(), wNewFileName.c_str());
}
BOOL DeleteFile(const yycc_u8string_view& lpFileName) {
std::wstring wFileName;
if (!YYCC::EncodingHelper::UTF8ToWchar(lpFileName, wFileName)) return FALSE;
return ::DeleteFileW(wFileName.c_str());
}
}
#endif

View File

@ -0,0 +1,106 @@
#pragma once
#include "YYCCInternal.hpp"
#if defined(YYCC_OS_WINDOWS)
#include <string>
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include "WinImportSuffix.hpp"
/**
* @brief The helper providing assistance of Win32 functions.
* @details
* This helper is Windows specific.
* If current environment is not Windows, the whole namespace will be unavailable.
* See also \ref win_fct_helper
*/
namespace YYCC::WinFctHelper {
/**
* @brief Get Windows used HANDLE for current module.
* @details
* If your target is EXE, the current module simply is your program self.
* However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL.
*
* This function is frequently used by DLL.
* Because some design need the HANDLE of current module, not the host EXE loading your DLL.
* For example, you may want to get the path of your built DLL, or fetch resources from your DLL at runtime,
* then you should pass current module HANDLE, not NULL or the HANDLE of EXE.
* @return A Windows HANDLE pointing to current module, NULL if failed.
*/
HMODULE GetCurrentModule();
/**
* @brief Get path to Windows temporary folder.
* @details Windows temporary folder usually is the target of \%TEMP\%.
* @param[out] ret The variable receiving UTF8 encoded path to Windows temp folder.
* @return True if success, otherwise false.
*/
bool GetTempDirectory(yycc_u8string& ret);
/**
* @brief Get the file name of given module HANDLE
* @param[in] hModule
* The HANDLE to the module where you want to get file name.
* It is same as the HANDLE parameter of Win32 \c GetModuleFileName.
* @param[out] ret The variable receiving UTF8 encoded file name of given module.
* @return True if success, otherwise false.
*/
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret);
/**
* @brief Get the path to \%LOCALAPPDATA\%.
* @details \%LOCALAPPDATA\% usually was used as putting local app data files
* @param[out] ret The variable receiving UTF8 encoded path to LOCALAPPDATA.
* @return True if success, otherwise false.
*/
bool GetLocalAppData(yycc_u8string& ret);
/**
* @brief Check whether given code page number is a valid one.
* @param[in] code_page The code page number.
* @return True if it is valid, otherwise false.
*/
bool IsValidCodePage(UINT code_page);
/**
* @brief Copies an existing file to a new file.
* @param lpExistingFileName The name of an existing file.
* @param lpNewFileName The name of the new file.
* @param bFailIfExists
* If this parameter is TRUE and the new file specified by \c lpNewFileName already exists, the function fails.
* If this parameter is FALSE and the new file already exists, the function overwrites the existing file and succeeds.
* @return
* If the function succeeds, the return value is nonzero.
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
* @remarks Same as Windows \c CopyFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
*/
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists);
/**
* @brief Moves an existing file or a directory, including its children.
* @param lpExistingFileName The current name of the file or directory on the local computer.
* @param lpNewFileName
* The new name for the file or directory. The new name must not already exist.
* A new file may be on a different file system or drive. A new directory must be on the same drive.
* @return
* If the function succeeds, the return value is nonzero.
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
* @remarks Same as Windows \c MoveFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefilew
*/
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName);
/**
* @brief Deletes an existing file.
* @param lpFileName The name of the file to be deleted.
* @return
* If the function succeeds, the return value is nonzero.
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
* @remarks Same as Windows \c DeleteFile: https://learn.microsoft.com/e-us/windows/win32/api/winbase/nf-winbase-deletefile
*/
BOOL DeleteFile(const yycc_u8string_view& lpFileName);
}
#endif

View File

@ -0,0 +1,147 @@
#pragma once
#pragma region Library Version and Comparison Macros
#include "YYCCVersion.hpp"
/// @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)
#pragma endregion
#pragma region Operating System Identifier Macros
// Define operating system macros
#define YYCC_OS_WINDOWS 2
#define YYCC_OS_LINUX 3
// Check current operating system.
#if defined(_WIN32)
#define YYCC_OS YYCC_OS_WINDOWS
#else
#define YYCC_OS YYCC_OS_LINUX
#endif
#pragma endregion
#pragma region Windows Shitty Behavior Disable Macros
// 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 defined(YYCC_OS_WINDOWS)
#if !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#if !defined(_CRT_NONSTDC_NO_DEPRECATE)
#define _CRT_NONSTDC_NO_DEPRECATE
#endif
#endif
#pragma endregion
#pragma region YYCC UTF8 Types
// Define the UTF8 char type we used.
// And do a polyfill if no embedded char8_t type.
#include <string>
#include <string_view>
/**
* @brief Library core namespace
* @details Almost library functions are located in this namespace.
*/
namespace YYCC {
#if defined(__cpp_char8_t)
using yycc_char8_t = char8_t;
using yycc_u8string = std::u8string;
using yycc_u8string_view = std::u8string_view;
#else
using yycc_char8_t = unsigned char;
using yycc_u8string = std::basic_string<yycc_char8_t>;
using yycc_u8string_view = std::basic_string_view<yycc_char8_t>;
#endif
}
/**
\typedef YYCC::yycc_char8_t
\brief YYCC UTF8 char type.
\details
This char type is an alias to \c std::char8_t if your current C++ standard support it.
Otherwise it is defined as <TT>unsigned char</TT> as C++ 20 stdandard does.
*/
/**
\typedef YYCC::yycc_u8string
\brief YYCC UTF8 string container type.
\details
This type is defined as \c std::basic_string<yycc_char8_t>.
It is equal to \c std::u8string if your current C++ standard support it.
*/
/**
\typedef YYCC::yycc_u8string_view
\brief YYCC UTF8 string view type.
\details
This type is defined as \c std::basic_string_view<yycc_char8_t>.
It is equal to \c std::u8string_view if your current C++ standard support it.
*/
#pragma endregion
#pragma region Batch Class Move / Copy Function Macros
/// @brief Explicitly remove copy (\c constructor and \c operator\=) for given class.
#define YYCC_DEL_CLS_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_DEL_CLS_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_DEL_CLS_COPY_MOVE(CLSNAME) \
YYCC_DEL_CLS_COPY(CLSNAME) \
YYCC_DEL_CLS_MOVE(CLSNAME)
/// @brief Explicitly set default copy (\c constructor and \c operator\=) for given class.
#define YYCC_DEF_CLS_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_DEF_CLS_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_DEF_CLS_COPY_MOVE(CLSNAME) \
YYCC_DEF_CLS_COPY(CLSNAME) \
YYCC_DEF_CLS_MOVE(CLSNAME)
#pragma endregion

View File

@ -1,16 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include "EncodingHelper.hpp"
#include "StringHelper.hpp"
#include "ConsoleHelper.hpp"
#include "COMHelper.hpp"
#include "DialogHelper.hpp"
#include "ParserHelper.hpp"
#include "IOHelper.hpp"
#include "WinFctHelper.hpp"
#include "FsPathPatch.hpp"
#include "ExceptionHelper.hpp"
#include "ConfigManager.hpp"

View File

@ -0,0 +1,18 @@
#pragma once
#include "YYCC/YYCCInternal.hpp"
#include "YYCC/EncodingHelper.hpp"
#include "YYCC/StringHelper.hpp"
#include "YYCC/ConsoleHelper.hpp"
#include "YYCC/COMHelper.hpp"
#include "YYCC/DialogHelper.hpp"
#include "YYCC/ParserHelper.hpp"
#include "YYCC/IOHelper.hpp"
#include "YYCC/WinFctHelper.hpp"
#include "YYCC/StdPatch.hpp"
#include "YYCC/EnumHelper.hpp"
#include "YYCC/ExceptionHelper.hpp"
#include "YYCC/ConfigManager.hpp"
#include "YYCC/ArgParser.hpp"

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));
}
}

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