67 Commits

Author SHA1 Message Date
7234b31ee0 temp 2025-08-20 21:59:51 +08:00
15aade052f refactor: expand ugly statement from while statement header to body. 2025-08-20 19:32:44 +08:00
050bed400d doc: update doc 2025-08-20 14:29:41 +08:00
244e39c4d1 revert: remove stacktrace feature for rust panic.
- remove stacktrace feature for rust panic function due to not all STL are ready for this.
- add more os type in CMake file.
- add lost header in fopen.
2025-08-19 21:47:21 +08:00
d52630ac5c feat: add tabulate but no test. 2025-08-19 20:53:51 +08:00
a76f10722d fix: fix todos waiting termcolor. 2025-08-19 13:58:05 +08:00
8a72e6a655 feat: add termcolor in carton.
- add termcolor and its testbench
- add integer() in flag_enum and update its testbench according to the requirements in termcolor.
2025-08-19 13:50:51 +08:00
dfc0c127c5 test: add testbench for macro namespace 2025-08-15 16:55:39 +08:00
2f11ba6023 feat: add new package wcwidth
- add wcwidth in carton.
- order clang-format do not format some generated content.
2025-08-15 16:42:28 +08:00
00c8f09907 doc: start to refactor doc 2025-08-14 21:33:17 +08:00
734cd01da8 feat: add Rust env namespace 2025-08-14 20:17:02 +08:00
bdeaea294f refactor: migrate windows specific content.
- move com, dialog and winfct function into new place.
- add testbench for com and winfct.
- dialog now still not working.
2025-08-13 15:29:47 +08:00
ff8c7d04cc fix: fix utf convertion issue under MSVC 2025-08-13 10:49:35 +08:00
0ab470367c fix: fix safe num overflow ops issue under MSVC 2025-08-13 09:51:40 +08:00
c4d441f5fa fix: fix pycodec MSVC error 2025-08-13 09:36:21 +08:00
f8a696b4e8 fix: fix MSVC __VA_OPT__ error 2025-08-13 09:24:19 +08:00
f65eff6edf fix: replace find with contains in constraint builder
- remove useless old files.
2025-08-13 08:56:09 +08:00
8fcfa180b4 fix: fix unfinished u8 fopen.
- finish unfinished utf8 version fopen.
- add testbench for it.
2025-08-13 08:49:18 +08:00
e23a1346eb test: add testbench for pycodec 2025-08-12 19:47:24 +08:00
2576523dbb fix: use new way to manage iconv token.
- use new way to manage iconv token, instead of std::unique_ptr which cause template error (no viable sizeof) in pycodec.
- fix the wrong name in pycodec.
- remove useless code.
2025-08-12 19:40:23 +08:00
9ce52e8d4b feat: finish pycodec but has compile issue 2025-08-12 17:34:03 +08:00
7785773196 refactor: use new layout for YYCC 2025-08-12 16:32:59 +08:00
cfbc3c68e0 doc: add Doxygen comment 2025-08-12 16:15:00 +08:00
8dbe32cb8e fix: fix fatal error in encoding/iconv.
- fix the size of input data error (forget mul with the size of unit)
- fix the wrong code name for UTF16 and UTF32.
- fix wrong assign for `outbytesleft`.
2025-08-12 16:05:11 +08:00
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
152 changed files with 11521 additions and 3184 deletions

317
.clang-format Normal file
View File

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

106
COMPILE.md Normal file
View File

@ -0,0 +1,106 @@
# Compile Manual
## Choose Version
We suggest that you only use stable version (tagged commit).
The latest commit always present current works.
It means that it is not stable and work in progress.
## Requirements
* CMake 3.23 at least.
* The common compiler supporting C++ 23 (GCC / Clang / MSVC).
* Iconv (Optional on Windows. Required on other systems).
* [GoogleTest](https://github.com/google/googletest) (Required if you build testbench).
* Doxygen (Required if you build documentation).
* Python and Astral UV (Required if you use "User Build" method)
> [!WARNING]
> You may face some issues when building on macOS with Clang. That's not your fault.
> Clang used libc++ library lacks some essential features used by this project.
> You may try other solutions for compiling this project on macOS or with Clang.
## Preparing
### GoogleTest
GoogleTest is required if you need to build testbench.
If you don't need this please skip this chapter.
We use GoogleTest v1.17.0.
It would be okey use other versions but I have not test on them.
> [!WARNING]
> When building this project, you may face link error with GoogleTest, especially on Linux.
> 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.
> See this [GitHub Issue](https://github.com/google/googletest/issues/4591) for more infomation.
> The solution is that download GoogleTest source code and build it in C++23 on your own.
> Following content tell you how to do this.
There are the steps instructing you how to compile GoogleTest manually.
1. Download GoogleTest source code with given version in GitHub Release page.
1. Extract it into a directory.
1. Enter this directory and create 2 subdirectory `build` and `install` for CMake build and install respectively.
1. Enter `build` directory and configure CMake with extra `-DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON` parameters.
1. Use CMake to build GoogleTest
1. Use CMake to install GoogleTest into previous we created `install` directory.
### Iconv
Iconv is optional on Windows and disabled in default.
However, if you are building project on MSYS2 or MINGW platform in Windows, we suggest you enable Iconv feature for more functions.
Once you enable this feature, you must prepare installed Iconv which is no problem for MSYS2 environment via package manager.
You also can enable this feature under MSVC but you must make sure that you can compile Iconv under MSVC.
For how to enable this feature forcely, see following chapters for more infomations.
On other platforms, Iconv is enabled automatically and can not be disabled.
Because there is no other encoding convertion libraries that we can use (Windows has a builtin set of encoding convertion Win32 functions).
### Doxygen
Doxygen is required only if you need to build documentation.
If you don't need this please skip this chapter.
We use Doxygen 1.9.7.
It would be okey use other versions but I have not test on them.
YYCCommonplace use Doxygen as its documentation system.
So before compiling, you must make sure `doxygen` are presented in your environment.
## Build and Install
There are 2 different ways to build this project.
If you are the user of this project (just want this project to make something work), please choose "User Build".
If you are a developer (developer of this project, or use this project as dependency to develop your project), please choose "Developer Build".
### User Build
"User Build" is basically how GitHub Action build this project.
We use Python 3.11 and UV 0.7.17 to manage our build script generator.
It would be okey use other versions but I have not test on them.
TODO...
### Developer Build
TODO...
There is a list listing all variables you may configure during compiling.
* `YYCC_BUILD_TESTBENCH`: Set it to `ON` to build testbench. `OFF` in default.
It is useful for the developer of this project.
It also suit for the user who has runtime issues on their platforms to check whether this project works as expected.
* `YYCC_BUILD_DOC`: Set it to `ON` to build documentation. `OFF` in default.
It may be useful for the developer who firstly use this project in their own projects.
Please note that generated documentation is different in different platforms.
* `YYCC_ENFORCE_ICONV`: Set it to `ON` to enable Iconv feature forcely. `OFF` in default.
The usage of this option has been introduced in previous "Iconv" chapter.
* `GTest_ROOT`: TODO
* `Iconv_ROOT`: TODO
* `CMAKE_CXX_STANDARD`: Set C++ standard version of project.
`23` in default and this version can not be lower than C++23.
You usually do not need change this.
* `CMAKE_POSITION_INDEPENDENT_CODE`: Set it to `True` to enable PIC.
This is essential for those project which use this project and produce dynamicing library as final artifact.

View File

@ -4,14 +4,9 @@ YYC Commonplace, or YYCCommonplace (abbr. YYCC) is a static library specifically
## Usage
For more usage about this library, please build documentation of this project via Doxygen and read it.
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.
For more usage about this library, please read documentation after building this project with documentation.
I also highly recommend that you read documentation first before writing with this library.
## 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.
See documentation for how to build this project.
See [Compile Manual](./COMPILE.md).

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

View File

@ -86,12 +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.
*/
}

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

@ -29,6 +29,9 @@
\li \subpage intro
\li \subpage premise_and_principle
<!--
\li \subpage library_macros
\li \subpage library_encoding
@ -45,19 +48,25 @@
\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">
<B>Windows Specific Features</B>
<!--
\li \subpage win_import
\li \subpage com_helper
@ -67,6 +76,7 @@
\li \subpage win_fct_helper
\li \subpage exception_helper
-->
</TD>
</TR>

View File

@ -6,7 +6,7 @@ YYCCommonplace, or YYC Commonplace (abbr. YYCC),
is a static library providing various useful C++ functions
when programming with standard library or Windows environment.
During the development of a few projects,
At the beginning, during the development of a few projects,
I gradually understand how Windows make the compromise with the code written by its old developers,
and what is developer wanted in contemporary C++ standard library under Windows environment.
So I create this static library for all of my C++ project.
@ -15,6 +15,16 @@ I can use a clear and easy way to manage these codes.
I can easily fix issues found in project using this library by updating a single project,
rather than fixing these duplicated code in each project one by one
because all of them share the same implementations.
This is the origin of the 1.x version of YYCC.
After a few years ago, I start to write in Rust and more complicated codes.
I was allured by Rust and hope all these feature Rust holded can be adapted into C++,
so I start to refactor this library in modern way.
However, the compatibility with low C++ standard version is now become the shortcoming of this library.
I was forced to considering the compatibility with C++ 17 and it cause a huge work.
So, after think twice, I decide to drop the support of C++ 17, which the higest C++ standard I can used for Virtools project.
And increase the standard to C++ 23 directly to have better experience.
That's the origin of the 2.x version of YYCC.
This project mainly is served for my personal use.
But I would be honored if you would like to use this in your project.
@ -46,17 +56,18 @@ Thus I can have a similar Linux C++ programming experience on Windows.
The eccentric decision of standard commission also is the reason why I create this library.
\li C++ standard commission loves to bring one feature with N concepts and P assistant classes.
\li C++ standard commission prefer to provide one function with very fundamental classes and functions
and programmer need to write too much code to achieve a simple work.
\li C++ standard commission seems doesn't want to bring any features the programmer urgent needed.
\li C++ standard commission loves delete programmer loved convenient functions and classes.
\li etc...
There is not a proper way to \e format a string in C++ until C++ 20 (\c std::format).
String related functions, such as split, lower, higher, replace, now still not be included in standard library.
Programmer loved, easy to used UTF8 procession functions and classes was deprecate now and will be removed in future.
Programmer loved, easy to used encoding convertion functions and classes are deprecate now and will be removed in future.
That's why I create this library.
I bring these function in this library.
I bring these functions in this library.
Not industrial level, but easy to use and have enough performance in my project.
\subsection intro__why__boost Boost Issues
@ -65,7 +76,7 @@ Bosst is a powerful C++ library. But the shortcoming is overt. It's tooooo big.
This drawback will be more obvious considering the bad dependency mechanism of C++.
Although the most of Boost sub-library is header-only, but some library still need to link with Boost.
It order you download the whole Boost library and extract it in your hard disk.
Cost more than half hours, 5+ GB disk space and the life time of your disk.
Cost more than half hour of your life, 5+ GB disk space and the life time of your disk.
The functions belonging to Boost is industrial level.
But what I want is not industrial level functions.
@ -74,6 +85,7 @@ I don't need extreme performance. I just want my code works.
So I create this library, bring some Boost functions with ordinary but not bad implementation.
<!--
\section intro__usage Library Usage
Before using this library, I suggest you read this manual fully to have a full overview of this library.
@ -116,11 +128,14 @@ 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 --no-doc: Specify this if you don't want to build documentation.
\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 build documentation automatically.
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>.
@ -184,5 +199,5 @@ 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

@ -6,6 +6,48 @@ namespace YYCC {
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,
@ -31,7 +73,7 @@ Assume \c blabla() function is Windows specific.
We have following example code:
\code
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
blabla();
#endif
\endcode

View File

@ -0,0 +1,47 @@
/**
\page premise_and_principle Premise and Principle
When programming with this library, there is some premise and principle you should noticed.
\section premise_and_principle__exception_is_error Exception is Error
The most crucial spot of this library is <B>"Exception is Error"</B>.
When some functions throw exception, it should cause program paniked, rather than recover from it.
This is inspired from Rust, and also the compromise with STL.
Most functions this library provided has Rust-Result-like return value.
It means that programmer can handle error correctly.
However, this library is based on STL, another library that may throw C++ exception to indicate error.
We can not control this behavior of STL, so I forcely apply this rule.
\section premise_and_principle__os_encoding OS Encoding
This library has special treat with Windows to make it works on Windows.
However, for other operating system, it do not have too much care.
We brutally make a premise that other operating systems are UNIX-liked and use UTF8 as its encoding.
\section premise_and_principle__string_encoding String Encoding
Before using this library, you should know the encoding strategy of this library first.
In short words, this library use UTF8 encoding everywhere except some special cases list following (not all).
\li Traditional format function in yycc::string::op.
Traditional format function provide some overloads for ordinary string formatting.
That's because this feature is so common to use in some cases.
\li The message of Rust panic in yycc::rust::panic.
Due to the limitation of \c std::format, we only can use ordinary string as its message content.
\li The message of standard library exception.
For the compatibility with C++ standard library exception,
we only can use ordinary string as the message of exception.
\section premise_and_principle__cmake All in CMake
Since YYCC 2.0 version, we do not provide MSVC install layout.
Any projects use this project should use CMake or CMake-compatible software as its build system.
The reason why we make this decision is that some essential contents are written in CMake files.
For example, some environment detection macros and Windows environment patches.
If you do not use CMake, these contents will not be presented in project and cause bad behavior when using this project.
*/

View File

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

View File

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

216
script/.gitignore vendored
View File

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

106
script/gen_build_script.py Normal file
View File

@ -0,0 +1,106 @@
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:
pic: bool
cpp_version: str
build_doc: bool
build_testbench: 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 with 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,170 +0,0 @@
import argparse
import os
import io
import re
def validate_cpp_ver(ver: str) -> str:
if re.match(r'^[0-9]+$', ver) is not None: return ver
else: raise argparse.ArgumentTypeError('invalid version of C++ standard.')
def write_line(f: io.TextIOWrapper, val: str) -> None:
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_argument(arg):
if not arg or re.search(r'(["\s])', arg):
arg = '"' + arg.replace('"', r'\"') + '"'
return escape_for_cmd_exe(arg)
def escape_for_cmd_exe(arg):
meta_re = re.compile(r'([()%!^"<>&|])')
return meta_re.sub('^\1', arg)
class ScriptSettings:
m_CppVersion: str
m_NoDoc: bool
def __init__(self, cpp_ver: str, no_doc: bool):
self.m_CppVersion = cpp_ver
self.m_NoDoc = no_doc
def script_head(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# change directory to root folder
write_line(f, ':: Navigate to project root directory')
root_dir: str = os.path.dirname(os.path.dirname(__file__))
write_line(f, f'CD /d {escape_argument(root_dir)}')
# create build directory and enter
write_line(f, ':: Create build directory and enter it')
write_line(f, 'MKDIR bin')
write_line(f, 'CD bin')
cpp_dir: str = f'cpp{s.m_CppVersion}'
write_line(f, f'MKDIR {cpp_dir}')
write_line(f, f'CD {cpp_dir}')
# blank line
write_line(f, '')
def script_tail(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# leave build directory and report success
write_line(f, ':: Leave build directory and report')
write_line(f, 'CD ..\\..')
write_line(f, 'ECHO Windows CMake Build Done')
def create_directory(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# create build directory
write_line(f, ':: Create internal build directory')
write_line(f, 'MKDIR Win32')
write_line(f, 'MKDIR x64')
write_line(f, 'MKDIR documentation')
# create install directory
write_line(f, ':: Create internal install directory')
write_line(f, 'MKDIR install')
write_line(f, 'CD install')
write_line(f, 'MKDIR Win32_Debug')
write_line(f, 'MKDIR Win32_Release')
write_line(f, 'MKDIR x64_Debug')
write_line(f, 'MKDIR x64_Release')
write_line(f, 'CD ..')
# create msvc install directory
write_line(f, ':: Create internal MSVC specific install directory')
write_line(f, 'MKDIR msvc_install')
write_line(f, 'CD msvc_install')
write_line(f, 'MKDIR bin')
write_line(f, 'MKDIR include')
write_line(f, 'MKDIR lib')
write_line(f, 'MKDIR share')
write_line(f, 'CD bin')
write_line(f, 'MKDIR Win32')
write_line(f, 'MKDIR x64')
write_line(f, 'CD ..')
write_line(f, 'CD lib')
write_line(f, 'MKDIR Win32\\Debug')
write_line(f, 'MKDIR Win32\\Release')
write_line(f, 'MKDIR x64\\Debug')
write_line(f, 'MKDIR x64\\Release')
write_line(f, 'CD ..')
write_line(f, 'CD ..')
# blank line
write_line(f, '')
def cmake_build(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# build for Win32
write_line(f, ':: Build for Win32')
write_line(f, 'CD Win32')
write_line(f, f'cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_CXX_STANDARD={s.m_CppVersion} -DYYCC_BUILD_TESTBENCH=ON ../../..')
write_line(f, 'cmake --build . --config Debug')
write_line(f, 'cmake --install . --prefix=../install/Win32_Debug --config Debug')
write_line(f, 'cmake --build . --config Release')
write_line(f, 'cmake --install . --prefix=../install/Win32_Release --config Release')
write_line(f, 'CD ..')
# build for x64
write_line(f, ':: Build for x64')
write_line(f, 'CD x64')
write_line(f, f'cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_CXX_STANDARD={s.m_CppVersion} -DYYCC_BUILD_TESTBENCH=ON ../../..')
write_line(f, 'cmake --build . --config Debug')
write_line(f, 'cmake --install . --prefix=../install/x64_Debug --config Debug')
write_line(f, 'cmake --build . --config Release')
write_line(f, 'cmake --install . --prefix=../install/x64_Release --config Release')
write_line(f, 'CD ..')
# build for documentation
if not s.m_NoDoc:
write_line(f, ':: Build for documentation')
write_line(f, 'CD documentation')
write_line(f, f'cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_CXX_STANDARD={s.m_CppVersion} -DYYCC_BUILD_DOC=ON ../../..')
write_line(f, 'cmake --build . --config Release')
write_line(f, 'cmake --build . --target YYCCDocumentation')
write_line(f, 'cmake --install . --prefix=../install/x64_Release --config Release')
write_line(f, 'CD ..')
# blank line
write_line(f, '')
def msvc_build(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# copy include from x64_Release build
write_line(f, ':: Copy header files')
write_line(f, 'XCOPY install\\x64_Release\\include msvc_install\\include\\ /E /Y')
# copy binary testbench
write_line(f, ':: Copy binary files')
write_line(f, 'COPY install\\Win32_Release\\bin\\YYCCTestbench.exe msvc_install\\bin\\Win32\\YYCCTestbench.exe /Y')
write_line(f, 'COPY install\\x64_Release\\bin\\YYCCTestbench.exe msvc_install\\bin\\x64\\YYCCTestbench.exe /Y')
# copy static library
write_line(f, ':: Copy library files')
write_line(f, 'COPY install\\Win32_Debug\\lib\\YYCCommonplace.lib msvc_install\\lib\\Win32\\Debug\\YYCCommonplace.lib /Y')
write_line(f, 'COPY install\\Win32_Release\\lib\\YYCCommonplace.lib msvc_install\\lib\\Win32\\Release\\YYCCommonplace.lib /Y')
write_line(f, 'COPY install\\x64_Debug\\lib\\YYCCommonplace.lib msvc_install\\lib\\x64\\Debug\\YYCCommonplace.lib /Y')
write_line(f, 'COPY install\\x64_Release\\lib\\YYCCommonplace.lib msvc_install\\lib\\x64\\Release\\YYCCommonplace.lib /Y')
# Copy document from x64_Release build
if not s.m_NoDoc:
write_line(f, ':: Copy documentation files')
write_line(f, 'XCOPY install\\x64_Release\\share msvc_install\\share\\ /E /Y')
# blank line
write_line(f, '')
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', '--no-doc',
action='store_true', dest='no_doc',
help='Build YYCC without documentation.'
)
args = parser.parse_args()
# build settings
settings = ScriptSettings(args.cpp, args.no_doc)
# write result
filepath = os.path.join(os.path.dirname(__file__), 'win_build.bat')
with open(filepath, 'w') as f:
write_line(f, '@ECHO OFF')
script_head(f, settings)
create_directory(f, settings)
cmake_build(f, settings)
msvc_build(f, settings)
script_tail(f, settings)

View File

@ -1,32 +0,0 @@
#!/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
# Create main binary directory
mkdir bin
cd bin
# Create build directory
mkdir build
# Create install directory
mkdir install
cd install
mkdir Debug
mkdir Release
cd ..
# Build current system debug and release version
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ../.. --fresh
cmake --build .
cmake --install . --prefix ../install/Debug
cmake -DCMAKE_BUILD_TYPE=Release -DYYCC_BUILD_TESTBENCH=ON ../.. --fresh
cmake --build .
cmake --install . --prefix ../install/Release
cd ..
# Exit to original path
cd ..
echo "Linux CMake Build Done"

View File

@ -0,0 +1,17 @@
#!/bin/bash
# Navigate to project root directory
cd {{ repo_root_dir }}
# Create build and install directory
mkdir build
mkdir install
# Build as release version
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} {{ '-DYYCC_BUILD_DOC=ON' if build_doc }} {{ '-DYYCC_BUILD_TESTBENCH=ON' if build_testbench }} ../.. --fresh
cmake --build .
cmake --install . --prefix ../install
# Exit to original path
cd ..
echo "YYCC Linux CMake build done"

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

@ -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,57 +0,0 @@
@ECHO OFF
:: Check environment
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
IF NOT "%1"=="NODOC" (
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

@ -1,52 +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
IF NOT "%1"=="NODOC" (
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,48 +1,78 @@
# 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
ArgParser.cpp
ConfigManager.cpp
ConsoleHelper.cpp
DialogHelper.cpp
EncodingHelper.cpp
ExceptionHelper.cpp
StdPatch.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/rust/env.cpp
yycc/windows/com.cpp
yycc/windows/dialog.cpp
yycc/windows/winfct.cpp
yycc/encoding/stl.cpp
yycc/encoding/windows.cpp
yycc/encoding/iconv.cpp
yycc/carton/pycodec.cpp
yycc/carton/termcolor.cpp
yycc/carton/wcwidth.cpp
yycc/carton/tabulate.cpp
)
target_sources(YYCCommonplace
PUBLIC
FILE_SET HEADERS
FILES
# Headers
# Common headers
Constraints.hpp
COMHelper.hpp
ArgParser.hpp
ConfigManager.hpp
ConsoleHelper.hpp
DialogHelper.hpp
EncodingHelper.hpp
ExceptionHelper.hpp
StdPatch.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/rust/env.hpp
yycc/windows/import_guard_head.hpp
yycc/windows/import_guard_tail.hpp
yycc/windows/com.hpp
yycc/windows/dialog.hpp
yycc/windows/winfct.hpp
yycc/constraint.hpp
yycc/constraint/builder.hpp
yycc/encoding/stl.hpp
yycc/encoding/windows.hpp
yycc/encoding/iconv.hpp
yycc/carton/pycodec.hpp
yycc/carton/termcolor.hpp
yycc/carton/wcwidth.hpp
yycc/carton/tabulate.hpp
)
# Setup header infomations
target_include_directories(YYCCommonplace
@ -50,28 +80,59 @@ PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
# Link Iconv if we have import it
if (Iconv_FOUND)
target_link_libraries(YYCCommonplace
PRIVATE
Iconv::Iconv
)
endif ()
# Link with DbgHelp.lib on Windows
target_link_libraries(YYCCommonplace
PRIVATE
$<$<BOOL:${WIN32}>:DbgHelp.lib>
)
# Setup C++ standard
target_compile_features(YYCCommonplace PUBLIC cxx_std_17)
set_target_properties(YYCCommonplace PROPERTIES CXX_EXTENSION OFF)
# Setup macros
target_compile_definitions(YYCCommonplace
# Debug macro should populate to child projects
PUBLIC
$<$<BOOL:${YYCC_DEBUG_UE_FILTER}>:YYCC_DEBUG_UE_FILTER>
# Unicode charset for private using
PRIVATE
# Iconv environment macro
$<$<BOOL:${Iconv_FOUND}>:YYCC_FEAT_ICONV>
# OS macro
$<$<PLATFORM_ID:Windows>:YYCC_OS_WINDOWS>
$<$<PLATFORM_ID:Linux>:YYCC_OS_LINUX>
$<$<PLATFORM_ID:Android>:YYCC_OS_LINUX> # We brutally think Android as Linux.
$<$<PLATFORM_ID:Darwin>:YYCC_OS_MACOS>
$<$<PLATFORM_ID:iOS>:YYCC_OS_MACOS> # We brutally think iOS as macOS.
# Compiler macro
$<$<CXX_COMPILER_ID:GNU>:YYCC_CC_GCC>
$<$<CXX_COMPILER_ID:Clang>:YYCC_CC_CLANG>
$<$<CXX_COMPILER_ID:MSVC>:YYCC_CC_MSVC>
# Endian macro
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},LITTLE_ENDIAN>:YYCC_ENDIAN_LITTLE>
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},BIG_ENDIAN>:YYCC_ENDIAN_BIG>
# Pointer size macro
$<$<EQUAL:${CMAKE_SIZEOF_VOID_P},4>:YYCC_PTRSIZE_32>
$<$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>:YYCC_PTRSIZE_64>
# Use Unicode charset on MSVC
$<$<CXX_COMPILER_ID:MSVC>:UNICODE>
$<$<CXX_COMPILER_ID:MSVC>:_UNICODE>
# Fix MSVC shit
$<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>
$<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_DEPRECATE>
$<$<CXX_COMPILER_ID:MSVC>:_CRT_NONSTDC_NO_WARNINGS>
$<$<CXX_COMPILER_ID:MSVC>:_CRT_NONSTDC_NO_DEPRECATE>
# Fix Windows header file shit
$<$<BOOL:${WIN32}>:WIN32_LEAN_AND_MEAN>
$<$<BOOL:${WIN32}>:NOMINMAX>
)
target_compile_options(YYCCommonplace
# Order build as UTF-8 in MSVC
PRIVATE
PUBLIC
# Order build as UTF-8 in MSVC
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
# Order preprocessor conformance mode (fix __VA_OPT__ error in MSVC)
$<$<CXX_COMPILER_ID:MSVC>:/Zc:preprocessor>
# Resolve MSVC __cplusplus macro value error.
$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>
)
# Install binary and headers

View File

@ -1,48 +0,0 @@
#include "COMHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
namespace YYCC::COMHelper {
/**
* @brief The guard for initialize COM environment.
* @details This class will try initializing COM environment by calling CoInitialize when constructing,
* and it also will try uninitializing COM environment when destructing.
* If initialization failed, uninitialization will not be executed.
*/
class ComGuard {
public:
ComGuard() : m_HasInit(false) {
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr)) m_HasInit = true;
}
~ComGuard() {
if (m_HasInit) {
CoUninitialize();
}
}
bool IsInitialized() const {
return m_HasInit;
}
protected:
bool m_HasInit;
};
/**
* @brief The instance of COM environment guard.
* @details Dialog related function need COM environment,
* so we need initializing COM environment when loading this module,
* and uninitializing COM environment when we no longer use this module.
* So we use a static instance in here.
* And make it be const so no one can change it.
*/
static const ComGuard c_ComGuard {};
bool IsInitialized() {
return c_ComGuard.IsInitialized();
}
}
#endif

View File

@ -1,71 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS
#include <memory>
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <shlobj_core.h>
#include "WinImportSuffix.hpp"
/**
* @brief Windows COM related types and checker.
* @details
* This namespace is Windows specific.
* In other platforms, this whole namespace will be unavailable.
*
* See also \ref com_helper.
*/
namespace YYCC::COMHelper {
/// @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
class ComPtrDeleter {
public:
ComPtrDeleter() {}
void operator() (IUnknown* com_ptr) {
if (com_ptr != nullptr) {
com_ptr->Release();
}
}
};
/// @brief Smart unique pointer of \c IFileDialog
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
/// @brief Smart unique pointer of \c IFileOpenDialog
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
/// @brief Smart unique pointer of \c IShellItem
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
/// @brief Smart unique pointer of \c IShellItemArray
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
/// @brief Smart unique pointer of \c IShellFolder
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
/// @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
class CoTaskMemDeleter {
public:
CoTaskMemDeleter() {}
void operator() (void* com_ptr) {
if (com_ptr != nullptr) {
CoTaskMemFree(com_ptr);
}
}
};
/// @brief Smart unique pointer of COM created \c WCHAR sequence.
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
/**
* @brief Check whether COM environment has been initialized.
* @return True if it is, otherwise false.
* @remarks
* This function will call corresponding function of COM Guard.
* Do not remove this function and you must preserve at least one reference to this function in final program.
* Some compiler will try to drop COM Guard in final program if no reference to it and it will cause the initialization of COM environment failed.
* This is the reason why I order you do the things said above.
*/
bool IsInitialized();
}
#endif

View File

@ -1,163 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <cstdio>
#include <string>
/**
* @brief The helper providing universal C\# style console function and other console related stuff
* @details
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
*/
namespace YYCC::ConsoleHelper {
/// @brief The head of ASCII escape code of black color.
#define YYCC_COLORHDR_BLACK "\033[30m"
/// @brief The head of ASCII escape code of red color.
#define YYCC_COLORHDR_RED "\033[31m"
/// @brief The head of ASCII escape code of green color.
#define YYCC_COLORHDR_GREEN "\033[32m"
/// @brief The head of ASCII escape code of yellow color.
#define YYCC_COLORHDR_YELLOW "\033[33m"
/// @brief The head of ASCII escape code of blue color.
#define YYCC_COLORHDR_BLUE "\033[34m"
/// @brief The head of ASCII escape code of magenta color.
#define YYCC_COLORHDR_MAGENTA "\033[35m"
/// @brief The head of ASCII escape code of cyan color.
#define YYCC_COLORHDR_CYAN "\033[36m"
/// @brief The head of ASCII escape code of white color.
#define YYCC_COLORHDR_WHITE "\033[37m"
/// @brief The head of ASCII escape code of light black color.
#define YYCC_COLORHDR_LIGHT_BLACK "\033[90m"
/// @brief The head of ASCII escape code of light red color.
#define YYCC_COLORHDR_LIGHT_RED "\033[91m"
/// @brief The head of ASCII escape code of light green color.
#define YYCC_COLORHDR_LIGHT_GREEN "\033[92m"
/// @brief The head of ASCII escape code of light yellow color.
#define YYCC_COLORHDR_LIGHT_YELLOW "\033[93m"
/// @brief The head of ASCII escape code of light blue color.
#define YYCC_COLORHDR_LIGHT_BLUE "\033[94m"
/// @brief The head of ASCII escape code of light magenta color.
#define YYCC_COLORHDR_LIGHT_MAGENTA "\033[95m"
/// @brief The head of ASCII escape code of light cyan color.
#define YYCC_COLORHDR_LIGHT_CYAN "\033[96m"
/// @brief The head of ASCII escape code of light white color.
#define YYCC_COLORHDR_LIGHT_WHITE "\033[97m"
/// @brief The tail of ASCII escape code of every color.
#define YYCC_COLORTAIL "\033[0m"
/// @brief The ASCII escape code pair of black color.
#define YYCC_COLOR_BLACK(T) "\033[30m" T "\033[0m"
/// @brief The ASCII escape code pair of red color.
#define YYCC_COLOR_RED(T) "\033[31m" T "\033[0m"
/// @brief The ASCII escape code pair of green color.
#define YYCC_COLOR_GREEN(T) "\033[32m" T "\033[0m"
/// @brief The ASCII escape code pair of yellow color.
#define YYCC_COLOR_YELLOW(T) "\033[33m" T "\033[0m"
/// @brief The ASCII escape code pair of blue color.
#define YYCC_COLOR_BLUE(T) "\033[34m" T "\033[0m"
/// @brief The ASCII escape code pair of magenta color.
#define YYCC_COLOR_MAGENTA(T) "\033[35m" T "\033[0m"
/// @brief The ASCII escape code pair of cyan color.
#define YYCC_COLOR_CYAN(T) "\033[36m" T "\033[0m"
/// @brief The ASCII escape code pair of white color.
#define YYCC_COLOR_WHITE(T) "\033[37m" T "\033[0m"
/// @brief The ASCII escape code pair of light black color.
#define YYCC_COLOR_LIGHT_BLACK(T) "\033[90m" T "\033[0m"
/// @brief The ASCII escape code pair of light red color.
#define YYCC_COLOR_LIGHT_RED(T) "\033[91m" T "\033[0m"
/// @brief The ASCII escape code pair of light green color.
#define YYCC_COLOR_LIGHT_GREEN(T) "\033[92m" T "\033[0m"
/// @brief The ASCII escape code pair of light yellow color.
#define YYCC_COLOR_LIGHT_YELLOW(T) "\033[93m" T "\033[0m"
/// @brief The ASCII escape code pair of light blue color.
#define YYCC_COLOR_LIGHT_BLUE(T) "\033[94m" T "\033[0m"
/// @brief The ASCII escape code pair of light magenta color.
#define YYCC_COLOR_LIGHT_MAGENTA(T) "\033[95m" T "\033[0m"
/// @brief The ASCII escape code pair of light cyan color.
#define YYCC_COLOR_LIGHT_CYAN(T) "\033[96m" T "\033[0m"
/// @brief The ASCII escape code pair of light white color.
#define YYCC_COLOR_LIGHT_WHITE(T) "\033[97m" T "\033[0m"
/**
* @brief Enable console color support for Windows.
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
* @return True if success, otherwise false.
* @remarks
* This function only works on Windows and do nothing on other platforms such as Linux,
* because we assume all terminals existing on other platform support color feature as default.
*/
bool EnableColorfulConsole();
/**
* @brief Reads the next line of UTF8 characters from the standard input stream.
* @return
* The next line of UTF8 characters from the input stream.
* Empty string if user just press Enter key or function failed.
*/
yycc_u8string ReadLine();
/**
* @brief
* Writes the text representation of the specified object
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void Format(const yycc_char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void FormatLine(const yycc_char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void Write(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void WriteLine(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the text representation of the specified object
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void ErrFormat(const yycc_char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ErrWrite(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ErrWriteLine(const yycc_char8_t* u8_strl);
}

View File

@ -1,85 +0,0 @@
#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,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,37 +0,0 @@
#include "IOHelper.hpp"
#include "EncodingHelper.hpp"
#include <cstdio>
#include <iostream>
#include <string>
#include <stdexcept>
#include <memory>
#if YYCC_OS == YYCC_OS_WINDOWS
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include "WinImportSuffix.hpp"
#endif
namespace YYCC::IOHelper {
std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode) {
#if YYCC_OS == 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(EncodingHelper::ToOrdinary(u8_filepath), EncodingHelper::ToOrdinary(u8_mode));
#endif
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,116 +0,0 @@
#pragma once
#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 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
#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

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

View File

@ -4,6 +4,7 @@
#include "Constraints.hpp"
#include "EncodingHelper.hpp"
#include "ParserHelper.hpp"
#include <cstring>
#include <functional>
#include <vector>
#include <map>
@ -36,7 +37,7 @@ namespace YYCC::ArgParser {
* and should not be seen as a part of arguments.
*/
static ArgumentList CreateFromStd(int argc, char* argv[]);
#if YYCC_OS == YYCC_OS_WINDOWS
#if defined(YYCC_OS_WINDOWS)
/**
* @brief Create argument list from Win32 function.
* @details

View File

@ -2,6 +2,7 @@
#include "EncodingHelper.hpp"
#include "IOHelper.hpp"
#include "EnumHelper.hpp"
#include <stdexcept>
namespace YYCC::ConfigManager {
@ -33,7 +34,7 @@ namespace YYCC::ConfigManager {
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);
@ -44,7 +45,10 @@ namespace YYCC::ConfigManager {
}
}
bool CoreManager::Load() {
ConfigLoadResult CoreManager::Load() {
// prepare result variables
ConfigLoadResult ret = ConfigLoadResult::OK;
// reset all settings first
Reset();
@ -53,20 +57,27 @@ namespace YYCC::ConfigManager {
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;
@ -77,37 +88,50 @@ 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() {

View File

@ -17,6 +17,19 @@
* @details For how to use this namespace, please see \ref config_manager.
*/
namespace YYCC::ConfigManager {
/**
* @brief The load result of loading config.
*/
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.
};
using UnderlyingConfigLoadResult_t = std::underlying_type_t<ConfigLoadResult>;
/// @brief The base class of every setting.
/// @details Programmer can inherit this class and implement essential functions to create custom setting.
@ -75,7 +88,7 @@ namespace YYCC::ConfigManager {
private:
std::vector<uint8_t> m_RawData;
};
/// @brief Settings manager and config file reader writer.
class CoreManager {
public:
@ -96,8 +109,8 @@ namespace YYCC::ConfigManager {
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();

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,92 @@
#pragma once
#include "YYCCInternal.hpp"
#include <cstdio>
#include <string>
/**
* @brief The helper providing universal C\# style console function and other console related stuff
* @details
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
*/
namespace YYCC::ConsoleHelper {
/**
* @brief Enable console color support for Windows.
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
* @return True if success, otherwise false.
* @remarks
* This function only works on Windows and do nothing on other platforms such as Linux,
* because we assume all terminals existing on other platform support color feature as default.
*/
bool EnableColorfulConsole();
/**
* @brief Reads the next line of UTF8 characters from the standard input stream.
* @return
* The next line of UTF8 characters from the input stream.
* Empty string if user just press Enter key or function failed.
*/
yycc_u8string ReadLine();
/**
* @brief
* Writes the text representation of the specified object
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void Format(const yycc_char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void FormatLine(const yycc_char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void Write(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void WriteLine(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the text representation of the specified object
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void ErrFormat(const yycc_char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ErrWrite(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ErrWriteLine(const yycc_char8_t* u8_strl);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +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 "StdPatch.hpp"
#include "ExceptionHelper.hpp"
#include "ConfigManager.hpp"
#include "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

View File

591
src/yycc/carton/pycodec.cpp Normal file
View File

@ -0,0 +1,591 @@
#include "pycodec.hpp"
#include "../string/op.hpp"
#include <map>
using namespace std::literals::string_view_literals;
namespace op = ::yycc::string::op;
namespace yycc::carton::pycodec {
/// @brief Error occurs when fetching token from encoding name.
enum class FetchError {
NoSuchName, ///< Given name can not be resolved.
};
/// @brief The Result type used when fetching token from encoding name.
template<typename T>
using FetchResult = std::expected<T, FetchError>;
#pragma region Encoding Name
// clang-format off
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},
};
// clang-format on
/**
* @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;
// clang-format off
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) },
};
// clang-format on
static FetchResult<CodePage> fetch_code_page(const std::u8string_view& enc_name) {
// 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 std::unexpected(FetchError::NoSuchName);
// okey, we found it.
return finder->second;
}
#else
// clang-format off
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},
};
// clang-format on
static FetchResult<std::string_view> fetch_iconv_name(const std::u8string_view& enc_name) {
// 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 std::unexpected(FetchError::NoSuchName);
// okey, we found it.
return finder->second;
}
#endif
#pragma endregion
#pragma region Misc
ConvError::ConvError(const ConvBackendError& err) : inner(err) {}
ConvError::ConvError(const ConvFrontendError& err) : inner(err) {}
ConvError::ConvError(ConvBackendError&& err) noexcept : inner(std::move(err)) {}
ConvError::ConvError(ConvFrontendError&& err) noexcept : inner(std::move(err)) {}
bool is_valid_encoding_name(const EncodingName& name) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
return fetch_code_page(name).has_value();
#else
return fetch_iconv_name(name).has_value();
#endif
}
// YYC MARK:
// Define a macro for following class ctor
// We only need initialize member if we are in Iconv environment.
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
#define CTOR_INITLIST_TYPE1
#else
#define CTOR_INITLIST_TYPE1 : inner()
#endif
#pragma endregion
#pragma region Char -> UTF8
CharToUtf8::CharToUtf8(const EncodingName& name) : inner(std::nullopt) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
auto rv = fetch_code_page(name);
if (rv.has_value()) inner = rv.value();
#else
auto rv = fetch_iconv_name(name);
if (rv.has_value()) inner = YYCC_PYCODEC_BACKEND_NS::CharToUtf8(rv.value());
#endif
}
CharToUtf8::~CharToUtf8() {}
ConvResult<std::u8string> CharToUtf8::to_utf8(const std::string_view& src) {
if (!inner.has_value()) return std::unexpected(ConvFrontendError::NoSuchName);
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src, inner.value());
#else
return inner.value().to_utf8(src);
#endif
}
#pragma endregion
#pragma region UTF8 -> Char
Utf8ToChar::Utf8ToChar(const EncodingName& name) : inner(std::nullopt) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
auto rv = fetch_code_page(name);
if (rv.has_value()) inner = rv.value();
#else
auto rv = fetch_iconv_name(name);
if (rv.has_value()) inner = YYCC_PYCODEC_BACKEND_NS::Utf8ToChar(rv.value());
#endif
}
Utf8ToChar::~Utf8ToChar() {}
ConvResult<std::string> Utf8ToChar::to_char(const std::u8string_view& src) {
if (!inner.has_value()) return std::unexpected(ConvFrontendError::NoSuchName);
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
return YYCC_PYCODEC_BACKEND_NS::to_char(src, inner.value());
#else
return inner.value().to_char(src);
#endif
}
#pragma endregion
#pragma region WChar -> UTF8
WcharToUtf8::WcharToUtf8() CTOR_INITLIST_TYPE1 {}
WcharToUtf8::~WcharToUtf8() {}
ConvResult<std::u8string> WcharToUtf8::to_utf8(const std::wstring_view& src) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src);
#else
return inner.to_utf8(src);
#endif
}
#pragma endregion
#pragma region UTF8 -> WChar
Utf8ToWchar::Utf8ToWchar() CTOR_INITLIST_TYPE1 {}
Utf8ToWchar::~Utf8ToWchar() {}
ConvResult<std::wstring> Utf8ToWchar::to_wchar(const std::u8string_view& src) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
return YYCC_PYCODEC_BACKEND_NS::to_wchar(src);
#else
return inner.to_wchar(src);
#endif
}
#pragma endregion
#pragma region UTF8 -> UTF16
Utf8ToUtf16::Utf8ToUtf16() CTOR_INITLIST_TYPE1 {}
Utf8ToUtf16::~Utf8ToUtf16() {}
ConvResult<std::u16string> Utf8ToUtf16::to_utf16(const std::u8string_view& src) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
return YYCC_PYCODEC_BACKEND_NS::to_utf16(src);
#else
return inner.to_utf16(src);
#endif
}
#pragma endregion
#pragma region UTF16 -> UTF8
Utf16ToUtf8::Utf16ToUtf8() CTOR_INITLIST_TYPE1 {}
Utf16ToUtf8::~Utf16ToUtf8() {}
ConvResult<std::u8string> Utf16ToUtf8::to_utf8(const std::u16string_view& src) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src);
#else
return inner.to_utf8(src);
#endif
}
#pragma endregion
#pragma region UTF8 -> UTF32
Utf8ToUtf32::Utf8ToUtf32() CTOR_INITLIST_TYPE1 {}
Utf8ToUtf32::~Utf8ToUtf32() {}
ConvResult<std::u32string> Utf8ToUtf32::to_utf32(const std::u8string_view& src) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
return YYCC_PYCODEC_BACKEND_NS::to_utf32(src);
#else
return inner.to_utf32(src);
#endif
}
#pragma endregion
#pragma region UTF32 -> UTF8
Utf32ToUtf8::Utf32ToUtf8() CTOR_INITLIST_TYPE1 {}
Utf32ToUtf8::~Utf32ToUtf8() {}
ConvResult<std::u8string> Utf32ToUtf8::to_utf8(const std::u32string_view& src) {
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src);
#else
return inner.to_utf8(src);
#endif
}
#pragma endregion
} // namespace yycc::carton::pycodec

204
src/yycc/carton/pycodec.hpp Normal file
View File

@ -0,0 +1,204 @@
#pragma once
#include "../macro/os_detector.hpp"
#include "../macro/stl_detector.hpp"
#include "../macro/class_copy_move.hpp"
#include <string>
#include <string_view>
#include <variant>
#include <optional>
#include <expected>
// Choose the backend of PyCodec module
#if defined(YYCC_FEAT_ICONV)
// We try Iconv first in any cases.
#include "../encoding/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 "../encoding/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::carton::pycodec {
/// @brief The universal name of encoding.
using EncodingName = std::u8string_view;
/// @brief The alias to error type of backend.
using ConvBackendError = YYCC_PYCODEC_BACKEND_NS::ConvError;
/// @brief The error occurs in this module self.
enum class ConvFrontendError {
NoSuchName, ///< Can not find suitable backend token for given encoding name.
};
/// @brief The possible error occurs in this module.
class ConvError {
public:
ConvError(const ConvBackendError& err);
ConvError(const ConvFrontendError& err);
ConvError(ConvBackendError&& err) noexcept;
ConvError(ConvFrontendError&& err) noexcept;
YYCC_DEFAULT_COPY_MOVE(ConvError)
private:
std::variant<ConvBackendError, ConvFrontendError> 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)
std::optional<YYCC_PYCODEC_BACKEND_NS::CodePage> inner;
#else
std::optional<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)
std::optional<YYCC_PYCODEC_BACKEND_NS::CodePage> inner;
#else
std::optional<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::carton::pycodec

View File

@ -0,0 +1,192 @@
#include "tabulate.hpp"
#include "wcwidth.hpp"
#include "../num/safe_op.hpp"
#include "../string/reinterpret.hpp"
#include <stdexcept>
#include <ranges>
#define WCWIDTH ::yycc::carton::wcwidth
#define REINTERPRET ::yycc::string::reinterpret
#define SAFEOP ::yycc::num::safe_op
namespace yycc::carton::tabulate {
#pragma region Tabulate Width
TabulateWidth::TabulateWidth(size_t n) : widths(n, 0u) {}
TabulateWidth::~TabulateWidth() {}
size_t TabulateWidth::get_column_count() const {
return widths.size();
}
size_t TabulateWidth::get_column_width(size_t column_index) const {
return widths.at(column_index);
}
void TabulateWidth::update_column_width(size_t column_index, size_t new_size) {
auto& width = widths.at(column_index);
width = std::max(width, new_size);
}
void TabulateWidth::clear() {
std::fill(widths.begin(), widths.end(), 0u);
}
#pragma endregion
#pragma region Tabulate Cell
TabulateCell::TabulateCell(const std::u8string_view& text) : text(text), text_width(WCWIDTH::wcswidth(text).value_or(0u)) {}
TabulateCell::~TabulateCell() {}
const std::u8string& TabulateCell::get_text() const {
return text;
}
size_t TabulateCell::get_text_width() const {
return text_width;
}
#pragma endregion
#pragma region Tabulate
/// @brief Default separator literal for Tabulate.
static constexpr char8_t SEPARATOR_BAR[] = u8"---";
/// @brief A stupid size_t ZERO literal to trigger template type deduce for std::views::iota.
static constexpr size_t ZERO = 0;
Tabulate::Tabulate(size_t n) :
n(n), header_display(true), bar_display(true), prefix_string(), rows_widths(n), header_widths(n), header(n, TabulateCell(u8"")),
bar(SEPARATOR_BAR), rows() {}
Tabulate::~Tabulate() {}
void Tabulate::print(std::ostream& dst) const {
// Get column count
auto n = this->get_column_count();
// Create width recorder for final printing
// according to whether we show table header and separator bar.
auto widths = this->rows_widths;
if (this->header_display) {
for (auto index : std::views::iota(ZERO, n)) {
widths.update_column_width(index, this->header_widths.get_column_width(index));
}
}
if (this->bar_display) {
auto bar_width = this->bar.get_text_width();
for (auto index : std::views::iota(ZERO, n)) {
widths.update_column_width(index, bar_width);
}
}
// Get the maximum space char count to build a string filled with spaces,
// for the convenient about following printing.
size_t max_space = 1;
for (auto index : std::views::iota(ZERO, n)) {
max_space = std::max(max_space, widths.get_column_width(index));
}
std::u8string spaces(max_space, u8' ');
std::u8string_view spaces_view(spaces);
// Print table
// Define a convenient macro
#define CVT(data) REINTERPRET::as_ordinary_view(data)
// Show header
if (this->header_display) {
dst << CVT(this->prefix_string);
for (const auto [index, item] : std::views::enumerate(header)) {
auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), item.get_text_width());
dst << CVT(item.get_text()) << CVT(spaces_view.substr(0, diff)) << " ";
}
dst << std::endl;
}
// Show bar
if (this->bar_display) {
dst << CVT(this->prefix_string);
auto bar_width = this->bar.get_text_width();
for (auto index : std::views::iota(ZERO, n)) {
auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), bar_width);
dst << CVT(this->bar.get_text()) << CVT(spaces_view.substr(0, diff)) << " ";
}
dst << std::endl;
}
// Show data
for (const auto& row : this->rows) {
dst << CVT(this->prefix_string);
for (const auto [index, item] : std::views::enumerate(row)) {
auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), item.get_text_width());
dst << CVT(item.get_text()) << CVT(spaces_view.substr(0, diff)) << " ";
}
dst << std::endl;
}
// Undef macro
#undef CVT
}
size_t Tabulate::get_column_count() const {
return this->n;
}
void Tabulate::show_header(bool show_header) {
this->header_display = show_header;
}
void Tabulate::show_bar(bool show_bar) {
this->bar_display = show_bar;
}
void Tabulate::set_prefix(const std::u8string_view& prefix) {
this->prefix_string = prefix;
}
void Tabulate::set_header(std::initializer_list<std::u8string_view> hdr) {
// Check data size.
if (hdr.size() != get_column_count()) {
throw std::invalid_argument("the size of given header is not equal to column count");
}
// Change header data and update header width recorder.
header.clear();
header_widths.clear();
for (const auto [index, item] : std::views::enumerate(hdr)) {
auto cell = header.emplace_back(item);
header_widths.update_column_width(index, cell.get_text_width());
}
}
void Tabulate::set_bar(const std::u8string_view& bar) {
this->bar = bar;
}
void Tabulate::add_row(std::initializer_list<std::u8string_view> row) {
// Check data size.
if (row.size() != get_column_count()) {
throw std::invalid_argument("the size of given row is not equal to column count");
}
// Prepare inserted row, and update data width recorder.
Row inserted_row;
inserted_row.reserve(row.size());
for (const auto [index, item] : std::views::enumerate(row)) {
auto cell = inserted_row.emplace_back(item);
rows_widths.update_column_width(index, cell.get_text_width());
}
// Insert row
rows.emplace_back(std::move(inserted_row));
}
void Tabulate::clear() {
// Clear data and data width recorder.
rows.clear();
rows_widths.clear();
}
#pragma endregion
} // namespace yycc::carton::tabulate

View File

@ -0,0 +1,198 @@
#pragma once
#include "../macro/class_copy_move.hpp"
#include <initializer_list>
#include <string>
#include <string_view>
#include <vector>
#include <iostream>
namespace yycc::carton::tabulate {
/**
* @private
* @brief Assistant class recording column width.
*/
class TabulateWidth {
public:
/**
* @brief Create width recorder with given column count.
* @param[in] n Column count.
*/
TabulateWidth(size_t n);
~TabulateWidth();
YYCC_DEFAULT_COPY_MOVE(TabulateWidth)
public:
/**
* @brief Get column count.
* @return Column count.
*/
size_t get_column_count() const;
/**
* @brief Get column width of given index.
* @param[in] column_index Column index for fetching width.
* @return Column width of given index.
*/
size_t get_column_width(size_t column_index) const;
/**
* @brief Update column width of given index with given value.
* @details The width of column will be updated to the maximum between given value and old value.
* @param[in] column_index Column index for updating width.
* @param[in] new_size New width value.
*/
void update_column_width(size_t column_index, size_t new_size);
/**
* @brief Clear all width data
* @details All width data will be reset to zero.
*/
void clear();
private:
std::vector<size_t> widths;
};
/**
* @private
* @brief Assistant class holding table cell data.
* @details
* This class holds the data of table cell.
* Also make a cache for the width this cell's text occupied in console,
* to avoid duplicated calculation for occupied width.
*/
class TabulateCell {
public:
/**
* @brief Build cell with given text.
* @param[in] text Data of cell.
*/
TabulateCell(const std::u8string_view& text);
~TabulateCell();
YYCC_DEFAULT_COPY_MOVE(TabulateCell)
public:
/**
* @brief Get the text of cell.
* @return The text of cell.
*/
const std::u8string& get_text() const;
/**
* @brief Get width this cell's text occupied in console.
* @return The width this cell occupied.
*/
size_t get_text_width() const;
private:
/// @brief The data of cell.
std::u8string text;
/// @brief The width cache of this data occupied in console.
size_t text_width;
};
/**
* @private
* @brief The type representing one row of data in table.
*/
using Row = std::vector<TabulateCell>;
/**
* @private
* @brief The type representing row collection in table.
*/
using Rows = std::vector<Row>;
/**
* @brief Main class of Tabulate
*/
class Tabulate {
public:
/**
* @brief Create Tabulate class with given column count.
* @details
* In default, the separator bar of table is 3 dash.
* Header and separator bar are also shown in default.
* @param[in] n Column count of table.
*/
Tabulate(size_t n);
~Tabulate();
YYCC_DELETE_COPY_MOVE(Tabulate)
public:
/**
* @brief Print table into given stream.
* @details In default, stream is \c stdout.
* @param[in] dst The stream printed into.
*/
void print(std::ostream& dst = std::cout) const;
/**
* @brief Get the column count of table.
* @return Column count of table.
*/
size_t get_column_count() const;
/**
* @brief Change whether show table header when printing.
* @param[in] show_header True for showing, otherwise false.
*/
void show_header(bool show_header);
/**
* @brief Change whether show separator bar when printing.
* @param[in] show_bar True for showing, otherwise false.
*/
void show_bar(bool show_bar);
/**
* @brief Modify the prefix string of table.
* @details
* The prefix string of table is the string
* which will be printed before each lines of output.
* It is usually used for indent.
* @param[in] prefix The prefix string.
*/
void set_prefix(const std::u8string_view& prefix);
/**
* @brief Modify the header of table.
* @param[in] hdr An initializer list holding header texts one by one.
* @exception std::invalid_argument The size of given header is mismatch with column count.
*/
void set_header(std::initializer_list<std::u8string_view> hdr);
/**
* @brief Modify separator bar string of table.
* @param[in] bar New separator bar string.
*/
void set_bar(const std::u8string_view& bar);
/**
* @brief Add one data row into table.
* @param[in] row An initializer list holding row texts one by one.
* @exception std::invalid_argument The size of given header is mismatch with column count.
*/
void add_row(std::initializer_list<std::u8string_view> row);
/**
* @brief Clear all data rows of table.
* @details Table header and separator bar will not be changed.
*/
void clear();
private:
/// @brief The column count of table.
size_t n;
/// @brief Whether showing table header.
bool header_display;
/// @brief Whether showing table separator bar between header and data.
bool bar_display;
/// @brief The prefix string presented in each lines of output table.
std::u8string prefix_string;
/// @brief Width recorder for header.
TabulateWidth header_widths;
/// @brief Width recorder for data.
TabulateWidth rows_widths;
/// @brief The header of table.
Row header;
/// @brief The separator bar of table.
TabulateCell bar;
/// @brief The data of table.
Rows rows;
};
}

View File

@ -0,0 +1,234 @@
#include "termcolor.hpp"
#include "../flag_enum.hpp"
#include "../string/reinterpret.hpp"
#include <stdexcept>
#include <bit>
#define FLAG_ENUM ::yycc::flag_enum
#define REINTERPRET ::yycc::string::reinterpret
using namespace std::literals::string_view_literals;
namespace yycc::carton::termcolor {
#pragma region Lowlevel Functions
const std::u8string_view foreground(Color color) {
switch (color) {
case Color::Default:
return u8""sv;
case Color::Black:
return u8"\033[30m"sv;
case Color::Red:
return u8"\033[31m"sv;
case Color::Green:
return u8"\033[32m"sv;
case Color::Yellow:
return u8"\033[33m"sv;
case Color::Blue:
return u8"\033[34m"sv;
case Color::Magenta:
return u8"\033[35m"sv;
case Color::Cyan:
return u8"\033[36m"sv;
case Color::White:
return u8"\033[37m"sv;
case Color::LightBlack:
return u8"\033[90m"sv;
case Color::LightRed:
return u8"\033[91m"sv;
case Color::LightGreen:
return u8"\033[92m"sv;
case Color::LightYellow:
return u8"\033[93m"sv;
case Color::LightBlue:
return u8"\033[94m"sv;
case Color::LightMagenta:
return u8"\033[95m"sv;
case Color::LightCyan:
return u8"\033[96m"sv;
case Color::LightWhite:
return u8"\033[97m"sv;
default:
throw std::invalid_argument("invalid color kind");
}
}
const std::u8string_view background(Color color) {
switch (color) {
case Color::Default:
return u8""sv;
case Color::Black:
return u8"\033[40m"sv;
case Color::Red:
return u8"\033[41m"sv;
case Color::Green:
return u8"\033[42m"sv;
case Color::Yellow:
return u8"\033[43m"sv;
case Color::Blue:
return u8"\033[44m"sv;
case Color::Magenta:
return u8"\033[45m"sv;
case Color::Cyan:
return u8"\033[46m"sv;
case Color::White:
return u8"\033[47m"sv;
case Color::LightBlack:
return u8"\033[100m"sv;
case Color::LightRed:
return u8"\033[101m"sv;
case Color::LightGreen:
return u8"\033[102m"sv;
case Color::LightYellow:
return u8"\033[103m"sv;
case Color::LightBlue:
return u8"\033[104m"sv;
case Color::LightMagenta:
return u8"\033[105m"sv;
case Color::LightCyan:
return u8"\033[106m"sv;
case Color::LightWhite:
return u8"\033[107m"sv;
default:
throw std::invalid_argument("invalid color kind");
}
}
const std::u8string_view style(Attribute attr) {
// Return for Default first because it can not pass following test
if (attr == Attribute::Default) {
return u8""sv;
}
// Check whether it only has one flag
if (!std::has_single_bit(FLAG_ENUM::integer(attr))) {
throw std::invalid_argument("style() only accept single flag attribute");
}
// Return result
switch (attr) {
case Attribute::Bold:
return u8"\033[1m"sv;
case Attribute::Dark:
return u8"\033[2m"sv;
case Attribute::Italic:
return u8"\033[3m"sv;
case Attribute::Underline:
return u8"\033[4m"sv;
case Attribute::Blink:
return u8"\033[5m"sv;
case Attribute::Reverse:
return u8"\033[6m"sv;
case Attribute::Concealed:
return u8"\033[7m"sv;
default:
throw std::invalid_argument("invalid attribute kind");
}
}
/**
* @private
* @brief The possible maximum length of ANSI Escape Sequence used in this module.
* @details This const value is used for computing reserved size of final built string.
*/
static constexpr size_t ANSI_ESC_LEN = sizeof(u8"\033[000m") - 1;
/**
* @private
* @brief Count how many single flags combine given attributes.
* @details
* For function styles() involving multiple font style ANSI Escape Sequence,
* this function may be useful for computing desired size of final result,
* to reduce useless memory re-allocation.
* @param[in] attrs Attributes for counting.
* @return The count of single flag.
*/
static size_t count_attribute_flags(Attribute attrs) {
return static_cast<size_t>(std::popcount(FLAG_ENUM::integer(attrs)));
}
/**
* @private
* @brief Append multiple font styles into given string.
* @details
* This function will decompose given font styles into single flag.
* And append its components one by one into given string.
* If there is enough reserved space in given string,
* there is no memory re-allocation happened.
* @remarks
* This function is served for styles() and colored().
* @param[in] s The string to be appended.
* @param[in] attrs The attributes for writting.
*/
static void append_styles(std::u8string& s, Attribute attrs) {
#define CHECK_ATTR(probe) \
if (FLAG_ENUM::has(attrs, probe)) s.append(termcolor::style(probe));
if (attrs != Attribute::Default) {
CHECK_ATTR(Attribute::Bold);
CHECK_ATTR(Attribute::Dark);
CHECK_ATTR(Attribute::Italic);
CHECK_ATTR(Attribute::Blink);
CHECK_ATTR(Attribute::Reverse);
CHECK_ATTR(Attribute::Concealed);
}
#undef CHECK_ATTR
}
std::u8string styles(Attribute attrs) {
// Prepare the result string
std::u8string rv;
rv.reserve(count_attribute_flags(attrs) * ANSI_ESC_LEN);
// Append styles and return
append_styles(rv, attrs);
return rv;
}
const std::u8string_view reset() {
return u8"\033[0m"sv;
}
#pragma endregion
#pragma region Highlevel Functions
std::u8string colored(const std::u8string_view& words, Color foreground, Color background, Attribute styles) {
// Calculate the expected size of result string.
// final count = styles count + 1 (foreground) + 1 (background) + 1 (reset)
std::u8string rv;
size_t ansi_esc_count = count_attribute_flags(styles) + 3;
rv.reserve(ansi_esc_count * ANSI_ESC_LEN + words.size());
// Append data one by one
rv.append(termcolor::foreground(foreground));
rv.append(termcolor::background(background));
append_styles(rv, styles);
rv.append(words);
rv.append(termcolor::reset());
// Return result
return rv;
}
void cprint(const std::u8string_view& words, Color foreground, Color background, Attribute styles, std::ostream& dst) {
dst << REINTERPRET::as_ordinary_view(colored(words, foreground, background, styles));
}
void ecprint(const std::u8string_view& words, Color foreground, Color background, Attribute styles) {
cprint(words, foreground, background, styles, std::cerr);
}
void cprintln(const std::u8string_view& words, Color foreground, Color background, Attribute styles, std::ostream& dst) {
cprint(words, foreground, background, styles, dst);
dst << std::endl;
}
void ecprintln(const std::u8string_view& words, Color foreground, Color background, Attribute styles) {
cprintln(words, foreground, background, styles, std::cerr);
}
#pragma endregion
} // namespace yycc::carton::termcolor

View File

@ -0,0 +1,166 @@
#pragma once
#include <string>
#include <string_view>
#include <iostream>
/**
* @brief The namespace for terminal font color and style.
* @details
* This namespace provides functions to generate ANSI escape sequence for terminal font color and style.
* It also provides functions to add color and style for given string with ANSI Escape Sequence.
*
* This namespace is basically the immitation of the Python package with same name.
*/
namespace yycc::carton::termcolor {
#pragma region Lowlevel Functions
/**
* @brief The color of font.
*/
enum class Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
LightBlack,
LightRed,
LightGreen,
LightYellow,
LightBlue,
LightMagenta,
LightCyan,
LightWhite,
Default
};
/**
* @brief Get ANSI escape sequence for foreground color
* @param[in] color The color to generate sequence for
* @return Gotten ANSI escape sequence
*/
const std::u8string_view foreground(Color color);
/**
* @brief Get ANSI escape sequence for background color
* @param[in] color The color to generate sequence for
* @return Gotten ANSI escape sequence
*/
const std::u8string_view background(Color color);
/**
* @brief The attribute of font
* @remarks We define this enum as unsigned integral, so that we can use \c std::has_single_bit.
*/
enum class Attribute : uint32_t {
Default = 0,
Bold = 1 << 0,
Dark = 1 << 1,
Italic = 1 << 2,
Underline = 1 << 3,
Blink = 1 << 4,
Reverse = 1 << 5,
Concealed = 1 << 6
};
/**
* @brief Get ANSI escape sequence for text style
* @details
* Please note that this function only support single attribute flag.
* If you want to use multiple attributes, please use styles() instead.
*
* However, the difference between this function and styles() is that
* there is no memory allocation in this function.
* It may have better performance that styles().
* @param[in] attr Single attribute to generate sequence for
* @return Gotten ANSI escape sequence
* @throws std::invalid_argument if attribute is not a single flag
*/
const std::u8string_view style(Attribute attr);
/**
* @brief Generates ANSI escape sequence for multiple text styles
* @param[in] attrs Combination of attributes to generate sequences for
* @return Generated ANSI escape sequence
*/
std::u8string styles(Attribute attrs);
/**
* @brief Get ANSI escape sequence for reset style
* @return Gotten ANSI reset sequence
*/
const std::u8string_view reset();
#pragma endregion
#pragma region Highlevel Functions
/**
* @brief Add color and style for given string with ANSI Escape Sequence.
* @param[in] words The words to be decorated.
* @param[in] foreground The foreground of words.
* @param[in] background The background of words.
* @param[in] styles The font style of words.
* @return Decorated words.
*/
std::u8string colored(const std::u8string_view& words,
Color foreground = Color::Default,
Color background = Color::Default,
Attribute styles = Attribute::Default);
/**
* @brief Print words into stream with given styles.
* @param[in] words The words to be printed.
* @param[in] foreground The foreground of words.
* @param[in] background The background of words.
* @param[in] styles The font style of words.
* @param[in] dst The stream written into. \c stdout in default.
*/
void cprint(const std::u8string_view& words = std::u8string_view(u8""),
Color foreground = Color::Default,
Color background = Color::Default,
Attribute styles = Attribute::Default,
std::ostream& dst = std::cout);
/**
* @brief Print words into \c stderr with given styles.
* @param[in] words The words to be printed.
* @param[in] foreground The foreground of words.
* @param[in] background The background of words.
* @param[in] styles The font style of words.
*/
void ceprint(const std::u8string_view& words = std::u8string_view(u8""),
Color foreground = Color::Default,
Color background = Color::Default,
Attribute styles = Attribute::Default);
/**
* @brief Print words into stream with given styles and break line.
* @param[in] words The words to be printed.
* @param[in] foreground The foreground of words.
* @param[in] background The background of words.
* @param[in] styles The font style of words.
* @param[in] dst The stream written into. \c stdout in default.
*/
void cprintln(const std::u8string_view& words = std::u8string_view(u8""),
Color foreground = Color::Default,
Color background = Color::Default,
Attribute styles = Attribute::Default,
std::ostream& dst = std::cout);
/**
* @brief Print words into \c stderr with given styles and break line.
* @param[in] words The words to be printed.
* @param[in] foreground The foreground of words.
* @param[in] background The background of words.
* @param[in] styles The font style of words.
*/
void ceprintln(const std::u8string_view& words = std::u8string_view(u8""),
Color foreground = Color::Default,
Color background = Color::Default,
Attribute styles = Attribute::Default);
#pragma endregion
} // namespace yycc::carton::termcolor

478
src/yycc/carton/wcwidth.cpp Normal file
View File

@ -0,0 +1,478 @@
#include "wcwidth.hpp"
#include "../encoding/stl.hpp"
#include <utility>
#include <vector>
#include <optional>
#define ENC ::yycc::encoding::stl
namespace yycc::carton::wcwidth {
using Boundary = std::pair<char32_t, char32_t>;
using BoundaryVector = std::vector<Boundary>;
// YYC MARK:
// Following table and code are copied from Python package "wcwidth".
// Although the code of this package are also copied from the original "wcwidth" C implementation.
//
// I do not need so much exact measurement.
// I just want a "it works" wcwdith in all platforms.
// So these tables are coming from the table with lowest UNICODE version
// (original package provides different tables for different UNICODE versions).
// clang-format off
static const BoundaryVector ZERO_WIDTH{
{U'\x00000', U'\x00000'}, // (nil)
{U'\x000ad', U'\x000ad'}, // Soft Hyphen
{U'\x00300', U'\x0036f'}, // Combining Grave Accent ..Combining Latin Small Le
{U'\x00483', U'\x00486'}, // Combining Cyrillic Titlo..Combining Cyrillic Psili
{U'\x00488', U'\x00489'}, // Combining Cyrillic Hundr..Combining Cyrillic Milli
{U'\x00591', U'\x005b9'}, // Hebrew Accent Etnahta ..Hebrew Point Holam
{U'\x005bb', U'\x005bd'}, // Hebrew Point Qubuts ..Hebrew Point Meteg
{U'\x005bf', U'\x005bf'}, // Hebrew Point Rafe
{U'\x005c1', U'\x005c2'}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot
{U'\x005c4', U'\x005c5'}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot
{U'\x005c7', U'\x005c7'}, // Hebrew Point Qamats Qatan
{U'\x00600', U'\x00603'}, // Arabic Number Sign ..Arabic Sign Safha
{U'\x00610', U'\x00615'}, // Arabic Sign Sallallahou ..Arabic Small High Tah
{U'\x0064b', U'\x0065e'}, // Arabic Fathatan ..Arabic Fatha With Two Do
{U'\x00670', U'\x00670'}, // Arabic Letter Superscript Alef
{U'\x006d6', U'\x006e4'}, // Arabic Small High Ligatu..Arabic Small High Madda
{U'\x006e7', U'\x006e8'}, // Arabic Small High Yeh ..Arabic Small High Noon
{U'\x006ea', U'\x006ed'}, // Arabic Empty Centre Low ..Arabic Small Low Meem
{U'\x0070f', U'\x0070f'}, // Syriac Abbreviation Mark
{U'\x00711', U'\x00711'}, // Syriac Letter Superscript Alaph
{U'\x00730', U'\x0074a'}, // Syriac Pthaha Above ..Syriac Barrekh
{U'\x007a6', U'\x007b0'}, // Thaana Abafili ..Thaana Sukun
{U'\x00901', U'\x00903'}, // Devanagari Sign Candrabi..Devanagari Sign Visarga
{U'\x0093c', U'\x0093c'}, // Devanagari Sign Nukta
{U'\x0093e', U'\x0094d'}, // Devanagari Vowel Sign Aa..Devanagari Sign Virama
{U'\x00951', U'\x00954'}, // Devanagari Stress Sign U..Devanagari Acute Accent
{U'\x00962', U'\x00963'}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo
{U'\x00981', U'\x00983'}, // Bengali Sign Candrabindu..Bengali Sign Visarga
{U'\x009bc', U'\x009bc'}, // Bengali Sign Nukta
{U'\x009be', U'\x009c4'}, // Bengali Vowel Sign Aa ..Bengali Vowel Sign Vocal
{U'\x009c7', U'\x009c8'}, // Bengali Vowel Sign E ..Bengali Vowel Sign Ai
{U'\x009cb', U'\x009cd'}, // Bengali Vowel Sign O ..Bengali Sign Virama
{U'\x009d7', U'\x009d7'}, // Bengali Au Length Mark
{U'\x009e2', U'\x009e3'}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal
{U'\x00a01', U'\x00a03'}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Visarga
{U'\x00a3c', U'\x00a3c'}, // Gurmukhi Sign Nukta
{U'\x00a3e', U'\x00a42'}, // Gurmukhi Vowel Sign Aa ..Gurmukhi Vowel Sign Uu
{U'\x00a47', U'\x00a48'}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai
{U'\x00a4b', U'\x00a4d'}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama
{U'\x00a70', U'\x00a71'}, // Gurmukhi Tippi ..Gurmukhi Addak
{U'\x00a81', U'\x00a83'}, // Gujarati Sign Candrabind..Gujarati Sign Visarga
{U'\x00abc', U'\x00abc'}, // Gujarati Sign Nukta
{U'\x00abe', U'\x00ac5'}, // Gujarati Vowel Sign Aa ..Gujarati Vowel Sign Cand
{U'\x00ac7', U'\x00ac9'}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Cand
{U'\x00acb', U'\x00acd'}, // Gujarati Vowel Sign O ..Gujarati Sign Virama
{U'\x00ae2', U'\x00ae3'}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca
{U'\x00b01', U'\x00b03'}, // Oriya Sign Candrabindu ..Oriya Sign Visarga
{U'\x00b3c', U'\x00b3c'}, // Oriya Sign Nukta
{U'\x00b3e', U'\x00b43'}, // Oriya Vowel Sign Aa ..Oriya Vowel Sign Vocalic
{U'\x00b47', U'\x00b48'}, // Oriya Vowel Sign E ..Oriya Vowel Sign Ai
{U'\x00b4b', U'\x00b4d'}, // Oriya Vowel Sign O ..Oriya Sign Virama
{U'\x00b56', U'\x00b57'}, // Oriya Ai Length Mark ..Oriya Au Length Mark
{U'\x00b82', U'\x00b82'}, // Tamil Sign Anusvara
{U'\x00bbe', U'\x00bc2'}, // Tamil Vowel Sign Aa ..Tamil Vowel Sign Uu
{U'\x00bc6', U'\x00bc8'}, // Tamil Vowel Sign E ..Tamil Vowel Sign Ai
{U'\x00bca', U'\x00bcd'}, // Tamil Vowel Sign O ..Tamil Sign Virama
{U'\x00bd7', U'\x00bd7'}, // Tamil Au Length Mark
{U'\x00c01', U'\x00c03'}, // Telugu Sign Candrabindu ..Telugu Sign Visarga
{U'\x00c3e', U'\x00c44'}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Vocali
{U'\x00c46', U'\x00c48'}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
{U'\x00c4a', U'\x00c4d'}, // Telugu Vowel Sign O ..Telugu Sign Virama
{U'\x00c55', U'\x00c56'}, // Telugu Length Mark ..Telugu Ai Length Mark
{U'\x00c82', U'\x00c83'}, // Kannada Sign Anusvara ..Kannada Sign Visarga
{U'\x00cbc', U'\x00cbc'}, // Kannada Sign Nukta
{U'\x00cbe', U'\x00cc4'}, // Kannada Vowel Sign Aa ..Kannada Vowel Sign Vocal
{U'\x00cc6', U'\x00cc8'}, // Kannada Vowel Sign E ..Kannada Vowel Sign Ai
{U'\x00cca', U'\x00ccd'}, // Kannada Vowel Sign O ..Kannada Sign Virama
{U'\x00cd5', U'\x00cd6'}, // Kannada Length Mark ..Kannada Ai Length Mark
{U'\x00d02', U'\x00d03'}, // Malayalam Sign Anusvara ..Malayalam Sign Visarga
{U'\x00d3e', U'\x00d43'}, // Malayalam Vowel Sign Aa ..Malayalam Vowel Sign Voc
{U'\x00d46', U'\x00d48'}, // Malayalam Vowel Sign E ..Malayalam Vowel Sign Ai
{U'\x00d4a', U'\x00d4d'}, // Malayalam Vowel Sign O ..Malayalam Sign Virama
{U'\x00d57', U'\x00d57'}, // Malayalam Au Length Mark
{U'\x00d82', U'\x00d83'}, // Sinhala Sign Anusvaraya ..Sinhala Sign Visargaya
{U'\x00dca', U'\x00dca'}, // Sinhala Sign Al-lakuna
{U'\x00dcf', U'\x00dd4'}, // Sinhala Vowel Sign Aela-..Sinhala Vowel Sign Ketti
{U'\x00dd6', U'\x00dd6'}, // Sinhala Vowel Sign Diga Paa-pilla
{U'\x00dd8', U'\x00ddf'}, // Sinhala Vowel Sign Gaett..Sinhala Vowel Sign Gayan
{U'\x00df2', U'\x00df3'}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
{U'\x00e31', U'\x00e31'}, // Thai Character Mai Han-akat
{U'\x00e34', U'\x00e3a'}, // Thai Character Sara I ..Thai Character Phinthu
{U'\x00e47', U'\x00e4e'}, // Thai Character Maitaikhu..Thai Character Yamakkan
{U'\x00eb1', U'\x00eb1'}, // Lao Vowel Sign Mai Kan
{U'\x00eb4', U'\x00eb9'}, // Lao Vowel Sign I ..Lao Vowel Sign Uu
{U'\x00ebb', U'\x00ebc'}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo
{U'\x00ec8', U'\x00ecd'}, // Lao Tone Mai Ek ..Lao Niggahita
{U'\x00f18', U'\x00f19'}, // Tibetan Astrological Sig..Tibetan Astrological Sig
{U'\x00f35', U'\x00f35'}, // Tibetan Mark Ngas Bzung Nyi Zla
{U'\x00f37', U'\x00f37'}, // Tibetan Mark Ngas Bzung Sgor Rtags
{U'\x00f39', U'\x00f39'}, // Tibetan Mark Tsa -phru
{U'\x00f3e', U'\x00f3f'}, // Tibetan Sign Yar Tshes ..Tibetan Sign Mar Tshes
{U'\x00f71', U'\x00f84'}, // Tibetan Vowel Sign Aa ..Tibetan Mark Halanta
{U'\x00f86', U'\x00f87'}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags
{U'\x00f90', U'\x00f97'}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
{U'\x00f99', U'\x00fbc'}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
{U'\x00fc6', U'\x00fc6'}, // Tibetan Symbol Padma Gdan
{U'\x0102c', U'\x01032'}, // Myanmar Vowel Sign Aa ..Myanmar Vowel Sign Ai
{U'\x01036', U'\x01039'}, // Myanmar Sign Anusvara ..Myanmar Sign Virama
{U'\x01056', U'\x01059'}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal
{U'\x01160', U'\x011ff'}, // Hangul Jungseong Filler ..Hangul Jongseong Ssangni
{U'\x0135f', U'\x0135f'}, // Ethiopic Combining Gemination Mark
{U'\x01712', U'\x01714'}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
{U'\x01732', U'\x01734'}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod
{U'\x01752', U'\x01753'}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
{U'\x01772', U'\x01773'}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
{U'\x017b4', U'\x017d3'}, // Khmer Vowel Inherent Aq ..Khmer Sign Bathamasat
{U'\x017dd', U'\x017dd'}, // Khmer Sign Atthacan
{U'\x0180b', U'\x0180d'}, // Mongolian Free Variation..Mongolian Free Variation
{U'\x018a9', U'\x018a9'}, // Mongolian Letter Ali Gali Dagalga
{U'\x01920', U'\x0192b'}, // Limbu Vowel Sign A ..Limbu Subjoined Letter W
{U'\x01930', U'\x0193b'}, // Limbu Small Letter Ka ..Limbu Sign Sa-i
{U'\x019b0', U'\x019c0'}, // New Tai Lue Vowel Sign V..New Tai Lue Vowel Sign I
{U'\x019c8', U'\x019c9'}, // New Tai Lue Tone Mark-1 ..New Tai Lue Tone Mark-2
{U'\x01a17', U'\x01a1b'}, // Buginese Vowel Sign I ..Buginese Vowel Sign Ae
{U'\x01dc0', U'\x01dc3'}, // Combining Dotted Grave A..Combining Suspension Mar
{U'\x0200b', U'\x0200f'}, // Zero Width Space ..Right-to-left Mark
{U'\x02028', U'\x0202e'}, // Line Separator ..Right-to-left Override
{U'\x02060', U'\x02063'}, // Word Joiner ..Invisible Separator
{U'\x0206a', U'\x0206f'}, // Inhibit Symmetric Swappi..Nominal Digit Shapes
{U'\x020d0', U'\x020eb'}, // Combining Left Harpoon A..Combining Long Double So
{U'\x0302a', U'\x0302f'}, // Ideographic Level Tone M..Hangul Double Dot Tone M
{U'\x03099', U'\x0309a'}, // Combining Katakana-hirag..Combining Katakana-hirag
{U'\x0a802', U'\x0a802'}, // Syloti Nagri Sign Dvisvara
{U'\x0a806', U'\x0a806'}, // Syloti Nagri Sign Hasanta
{U'\x0a80b', U'\x0a80b'}, // Syloti Nagri Sign Anusvara
{U'\x0a823', U'\x0a827'}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
{U'\x0d7b0', U'\x0d7ff'}, // Hangul Jungseong O-yeo ..(nil)
{U'\x0fb1e', U'\x0fb1e'}, // Hebrew Point Judeo-spanish Varika
{U'\x0fe00', U'\x0fe0f'}, // Variation Selector-1 ..Variation Selector-16
{U'\x0fe20', U'\x0fe23'}, // Combining Ligature Left ..Combining Double Tilde R
{U'\x0feff', U'\x0feff'}, // Zero Width No-break Space
{U'\x0fff9', U'\x0fffb'}, // Interlinear Annotation A..Interlinear Annotation T
{U'\x10a01', U'\x10a03'}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo
{U'\x10a05', U'\x10a06'}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O
{U'\x10a0c', U'\x10a0f'}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga
{U'\x10a38', U'\x10a3a'}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo
{U'\x10a3f', U'\x10a3f'}, // Kharoshthi Virama
{U'\x1d165', U'\x1d169'}, // Musical Symbol Combining..Musical Symbol Combining
{U'\x1d16d', U'\x1d182'}, // Musical Symbol Combining..Musical Symbol Combining
{U'\x1d185', U'\x1d18b'}, // Musical Symbol Combining..Musical Symbol Combining
{U'\x1d1aa', U'\x1d1ad'}, // Musical Symbol Combining..Musical Symbol Combining
{U'\x1d242', U'\x1d244'}, // Combining Greek Musical ..Combining Greek Musical
{U'\xe0001', U'\xe0001'}, // Language Tag
{U'\xe0020', U'\xe007f'}, // Tag Space ..Cancel Tag
{U'\xe0100', U'\xe01ef'}, // Variation Selector-17 ..Variation Selector-256
};
static const BoundaryVector WIDE_EAST_ASIAN{
{U'\x01100', U'\x01159'}, // Hangul Choseong Kiyeok ..Hangul Choseong Yeorinhi
{U'\x0115f', U'\x0115f'}, // Hangul Choseong Filler
{U'\x02329', U'\x0232a'}, // Left-pointing Angle Brac..Right-pointing Angle Bra
{U'\x02e80', U'\x02e99'}, // Cjk Radical Repeat ..Cjk Radical Rap
{U'\x02e9b', U'\x02ef3'}, // Cjk Radical Choke ..Cjk Radical C-simplified
{U'\x02f00', U'\x02fd5'}, // Kangxi Radical One ..Kangxi Radical Flute
{U'\x02ff0', U'\x02ffb'}, // Ideographic Description ..Ideographic Description
{U'\x03000', U'\x03029'}, // Ideographic Space ..Hangzhou Numeral Nine
{U'\x03030', U'\x0303e'}, // Wavy Dash ..Ideographic Variation In
{U'\x03041', U'\x03096'}, // Hiragana Letter Small A ..Hiragana Letter Small Ke
{U'\x0309b', U'\x030ff'}, // Katakana-hiragana Voiced..Katakana Digraph Koto
{U'\x03105', U'\x0312c'}, // Bopomofo Letter B ..Bopomofo Letter Gn
{U'\x03131', U'\x0318e'}, // Hangul Letter Kiyeok ..Hangul Letter Araeae
{U'\x03190', U'\x031b7'}, // Ideographic Annotation L..Bopomofo Final Letter H
{U'\x031c0', U'\x031cf'}, // Cjk Stroke T ..Cjk Stroke N
{U'\x031f0', U'\x0321e'}, // Katakana Letter Small Ku..Parenthesized Korean Cha
{U'\x03220', U'\x03243'}, // Parenthesized Ideograph ..Parenthesized Ideograph
{U'\x03250', U'\x032fe'}, // Partnership Sign ..Circled Katakana Wo
{U'\x03300', U'\x04db5'}, // Square Apaato ..Cjk Unified Ideograph-4d
{U'\x04e00', U'\x09fbb'}, // Cjk Unified Ideograph-4e..Cjk Unified Ideograph-9f
{U'\x0a000', U'\x0a48c'}, // Yi Syllable It ..Yi Syllable Yyr
{U'\x0a490', U'\x0a4c6'}, // Yi Radical Qot ..Yi Radical Ke
{U'\x0ac00', U'\x0d7a3'}, // Hangul Syllable Ga ..Hangul Syllable Hih
{U'\x0f900', U'\x0fa2d'}, // Cjk Compatibility Ideogr..Cjk Compatibility Ideogr
{U'\x0fa30', U'\x0fa6a'}, // Cjk Compatibility Ideogr..Cjk Compatibility Ideogr
{U'\x0fa70', U'\x0fad9'}, // Cjk Compatibility Ideogr..Cjk Compatibility Ideogr
{U'\x0fe10', U'\x0fe19'}, // Presentation Form For Ve..Presentation Form For Ve
{U'\x0fe30', U'\x0fe52'}, // Presentation Form For Ve..Small Full Stop
{U'\x0fe54', U'\x0fe66'}, // Small Semicolon ..Small Equals Sign
{U'\x0fe68', U'\x0fe6b'}, // Small Reverse Solidus ..Small Commercial At
{U'\x0ff01', U'\x0ff60'}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
{U'\x0ffe0', U'\x0ffe6'}, // Fullwidth Cent Sign ..Fullwidth Won Sign
{U'\x20000', U'\x2fffd'}, // Cjk Unified Ideograph-20..(nil)
{U'\x30000', U'\x3fffd'}, // Cjk Unified Ideograph-30..(nil)
};
static const BoundaryVector VS16_NARROW_TO_WIDE{
{U'\x00023', U'\x00023'}, // Number Sign
{U'\x0002a', U'\x0002a'}, // Asterisk
{U'\x00030', U'\x00039'}, // Digit Zero ..Digit Nine
{U'\x000a9', U'\x000a9'}, // Copyright Sign
{U'\x000ae', U'\x000ae'}, // Registered Sign
{U'\x0203c', U'\x0203c'}, // Double Exclamation Mark
{U'\x02049', U'\x02049'}, // Exclamation Question Mark
{U'\x02122', U'\x02122'}, // Trade Mark Sign
{U'\x02139', U'\x02139'}, // Information Source
{U'\x02194', U'\x02199'}, // Left Right Arrow ..South West Arrow
{U'\x021a9', U'\x021aa'}, // Leftwards Arrow With Hoo..Rightwards Arrow With Ho
{U'\x02328', U'\x02328'}, // Keyboard
{U'\x023cf', U'\x023cf'}, // Eject Symbol
{U'\x023ed', U'\x023ef'}, // Black Right-pointing Dou..Black Right-pointing Tri
{U'\x023f1', U'\x023f2'}, // Stopwatch ..Timer Clock
{U'\x023f8', U'\x023fa'}, // Double Vertical Bar ..Black Circle For Record
{U'\x024c2', U'\x024c2'}, // Circled Latin Capital Letter M
{U'\x025aa', U'\x025ab'}, // Black Small Square ..White Small Square
{U'\x025b6', U'\x025b6'}, // Black Right-pointing Triangle
{U'\x025c0', U'\x025c0'}, // Black Left-pointing Triangle
{U'\x025fb', U'\x025fc'}, // White Medium Square ..Black Medium Square
{U'\x02600', U'\x02604'}, // Black Sun With Rays ..Comet
{U'\x0260e', U'\x0260e'}, // Black Telephone
{U'\x02611', U'\x02611'}, // Ballot Box With Check
{U'\x02618', U'\x02618'}, // Shamrock
{U'\x0261d', U'\x0261d'}, // White Up Pointing Index
{U'\x02620', U'\x02620'}, // Skull And Crossbones
{U'\x02622', U'\x02623'}, // Radioactive Sign ..Biohazard Sign
{U'\x02626', U'\x02626'}, // Orthodox Cross
{U'\x0262a', U'\x0262a'}, // Star And Crescent
{U'\x0262e', U'\x0262f'}, // Peace Symbol ..Yin Yang
{U'\x02638', U'\x0263a'}, // Wheel Of Dharma ..White Smiling Face
{U'\x02640', U'\x02640'}, // Female Sign
{U'\x02642', U'\x02642'}, // Male Sign
{U'\x0265f', U'\x02660'}, // Black Chess Pawn ..Black Spade Suit
{U'\x02663', U'\x02663'}, // Black Club Suit
{U'\x02665', U'\x02666'}, // Black Heart Suit ..Black Diamond Suit
{U'\x02668', U'\x02668'}, // Hot Springs
{U'\x0267b', U'\x0267b'}, // Black Universal Recycling Symbol
{U'\x0267e', U'\x0267e'}, // Permanent Paper Sign
{U'\x02692', U'\x02692'}, // Hammer And Pick
{U'\x02694', U'\x02697'}, // Crossed Swords ..Alembic
{U'\x02699', U'\x02699'}, // Gear
{U'\x0269b', U'\x0269c'}, // Atom Symbol ..Fleur-de-lis
{U'\x026a0', U'\x026a0'}, // Warning Sign
{U'\x026a7', U'\x026a7'}, // Male With Stroke And Male And Female Sign
{U'\x026b0', U'\x026b1'}, // Coffin ..Funeral Urn
{U'\x026c8', U'\x026c8'}, // Thunder Cloud And Rain
{U'\x026cf', U'\x026cf'}, // Pick
{U'\x026d1', U'\x026d1'}, // Helmet With White Cross
{U'\x026d3', U'\x026d3'}, // Chains
{U'\x026e9', U'\x026e9'}, // Shinto Shrine
{U'\x026f0', U'\x026f1'}, // Mountain ..Umbrella On Ground
{U'\x026f4', U'\x026f4'}, // Ferry
{U'\x026f7', U'\x026f9'}, // Skier ..Person With Ball
{U'\x02702', U'\x02702'}, // Black Scissors
{U'\x02708', U'\x02709'}, // Airplane ..Envelope
{U'\x0270c', U'\x0270d'}, // Victory Hand ..Writing Hand
{U'\x0270f', U'\x0270f'}, // Pencil
{U'\x02712', U'\x02712'}, // Black Nib
{U'\x02714', U'\x02714'}, // Heavy Check Mark
{U'\x02716', U'\x02716'}, // Heavy Multiplication X
{U'\x0271d', U'\x0271d'}, // Latin Cross
{U'\x02721', U'\x02721'}, // Star Of David
{U'\x02733', U'\x02734'}, // Eight Spoked Asterisk ..Eight Pointed Black Star
{U'\x02744', U'\x02744'}, // Snowflake
{U'\x02747', U'\x02747'}, // Sparkle
{U'\x02763', U'\x02764'}, // Heavy Heart Exclamation ..Heavy Black Heart
{U'\x027a1', U'\x027a1'}, // Black Rightwards Arrow
{U'\x02934', U'\x02935'}, // Arrow Pointing Rightward..Arrow Pointing Rightward
{U'\x02b05', U'\x02b07'}, // Leftwards Black Arrow ..Downwards Black Arrow
{U'\x1f170', U'\x1f171'}, // Negative Squared Latin C..Negative Squared Latin C
{U'\x1f17e', U'\x1f17f'}, // Negative Squared Latin C..Negative Squared Latin C
{U'\x1f321', U'\x1f321'}, // Thermometer
{U'\x1f324', U'\x1f32c'}, // White Sun With Small Clo..Wind Blowing Face
{U'\x1f336', U'\x1f336'}, // Hot Pepper
{U'\x1f37d', U'\x1f37d'}, // Fork And Knife With Plate
{U'\x1f396', U'\x1f397'}, // Military Medal ..Reminder Ribbon
{U'\x1f399', U'\x1f39b'}, // Studio Microphone ..Control Knobs
{U'\x1f39e', U'\x1f39f'}, // Film Frames ..Admission Tickets
{U'\x1f3cb', U'\x1f3ce'}, // Weight Lifter ..Racing Car
{U'\x1f3d4', U'\x1f3df'}, // Snow Capped Mountain ..Stadium
{U'\x1f3f3', U'\x1f3f3'}, // Waving White Flag
{U'\x1f3f5', U'\x1f3f5'}, // Rosette
{U'\x1f3f7', U'\x1f3f7'}, // Label
{U'\x1f43f', U'\x1f43f'}, // Chipmunk
{U'\x1f441', U'\x1f441'}, // Eye
{U'\x1f4fd', U'\x1f4fd'}, // Film Projector
{U'\x1f549', U'\x1f54a'}, // Om Symbol ..Dove Of Peace
{U'\x1f56f', U'\x1f570'}, // Candle ..Mantelpiece Clock
{U'\x1f573', U'\x1f579'}, // Hole ..Joystick
{U'\x1f587', U'\x1f587'}, // Linked Paperclips
{U'\x1f58a', U'\x1f58d'}, // Lower Left Ballpoint Pen..Lower Left Crayon
{U'\x1f590', U'\x1f590'}, // Raised Hand With Fingers Splayed
{U'\x1f5a5', U'\x1f5a5'}, // Desktop Computer
{U'\x1f5a8', U'\x1f5a8'}, // Printer
{U'\x1f5b1', U'\x1f5b2'}, // Three Button Mouse ..Trackball
{U'\x1f5bc', U'\x1f5bc'}, // Frame With Picture
{U'\x1f5c2', U'\x1f5c4'}, // Card Index Dividers ..File Cabinet
{U'\x1f5d1', U'\x1f5d3'}, // Wastebasket ..Spiral Calendar Pad
{U'\x1f5dc', U'\x1f5de'}, // Compression ..Rolled-up Newspaper
{U'\x1f5e1', U'\x1f5e1'}, // Dagger Knife
{U'\x1f5e3', U'\x1f5e3'}, // Speaking Head In Silhouette
{U'\x1f5e8', U'\x1f5e8'}, // Left Speech Bubble
{U'\x1f5ef', U'\x1f5ef'}, // Right Anger Bubble
{U'\x1f5f3', U'\x1f5f3'}, // Ballot Box With Ballot
{U'\x1f5fa', U'\x1f5fa'}, // World Map
{U'\x1f6cb', U'\x1f6cb'}, // Couch And Lamp
{U'\x1f6cd', U'\x1f6cf'}, // Shopping Bags ..Bed
{U'\x1f6e0', U'\x1f6e5'}, // Hammer And Wrench ..Motor Boat
{U'\x1f6e9', U'\x1f6e9'}, // Small Airplane
{U'\x1f6f0', U'\x1f6f0'}, // Satellite
{U'\x1f6f3', U'\x1f6f3'}, // Passenger Ship
};
// clang-format on
static size_t bisearch(char32_t ucs, const BoundaryVector& table) {
// TODO: Use STD algorithm to optimize this function
// YYC MARK:
// Do not change this "int" to "size_t" casually,
// because the result of arithmetic operation may be negative.
// Do not change this type before using new algorithm.
int lbound = 0, ubound = table.size() - 1;
if (ucs < table.front().first || ucs > table.back().second) return 0;
while (ubound >= lbound) {
int mid = (lbound + ubound) / 2;
if (ucs > table[mid].second) lbound = mid + 1;
else if (ucs < table[mid].first) ubound = mid - 1;
else return 1;
}
return 0;
}
size_t wcwidth(char32_t wc) {
// TODO: Add lru_cache(maxsize=1000) for this function
// Small optimize for ASCII
if (U'\x20' <= wc && wc < U'\x7F') [[likely]]
return 1;
// C0/C1 control char
// NOTE: Not vanilla implementation. Return 0 instead of 1.
if ((wc && wc < L'\x20') || (L'\x7F' <= wc && wc < L'\xA0')) return 0;
// Zero-width char
if (bisearch(wc, ZERO_WIDTH)) return 0;
// Width 1 or 2
return 1 + bisearch(wc, WIDE_EAST_ASIAN);
}
enum class WcswidthState {
/// Normal character.
Normal,
/// Under ZWJ control char.
/// Ignore the width of next char.
ZeroWidthJoiner,
/// Under ANSI Escape Sequence.
/// Following chars should be treated as escape char.
AnsiEscape,
/// Under CSI control sequence, a part of ANSI Escape Sequence.
/// No width was accumulated before terminal char.
AnsiCsiEscape,
};
struct WcswidthContext {
/// Current state.
WcswidthState state;
/// Tract the last computed char.
/// It will be used for VS16 char.
std::optional<char32_t> last_measured_char;
};
Result<size_t> wcswidth(const std::u32string_view& rhs) {
WcswidthContext ctx{WcswidthState::Normal, std::nullopt};
size_t width = 0;
for (char32_t chr : rhs) {
// Match char value
switch (ctx.state) {
case WcswidthState::Normal: {
switch (chr) {
case U'\x200D': {
// ZWJ control char
ctx.state = WcswidthState::ZeroWidthJoiner;
break;
}
case U'\xFE0F': {
// VS16 control char
// If we have a char which was acknowledged and has width,
// analyse it instead of this control char.
if (ctx.last_measured_char.has_value()) {
width += bisearch(ctx.last_measured_char.value(), VS16_NARROW_TO_WIDE);
ctx.last_measured_char = std::nullopt;
}
break;
}
case U'\x1B': {
// ANSI escape sequence
ctx.state = WcswidthState::AnsiEscape;
break;
}
default: {
// Fetch widht for normal char
int wcw = wcwidth(chr);
// Tract the final non-zero char for VS16 control char
if (wcw > 0) ctx.last_measured_char = wcw;
// Accumulate width
width += wcw;
break;
}
}
break;
}
case WcswidthState::ZeroWidthJoiner: {
// Eat this char and back to normal state.
// This is what ZWJ does.
ctx.state = WcswidthState::Normal;
break;
}
case WcswidthState::AnsiEscape: {
// Check the second char of escape sequence.
// If it is '[', we enter CSI state,
// otherwise we eat it and back to normal state.
// Additionally, there is a range requirement for this char (0x40-0x5F).
if (chr == U'[') {
ctx.state = WcswidthState::AnsiCsiEscape;
} else if (chr >= U'\x40' && chr <= U'\x5F') {
ctx.state = WcswidthState::Normal;
} else {
return std::unexpected(Error::BadAnsiEscSeq);
}
break;
}
case WcswidthState::AnsiCsiEscape: {
// CSI sequence is aonsisted by variable Parameter Char (count can be zero),
// at least one Middle Char and only one Final Char.
// So we eat all chars until we reach the terminal char.
if (chr >= U'\x40' && chr <= U'\x7E') {
// Final Char. Back to normal state.
ctx.state = WcswidthState::Normal;
} else if (chr >= U'\x30' && chr <= U'\x3F') {
; // Parameter Char. Do nothing
} else if (chr >= U'\x20' && chr <= U'\x2F') {
; // Middle Char. Do nothing
} else {
return std::unexpected(Error::BadCsiSeq);
}
break;
}
}
}
return width;
}
Result<size_t> wcswidth(const std::u8string_view& rhs) {
// Cast encoding
auto u32str = ENC::to_utf32(rhs);
if (!u32str.has_value()) return std::unexpected(Error::BadEncoding);
// Call underlying function
return wcswidth(u32str.value());
}
} // namespace yycc::carton::wcwidth

View File

@ -0,0 +1,47 @@
#pragma once
#include <string_view>
#include <expected>
/**
* @brief The namespace replicating Linux-specialized function, "wcswidth", in all platforms.
* @details
* "wcswdith" is a specialized function in Linux.
* It was not included in POSIX standard and only provided on Linux.
* This function can fetch how many space which given string occupied in terminal.
* This is essential and useful function in our library.
* So I create this namespace to make "wcswidth" be available on all platforms.
*
* "wcswidth" is based on \c wchar_t. In Linux, \c wchar_t is 4-bytes length.
* It can represent any characters without surrogate pair.
* However, in Windows, \c wchar_t is 2-bytes length.
* There is possible surrogate pair within \c wchar_t string, which is inconvenient for our programming.
* So in this homebrew namespace, I forcelt use \c char32_t as the basic char type.
*
* Due to the requirements of mine, this implementation is slightly different with original one.
* These differences are list below:
*
* \li We do not return negative value for Control Char in "wcwidth",
* because we need to support the analyse of ANSI Escape Sequence.
* \li Due to the previous change, the type of return value of "wcwidth" and "wcswidth"
* are changed from \c int to \c size_t because there is no negative return value.
* \li "wcswidth" now support ANSI Escape Sequence (e.g. terminal color).
* So it can analyse colorful output with correct space.
*/
namespace yycc::carton::wcwidth {
/// @brief Error occurs in this module
enum class Error {
BadEncoding, ///< Given
BadAnsiEscSeq, ///< Bad char when processing ANSI Escape Sequence
BadCsiSeq, ///< Bad char when processing CSI Sequence.
};
/// @brief Result type for this module
template<typename T>
using Result = std::expected<T, Error>;
size_t wcwidth(char32_t wc);
Result<size_t> wcswidth(const std::u32string_view& rhs);
Result<size_t> wcswidth(const std::u8string_view& rhs);
} // namespace yycc::carton::wcwidth

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,71 @@
#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);
auto fn_check = [entries](const T& val) -> bool { return entries.contains(val); };
auto fn_clamp = [entries, default_entry](const T& val) -> T {
if (entries.contains(val)) 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

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

@ -0,0 +1,340 @@
#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) noexcept : inner(rhs.inner) {
// Reset rhs inner
rhs.inner = INVALID_ICONV_TOKEN;
}
PrivToken& operator=(PrivToken&& rhs) noexcept {
// 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(nullptr) {
this->inner = new PrivToken(from_code, to_code);
}
Token::~Token() {
if (this->inner != nullptr) {
delete this->inner;
}
}
Token::Token(Token&& rhs) noexcept : inner(rhs.inner) {
rhs.inner = nullptr;
}
Token& Token::operator=(Token&& rhs) noexcept {
this->inner = rhs.inner;
rhs.inner = nullptr;
return *this;
}
bool Token::is_valid() const {
return this->inner->is_valid();
}
PrivToken* Token::get_inner() const {
return this->inner;
}
#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 += ICONV_INC_LEN;
// 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)
"UTF-16LE"sv;
#else
"UTF-16BE"sv;
#endif
constexpr auto UTF32_CODENAME_LITERAL =
#if defined(YYCC_ENDIAN_LITTLE)
"UTF-32LE"sv;
#else
"UTF-32BE"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() * sizeof(src_char_type)); \
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>
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();
Token(Token&& rhs) noexcept;
Token& operator=(Token&& rhs) noexcept;
YYCC_DELETE_COPY(Token)
bool is_valid() const;
PrivToken* get_inner() const;
private:
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

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[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
/**
* @brief UTF16 -> UTF8
* @param[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
/**
* @brief UTF8 -> UTF32
* @param[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
/**
* @brief UTF32 -> UTF8
* @param[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
}

View File

@ -0,0 +1,253 @@
#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();
// YYC MARK:
// Due to the shitty design of mbrtoc16, it forcely assume that passed string is null-terminated.
// And the third argument should >= 1.
// However, our given string is string view which do not have null-terminated guaranteen.
//
// So we manually check whether we have reach the tail of string and simulate a fake null terminal.
// If string is still processing, we pass given string.
// If we have reach the tail of string, we pass our homemade NULL_TERMINAL to this function to make it works normally.
//
// This is a stupid polyfill, however, it I do not do this,
// there is a bug that the second part of surrogate pair will be dropped in final string,
// if there is a Unicode character located at the tail of string which need surrogate pair to be presented.
static const char NULL_TERMINAL = '\0';
while (true) {
bool not_tail = ptr < end;
const char* new_ptr = not_tail ? ptr : &NULL_TERMINAL;
size_t new_size = not_tail ? end - ptr : sizeof(NULL_TERMINAL);
size_t rc = std::mbrtoc16(&c16, new_ptr, new_size, &state);
if (!rc) break;
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]{};
size_t rc = 1; // Assign it to ONE to avoid mismatching surrogate pair checker when string is empty.
for (char16_t c : src) {
rc = std::c16rtomb(mbout, c, &state);
if (rc == (size_t) -1) return std::unexpected(ConvError::InvalidUtf16);
else dst.append(reinterpret_cast<char8_t*>(mbout), rc);
}
if (rc == 0) {
// YYC MARK:
// If rc is zero after processing all chars,
// it means that we are aborted when processing an UTF16 surrogate pair.
// We should report it as an error.
return std::unexpected(ConvError::InvalidUtf16);
}
// Okey, return result.
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) {
// YYC MARK:
// There is no surrogate pair in UTF32,
// so we do not need do that stupid things in UTF8 to UTF32 functions.
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) return std::unexpected(ConvError::InvalidUtf32);
else dst.append(reinterpret_cast<char8_t*>(mbout), rc);
}
// YYC MARK:
// There is no surrogate pair for UTF32,
// so this "if" statement only presented in UTF16 to UTF8 function.
// In this function, we directly return value.
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[in] src The string to be converted.
* @param[in] code_page The code page of native string.
* @return The converted string, or error occurring.
*/
ConvResult<std::string> to_char(const std::wstring_view& src, CodePage code_page);
/**
* @brief Char -> WChar
* @param[in] src The string to be converted.
* @param[in] code_page The code page of native string.
* @return The converted string, or error occurring.
*/
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[in] src The string to be converted.
* @param[in] src_code_page The code page of source string.
* @param[in] dst_code_page The code page of destination string.
* @return The converted string, or error occurring.
*/
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[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::u8string> to_utf8(const std::wstring_view& src);
/**
* @brief UTF8 -> WChar
* @details This is the specialization of "Char -> WChar"
* @param[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::wstring> to_wchar(const std::u8string_view& src);
/**
* @brief Char -> UTF8
* @details This is the specialization of "Char -> Char"
* @param[in] src The string to be converted.
* @param[in] code_page The code page of native string.
* @return The converted string, or error occurring.
*/
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[in] src The string to be converted.
* @param[in] code_page The code page of native string.
* @return The converted string, or error occurring.
*/
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[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
/**
* @brief UTF16 -> UTF8
* @param[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
/**
* @brief UTF8 -> UTF32
* @param[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
/**
* @brief UTF32 -> UTF8
* @param[in] src The string to be converted.
* @return The converted string, or error occurring.
*/
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
#endif
#endif
} // namespace yycc::encoding::windows

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

@ -0,0 +1,207 @@
#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));
}
/**
* @brief Cast given enum flags to its equvalent underlying integer value like performing <TT>static_cast<std::underlying_type_t<T>>(e)</TT>
* @tparam TEnum Enum type for processing.
* @param e The enum flags to be cast.
* @return The equvalent integer value of given enum flag.
*/
template<typename TEnum>
requires(std::is_enum_v<TEnum>)
constexpr std::underlying_type_t<TEnum> integer(TEnum e) {
using ut = std::underlying_type_t<TEnum>;
return static_cast<ut>(e);
}
} // namespace yycc::flag_enum

View File

@ -0,0 +1,62 @@
#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&&) noexcept = delete; \
CLSNAME& operator=(CLSNAME&&) noexcept = 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&&) noexcept = default; \
CLSNAME& operator=(CLSNAME&&) noexcept = 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)
/// @brief Make declaration of copy (\c constructor and \c operator\=) for given class to avoid typo.
#define YYCC_DECL_COPY(CLSNAME) \
CLSNAME(const CLSNAME&); \
CLSNAME& operator=(const CLSNAME&);
/// @brief Make declaration of move (\c constructor and \c operator\=) for given class to avoid typo.
#define YYCC_DECL_MOVE(CLSNAME) \
CLSNAME(CLSNAME&&) noexcept; \
CLSNAME& operator=(CLSNAME&&) noexcept;
/// @brief Make declaration of copy and move (\c constructor and \c operator\=) for given class to avoid typo.
#define YYCC_DECL_COPY_MOVE(CLSNAME) \
YYCC_DECL_COPY(CLSNAME) \
YYCC_DECL_MOVE(CLSNAME)
/// @brief Make implementation signature of copy \c constrctor for given class and right operand name to avoid typo.
#define YYCC_IMPL_COPY_CTOR(CLSNAME, RHS) \
CLSNAME::CLSNAME(const CLSNAME& RHS)
/// @brief Make implementation signature of copy \c operator\= for given class and right operand name to avoid typo.
#define YYCC_IMPL_COPY_OPER(CLSNAME, RHS) \
CLSNAME& CLSNAME::operator=(const CLSNAME& RHS)
/// @brief Make implementation signature of move \c constrctor for given class and right operand name to avoid typo.
#define YYCC_IMPL_MOVE_CTOR(CLSNAME, RHS) \
CLSNAME::CLSNAME(CLSNAME&& RHS) noexcept
/// @brief Make implementation signature of move \c operator\= for given class and right operand name to avoid typo.
#define YYCC_IMPL_MOVE_OPER(CLSNAME, RHS) \
CLSNAME& CLSNAME::operator=(CLSNAME&& RHS) noexcept

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

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

@ -0,0 +1,121 @@
#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>;
/**
* @brief Parse given string into 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.");
}
}
/**
* @brief Parse given string into 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.");
}
}
/**
* @brief Parse given string into 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

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

@ -0,0 +1,774 @@
#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)
// YYC MARK:
// This macro is crucial for including "intsafe.h"
// Without this, "intsafe.h" will not enable signed integral operations.
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
#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 indicating whether an overflow has occurred. True means overflow, otherwise false.
*/
template<typename T>
requires std::integral<T>
using OverflowingPair = std::pair<T, bool>;
/**
* @brief Performs an addition operation with overflow detection.
* @tparam T Integer type.
* @param[in] a The left operand of the addition.
* @param[in] b The right operand of the addition.
* @return A pair holding result and overflow flag.
*/
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);
}
/**
* @brief Performs a subtraction operation with overflow detection.
* @tparam T Integer type.
* @param[in] a The left operand of the subtraction.
* @param[in] b The right operand of the subtraction.
* @return A pair holding result and overflow flag.
*/
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);
}
/**
* @brief Performs a multiplication operation with overflow detection.
* @tparam T Integer type.
* @param[in] a The left operand of the multiplication.
* @param[in] b The right operand of the multiplication.
* @return A pair holding result and overflow flag.
*/
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);
}
/**
* @brief Performs a division operation with overflow detection.
* @tparam T Integer type.
* @param[in] a The left operand of the division.
* @param[in] b The right operand of the division.
* @return A pair holding result and overflow flag.
* @exception std::logic_error If division by zero occurs.
*/
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

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

@ -0,0 +1,102 @@
#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

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

@ -0,0 +1,39 @@
#include "fopen.hpp"
#include "../macro/os_detector.hpp"
#if defined(YYCC_OS_WINDOWS)
// Include header file including MSVCRT specific function.
#include <wchar.h>
// Include encoding convertion header
#include "../encoding/windows.hpp"
// Define namespace macro
#define ENC ::yycc::encoding::windows
#else
// Include reinterpret header and define namespace macro
#include "../string/reinterpret.hpp"
#define REINTERPRET ::yycc::string::reinterpret
#endif
namespace yycc::patch::fopen {
std::FILE* fopen(const char8_t* u8_filepath, const char8_t* u8_mode) {
#if defined(YYCC_OS_WINDOWS)
// convert mode and file path to wchar
auto wmode = ENC::to_wchar(u8_mode);
auto wpath = ENC::to_wchar(u8_filepath);
// check convertion success
if (wmode.has_value() && wpath.has_value()) {
// Call MSVCRT specified fopen which support wchar as argument.
// Reference: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170
return _wfopen(wpath.value().c_str(), wmode.value().c_str());
} else {
// fail to convert encoding
return nullptr;
}
#else
return std::fopen(REINTERPRET::as_ordinary(u8_filepath), REINTERPRET::as_ordinary(u8_mode));
#endif
}
} // namespace yycc::patch::fopen

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

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

View File

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

159
src/yycc/rust/env.cpp Normal file
View File

@ -0,0 +1,159 @@
#include "env.hpp"
#include "../macro/os_detector.hpp"
#if defined(YYCC_OS_WINDOWS)
#include "../encoding/windows.hpp"
#include "../num/safe_op.hpp"
#include "../num/safe_cast.hpp"
#include <Windows.h>
#include <winbase.h>
#else
#include "../string/reinterpret.hpp"
#include <cstdlib>
#include <cerrno>
#include <stdexcept>
#endif
#define SAFECAST ::yycc::num::safe_cast
#define SAFEOP ::yycc::num::safe_op
#define ENC ::yycc::encoding::windows
#define REINTERPRET ::yycc::string::reinterpret
namespace yycc::rust::env {
EnvResult<std::u8string> get_var(const std::u8string_view &name) {
#if defined(YYCC_OS_WINDOWS)
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew
// Convert to wchar
auto wname = ENC::to_wchar(name);
if (!wname.has_value()) {
return std::unexpected(EnvError::BadEncoding);
}
// Prepare a variable with proper init size for holding value.
std::wstring wvalue;
wvalue.resize(256);
// Start to fetch value
while (true) {
// YYC MARK:
// Due to the shitty design of GetEnvironmentVariableW,
// the size passed to this function must include NULL terminal.
// So we forcely use checked add and sub for this bad behavior.
auto fct_size = SAFEOP::checked_add<size_t>(wvalue.size(), 1);
if (!fct_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
auto rv = ::GetEnvironmentVariableW(wname.value().c_str(), wvalue.data(), fct_size.value());
// Check the return value
if (rv == 0) {
// Function failed. Extract error reason.
auto ec = GetLastError();
if (ec == ERROR_ENVVAR_NOT_FOUND) return std::unexpected(EnvError::NoSuchName);
else return std::unexpected(EnvError::BadCall);
} else {
// Function okey. Check the size.
// Fetch function expected size.
auto rv_size = SAFECAST::try_to<size_t>(rv);
if (!rv_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
auto exp_size = SAFEOP::checked_sub<size_t>(rv_size.value(), 1);
if (!exp_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
// YYC MARK:
// According to Microsoft, the return value of this function is just a bullshit.
// If "wvalue" is big enough, the range of return value is [0, wvalue.size()],
// indicating the size of final string, excluding NULL terminal.
// otherwise, the range of return value is [wvalue.size() + 1, +inf),
// indicating the required size of buffer including NULL terminal.
// So we must treat this return value carefully.
if (exp_size.value() <= wvalue.size()) {
// Okey, shrink to the real size of value and break.
wvalue.resize(rv_size.value());
break;
} else {
// We need resize it and try again.
wvalue.resize(exp_size.value());
continue;
}
}
}
// Convert back to UTF8 string and return.
return ENC::to_utf8(wvalue).transform_error([](auto err) { return EnvError::BadEncoding; });
#else
// String view is not NULL-terminal-guaranted,
// so we solve this when casting its type.
auto ordinary_name = REINTERPRET::as_ordinary(name);
// Fetch variable
auto finder = std::getenv(ordinary_name.c_str());
if (finder == nullptr) return std::unexpected(EnvError::NoSuchName);
else return REINTERPRET::as_utf8(finder);
#endif
}
EnvResult<void> set_var(const std::u8string_view &name, const std::u8string_view &value) {
#if defined(YYCC_OS_WINDOWS)
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
// Convert to wchar
auto wname = ENC::to_wchar(name);
auto wvalue = ENC::to_wchar(value);
if (!(wname.has_value() && wvalue.has_value())) {
return std::unexpected(EnvError::BadEncoding);
}
// Delete variable and check result.
auto rv = ::SetEnvironmentVariableW(wname.value().c_str(), wvalue.value().c_str());
if (!rv) {
return std::unexpected(EnvError::BadCall);
}
return {};
#else
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html
// Set variable
auto ordinary_name = REINTERPRET::as_ordinary(name);
auto ordinary_value = REINTERPRET::as_ordinary(value);
auto rv = setenv(ordinary_name.c_str(), ordinary_value.c_str(), 1);
if (rv == 0) return {};
// Check error type
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
else if (errno == ENOMEM) return std::unexpected(EnvError::NoMemory);
else throw std::runtime_error("impossible errno");
#endif
}
EnvResult<void> del_var(const std::u8string_view &name) {
#if defined(YYCC_OS_WINDOWS)
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
// Convert to wchar
auto wname = ENC::to_wchar(name);
if (!wname.has_value()) {
return std::unexpected(EnvError::BadEncoding);
}
// Delete variable and check result.
auto rv = ::SetEnvironmentVariableW(wname.value().c_str(), NULL);
if (!rv) {
return std::unexpected(EnvError::BadCall);
}
return {};
#else
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html
// Delete variable
auto ordinary_name = REINTERPRET::as_ordinary(name);
auto rv = unsetenv(ordinary_name.c_str());
if (rv == 0) return {};
// Check error type
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
else throw std::runtime_error("impossible errno");
#endif
}
} // namespace yycc::rust::env

62
src/yycc/rust/env.hpp Normal file
View File

@ -0,0 +1,62 @@
#pragma once
#include <string>
#include <string_view>
#include <expected>
/**
* @brief The namespace providing environment variable operations.
* @details
* When I programming with Rust, I was astonished that
* Rust standard library have so much robust environment variable operations.
* Oppositly, C++ STL still lake in this even in today.
*
* The functions manipulating environment variable is different in different OS.
* I create this namespace inspired from Rust standard library
* to glue all these things up and make a uniform interface.
*/
namespace yycc::rust::env {
/// @brief The error occurs in this module.
enum class EnvError {
NoSuchName, ///< The variable with given name is not presented.
BadEncoding, ///< Error when performing encoding convertion.
BadArithmetic, ///< Error when performing arithmetic operations.
BadCall, ///< Error occurs when calling backend functions.
BadName, ///< Given name is ill-formated (empty string or has "=" character).
NoMemory, ///< No enough memory to finish this operation.
};
/// @brief The result type in this module.
template<typename T>
using EnvResult = std::expected<T, EnvError>;
/**
* @brief Get the value of given environment variable name.
* @param[in] name The name of environment variable
* @return Gotten value, or error occurs.
*/
EnvResult<std::u8string> get_var(const std::u8string_view& name);
/**
* @brief Set the value of given environment variable name.
* @details
* If there is no such name variable presented in environment,
* a new variable will be created,
* otherwise, new value will overwrite old value.
* @param[in] name The name of environment variable
* @param[in] value The value to be written into.
* @return Nothing or error occurs.
*/
EnvResult<void> set_var(const std::u8string_view& name, const std::u8string_view& value);
/**
* @brief Delete environment variable with given name.
* @details
* If given variable is not presented in environment,
* this function will NOT return error.
* @param[in] name The name of environment variable
* @return Nothing, or error occurs.
*/
EnvResult<void> del_var(const std::u8string_view& name);
} // namespace yycc::rust::env

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

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

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

@ -0,0 +1,41 @@
#include "panic.hpp"
#include "../carton/termcolor.hpp"
#include "../string/reinterpret.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
#define TERMCOLOR ::yycc::carton::termcolor
#define REINTERPRET ::yycc::string::reinterpret
namespace yycc::rust::panic {
// TODO:
// I sadly remove the stacktrace feature for panic function.
// Because in GCC, it has link error (can be fixed by extra link option).
// In Clang, it even lack the whole header file.
// It seems that STL providers are not ready for this feature. So I decide remove it entirely.
// Once every STL probiders have ready for this, I will add it back.
void panic(const char* file, int line, const std::string_view& msg) {
// Output message in stderr.
auto& dst = std::cerr;
// Print error message if we support it.
// Setup color
dst << REINTERPRET::as_ordinary_view(TERMCOLOR::foreground(TERMCOLOR::Color::Red));
// File name and line number message
dst << "program paniked at " << std::quoted(file) << ":Ln" << line << std::endl;
// User custom message
dst << "note: " << msg << std::endl;
// Restore color
dst << REINTERPRET::as_ordinary_view(TERMCOLOR::reset());;
// Make sure all messages are flushed into screen.
dst.flush();
// Force exit
std::abort();
}
} // namespace yycc::rust::panic

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