Compare commits
14 Commits
50dd086b53
...
master
Author | SHA1 | Date | |
---|---|---|---|
e166dc41ac | |||
a6382d6a22 | |||
adc99274f4 | |||
3abd0969c0 | |||
28ff7008a8 | |||
ab8d74efe6 | |||
df3b602110 | |||
bec36b4b3c | |||
0b7e58c8e8 | |||
831fa130bc | |||
7adac00035 | |||
0cd9582757 | |||
2206825223 | |||
21f7e7f786 |
314
.clang-format
Normal file
314
.clang-format
Normal file
@ -0,0 +1,314 @@
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/clang-format.json
|
||||
---
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseArrows: false
|
||||
AlignCaseColons: false
|
||||
AlignConsecutiveTableGenBreakingDAGArgColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveTableGenCondOperatorColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveTableGenDefinitionColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowBreakBeforeNoexceptSpecifier: Never
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseExpressionOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortCompoundRequirementOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BreakAdjacentStringLiterals: true
|
||||
BreakAfterAttributes: Leave
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakAfterReturnType: None
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakFunctionDefinitionParameters: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: false
|
||||
BreakTemplateDeclarations: Yes
|
||||
ColumnLimit: 100
|
||||
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: CaseSensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: Lexicographic
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterPlacementOperator: true
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
ExceptDoubleParentheses: false
|
||||
InCStyleCasts: false
|
||||
InConditionalStatements: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
- emit
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
- Q_CLASSINFO
|
||||
- Q_ENUM
|
||||
- Q_ENUM_NS
|
||||
- Q_FLAG
|
||||
- Q_FLAG_NS
|
||||
- Q_GADGET
|
||||
- Q_GADGET_EXPORT
|
||||
- Q_INTERFACES
|
||||
- Q_LOGGING_CATEGORY
|
||||
- Q_MOC_INCLUDE
|
||||
- Q_NAMESPACE
|
||||
- Q_NAMESPACE_EXPORT
|
||||
- Q_OBJECT
|
||||
- Q_PROPERTY
|
||||
- Q_REVISION
|
||||
- Q_DISABLE_COPY
|
||||
- Q_DISABLE_COPY_MOVE
|
||||
- Q_SET_OBJECT_NAME
|
||||
- QT_BEGIN_NAMESPACE
|
||||
- QT_END_NAMESPACE
|
||||
- QML_ADDED_IN_MINOR_VERSION
|
||||
- QML_ANONYMOUS
|
||||
- QML_ATTACHED
|
||||
- QML_DECLARE_TYPE
|
||||
- QML_DECLARE_TYPEINFO
|
||||
- QML_ELEMENT
|
||||
- QML_EXTENDED
|
||||
- QML_EXTENDED_NAMESPACE
|
||||
- QML_EXTRA_VERSION
|
||||
- QML_FOREIGN
|
||||
- QML_FOREIGN_NAMESPACE
|
||||
- QML_IMPLEMENTS_INTERFACES
|
||||
- QML_INTERFACE
|
||||
- QML_NAMED_ELEMENT
|
||||
- QML_REMOVED_IN_MINOR_VERSION
|
||||
- QML_SINGLETON
|
||||
- QML_UNAVAILABLE
|
||||
- QML_UNCREATABLE
|
||||
- QML_VALUE_TYPE
|
||||
- YYCC_DELETE_COPY
|
||||
- YYCC_DELETE_MOVE
|
||||
- YYCC_DELETE_COPY_MOVE
|
||||
- YYCC_DEFAULT_COPY
|
||||
- YYCC_DEFAULT_MOVE
|
||||
- YYCC_DEFAULT_COPY_MOVE
|
||||
TableGenBreakInsideDAGArg: DontBreak
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
- BOOST_PP_STRINGIZE
|
||||
- CF_SWIFT_NAME
|
||||
- NS_SWIFT_NAME
|
||||
- PP_STRINGIZE
|
||||
- STRINGIZE
|
||||
...
|
||||
|
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@ -0,0 +1,3 @@
|
||||
[*.{cpp,hpp}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
# -------------------- Output --------------------
|
||||
out/
|
||||
src/YYCC/YYCCVersion.hpp
|
||||
CMakeSettings.json
|
||||
|
||||
# -------------------- VSCode --------------------
|
||||
|
@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.23)
|
||||
project(YYCC
|
||||
VERSION 1.2.0
|
||||
VERSION 2.0.0
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
@ -20,6 +20,14 @@ 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 ()
|
||||
|
||||
# Import 3 build targets
|
||||
add_subdirectory(src)
|
||||
if (YYCC_BUILD_TESTBENCH)
|
||||
@ -46,7 +54,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 +62,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
|
||||
)
|
||||
|
||||
|
@ -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}
|
||||
)
|
||||
|
@ -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
35
doc/src/enum_helper.dox
Normal 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.
|
||||
|
||||
*/
|
||||
}
|
@ -45,6 +45,8 @@
|
||||
|
||||
\li \subpage std_patch
|
||||
|
||||
\li \subpage enum_helper
|
||||
|
||||
<B>Advanced Features</B>
|
||||
|
||||
\li \subpage constraints
|
||||
|
@ -116,11 +116,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>.
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
||||
|
1
script/.gitignore
vendored
1
script/.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
# -------------------- Output --------------------
|
||||
win_build.bat
|
||||
linux_build.sh
|
||||
|
108
script/gen_build_script.py
Normal file
108
script/gen_build_script.py
Normal file
@ -0,0 +1,108 @@
|
||||
import jinja2
|
||||
import argparse
|
||||
import os
|
||||
import io
|
||||
import re
|
||||
import shlex
|
||||
|
||||
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_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)
|
||||
|
||||
class ScriptSettings:
|
||||
m_CppVersion: str
|
||||
m_BuildDoc: bool
|
||||
m_PIC: bool
|
||||
|
||||
def __init__(self, cpp_ver: str, build_doc: bool, pic: bool):
|
||||
self.m_CppVersion = cpp_ver
|
||||
self.m_BuildDoc = build_doc
|
||||
self.m_PIC = pic
|
||||
|
||||
class TemplateRender:
|
||||
m_Loader: jinja2.BaseLoader
|
||||
m_Environment: jinja2.Environment
|
||||
|
||||
m_WinTemplate: jinja2.Template
|
||||
m_LinuxTemplate: jinja2.Template
|
||||
|
||||
m_Settings: ScriptSettings
|
||||
|
||||
def __init__(self, settings: ScriptSettings) -> None:
|
||||
self.m_Loader = jinja2.FileSystemLoader(self.__get_dir())
|
||||
self.m_Environment = jinja2.Environment(loader=self.m_Loader)
|
||||
|
||||
self.m_WinTemplate = self.m_Environment.get_template('win_build.template.bat')
|
||||
self.m_LinuxTemplate = self.m_Environment.get_template('linux_build.template.sh')
|
||||
|
||||
self.m_Settings = settings
|
||||
|
||||
def __get_dir(self) -> str:
|
||||
return os.path.dirname(__file__)
|
||||
|
||||
def __escape_path(self, val: str, is_win: bool) -> str:
|
||||
if is_win: return escape_cmd_argument(val)
|
||||
else: return escape_sh_argument(val)
|
||||
|
||||
def __render(self, template: jinja2.Template, dest_file: str, is_win: bool) -> None:
|
||||
with open(os.path.join(self.__get_dir(), dest_file), 'w', encoding='utf-8') as f:
|
||||
f.write(template.render(
|
||||
repo_root_dir = self.__escape_path(os.path.dirname(self.__get_dir()), is_win),
|
||||
cpp_version = self.m_Settings.m_CppVersion,
|
||||
build_doc = self.m_Settings.m_BuildDoc,
|
||||
pic = settings.m_PIC
|
||||
))
|
||||
|
||||
def render_win_script(self) -> None:
|
||||
self.__render(self.m_WinTemplate, 'win_build.bat', True)
|
||||
|
||||
def render_linux_script(self) -> None:
|
||||
self.__render(self.m_LinuxTemplate, 'linux_build.sh', False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# parse argument
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='YYCC Windows Build Script Generator',
|
||||
description='YYCC Windows Build Script Generator'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-c', '--cpp',
|
||||
action='store', default='17', dest='cpp', type=validate_cpp_ver,
|
||||
help='The version of C++ standard used when building.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-d', '--build-doc',
|
||||
action='store_true', dest='build_doc',
|
||||
help='Build YYCC without documentation.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--pic',
|
||||
action='store_true', dest='pic',
|
||||
help='Enable Position Independent Code flag on non-Windows platform. This is crucial for compiling dynamic library using this library.'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# build settings
|
||||
settings = ScriptSettings(args.cpp, args.build_doc, args.pic)
|
||||
# build template render and render result
|
||||
render = TemplateRender(settings)
|
||||
render.render_win_script()
|
||||
render.render_linux_script()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -1,9 +1,6 @@
|
||||
#!/bin/bash
|
||||
README_PATH=$(pwd)/README.md
|
||||
if [ ! -f "$README_PATH" ]; then
|
||||
echo "Error: You must run this script at the root folder of this project!"
|
||||
exit
|
||||
fi
|
||||
# Navigate to project root directory
|
||||
cd {{ repo_root_dir }}
|
||||
|
||||
# Create main binary directory
|
||||
mkdir bin
|
||||
@ -19,10 +16,10 @@ cd ..
|
||||
|
||||
# Build current system debug and release version
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug ../.. --fresh
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} ../.. --fresh
|
||||
cmake --build .
|
||||
cmake --install . --prefix ../install/Debug
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DYYCC_BUILD_TESTBENCH=ON ../.. --fresh
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} -DYYCC_BUILD_TESTBENCH=ON ../.. --fresh
|
||||
cmake --build .
|
||||
cmake --install . --prefix ../install/Release
|
||||
cd ..
|
@ -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
|
85
script/win_build.template.bat
Normal file
85
script/win_build.template.bat
Normal 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
|
@ -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
|
@ -1,48 +1,87 @@
|
||||
# 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/rust/panic.cpp
|
||||
# YYCC/COMHelper.cpp
|
||||
# YYCC/ArgParser.cpp
|
||||
# YYCC/ConfigManager.cpp
|
||||
# YYCC/ConsoleHelper.cpp
|
||||
# YYCC/DialogHelper.cpp
|
||||
# YYCC/EncodingHelper.cpp
|
||||
# YYCC/ExceptionHelper.cpp
|
||||
# YYCC/StdPatch.cpp
|
||||
# YYCC/IOHelper.cpp
|
||||
# YYCC/StringHelper.cpp
|
||||
# YYCC/WinFctHelper.cpp
|
||||
# # Natvis (only for MSVC)
|
||||
# $<$<CXX_COMPILER_ID:MSVC>:YYCC.natvis>
|
||||
)
|
||||
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/prelude/core.hpp
|
||||
yycc/prelude/rust.hpp
|
||||
yycc/macro/version_cmp.hpp
|
||||
yycc/macro/feature_probe.hpp
|
||||
yycc/macro/os_detector.hpp
|
||||
yycc/macro/class_copy_move.hpp
|
||||
yycc/string.hpp
|
||||
yycc/string/reinterpret.hpp
|
||||
yycc/string/op.hpp
|
||||
yycc/string/parse.hpp
|
||||
yycc/string/stringify.hpp
|
||||
yycc/rust/primitive.hpp
|
||||
yycc/rust/panic.hpp
|
||||
yycc/rust/option.hpp
|
||||
yycc/rust/result.hpp
|
||||
yycc/rust/parse.hpp
|
||||
yycc/rust/stringify.hpp
|
||||
yycc/windows/unsafe_suppressor.hpp
|
||||
yycc/windows/import_guard_head.hpp
|
||||
yycc/windows/import_guard_tail.hpp
|
||||
yycc/constraint.hpp
|
||||
yycc/constraint/builder.hpp
|
||||
|
||||
# # Headers
|
||||
# # Common headers
|
||||
# YYCC/Constraints.hpp
|
||||
# YYCC/COMHelper.hpp
|
||||
# YYCC/ArgParser.hpp
|
||||
# YYCC/ConfigManager.hpp
|
||||
# YYCC/ConsoleHelper.hpp
|
||||
# YYCC/DialogHelper.hpp
|
||||
# YYCC/EncodingHelper.hpp
|
||||
# YYCC/EnumHelper.hpp
|
||||
# YYCC/ExceptionHelper.hpp
|
||||
# YYCC/StdPatch.hpp
|
||||
# YYCC/IOHelper.hpp
|
||||
# YYCC/ParserHelper.hpp
|
||||
# YYCC/StringHelper.hpp
|
||||
# YYCC/WinFctHelper.hpp
|
||||
# # Windows including guard pair
|
||||
# YYCC/WinImportPrefix.hpp
|
||||
# YYCC/WinImportSuffix.hpp
|
||||
# # Internal
|
||||
# YYCC/YYCCVersion.hpp
|
||||
# YYCC/YYCCInternal.hpp
|
||||
# # Exposed
|
||||
# YYCCommonplace.hpp
|
||||
)
|
||||
# Setup header infomations
|
||||
target_include_directories(YYCCommonplace
|
||||
|
@ -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
|
||||
|
||||
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "IOHelper.hpp"
|
||||
#include "EnumHelper.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace YYCC::ConfigManager {
|
||||
@ -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() {
|
@ -18,6 +18,19 @@
|
||||
*/
|
||||
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.
|
||||
class AbstractSetting {
|
||||
@ -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();
|
182
src/YYCCLegacy/EnumHelper.hpp
Normal file
182
src/YYCCLegacy/EnumHelper.hpp
Normal file
@ -0,0 +1,182 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
/**
|
||||
* @brief The namespace for convenient C++ enum class logic operations.
|
||||
* @details
|
||||
* C++ enum class statement is a modern way to declare enum in C++.
|
||||
* But it lack essential logic operations which is commonly used by programmer.
|
||||
* So we create this helper to resolve this issue.
|
||||
*/
|
||||
namespace YYCC::EnumHelper {
|
||||
|
||||
//// Reference:
|
||||
//// Enum operator overload: https://stackoverflow.com/a/71107019
|
||||
//// Constexpr operator overload: https://stackoverflow.com/a/17746099
|
||||
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator|(TEnum lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(static_cast<ut>(lhs) | static_cast<ut>(rhs));
|
||||
//}
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator|=(TEnum& lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// lhs = lhs | rhs;
|
||||
// return lhs;
|
||||
//}
|
||||
//
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator&(TEnum lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(static_cast<ut>(lhs) & static_cast<ut>(rhs));
|
||||
//}
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator&=(TEnum& lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// lhs = lhs & rhs;
|
||||
// return lhs;
|
||||
//}
|
||||
//
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator^(TEnum lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(static_cast<ut>(lhs) ^ static_cast<ut>(rhs));
|
||||
//}
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator^=(TEnum& lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// lhs = lhs ^ rhs;
|
||||
// return lhs;
|
||||
//}
|
||||
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator~(TEnum lhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(~(static_cast<ut>(lhs)));
|
||||
//}
|
||||
//
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr bool operator bool(TEnum lhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<bool>(static_cast<ut>(lhs));
|
||||
//}
|
||||
|
||||
/**
|
||||
* @brief The helper struct to check all given template argument are the same enum type.
|
||||
* @tparam TEnum The template parameter to be checked (first one).
|
||||
* @tparam Ts The template parameter to be checked.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
struct all_enum_values {
|
||||
public:
|
||||
// Please note it is std::is_same, not std::is_same_v!
|
||||
// That's std::conjunction_v required.
|
||||
static constexpr bool value = std::is_enum_v<std::remove_cv_t<TEnum>> && std::conjunction_v<std::is_same<std::remove_cv_t<TEnum>, std::remove_cv_t<Ts>>...>;
|
||||
};
|
||||
/**
|
||||
* @brief The convenient calling to all_enum_values::value to check enum template parameter.
|
||||
* @tparam TEnum The template parameter to be checked (first one).
|
||||
* @tparam Ts The template parameter to be checked.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
inline constexpr bool all_enum_values_v = all_enum_values<TEnum, Ts...>::value;
|
||||
|
||||
/**
|
||||
* @brief Merge given enum flags like performing <TT>e1 | e2 | ... | en</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in] val The first enum flag to be merged.
|
||||
* @param[in] val_left Left enum flags to be merged.
|
||||
* @return The merged enum flag.
|
||||
* @remarks
|
||||
* This function use recursive expansion to get final merge result.
|
||||
* So there is no difference of each arguments.
|
||||
* We independ first argument just served for expansion.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum, Ts...>, int> = 0>
|
||||
constexpr TEnum Merge(TEnum val, Ts... val_left) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
ut result = static_cast<ut>(val);
|
||||
if constexpr (sizeof...(val_left) > 0) {
|
||||
result |= static_cast<ut>(Merge(val_left...));
|
||||
}
|
||||
return static_cast<TEnum>(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reverse given enum flags like performing <TT>~(e)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in] e The list of enum flags to be inversed.
|
||||
* @return The inversed enum flag.
|
||||
*/
|
||||
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
constexpr TEnum Invert(TEnum e) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<TEnum>(~(static_cast<ut>(e)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Use specified enum flag to mask given enum flag like performing <TT>e1 &= e2</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in,out] e1 The enum flags to be masked.
|
||||
* @param[in] e2 The mask enum flag.
|
||||
*/
|
||||
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
constexpr void Mask(TEnum& e1, TEnum e2) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(e2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add multiple enum flags to given enum flag like performing <TT>e1 |= (e2 | e3 | ... | en)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in,out] e1 The enum flag which flags add on.
|
||||
* @param[in] vals The enum flag to be added.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum, Ts...>, int> = 0>
|
||||
constexpr void Add(TEnum& e1, Ts... vals) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
e1 = static_cast<TEnum>(static_cast<ut>(e1) | static_cast<ut>(Merge(vals...)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove multiple enum flags from given enum flag like performing <TT>e1 &= ~(e2 | e3 | ... | en)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in,out] e1 The enum flag which flags removed from.
|
||||
* @param[in] vals The enum flag to be removed.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum>, int> = 0>
|
||||
constexpr void Remove(TEnum& e1, Ts... vals) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(Invert(Merge(vals...))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether given enum flag has any of specified multiple enum flags (OR) like performing <TT>bool(e1 & (e2 | e3 | ... | en))</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in] e1 The enum flag where we check.
|
||||
* @param[in] vals The enum flags for checking.
|
||||
* @return True if it has any of given flags (OR), otherwise false.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
constexpr bool Has(TEnum e1, Ts... vals) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<bool>(static_cast<ut>(e1) & static_cast<ut>(Merge(vals...)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cast given enum flags to its equvalent boolean value like performing <TT>bool(e)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param e The enum flags to be cast.
|
||||
* @return The equvalent bool value of given enum flag.
|
||||
*/
|
||||
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
constexpr bool Bool(TEnum e) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<bool>(static_cast<ut>(e));
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#pragma region Library Version and Comparison Macros
|
||||
|
||||
#include "YYCCVersion.hpp"
|
||||
|
||||
/// @brief Return true if left version number is equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3) ((av1) == (bv1) && (av2) == (bv2) && (av3) == (bv3))
|
||||
/// @brief Return true if left version number is not equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_NE(av1, av2, av3, bv1, bv2, bv3) (!YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
|
||||
/// @brief Return true if left version number is greater than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_G(av1, av2, av3, bv1, bv2, bv3) ( \
|
||||
((av1) > (bv1)) || \
|
||||
((av1) == (bv1) && (av2) > (bv2)) || \
|
||||
((av1) == (bv1) && (av2) == (bv2) && (av3) > (bv3)) \
|
||||
)
|
||||
/// @brief Return true if left version number is greater than or equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_GE(av1, av2, av3, bv1, bv2, bv3) (YYCC_VERCMP_G(av1, av2, av3, bv1, bv2, bv3) || YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
|
||||
/// @brief Return true if left version number is not lower than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_NL(av1, av2, av3, bv1, bv2, bv3) YYCC_VERCMP_GE(av1, av2, av3, bv1, bv2, bv3)
|
||||
/// @brief Return true if left version number is lower than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_L(av1, av2, av3, bv1, bv2, bv3) ( \
|
||||
((av1) < (bv1)) || \
|
||||
((av1) == (bv1) && (av2) < (bv2)) || \
|
||||
((av1) == (bv1) && (av2) == (bv2) && (av3) < (bv3)) \
|
||||
)
|
||||
/// @brief Return true if left version number is lower than or equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_LE(av1, av2, av3, bv1, bv2, bv3) (YYCC_VERCMP_L(av1, av2, av3, bv1, bv2, bv3) || YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
|
||||
/// @brief Return true if left version number is not greater than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_NG(av1, av2, av3, bv1, bv2, bv3) YYCC_VERCMP_LE(av1, av2, av3, bv1, bv2, bv3)
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Operating System Identifier Macros
|
||||
|
||||
// Define operating system macros
|
@ -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"
|
18
src/YYCCommonplaceLegacy.hpp
Normal file
18
src/YYCCommonplaceLegacy.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "YYCC/YYCCInternal.hpp"
|
||||
|
||||
#include "YYCC/EncodingHelper.hpp"
|
||||
#include "YYCC/StringHelper.hpp"
|
||||
#include "YYCC/ConsoleHelper.hpp"
|
||||
#include "YYCC/COMHelper.hpp"
|
||||
#include "YYCC/DialogHelper.hpp"
|
||||
#include "YYCC/ParserHelper.hpp"
|
||||
#include "YYCC/IOHelper.hpp"
|
||||
#include "YYCC/WinFctHelper.hpp"
|
||||
#include "YYCC/StdPatch.hpp"
|
||||
#include "YYCC/EnumHelper.hpp"
|
||||
#include "YYCC/ExceptionHelper.hpp"
|
||||
|
||||
#include "YYCC/ConfigManager.hpp"
|
||||
#include "YYCC/ArgParser.hpp"
|
16
src/yycc.hpp
Normal file
16
src/yycc.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
// Library Version and Comparison Macros
|
||||
#include "yycc/version.hpp"
|
||||
#include "yycc/macro/version_cmp.hpp"
|
||||
|
||||
// Operating System Identifier Macros
|
||||
#include "yycc/macro/os_detector.hpp"
|
||||
|
||||
// Windows Shitty Behavior Disable Macros
|
||||
#include "yycc/windows/unsafe_suppressor.hpp"
|
||||
|
||||
// Batch Class Move / Copy Function Macros
|
||||
#include "yycc/macro/class_copy_move.hpp"
|
||||
|
||||
namespace yycc {}
|
0
src/yycc/binstore/kernel.hpp
Normal file
0
src/yycc/binstore/kernel.hpp
Normal file
0
src/yycc/clap/kernel.hpp
Normal file
0
src/yycc/clap/kernel.hpp
Normal file
78
src/yycc/constraint.hpp
Normal file
78
src/yycc/constraint.hpp
Normal 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
|
67
src/yycc/constraint/builder.hpp
Normal file
67
src/yycc/constraint/builder.hpp
Normal file
@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include "../constraint.hpp"
|
||||
#include "../string.hpp"
|
||||
#include <set>
|
||||
|
||||
#define NS_YYCC_STRING ::yycc::string
|
||||
|
||||
/// @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.
|
||||
* @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) {
|
||||
std::set<T> data(il);
|
||||
|
||||
auto fn_check = [data](const T& val) -> bool { return data.find(val) != data.end(); };
|
||||
return Constraint<T>(std::move(fn_check), nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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<NS_YYCC_STRING::u8string> GetStringEnumerationConstraint(
|
||||
const std::initializer_list<NS_YYCC_STRING::u8string_view>& il) {
|
||||
std::set<NS_YYCC_STRING::u8string_view> data(il);
|
||||
|
||||
auto fn_check = [data](const NS_YYCC_STRING::u8string& val) -> bool {
|
||||
return data.find(NS_YYCC_STRING::u8string_view(val)) != data.end();
|
||||
};
|
||||
return Constraint<NS_YYCC_STRING::u8string>(std::move(fn_check), nullptr);
|
||||
}
|
||||
|
||||
} // namespace yycc::constraint::builder
|
||||
|
||||
#undef NS_YYCC_STRING
|
31
src/yycc/macro/class_copy_move.hpp
Normal file
31
src/yycc/macro/class_copy_move.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief Explicitly remove copy (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&) = delete; \
|
||||
CLSNAME& operator=(const CLSNAME&) = delete;
|
||||
|
||||
/// @brief Explicitly remove move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = delete; \
|
||||
CLSNAME& operator=(CLSNAME&&) = delete;
|
||||
|
||||
/// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DELETE_COPY(CLSNAME) \
|
||||
YYCC_DELETE_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Explicitly set default copy (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&) = default; \
|
||||
CLSNAME& operator=(const CLSNAME&) = default;
|
||||
|
||||
/// @brief Explicitly set default move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = default; \
|
||||
CLSNAME& operator=(CLSNAME&&) = default;
|
||||
|
||||
/// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DEFAULT_COPY(CLSNAME) \
|
||||
YYCC_DEFAULT_MOVE(CLSNAME)
|
49
src/yycc/macro/feature_probe.hpp
Normal file
49
src/yycc/macro/feature_probe.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
// Hint for C++ feature detection:
|
||||
// __cplusplus macro need special compiler switch enabled when compiling.
|
||||
// So we use _MSVC_LANG check it instead.
|
||||
|
||||
// ===== C++ Version =====
|
||||
|
||||
// Detect C++ 20
|
||||
#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
|
||||
#define YYCC_CPPFEAT_GE_CPP20
|
||||
#endif
|
||||
|
||||
// Detect C++ 23
|
||||
#if __cplusplus >= 202302L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202302L)
|
||||
#define YYCC_CPPFEAT_GE_CPP23
|
||||
#endif
|
||||
|
||||
// ===== C++ Features =====
|
||||
|
||||
// Check whether there is support of `contains` for `set` and `map` including their varients.
|
||||
#if defined(YYCC_CPPFEAT_GE_CPP20)
|
||||
#define YYCC_CPPFEAT_CONTAINS
|
||||
#endif
|
||||
|
||||
// Check whether there is support of `starts_with` and `ends_with` for `basic_string`.
|
||||
#if defined(__cpp_lib_starts_ends_with) || defined(YYCC_CPPFEAT_GE_CPP20)
|
||||
#define YYCC_CPPFEAT_STARTS_ENDS_WITH
|
||||
#endif
|
||||
|
||||
// Check whether there is support of `std::expected`.
|
||||
#if defined(__cpp_lib_expected) || defined(YYCC_CPPFEAT_GE_CPP23)
|
||||
#define YYCC_CPPFEAT_EXPECTED
|
||||
#endif
|
||||
|
||||
// Check whether there is support of `std::format`.
|
||||
#if defined(YYCC_CPPFEAT_GE_CPP20)
|
||||
#define YYCC_CPPFEAT_FORMAT
|
||||
#endif
|
||||
|
||||
// Check whether there is support of `__VA_OPT__`
|
||||
#if defined(YYCC_CPPFEAT_GE_CPP20)
|
||||
#define YYCC_CPPFEAT_VA_OPT
|
||||
#endif
|
||||
|
||||
// Check whether there is support of `std::stacktrace` and its formatter.
|
||||
#if (defined(__cpp_lib_starts_ends_with) && defined(__cpp_lib_formatters)) || defined(YYCC_CPPFEAT_GE_CPP23)
|
||||
#define YYCC_CPPFEAT_STACKTRACE
|
||||
#endif
|
11
src/yycc/macro/os_detector.hpp
Normal file
11
src/yycc/macro/os_detector.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
// Define operating system macros
|
||||
#define YYCC_OS_WINDOWS 2
|
||||
#define YYCC_OS_LINUX 3
|
||||
// Check current operating system.
|
||||
#if defined(_WIN32)
|
||||
#define YYCC_OS YYCC_OS_WINDOWS
|
||||
#else
|
||||
#define YYCC_OS YYCC_OS_LINUX
|
||||
#endif
|
26
src/yycc/macro/version_cmp.hpp
Normal file
26
src/yycc/macro/version_cmp.hpp
Normal 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)
|
0
src/yycc/patch/container.hpp
Normal file
0
src/yycc/patch/container.hpp
Normal file
0
src/yycc/patch/path.hpp
Normal file
0
src/yycc/patch/path.hpp
Normal file
0
src/yycc/patch/string.hpp
Normal file
0
src/yycc/patch/string.hpp
Normal file
18
src/yycc/prelude/core.hpp
Normal file
18
src/yycc/prelude/core.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
// Prelude section
|
||||
#include "../string.hpp"
|
||||
namespace yycc::prelude {
|
||||
|
||||
#define NS_YYCC_STRING ::yycc::string
|
||||
|
||||
using u8char = NS_YYCC_STRING::u8char;
|
||||
using u8string = NS_YYCC_STRING::u8string;
|
||||
using u8string_view = NS_YYCC_STRING::u8string_view;
|
||||
|
||||
#undef NS_YYCC_STRING
|
||||
|
||||
} // namespace yycc::prelude
|
||||
|
||||
// Expose all members
|
||||
using namespace yycc::prelude;
|
52
src/yycc/prelude/rust.hpp
Normal file
52
src/yycc/prelude/rust.hpp
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
// Include YYCC prelude first
|
||||
#include "core.hpp"
|
||||
|
||||
// Rust prelude section
|
||||
#include "../rust/primitive.hpp"
|
||||
#include "../rust/result.hpp"
|
||||
#include "../rust/option.hpp"
|
||||
#include "../rust/panic.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace yycc::prelude::rust {
|
||||
// Include primitive types
|
||||
|
||||
#define NS_RUST_PRIMITIVE ::yycc::rust::primitive
|
||||
|
||||
using i8 = NS_RUST_PRIMITIVE::i8;
|
||||
using i16 = NS_RUST_PRIMITIVE::i16;
|
||||
using i32 = NS_RUST_PRIMITIVE::i32;
|
||||
using i64 = NS_RUST_PRIMITIVE::i64;
|
||||
using u8 = NS_RUST_PRIMITIVE::u8;
|
||||
using u16 = NS_RUST_PRIMITIVE::u16;
|
||||
using u32 = NS_RUST_PRIMITIVE::u32;
|
||||
using u64 = NS_RUST_PRIMITIVE::u64;
|
||||
|
||||
using isize = NS_RUST_PRIMITIVE::isize;
|
||||
using usize = NS_RUST_PRIMITIVE::usize;
|
||||
|
||||
using f32 = NS_RUST_PRIMITIVE::f32;
|
||||
using f64 = NS_RUST_PRIMITIVE::f64;
|
||||
|
||||
using str = NS_RUST_PRIMITIVE::str;
|
||||
|
||||
#undef NS_RUST_PRIMITIVE
|
||||
|
||||
// Other types
|
||||
using String = ::yycc::string::u8string;
|
||||
template<typename T>
|
||||
using Vec = std::vector<T>;
|
||||
|
||||
// Expose Result and Option
|
||||
using namespace ::yycc::rust::option;
|
||||
using namespace ::yycc::rust::result;
|
||||
|
||||
// Panic are introduced by including header file
|
||||
// so we do not need re-expose it.
|
||||
|
||||
} // namespace yycc::prelude::rust
|
||||
|
||||
// Expose all members
|
||||
using namespace yycc::prelude::rust;
|
23
src/yycc/rust/option.hpp
Normal file
23
src/yycc/rust/option.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#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
|
43
src/yycc/rust/panic.cpp
Normal file
43
src/yycc/rust/panic.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "panic.hpp"
|
||||
#include "../macro/feature_probe.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#if defined(YYCC_CPPFEAT_STACKTRACE)
|
||||
#include <stacktrace>
|
||||
#endif
|
||||
|
||||
namespace yycc::rust::panic {
|
||||
|
||||
void panic(const char* file, int line, const std::string_view& msg) {
|
||||
// Output message in stderr.
|
||||
auto& dst = std::cerr;
|
||||
|
||||
// TODO: Fix colorful output when finishing `termcolor` lib.
|
||||
|
||||
// Print error message if we support it.
|
||||
// // Setup color
|
||||
// dst << FOREGROUND<Color::Red>;
|
||||
// File name and line number message
|
||||
dst << "program paniked at " << std::quoted(file) << ":Ln" << line << std::endl;
|
||||
// User custom message
|
||||
dst << "note: " << msg << std::endl;
|
||||
// Stacktrace message if we support it.
|
||||
#if defined(YYCC_CPPFEAT_STACKTRACE)
|
||||
dst << "stacktrace: " << std::endl;
|
||||
dst << std::stacktrace::current() << std::endl;
|
||||
#else
|
||||
dst << "there is no stacktrace because your C++ runtime do not support it." << std::endl;
|
||||
#endif
|
||||
// // Restore color
|
||||
// dst << RESET;
|
||||
|
||||
// Make sure all messages are flushed into screen.
|
||||
dst.flush();
|
||||
|
||||
// Force exit
|
||||
std::abort();
|
||||
}
|
||||
|
||||
} // namespace yycc::rust::panic
|
60
src/yycc/rust/panic.hpp
Normal file
60
src/yycc/rust/panic.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include "../macro/feature_probe.hpp"
|
||||
#include <string_view>
|
||||
|
||||
#if defined(YYCC_CPPFEAT_FORMAT)
|
||||
#include <format>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Provides Rust-style panic functionality for immediate program termination on unrecoverable errors.
|
||||
* @details
|
||||
* This namespace provides macros and functions to handle unrecoverable errors in C++ code.
|
||||
* It imitate Rust's \c panic! macro behavior, allowing the program to immediately exit with error information and stack traces.
|
||||
*
|
||||
* After writing programs in Rust, I deeply realized the necessity of handling errors immediately.
|
||||
* When encountering unrecoverable errors, the program should exit immediately and report the error, which ensures program robustness.
|
||||
* Therefore, I introduced this namespace and implemented macros and functions equivalent to Rust's \c panic! macro.
|
||||
*
|
||||
* Unfortunately, I cannot change the exception mechanism in the standard library.
|
||||
* The standard library will still throw exceptions where it does, and I cannot prevent that.
|
||||
* Therefore, I suggest a good practice that any C++ exception should be immediately treated as an error and cause the program to crash and exit.
|
||||
* For this reason, registering any unhandled error callbacks which may resume the execution of program is prohibited to prevent unexpected continuation of execution.
|
||||
* For code we write ourselves that we can control, we should use the macros provided in this file instead of throwing exceptions.
|
||||
* In this way, unexpected behavior in our code will cause the program to exit immediately, outputting error information and stack traces.
|
||||
* Standard library exceptions will also cause the program to exit, but without stack information.
|
||||
*/
|
||||
namespace yycc::rust::panic {
|
||||
|
||||
/**
|
||||
* @brief Immediately crashes the entire program like Rust's \c panic! macro.
|
||||
* @details The macro parameter is the additional message to display.
|
||||
*/
|
||||
#define RS_PANIC(msg) ::yycc::rust::panic::panic(__FILE__, __LINE__, (msg))
|
||||
|
||||
/**
|
||||
* @brief Immediately crashes the entire program like Rust's \c panic! macro.
|
||||
* @details
|
||||
* The macro parameters are the message to format and its arguments, following \c std::format syntax.
|
||||
* This macro essentially calls \c std::format internally.
|
||||
*/
|
||||
#if defined(YYCC_CPPFEAT_FORMAT)
|
||||
#if defined(YYCC_CPPFEAT_VA_OPT)
|
||||
#define RS_PANICF(msg, ...) RS_PANIC(std::format(msg __VA_OPT__(, ) __VA_ARGS__))
|
||||
#else
|
||||
#define RS_PANICF(msg, ...) RS_PANIC(std::format(msg, ##__VA_ARGS__))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Immediately crashes the entire program like Rust's \c panic! macro.
|
||||
* @details
|
||||
* This is the actual crash output function called by the macros.
|
||||
* The crash information will be written to \c stderr, including stack traces if your C++ runtime support it.
|
||||
* @param[in] file Source file name where panic occurred. Usually filled by macros.
|
||||
* @param[in] line Line number in source file where panic occurred. Usually filled by macros.
|
||||
* @param[in] msg Message to display during panic.
|
||||
*/
|
||||
[[noreturn]] void panic(const char* file, int line, const std::string_view& msg);
|
||||
|
||||
} // namespace yycc::rust::panic
|
99
src/yycc/rust/parse.hpp
Normal file
99
src/yycc/rust/parse.hpp
Normal file
@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
#include "../macro/feature_probe.hpp"
|
||||
#include "../string/parse.hpp"
|
||||
#include "panic.hpp"
|
||||
#include "result.hpp"
|
||||
|
||||
#define NS_YYCC_STRING ::yycc::string
|
||||
#define NS_YYCC_STRING_PARSE ::yycc::string::parse
|
||||
#define NS_YYCC_RUST_RESULT ::yycc::rust::result
|
||||
|
||||
/**
|
||||
* @namespace yycc::rust::parse
|
||||
* @brief Provides Rust-inspired parsing utilities for converting strings to various types.
|
||||
* @details
|
||||
* This namespace contains template functions for parsing strings into different types
|
||||
* (floating-point, integral, boolean) with Rust-like Result error handling.
|
||||
*/
|
||||
namespace yycc::rust::parse {
|
||||
|
||||
#if defined(YYCC_CPPFEAT_EXPECTED)
|
||||
|
||||
/// @brief The error type of parsing.
|
||||
using Error = NS_YYCC_STRING_PARSE::ParseError;
|
||||
|
||||
/// @brief The result type of parsing.
|
||||
/// @tparam T The expected value type in result.
|
||||
template<typename T>
|
||||
using Result = NS_YYCC_RUST_RESULT::Result<T, Error>;
|
||||
|
||||
/**
|
||||
* @brief Parses a string into a floating-point value.
|
||||
* @tparam T Floating-point type to parse into (float, double, etc.)
|
||||
* @param strl String view to parse
|
||||
* @param fmt Formatting flags for parsing (default: general)
|
||||
* @return Result<T> containing either the parsed value or an error
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
|
||||
Result<T> parse(const NS_YYCC_STRING::u8string_view& strl,
|
||||
std::chars_format fmt = std::chars_format::general) {
|
||||
auto rv = NS_YYCC_STRING_PARSE::priv_parse<T>(strl, fmt);
|
||||
|
||||
if (const auto* ptr = std::get_if<T>(&rv)) {
|
||||
return NS_YYCC_RUST_RESULT::Ok<Result<T>>(*ptr);
|
||||
} else if (const auto* ptr = std::get_if<Error>(&rv)) {
|
||||
return NS_YYCC_RUST_RESULT::Err<Result<T>>(*ptr);
|
||||
} else {
|
||||
// Unreachable
|
||||
RS_PANIC("unreachable code.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses a string into an integral value (excluding bool).
|
||||
* @tparam T Integral type to parse into (int, long, etc.)
|
||||
* @param strl String view to parse
|
||||
* @param base Numeric base for parsing (default: 10)
|
||||
* @return Result<T> containing either the parsed value or an error
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
|
||||
Result<T> parse(const NS_YYCC_STRING::u8string_view& strl, int base = 10) {
|
||||
auto rv = NS_YYCC_STRING_PARSE::priv_parse<T>(strl, base);
|
||||
|
||||
if (const auto* ptr = std::get_if<T>(&rv)) {
|
||||
return NS_YYCC_RUST_RESULT::Ok<Result<T>>(*ptr);
|
||||
} else if (const auto* ptr = std::get_if<Error>(&rv)) {
|
||||
return NS_YYCC_RUST_RESULT::Err<Result<T>>(*ptr);
|
||||
} else {
|
||||
// Unreachable
|
||||
RS_PANIC("unreachable code.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses a string into a boolean value.
|
||||
* @tparam T Must be bool type
|
||||
* @param strl String view to parse
|
||||
* @return Result<bool> containing either the parsed value or an error
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_same_v<T, bool>, int> = 0>
|
||||
Result<T> parse(const NS_YYCC_STRING::u8string_view& strl) {
|
||||
auto rv = NS_YYCC_STRING_PARSE::priv_parse<T>(strl);
|
||||
|
||||
if (const auto* ptr = std::get_if<T>(&rv)) {
|
||||
return NS_YYCC_RUST_RESULT::Ok<Result<T>>(*ptr);
|
||||
} else if (const auto* ptr = std::get_if<Error>(&rv)) {
|
||||
return NS_YYCC_RUST_RESULT::Err<Result<T>>(*ptr);
|
||||
} else {
|
||||
// Unreachable
|
||||
RS_PANIC("unreachable code.");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#undef NS_YYCC_RUST_RESULT
|
||||
#undef NS_YYCC_STRING_PARSE
|
||||
#undef NS_YYCC_STRING
|
28
src/yycc/rust/primitive.hpp
Normal file
28
src/yycc/rust/primitive.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "../string.hpp"
|
||||
|
||||
namespace yycc::rust::primitive {
|
||||
|
||||
// `bool` is keyword so should not declare it anymore.
|
||||
// `char` is keyword so should not declare it anymore.
|
||||
|
||||
using i8 = std::int8_t;
|
||||
using i16 = std::int16_t;
|
||||
using i32 = std::int32_t;
|
||||
using i64 = std::int64_t;
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
using isize = std::ptrdiff_t;
|
||||
using usize = std::size_t;
|
||||
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
|
||||
using str = ::yycc::string::u8string_view;
|
||||
}
|
||||
|
85
src/yycc/rust/result.hpp
Normal file
85
src/yycc/rust/result.hpp
Normal file
@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
#include "../macro/feature_probe.hpp"
|
||||
|
||||
#if defined(YYCC_CPPFEAT_EXPECTED)
|
||||
#include <expected>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The reproduction of Rust Option type.
|
||||
* @details
|
||||
* After writing programs in Rust, I deeply recognized the advantages of Rust and its indispensable infrastructure Result.
|
||||
* Therefore, introducing Result into C++ to enhance coding safety is essential.
|
||||
* I've done my best to simulate Rust's \c Result and its members \c Ok and \c Err (actually, I had DeepSeek simulate them).
|
||||
*
|
||||
* Why not write it in C++ style? Because C++'s way of using \c Result is too ugly and not explicit enough.
|
||||
* In C++'s approach, the expected value is returned directly,
|
||||
* and when encountering void specialization, you must write a pair of curly braces, which is very unclear.
|
||||
* For unexpected values, you need to manually construct \c std::unexpected, which is even more painful.
|
||||
* If you need in-place construction of unexpected values, you even need to put \c std::in_place as the first parameter of the constructor,
|
||||
* otherwise \c std::unexpected 's constructor won't forward the subsequent parameters to the unexpected value's constructor.
|
||||
*
|
||||
* In the \c Result type, type \c E can be any value according to your needs.
|
||||
* In Rust, an unexpected value type \c Ea can be converted to another unexpected value type \c Eb.
|
||||
* This feature is implemented through the \c From trait, allowing you to safely wrap one type of unexpected value into another in a function.
|
||||
* But in C++, we have C++ ways to do the same thing.
|
||||
* Assuming for each type \c E, we define a separate struct to describe them,
|
||||
* then we just need to add some extra constructors to the struct to convert them from one type to another.
|
||||
*
|
||||
* For example, type \c Ea is a struct named \c IoError.
|
||||
* In this struct, there is a member of type \c IoErrorKind indicating the category of this IO error.
|
||||
* At the same time, it has a constructor with its own type as the only parameter, used to construct (copy or move) itself.
|
||||
* Now in a function, we want to convert it to another type \c Eb named \c SystemError .
|
||||
* All you need to do is create a new struct named \c SystemError, then write all necessary constructors and other functions for it.
|
||||
* Then, the key point is to add a constructor with parameter type <TT>const IoError&</TT>.
|
||||
* This way, we can simply convert type \c Ea to type \c Eb through calls like: <TT>Err<Result<T, E>>(result.error());</TT>.
|
||||
*
|
||||
* In Rust, if you want to get human-readable descriptions of unexpected values, you must implement the \c Display trait.
|
||||
* But you don't need to do this in C++, you must write your own conversion functions to adapt to various output requirements.
|
||||
* For example, when using \c std::format, you need to write suitable formatting adapters for \c std::format.
|
||||
* Similarly, when using \c std::cerr 's \c operator<< overload, you also need to write suitable adapters.
|
||||
* @remarks This namespace only work with environment supporting `std::expected` (i.e. C++ 23).
|
||||
*/
|
||||
namespace yycc::rust::result {
|
||||
|
||||
#if defined(YYCC_CPPFEAT_EXPECTED)
|
||||
|
||||
/**
|
||||
* @brief Equivalent Rust \c Result in C++
|
||||
* @tparam T The type of the expected value.
|
||||
* @tparam E The type of the unexpected value.
|
||||
*/
|
||||
template<typename T, typename E>
|
||||
using Result = std::expected<T, E>;
|
||||
|
||||
/**
|
||||
* @brief Equvialent Rust \c Result::Ok in C++.
|
||||
* @tparam ResultType The type of the Result instance.
|
||||
* @param[in] args The arguments for building expected value.
|
||||
* @return An built Result instance with expected value.
|
||||
*/
|
||||
template<typename ResultType, typename... Args>
|
||||
ResultType Ok(Args &&...args) {
|
||||
using T = ResultType::value_type;
|
||||
if constexpr (!std::is_void_v<T>) {
|
||||
return ResultType(std::in_place, std::forward<Args>(args)...);
|
||||
} else {
|
||||
static_assert(sizeof...(Args) == 0, "Ok<void> cannot accept arguments");
|
||||
return ResultType(std::in_place);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equvialent Rust \c Result::Err in C++.
|
||||
* @tparam ResultType The type of the Result instance.
|
||||
* @param[in] args The arguments for building unexpected value.
|
||||
* @return An built Result instance with unexpected value.
|
||||
*/
|
||||
template<typename ResultType, typename... Args>
|
||||
ResultType Err(Args &&...args) {
|
||||
return ResultType(std::unexpect, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace yycc::rust::result
|
10
src/yycc/rust/stringify.hpp
Normal file
10
src/yycc/rust/stringify.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "../string/stringify.hpp"
|
||||
|
||||
namespace yycc::rust::stringify {
|
||||
|
||||
// There is no modification for legacy "stringify" functions like "parse".
|
||||
// So we simply expose all functions into this namespace.
|
||||
using namespace ::yycc::string::stringify;
|
||||
|
||||
} // namespace yycc::rust::stringify
|
41
src/yycc/string.hpp
Normal file
41
src/yycc/string.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
// Define the UTF8 char type we used.
|
||||
// And do a polyfill if no embedded char8_t type.
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace yycc::string {
|
||||
/**
|
||||
\typedef u8char_t
|
||||
\brief YYCC UTF8 char type.
|
||||
\details
|
||||
This char type is an alias to \c char8_t if your current C++ standard support it.
|
||||
Otherwise it is defined as <TT>unsigned char</TT> as C++ 20 stdandard does.
|
||||
*/
|
||||
/**
|
||||
\typedef 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 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.
|
||||
*/
|
||||
|
||||
#if defined(__cpp_char8_t)
|
||||
using u8char = char8_t;
|
||||
using u8string = std::u8string;
|
||||
using u8string_view = std::u8string_view;
|
||||
#else
|
||||
using u8char = unsigned char;
|
||||
using u8string = std::basic_string<u8char>;
|
||||
using u8string_view = std::basic_string_view<u8char>;
|
||||
#endif
|
||||
} // namespace yycc::string
|
226
src/yycc/string/op.cpp
Normal file
226
src/yycc/string/op.cpp
Normal file
@ -0,0 +1,226 @@
|
||||
#include "op.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include "reinterpret.hpp"
|
||||
|
||||
#define NS_YYCC_STRING ::yycc::string
|
||||
#define NS_YYCC_STRING_REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
namespace yycc::string::op {
|
||||
|
||||
#pragma region Printf VPrintf
|
||||
|
||||
bool printf(NS_YYCC_STRING::u8string& strl, const NS_YYCC_STRING::u8char* format, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
bool ret = vprintf(strl, format, argptr);
|
||||
va_end(argptr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool vprintf(NS_YYCC_STRING::u8string& strl, const NS_YYCC_STRING::u8char* 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,
|
||||
NS_YYCC_STRING_REINTERPRET::as_ordinary(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(
|
||||
NS_YYCC_STRING_REINTERPRET::as_ordinary(strl.data()),
|
||||
strl.size() + 1,
|
||||
NS_YYCC_STRING_REINTERPRET::as_ordinary(format),
|
||||
args2
|
||||
);
|
||||
va_end(args2);
|
||||
|
||||
if (write_result < 0 || write_result > count) {
|
||||
// invalid write result in vsnprintf.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NS_YYCC_STRING::u8string printf(const NS_YYCC_STRING::u8char* format, ...) {
|
||||
NS_YYCC_STRING::u8string ret;
|
||||
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
vprintf(ret, format, argptr);
|
||||
va_end(argptr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NS_YYCC_STRING::u8string vprintf(const NS_YYCC_STRING::u8char* format, va_list argptr) {
|
||||
NS_YYCC_STRING::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(NS_YYCC_STRING::u8string& strl, const NS_YYCC_STRING::u8string_view& _from_strl, const NS_YYCC_STRING::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
|
||||
NS_YYCC_STRING::u8string from_strl(_from_strl);
|
||||
NS_YYCC_STRING::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)) != NS_YYCC_STRING::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'
|
||||
}
|
||||
}
|
||||
|
||||
NS_YYCC_STRING::u8string replace(const NS_YYCC_STRING::u8string_view& _strl, const NS_YYCC_STRING::u8string_view& _from_strl, const NS_YYCC_STRING::u8string_view& _to_strl) {
|
||||
// prepare result
|
||||
NS_YYCC_STRING::u8string strl(_strl);
|
||||
replace(strl, _from_strl, _to_strl);
|
||||
// return value
|
||||
return strl;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Join
|
||||
|
||||
NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& delimiter) {
|
||||
NS_YYCC_STRING::u8string ret;
|
||||
bool is_first = true;
|
||||
NS_YYCC_STRING::u8string_view element;
|
||||
|
||||
// fetch element
|
||||
while (fct_data(element)) {
|
||||
// insert delimiter
|
||||
if (is_first) is_first = false;
|
||||
else {
|
||||
// append delimiter.
|
||||
ret.append(delimiter);
|
||||
}
|
||||
|
||||
// insert element if it is not empty
|
||||
if (!element.empty())
|
||||
ret.append(element);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Upper Lower
|
||||
|
||||
template<bool BIsToLower>
|
||||
static void generic_lower_upper(NS_YYCC_STRING::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(NS_YYCC_STRING::u8string& strl) {
|
||||
generic_lower_upper<true>(strl);
|
||||
}
|
||||
|
||||
NS_YYCC_STRING::u8string to_lower(const NS_YYCC_STRING::u8string_view& strl) {
|
||||
NS_YYCC_STRING::u8string ret(strl);
|
||||
lower(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void upper(NS_YYCC_STRING::u8string& strl) {
|
||||
generic_lower_upper<false>(strl);
|
||||
}
|
||||
|
||||
NS_YYCC_STRING::u8string to_upper(const NS_YYCC_STRING::u8string_view& strl) {
|
||||
// same as Lower, just replace char transform function.
|
||||
NS_YYCC_STRING::u8string ret(strl);
|
||||
upper(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Split
|
||||
|
||||
std::vector<NS_YYCC_STRING::u8string_view> split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter) {
|
||||
// Reference:
|
||||
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
|
||||
|
||||
// prepare return value
|
||||
std::vector<NS_YYCC_STRING::u8string_view> elems;
|
||||
|
||||
// if string need to be splitted is empty, return original string (empty string).
|
||||
// if delimiter is empty, return original string.
|
||||
NS_YYCC_STRING::u8string delimiter(_delimiter);
|
||||
if (strl.empty() || delimiter.empty()) {
|
||||
elems.emplace_back(strl);
|
||||
return elems;
|
||||
}
|
||||
|
||||
// start spliting
|
||||
std::size_t previous = 0, current;
|
||||
while ((current = strl.find(delimiter.c_str(), previous)) != NS_YYCC_STRING::u8string::npos) {
|
||||
elems.emplace_back(strl.substr(previous, current - previous));
|
||||
previous = current + delimiter.size();
|
||||
}
|
||||
// try insert last part but prevent possible out of range exception
|
||||
if (previous <= strl.size()) {
|
||||
elems.emplace_back(strl.substr(previous));
|
||||
}
|
||||
return elems;
|
||||
}
|
||||
|
||||
std::vector<NS_YYCC_STRING::u8string> split_owned(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter) {
|
||||
// call split view
|
||||
auto view_result = split(strl, _delimiter);
|
||||
|
||||
// copy string view result to string
|
||||
std::vector<NS_YYCC_STRING::u8string> elems;
|
||||
elems.reserve(view_result.size());
|
||||
for (const auto& strl_view : view_result) {
|
||||
elems.emplace_back(NS_YYCC_STRING::u8string(strl_view));
|
||||
}
|
||||
// return copied result
|
||||
return elems;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
@ -1,17 +1,12 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <cstdarg>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "../string.hpp"
|
||||
|
||||
/**
|
||||
* @brief The helper containing string operations
|
||||
* @details
|
||||
* See also \ref string_helper.
|
||||
*/
|
||||
namespace YYCC::StringHelper {
|
||||
#define NS_YYCC_STRING ::yycc::string
|
||||
|
||||
namespace yycc::string::op {
|
||||
|
||||
/**
|
||||
* @brief Perform a string formatting operation.
|
||||
@ -22,7 +17,7 @@ namespace YYCC::StringHelper {
|
||||
* @param[in] ... Argument list of format string.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool Printf(yycc_u8string& strl, const yycc_char8_t* format, ...);
|
||||
bool printf(NS_YYCC_STRING::u8string& strl, const NS_YYCC_STRING::u8char* format, ...);
|
||||
/**
|
||||
* @brief Perform a string formatting operation.
|
||||
* @param[out] strl
|
||||
@ -32,21 +27,21 @@ namespace YYCC::StringHelper {
|
||||
* @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);
|
||||
bool vprintf(NS_YYCC_STRING::u8string& strl, const NS_YYCC_STRING::u8char* 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, ...);
|
||||
NS_YYCC_STRING::u8string printf(const NS_YYCC_STRING::u8char* 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);
|
||||
NS_YYCC_STRING::u8string vprintf(const NS_YYCC_STRING::u8char* format, va_list argptr);
|
||||
|
||||
/**
|
||||
* @brief Modify given string with all occurrences of substring \e old replaced by \e new.
|
||||
@ -54,7 +49,7 @@ namespace YYCC::StringHelper {
|
||||
* @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);
|
||||
void replace(NS_YYCC_STRING::u8string& strl, const NS_YYCC_STRING::u8string_view& _from_strl, const NS_YYCC_STRING::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
|
||||
@ -62,7 +57,7 @@ namespace YYCC::StringHelper {
|
||||
* @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);
|
||||
NS_YYCC_STRING::u8string replace(const NS_YYCC_STRING::u8string_view& _strl, const NS_YYCC_STRING::u8string_view& _from_strl, const NS_YYCC_STRING::u8string_view& _to_strl);
|
||||
|
||||
/**
|
||||
* @brief The data provider of general join function.
|
||||
@ -73,7 +68,7 @@ namespace YYCC::StringHelper {
|
||||
* \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&)>;
|
||||
using JoinDataProvider = std::function<bool(NS_YYCC_STRING::u8string_view&)>;
|
||||
/**
|
||||
* @brief Universal join function.
|
||||
* @details
|
||||
@ -82,78 +77,81 @@ namespace YYCC::StringHelper {
|
||||
* 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.
|
||||
* @param[in] delimiter The delimiter used for joining.
|
||||
* @return The result string of joining.
|
||||
*/
|
||||
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer);
|
||||
NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& delimiter);
|
||||
/**
|
||||
* @brief Specialized join function for standard library container.
|
||||
* @tparam InputIt
|
||||
* Must meet the requirements of LegacyInputIterator.
|
||||
* It also can be dereferenced and then implicitly converted to yycc_u8string_view.
|
||||
* It also can be dereferenced and then implicitly converted to NS_YYCC_STRING::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.
|
||||
* @param[in] delimiter The delimiter 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 {
|
||||
NS_YYCC_STRING::u8string join(InputIt first, InputIt last, const NS_YYCC_STRING::u8string_view& delimiter) {
|
||||
return join([&first, &last](NS_YYCC_STRING::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);
|
||||
}, delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert given string to lowercase.
|
||||
* @param[in,out] strl The string to be lowercase.
|
||||
*/
|
||||
void Lower(yycc_u8string& strl);
|
||||
void lower(NS_YYCC_STRING::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);
|
||||
NS_YYCC_STRING::u8string to_lower(const NS_YYCC_STRING::u8string_view& strl);
|
||||
/**
|
||||
* @brief Convert given string to uppercase.
|
||||
* @param[in,out] strl The string to be uppercase.
|
||||
*/
|
||||
void Upper(yycc_u8string& strl);
|
||||
void upper(NS_YYCC_STRING::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);
|
||||
NS_YYCC_STRING::u8string to_upper(const NS_YYCC_STRING::u8string_view& strl);
|
||||
|
||||
/**
|
||||
* @brief Split given string with specified decilmer.
|
||||
* @brief Split given string with specified delimiter as string view.
|
||||
* @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.
|
||||
* @param[in] _delimiter The delimiter for splitting.
|
||||
* @return
|
||||
* The split result with string view format.
|
||||
* This will not produce any copy of original string.
|
||||
* \par
|
||||
* If given string or decilmer are empty,
|
||||
* If given string or delimiter 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*)
|
||||
* @see Split(const NS_YYCC_STRING::u8string_view&, const NS_YYCC_STRING::u8char*)
|
||||
*/
|
||||
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
|
||||
std::vector<NS_YYCC_STRING::u8string_view> split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter);
|
||||
/**
|
||||
* @brief Split given string with specified delimiter.
|
||||
* @param[in] strl The string need to be splitting.
|
||||
* @param[in] _delimiter The delimiter for splitting.
|
||||
* @return
|
||||
* The split result.
|
||||
* \par
|
||||
* If given string or delimiter are empty,
|
||||
* the result container will only contain 1 entry which is equal to given string.
|
||||
*/
|
||||
std::vector<NS_YYCC_STRING::u8string> split_owned(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter);
|
||||
// undefined lazy_split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter);
|
||||
|
||||
}
|
||||
|
||||
#undef NS_YYCC_STRING
|
248
src/yycc/string/parse.hpp
Normal file
248
src/yycc/string/parse.hpp
Normal file
@ -0,0 +1,248 @@
|
||||
#pragma once
|
||||
#include "../string.hpp"
|
||||
#include "op.hpp"
|
||||
#include "reinterpret.hpp"
|
||||
#include <charconv>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
#define NS_YYCC_STRING ::yycc::string
|
||||
#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::string::parse {
|
||||
|
||||
/// @private
|
||||
/// @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.
|
||||
};
|
||||
|
||||
/// @private
|
||||
/// @brief The return value of internal parse function which ape `std::expected`.
|
||||
template<typename T, std::enable_if_t<!std::is_same_v<T, ParseError>, int> = 0>
|
||||
using ParseResult = std::variant<T, ParseError>;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Internal parsing function for floating point types
|
||||
* @tparam T Floating point type (float, double, etc)
|
||||
* @param strl The UTF-8 string view to parse
|
||||
* @param fmt The floating point format to use
|
||||
* @return ParseResult<T> containing either the parsed value or a ParseError
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
|
||||
ParseResult<T> priv_parse(const NS_YYCC_STRING::u8string_view& strl, std::chars_format fmt) {
|
||||
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 ParseError::PartiallyParsed;
|
||||
} else if (ec == std::errc::invalid_argument) {
|
||||
// Given string is invalid
|
||||
return ParseError::InvalidString;
|
||||
} else if (ec == std::errc::result_out_of_range) {
|
||||
// Given string is out of range
|
||||
return ParseError::OutOfRange;
|
||||
} else {
|
||||
// Unreachable
|
||||
throw std::runtime_error("invalid ec.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Internal parsing function for integral types (except bool)
|
||||
* @tparam T Integral type (int, long, etc)
|
||||
* @param strl The UTF-8 string view to parse
|
||||
* @param base Numeric base (2-36)
|
||||
* @return ParseResult<T> containing either the parsed value or a ParseError
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
|
||||
ParseResult<T> priv_parse(const NS_YYCC_STRING::u8string_view& strl, int base) {
|
||||
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 ParseError::PartiallyParsed;
|
||||
} else if (ec == std::errc::invalid_argument) {
|
||||
// Given string is invalid
|
||||
return ParseError::InvalidString;
|
||||
} else if (ec == std::errc::result_out_of_range) {
|
||||
// Given string is out of range
|
||||
return ParseError::OutOfRange;
|
||||
} else {
|
||||
// Unreachable
|
||||
throw std::runtime_error("invalid ec.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Internal parsing function for boolean type
|
||||
* @tparam T Must be bool type
|
||||
* @param strl The UTF-8 string view to parse ("true" or "false", case insensitive)
|
||||
* @return ParseResult<bool> containing either the parsed value or a ParseError
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_same_v<T, bool>, int> = 0>
|
||||
ParseResult<T> priv_parse(const NS_YYCC_STRING::u8string_view& strl) {
|
||||
// Get lower case
|
||||
auto lower_case = NS_YYCC_STRING_OP::to_lower(strl);
|
||||
// Compare result
|
||||
if (lower_case == YYCC_U8("true")) return true;
|
||||
else if (lower_case == YYCC_U8("false")) return false;
|
||||
else return ParseError::InvalidString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Try parsing given string to floating point types.
|
||||
* @tparam T 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 T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
|
||||
bool try_parse(const NS_YYCC_STRING::u8string_view& strl,
|
||||
T& num,
|
||||
std::chars_format fmt = std::chars_format::general) {
|
||||
auto rv = priv_parse<T>(strl, fmt);
|
||||
if (const auto* ptr = std::get_if<T>(&rv)) {
|
||||
num = *ptr;
|
||||
return true;
|
||||
} else if (const auto* ptr = std::get_if<ParseError>(&rv)) {
|
||||
return false;
|
||||
} else {
|
||||
// Unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Try parsing given string to integral types.
|
||||
* @tparam T 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 T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
|
||||
bool try_parse(const NS_YYCC_STRING::u8string_view& strl, T& num, int base = 10) {
|
||||
auto rv = priv_parse<T>(strl, base);
|
||||
if (const auto* ptr = std::get_if<T>(&rv)) {
|
||||
num = *ptr;
|
||||
return true;
|
||||
} else if (const auto* ptr = std::get_if<ParseError>(&rv)) {
|
||||
return false;
|
||||
} else {
|
||||
// Unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Try parsing given string to bool types.
|
||||
* @tparam T 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 T, std::enable_if_t<std::is_same_v<T, bool>, int> = 0>
|
||||
bool try_parse(const NS_YYCC_STRING::u8string_view& strl, T& num) {
|
||||
auto rv = priv_parse<T>(strl);
|
||||
if (const auto* ptr = std::get_if<T>(&rv)) {
|
||||
num = *ptr;
|
||||
return true;
|
||||
} else if (const auto* ptr = std::get_if<ParseError>(&rv)) {
|
||||
return false;
|
||||
} else {
|
||||
// Unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse given string to floating point types.
|
||||
* @tparam T 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.
|
||||
* @exception std::invalid_argument Can not parse given string.
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
|
||||
T parse(const NS_YYCC_STRING::u8string_view& strl,
|
||||
std::chars_format fmt = std::chars_format::general) {
|
||||
T rv;
|
||||
if (try_parse(strl, rv, fmt)) return rv;
|
||||
else throw std::invalid_argument("can not parse given string");
|
||||
}
|
||||
/**
|
||||
* @brief Parse given string to integral type types.
|
||||
* @tparam T 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.
|
||||
* @exception std::invalid_argument Can not parse given string.
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
|
||||
T parse(const NS_YYCC_STRING::u8string_view& strl, int base = 10) {
|
||||
T rv;
|
||||
if (try_parse(strl, rv, base)) return rv;
|
||||
else throw std::invalid_argument("can not parse given string");
|
||||
}
|
||||
/**
|
||||
* @brief Parse given string to bool types.
|
||||
* @tparam T 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.
|
||||
* @exception std::invalid_argument Can not parse given string.
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_same_v<T, bool>, int> = 0>
|
||||
T parse(const NS_YYCC_STRING::u8string_view& strl) {
|
||||
T rv;
|
||||
if (try_parse(strl, rv)) return rv;
|
||||
else throw std::invalid_argument("can not parse given string");
|
||||
}
|
||||
|
||||
} // namespace yycc::string::parse
|
||||
|
||||
#undef NS_YYCC_STRING_OP
|
||||
#undef NS_YYCC_STRING_REINTERPRET
|
||||
#undef NS_YYCC_STRING
|
33
src/yycc/string/reinterpret.cpp
Normal file
33
src/yycc/string/reinterpret.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "reinterpret.hpp"
|
||||
|
||||
#define NS_YYCC_STRING ::yycc::string
|
||||
|
||||
namespace yycc::string::reinterpret {
|
||||
|
||||
const NS_YYCC_STRING::u8char* as_utf8(const char* src) {
|
||||
return reinterpret_cast<const NS_YYCC_STRING::u8char*>(src);
|
||||
}
|
||||
NS_YYCC_STRING::u8char* as_utf8(char* src) {
|
||||
return reinterpret_cast<NS_YYCC_STRING::u8char*>(src);
|
||||
}
|
||||
NS_YYCC_STRING::u8string as_utf8(const std::string_view& src) {
|
||||
return NS_YYCC_STRING::u8string(reinterpret_cast<const NS_YYCC_STRING::u8char*>(src.data()), src.size());
|
||||
}
|
||||
NS_YYCC_STRING::u8string_view as_utf8_view(const std::string_view& src) {
|
||||
return NS_YYCC_STRING::u8string_view(reinterpret_cast<const NS_YYCC_STRING::u8char*>(src.data()), src.size());
|
||||
}
|
||||
|
||||
const char* as_ordinary(const NS_YYCC_STRING::u8char* src) {
|
||||
return reinterpret_cast<const char*>(src);
|
||||
}
|
||||
char* as_ordinary(NS_YYCC_STRING::u8char* src) {
|
||||
return reinterpret_cast<char*>(src);
|
||||
}
|
||||
std::string as_ordinary(const NS_YYCC_STRING::u8string_view& src) {
|
||||
return std::string(reinterpret_cast<const char*>(src.data()), src.size());
|
||||
}
|
||||
std::string_view as_ordinary_view(const NS_YYCC_STRING::u8string_view& src) {
|
||||
return std::string_view(reinterpret_cast<const char*>(src.data()), src.size());
|
||||
}
|
||||
|
||||
}
|
71
src/yycc/string/reinterpret.hpp
Normal file
71
src/yycc/string/reinterpret.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "../string.hpp"
|
||||
|
||||
#define NS_YYCC_STRING ::yycc::string
|
||||
|
||||
/**
|
||||
* @brief Provides utilities for reinterpretation between UTF-8 and ordinary string types.
|
||||
* @details
|
||||
* Please note that there is no encoding convertion happended in this namespace provided functions.
|
||||
* They just simply reinterpret one string to another string.
|
||||
* The validation of UTF8 string is guaranteed by user self.
|
||||
*/
|
||||
namespace yycc::string::reinterpret {
|
||||
|
||||
#define _YYCC_U8(strl) u8 ## strl ///< The assistant macro for YYCC_U8.
|
||||
#define YYCC_U8(strl) (reinterpret_cast<const ::yycc::string::u8char*>(_YYCC_U8(strl))) ///< The macro for creating UTF8 string literal. See \ref library_encoding.
|
||||
#define YYCC_U8_CHAR(chr) (static_cast<::yycc::string::u8char>(chr)) ///< The macro for casting ordinary char type into YYCC UTF8 char type.
|
||||
|
||||
/**
|
||||
* @brief Reinterpret ordinary C-string to UTF-8 string (const version).
|
||||
* @param src Source ordinary string
|
||||
* @return Pointer to UTF-8 encoded string
|
||||
*/
|
||||
const NS_YYCC_STRING::u8char* as_utf8(const char* src);
|
||||
/**
|
||||
* @brief Reinterpret ordinary C-string as an UTF-8 string (non-const version).
|
||||
* @param src Source ordinary string
|
||||
* @return Pointer to UTF-8 encoded string
|
||||
*/
|
||||
NS_YYCC_STRING::u8char* as_utf8(char* src);
|
||||
/**
|
||||
* @brief Reinterpret ordinary string view to copied UTF-8 string.
|
||||
* @param src Source ordinary string view
|
||||
* @return UTF-8 encoded string
|
||||
*/
|
||||
NS_YYCC_STRING::u8string as_utf8(const std::string_view& src);
|
||||
/**
|
||||
* @brief Reinterpret ordinary string view to UTF-8 string view.
|
||||
* @param src Source ordinary string view
|
||||
* @return UTF-8 encoded string view
|
||||
*/
|
||||
NS_YYCC_STRING::u8string_view as_utf8_view(const std::string_view& src);
|
||||
|
||||
/**
|
||||
* @brief Reinterpret UTF-8 C-string to ordinary string (const version).
|
||||
* @param src Source UTF-8 string
|
||||
* @return Pointer to ordinary string
|
||||
*/
|
||||
const char* as_ordinary(const NS_YYCC_STRING::u8char* src);
|
||||
/**
|
||||
* @brief Reinterpret UTF-8 C-string to ordinary string (non-const version).
|
||||
* @param src Source UTF-8 string
|
||||
* @return Pointer to ordinary string
|
||||
*/
|
||||
char* as_ordinary(NS_YYCC_STRING::u8char* src);
|
||||
/**
|
||||
* @brief Reinterpret UTF-8 string view to ordinary string.
|
||||
* @param src Source UTF-8 string view
|
||||
* @return Ordinary string
|
||||
*/
|
||||
std::string as_ordinary(const NS_YYCC_STRING::u8string_view& src);
|
||||
/**
|
||||
* @brief Reinterpret UTF-8 string view to ordinary string view
|
||||
* @param src Source UTF-8 string view
|
||||
* @return Ordinary string view
|
||||
*/
|
||||
std::string_view as_ordinary_view(const NS_YYCC_STRING::u8string_view& src);
|
||||
|
||||
}
|
||||
|
||||
#undef NS_YYCC_STRING
|
101
src/yycc/string/stringify.hpp
Normal file
101
src/yycc/string/stringify.hpp
Normal file
@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
#include "../string.hpp"
|
||||
#include "reinterpret.hpp"
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#define NS_YYCC_STRING ::yycc::string
|
||||
#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::string::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<NS_YYCC_STRING::u8char, 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, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
|
||||
NS_YYCC_STRING::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 NS_YYCC_STRING::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, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
|
||||
NS_YYCC_STRING::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 NS_YYCC_STRING::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, std::enable_if_t<std::is_same_v<T, bool>, int> = 0>
|
||||
NS_YYCC_STRING::u8string stringify(T num) {
|
||||
if (num) return NS_YYCC_STRING::u8string(YYCC_U8("true"));
|
||||
else return NS_YYCC_STRING::u8string(YYCC_U8("false"));
|
||||
}
|
||||
|
||||
} // namespace yycc::string::stringify
|
||||
|
||||
#undef NS_YYCC_STRING_REINTERPRET
|
||||
#undef NS_YYCC_STRING
|
5
src/yycc/version.hpp
Normal file
5
src/yycc/version.hpp
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define YYCC_VER_MAJOR 2
|
||||
#define YYCC_VER_MINOR 0
|
||||
#define YYCC_VER_PATCH 0
|
5
src/yycc/version.hpp.in
Normal file
5
src/yycc/version.hpp.in
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define YYCC_VER_MAJOR @PROJECT_VERSION_MAJOR@
|
||||
#define YYCC_VER_MINOR @PROJECT_VERSION_MINOR@
|
||||
#define YYCC_VER_PATCH @PROJECT_VERSION_PATCH@
|
0
src/yycc/windows/com.cpp
Normal file
0
src/yycc/windows/com.cpp
Normal file
0
src/yycc/windows/com.hpp
Normal file
0
src/yycc/windows/com.hpp
Normal file
0
src/yycc/windows/dialog.cpp
Normal file
0
src/yycc/windows/dialog.cpp
Normal file
0
src/yycc/windows/dialog.hpp
Normal file
0
src/yycc/windows/dialog.hpp
Normal file
@ -2,7 +2,7 @@
|
||||
// Because this header is the part of wrapper, not a real header.
|
||||
// #pragma once
|
||||
|
||||
#include "YYCCInternal.hpp"
|
||||
#include "../macro/os_detector.hpp"
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Because this header is the part of wrapper, not a real header.
|
||||
// #pragma once
|
||||
|
||||
#include "YYCCInternal.hpp"
|
||||
#include "../macro/os_detector.hpp"
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
16
src/yycc/windows/unsafe_suppressor.hpp
Normal file
16
src/yycc/windows/unsafe_suppressor.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
|
||||
// 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
|
@ -4,15 +4,25 @@ add_executable(YYCCTestbench "")
|
||||
target_sources(YYCCTestbench
|
||||
PRIVATE
|
||||
main.cpp
|
||||
yycc/constraint.cpp
|
||||
yycc/constraint/builder.cpp
|
||||
yycc/string/op.cpp
|
||||
yycc/string/reinterpret.cpp
|
||||
yycc/string/parse.cpp
|
||||
yycc/string/stringify.cpp
|
||||
yycc/rust/parse.cpp
|
||||
yycc/rust/stringify.cpp
|
||||
)
|
||||
# Add YYCC as its library
|
||||
# Setup headers
|
||||
target_include_directories(YYCCTestbench
|
||||
PRIVATE
|
||||
YYCCommonplace
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_LIST_DIR}"
|
||||
)
|
||||
# Setup libraries
|
||||
target_link_libraries(YYCCTestbench
|
||||
PRIVATE
|
||||
YYCCommonplace
|
||||
GTest::gtest_main
|
||||
)
|
||||
# Setup C++ standard
|
||||
target_compile_features(YYCCTestbench PUBLIC cxx_std_17)
|
||||
@ -29,8 +39,6 @@ PRIVATE
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
|
||||
)
|
||||
|
||||
# Install testbench only on Release mode
|
||||
install(TARGETS YYCCTestbench
|
||||
CONFIGURATIONS Release
|
||||
RUNTIME DESTINATION ${YYCC_INSTALL_BIN_PATH}
|
||||
)
|
||||
# Discover all test
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(YYCCTestbench)
|
||||
|
@ -1,667 +1,6 @@
|
||||
#include <YYCCommonplace.hpp>
|
||||
#include <cstdio>
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
namespace Console = YYCC::ConsoleHelper;
|
||||
|
||||
namespace YYCCTestbench {
|
||||
|
||||
#pragma region Unicode Test Data
|
||||
|
||||
// UNICODE Test Strings
|
||||
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
|
||||
#define TEST_UNICODE_STR_JAPAN "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"
|
||||
#define TEST_UNICODE_STR_CHINA "\u7B80\u4F53\u4E2D\u6587"
|
||||
#define TEST_UNICODE_STR_KOREA "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"
|
||||
#define TEST_UNICODE_STR_ISRAEL "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"
|
||||
#define TEST_UNICODE_STR_EGYPT "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"
|
||||
#define TEST_UNICODE_STR_GREECE "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"
|
||||
#define TEST_UNICODE_STR_RUSSIA "\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"
|
||||
#define TEST_UNICODE_STR_THAILAND "\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"
|
||||
#define TEST_UNICODE_STR_FRANCE "fran\u00E7ais langue \u00E9trang\u00E8re"
|
||||
#define TEST_UNICODE_STR_SPAIN "ma\u00F1ana ol\u00E9"
|
||||
#define TEST_UNICODE_STR_MATHMATICS "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"
|
||||
#define TEST_UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
|
||||
|
||||
#define CONCAT(prefix, strl) prefix ## strl
|
||||
#define CPP_U8_LITERAL(strl) YYCC_U8(strl)
|
||||
#define CPP_U16_LITERAL(strl) CONCAT(u, strl)
|
||||
#define CPP_U32_LITERAL(strl) CONCAT(U, strl)
|
||||
#define CPP_WSTR_LITERAL(strl) CONCAT(L, strl)
|
||||
|
||||
static std::vector<YYCC::yycc_u8string> c_UTF8TestStrTable {
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::wstring> c_WStrTestStrTable {
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u16string> c_UTF16TestStrTable {
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u32string> c_UTF32TestStrTable {
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
|
||||
#undef CPP_WSTR_LITERAL
|
||||
#undef CPP_U32_LITERAL
|
||||
#undef CPP_U16_LITERAL
|
||||
#undef CPP_U8_LITERAL
|
||||
#undef CONCAT
|
||||
|
||||
#pragma endregion
|
||||
|
||||
static void Assert(bool condition, const YYCC::yycc_char8_t* description) {
|
||||
if (condition) {
|
||||
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("OK: %s")), description);
|
||||
} else {
|
||||
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("Failed: %s\n")), description);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void ConsoleTestbench() {
|
||||
// Color Test
|
||||
Console::EnableColorfulConsole();
|
||||
Console::WriteLine(YYCC_U8("Color Test:"));
|
||||
|
||||
#define TEST_MACRO(col) Console::WriteLine(YYCC_U8("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col ));
|
||||
// U+2588 is full block
|
||||
|
||||
TEST_MACRO(BLACK);
|
||||
TEST_MACRO(RED);
|
||||
TEST_MACRO(GREEN);
|
||||
TEST_MACRO(YELLOW);
|
||||
TEST_MACRO(BLUE);
|
||||
TEST_MACRO(MAGENTA);
|
||||
TEST_MACRO(CYAN);
|
||||
TEST_MACRO(WHITE);
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// UTF8 Output Test
|
||||
Console::WriteLine(YYCC_U8("UTF8 Output Test:"));
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
Console::FormatLine(YYCC_U8("\t%s"), strl.c_str());
|
||||
}
|
||||
|
||||
// UTF8 Input Test
|
||||
Console::WriteLine(YYCC_U8("UTF8 Input Test:"));
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
Console::FormatLine(YYCC_U8("\tPlease type: %s"), strl.c_str());
|
||||
Console::Write(YYCC_U8("\t> "));
|
||||
|
||||
YYCC::yycc_u8string gotten(Console::ReadLine());
|
||||
if (gotten == strl) Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("\tMatched! Got: %s")), gotten.c_str());
|
||||
else Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("\tNOT Matched! Got: %s")), gotten.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void EncodingTestbench() {
|
||||
// get test tuple size
|
||||
size_t count = c_UTF8TestStrTable.size();
|
||||
|
||||
// check the convertion between given string
|
||||
for (size_t i = 0u; i < count; ++i) {
|
||||
// get item
|
||||
const auto& u8str = c_UTF8TestStrTable[i];
|
||||
const auto& u16str = c_UTF16TestStrTable[i];
|
||||
const auto& u32str = c_UTF32TestStrTable[i];
|
||||
|
||||
// create cache variables
|
||||
YYCC::yycc_u8string u8cache;
|
||||
std::u16string u16cache;
|
||||
std::u32string u32cache;
|
||||
|
||||
// do convertion check
|
||||
Assert(YYCC::EncodingHelper::UTF8ToUTF16(u8str, u16cache) && u16cache == u16str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF16"));
|
||||
Assert(YYCC::EncodingHelper::UTF8ToUTF32(u8str, u32cache) && u32cache == u32str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF32"));
|
||||
|
||||
Assert(YYCC::EncodingHelper::UTF16ToUTF8(u16str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF16ToUTF8"));
|
||||
Assert(YYCC::EncodingHelper::UTF32ToUTF8(u32str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF32ToUTF8"));
|
||||
}
|
||||
|
||||
// check wstring convertion on windows
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
for (size_t i = 0u; i < count; ++i) {
|
||||
// get item
|
||||
const auto& u8str = c_UTF8TestStrTable[i];
|
||||
const auto& wstr = c_WStrTestStrTable[i];
|
||||
|
||||
// create cache variables
|
||||
YYCC::yycc_u8string u8cache;
|
||||
std::wstring wcache;
|
||||
|
||||
// do convertion check
|
||||
Assert(YYCC::EncodingHelper::UTF8ToWchar(u8str.c_str(), wcache) && wcache == wstr, YYCC_U8("YYCC::EncodingHelper::UTF8ToWchar"));
|
||||
Assert(YYCC::EncodingHelper::WcharToUTF8(wstr.c_str(), u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::WcharToUTF8"));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
static void StringTestbench() {
|
||||
// Test Printf
|
||||
auto test_printf = YYCC::StringHelper::Printf(YYCC_U8("%s == %s"), YYCC_U8("Hello World"), YYCC_U8("Hello, world"));
|
||||
Assert(test_printf == YYCC_U8("Hello World == Hello, world"), YYCC_U8("YYCC::StringHelper::Printf"));
|
||||
|
||||
// Test Replace
|
||||
auto test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8("bb"), YYCC_U8("dd")); // normal case
|
||||
Assert(test_replace == YYCC_U8("aaddcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8("zz"), YYCC_U8("yy")); // no replace
|
||||
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC::yycc_u8string_view(), YYCC_U8("zz")); // empty finding
|
||||
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaaabbaa"), YYCC_U8("aa"), YYCC_U8("")); // no replaced string
|
||||
Assert(test_replace == YYCC_U8("bb"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaxcc"), YYCC_U8("x"), YYCC_U8("yx")); // nested replacing
|
||||
Assert(test_replace == YYCC_U8("aayxcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC::yycc_u8string_view(), YYCC_U8(""), YYCC_U8("xy")); // empty source string
|
||||
Assert(test_replace == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
|
||||
// Test Upper / Lower
|
||||
auto test_lower = YYCC::StringHelper::Lower(YYCC_U8("LOWER"));
|
||||
Assert(test_lower == YYCC_U8("lower"), YYCC_U8("YYCC::StringHelper::Lower"));
|
||||
auto test_upper = YYCC::StringHelper::Upper(YYCC_U8("upper"));
|
||||
Assert(test_upper == YYCC_U8("UPPER"), YYCC_U8("YYCC::StringHelper::Upper"));
|
||||
|
||||
// Test Join
|
||||
std::vector<YYCC::yycc_u8string> test_join_container {
|
||||
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
|
||||
};
|
||||
auto test_join = YYCC::StringHelper::Join(test_join_container.begin(), test_join_container.end(), YYCC_U8(", "));
|
||||
Assert(test_join == YYCC_U8(", 1, 2, "), YYCC_U8("YYCC::StringHelper::Join"));
|
||||
|
||||
// Test Split
|
||||
auto test_split = YYCC::StringHelper::Split(YYCC_U8(", 1, 2, "), YYCC_U8(", ")); // normal
|
||||
Assert(test_split.size() == 4u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[1] == YYCC_U8("1"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[2] == YYCC_U8("2"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[3] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched decilmer
|
||||
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC::yycc_u8string_view()); // empty decilmer
|
||||
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
test_split = YYCC::StringHelper::Split(YYCC::yycc_u8string_view(), YYCC_U8("")); // empty source string
|
||||
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0].empty(), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
|
||||
}
|
||||
|
||||
static void ParserTestbench() {
|
||||
|
||||
// Test success TryParse
|
||||
#define TEST_MACRO(type_t, value, string_value, ...) { \
|
||||
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
|
||||
type_t cache; \
|
||||
Assert(YYCC::ParserHelper::TryParse<type_t>(cache_string, cache, ##__VA_ARGS__) && cache == value, YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(int8_t, INT8_C(-61), "-61");
|
||||
TEST_MACRO(uint8_t, UINT8_C(200), "200");
|
||||
TEST_MACRO(int16_t, INT16_C(6161), "6161");
|
||||
TEST_MACRO(uint16_t, UINT16_C(32800), "32800");
|
||||
TEST_MACRO(int32_t, INT32_C(61616161), "61616161");
|
||||
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
|
||||
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
|
||||
TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807");
|
||||
TEST_MACRO(uint32_t, UINT32_C(0xffff), "ffff", 16);
|
||||
TEST_MACRO(float, 1.0f, "1.0");
|
||||
TEST_MACRO(double, 1.0, "1.0");
|
||||
TEST_MACRO(bool, true, "true");
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// Test failed TryParse
|
||||
#define TEST_MACRO(type_t, string_value, ...) { \
|
||||
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
|
||||
type_t cache; \
|
||||
Assert(!YYCC::ParserHelper::TryParse<type_t>(cache_string, cache, ##__VA_ARGS__), YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(int8_t, "6161");
|
||||
TEST_MACRO(uint8_t, "32800");
|
||||
TEST_MACRO(int16_t, "61616161");
|
||||
TEST_MACRO(uint16_t, "4294967293");
|
||||
TEST_MACRO(int32_t, "616161616161");
|
||||
TEST_MACRO(uint32_t, "9223372036854775807");
|
||||
TEST_MACRO(int64_t, "616161616161616161616161");
|
||||
TEST_MACRO(uint64_t, "92233720368547758079223372036854775807");
|
||||
TEST_MACRO(float, "1e40");
|
||||
TEST_MACRO(double, "1e114514");
|
||||
TEST_MACRO(bool, "hello, world!");
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// Test ToString
|
||||
#define TEST_MACRO(type_t, value, string_value, ...) { \
|
||||
type_t cache = value; \
|
||||
YYCC::yycc_u8string ret(YYCC::ParserHelper::ToString<type_t>(cache, ##__VA_ARGS__)); \
|
||||
Assert(ret == YYCC_U8(string_value), YYCC_U8("YYCC::StringHelper::ToString<" #type_t ">")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(int8_t, INT8_C(-61), "-61");
|
||||
TEST_MACRO(uint8_t, UINT8_C(200), "200");
|
||||
TEST_MACRO(int16_t, INT16_C(6161), "6161");
|
||||
TEST_MACRO(uint16_t, UINT16_C(32800), "32800");
|
||||
TEST_MACRO(int32_t, INT32_C(61616161), "61616161");
|
||||
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
|
||||
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
|
||||
TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807");
|
||||
TEST_MACRO(uint32_t, UINT32_C(0xffff), "ffff", 16);
|
||||
TEST_MACRO(float, 1.0f, "1.0", std::chars_format::fixed, 1);
|
||||
TEST_MACRO(double, 1.0, "1.0", std::chars_format::fixed, 1);
|
||||
TEST_MACRO(bool, true, "true");
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
}
|
||||
|
||||
static void DialogTestbench() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
YYCC::yycc_u8string ret;
|
||||
std::vector<YYCC::yycc_u8string> rets;
|
||||
|
||||
YYCC::DialogHelper::FileDialog params;
|
||||
auto& filters = params.ConfigreFileTypes();
|
||||
filters.Add(YYCC_U8("Microsoft Word (*.docx; *.doc)"), { YYCC_U8("*.docx"), YYCC_U8("*.doc") });
|
||||
filters.Add(YYCC_U8("Microsoft Excel (*.xlsx; *.xls)"), { YYCC_U8("*.xlsx"), YYCC_U8("*.xls") });
|
||||
filters.Add(YYCC_U8("Microsoft PowerPoint (*.pptx; *.ppt)"), { YYCC_U8("*.pptx"), YYCC_U8("*.ppt") });
|
||||
filters.Add(YYCC_U8("Text File (*.txt)"), { YYCC_U8("*.txt") });
|
||||
filters.Add(YYCC_U8("All Files (*.*)"), { YYCC_U8("*.*") });
|
||||
params.SetDefaultFileTypeIndex(0u);
|
||||
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Open File: %s"), ret.c_str());
|
||||
}
|
||||
if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) {
|
||||
Console::WriteLine(YYCC_U8("Open Multiple Files:"));
|
||||
for (const auto& item : rets) {
|
||||
Console::FormatLine(YYCC_U8("\t%s"), item.c_str());
|
||||
}
|
||||
}
|
||||
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Save File: %s"), ret.c_str());
|
||||
}
|
||||
params.Clear();
|
||||
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Open Folder: %s"), ret.c_str());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ExceptionTestbench() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
YYCC::ExceptionHelper::Register([](const YYCC::yycc_u8string& log_path, const YYCC::yycc_u8string& coredump_path) -> void {
|
||||
MessageBoxW(
|
||||
NULL,
|
||||
YYCC::EncodingHelper::UTF8ToWchar(
|
||||
YYCC::StringHelper::Printf(YYCC_U8("Log generated:\nLog path: %s\nCore dump path: %s"), log_path.c_str(), coredump_path.c_str())
|
||||
).c_str(),
|
||||
L"Fatal Error", MB_OK + MB_ICONERROR
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Perform a div zero exception.
|
||||
#if defined (YYCC_DEBUG_UE_FILTER)
|
||||
// Reference: https://stackoverflow.com/questions/20981982/is-it-possible-to-debug-unhandledexceptionfilters-with-a-debugger
|
||||
__try {
|
||||
// all of code normally inside of main or WinMain here...
|
||||
int i = 1, j = 0;
|
||||
int k = i / j;
|
||||
} __except (YYCC::ExceptionHelper::DebugCallUExceptionImpl(GetExceptionInformation())) {
|
||||
OutputDebugStringW(L"executed filter function\n");
|
||||
}
|
||||
#else
|
||||
int i = 1, j = 0;
|
||||
int k = i / j;
|
||||
#endif
|
||||
|
||||
YYCC::ExceptionHelper::Unregister();
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void WinFctTestbench() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
HMODULE test_current_module;
|
||||
Assert((test_current_module = YYCC::WinFctHelper::GetCurrentModule()) != nullptr, YYCC_U8("YYCC::WinFctHelper::GetCurrentModule"));
|
||||
Console::FormatLine(YYCC_U8("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR), test_current_module);
|
||||
|
||||
YYCC::yycc_u8string test_temp;
|
||||
Assert(YYCC::WinFctHelper::GetTempDirectory(test_temp), YYCC_U8("YYCC::WinFctHelper::GetTempDirectory"));
|
||||
Console::FormatLine(YYCC_U8("Temp Directory: %s"), test_temp.c_str());
|
||||
|
||||
YYCC::yycc_u8string test_module_name;
|
||||
Assert(YYCC::WinFctHelper::GetModuleFileName(YYCC::WinFctHelper::GetCurrentModule(), test_module_name), YYCC_U8("YYCC::WinFctHelper::GetModuleFileName"));
|
||||
Console::FormatLine(YYCC_U8("Current Module File Name: %s"), test_module_name.c_str());
|
||||
|
||||
YYCC::yycc_u8string test_localappdata_path;
|
||||
Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), YYCC_U8("YYCC::WinFctHelper::GetLocalAppData"));
|
||||
Console::FormatLine(YYCC_U8("Local AppData: %s"), test_localappdata_path.c_str());
|
||||
|
||||
Assert(YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(1252)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
|
||||
Assert(!YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(114514)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
|
||||
|
||||
// MARK: There is no testbench for MoveFile, CopyFile DeleteFile.
|
||||
// Because they can operate file system files.
|
||||
// And may cause test environment entering unstable status.
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void StdPatch() {
|
||||
|
||||
// Std Path
|
||||
|
||||
std::filesystem::path test_path;
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
test_path /= YYCC::StdPatch::ToStdPath(strl);
|
||||
}
|
||||
YYCC::yycc_u8string test_slashed_path(YYCC::StdPatch::ToUTF8Path(test_path));
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
std::wstring wdecilmer(1u, std::filesystem::path::preferred_separator);
|
||||
YYCC::yycc_u8string decilmer(YYCC::EncodingHelper::WcharToUTF8(wdecilmer));
|
||||
#else
|
||||
YYCC::yycc_u8string decilmer(1u, std::filesystem::path::preferred_separator);
|
||||
#endif
|
||||
YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), decilmer));
|
||||
|
||||
Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::StdPatch::ToStdPath, YYCC::StdPatch::ToUTF8Path"));
|
||||
|
||||
// StartsWith, EndsWith
|
||||
YYCC::yycc_u8string test_starts_ends_with(YYCC_U8("aaabbbccc"));
|
||||
Assert(YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::StartsWith"));
|
||||
Assert(!YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::StartsWith"));
|
||||
Assert(!YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::EndsWith"));
|
||||
Assert(YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::EndsWith"));
|
||||
|
||||
// Contains
|
||||
std::set<int> test_set { 1, 2, 3, 4, 6, 7 };
|
||||
Assert(YYCC::StdPatch::Contains(test_set, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
Assert(!YYCC::StdPatch::Contains(test_set, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
std::map<int, float> test_map { { 1, 1.0f }, { 4, 4.0f } };
|
||||
Assert(YYCC::StdPatch::Contains(test_map, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
Assert(!YYCC::StdPatch::Contains(test_map, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
|
||||
}
|
||||
|
||||
enum class TestEnum : int8_t {
|
||||
Test1, Test2, Test3
|
||||
};
|
||||
|
||||
class TestConfigManager {
|
||||
public:
|
||||
TestConfigManager() :
|
||||
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
|
||||
m_FloatSetting(YYCC_U8("float-setting"), 0.0f),
|
||||
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
|
||||
m_BoolSetting(YYCC_U8("bool-setting"), false),
|
||||
m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
|
||||
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
|
||||
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
|
||||
&m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting
|
||||
}) {}
|
||||
~TestConfigManager() {}
|
||||
|
||||
void PrintSettings() {
|
||||
Console::WriteLine(YYCC_U8("Config Manager Settings:"));
|
||||
|
||||
Console::FormatLine(YYCC_U8("\tint-setting: %" PRIi32), m_IntSetting.Get());
|
||||
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_FloatSetting.Get());
|
||||
Console::FormatLine(YYCC_U8("\tstring-setting: %s"), m_StringSetting.Get().c_str());
|
||||
|
||||
Console::FormatLine(YYCC_U8("\tbool-setting: %s"), m_BoolSetting.Get() ? YYCC_U8("true") : YYCC_U8("false"));
|
||||
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_ClampedFloatSetting.Get());
|
||||
Console::FormatLine(YYCC_U8("\tenum-setting: %" PRIi8), static_cast<std::underlying_type_t<TestEnum>>(m_EnumSetting.Get()));
|
||||
}
|
||||
|
||||
YYCC::ConfigManager::NumberSetting<int32_t> m_IntSetting;
|
||||
YYCC::ConfigManager::NumberSetting<float> m_FloatSetting;
|
||||
YYCC::ConfigManager::StringSetting m_StringSetting;
|
||||
|
||||
YYCC::ConfigManager::NumberSetting<bool> m_BoolSetting;
|
||||
YYCC::ConfigManager::NumberSetting<float> m_ClampedFloatSetting;
|
||||
YYCC::ConfigManager::NumberSetting<TestEnum> m_EnumSetting;
|
||||
|
||||
YYCC::ConfigManager::CoreManager m_CoreManager;
|
||||
};
|
||||
|
||||
static void ConfigManagerTestbench() {
|
||||
// init cfg manager
|
||||
TestConfigManager test;
|
||||
|
||||
// test constraint works
|
||||
Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::Constraints::Constraint"));
|
||||
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::Constraints::Constraint"));
|
||||
|
||||
// test modify settings
|
||||
#define TEST_MACRO(member_name, set_val) { \
|
||||
Assert(test.member_name.Set(set_val), YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
|
||||
Assert(test.member_name.Get() == set_val, YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(m_IntSetting, INT32_C(114));
|
||||
TEST_MACRO(m_FloatSetting, 2.0f);
|
||||
TEST_MACRO(m_StringSetting, YYCC_U8("fuck"));
|
||||
TEST_MACRO(m_BoolSetting, true);
|
||||
TEST_MACRO(m_ClampedFloatSetting, 0.5f);
|
||||
TEST_MACRO(m_EnumSetting, TestEnum::Test2);
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// test save
|
||||
test.PrintSettings();
|
||||
Assert(test.m_CoreManager.Save(), YYCC_U8("YYCC::ConfigManager::CoreManager::Save"));
|
||||
|
||||
// test reset
|
||||
test.m_CoreManager.Reset();
|
||||
test.PrintSettings();
|
||||
Assert(test.m_IntSetting.Get() == INT32_C(0), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_FloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_StringSetting.Get() == YYCC_U8(""), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_BoolSetting.Get() == false, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_EnumSetting.Get() == TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
|
||||
// test load
|
||||
Assert(test.m_CoreManager.Load(), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
test.PrintSettings();
|
||||
Assert(test.m_IntSetting.Get() == INT32_C(114), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_FloatSetting.Get() == 2.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_StringSetting.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_BoolSetting.Get() == true, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_ClampedFloatSetting.Get() == 0.5f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_EnumSetting.Get() == TestEnum::Test2, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
|
||||
}
|
||||
|
||||
class TestArgParser {
|
||||
public:
|
||||
TestArgParser() :
|
||||
m_IntArgument(YYCC_U8("int"), YYCC_U8_CHAR('i'), YYCC_U8("integral argument"), YYCC_U8("114514")),
|
||||
m_FloatArgument(nullptr, YYCC_U8_CHAR('f'), nullptr, nullptr, true),
|
||||
m_StringArgument(YYCC_U8("string"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true),
|
||||
m_BoolArgument(nullptr, YYCC_U8_CHAR('b'), nullptr),
|
||||
m_ClampedFloatArgument(YYCC_U8("clamped-float"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
|
||||
m_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the testbench of argument parser."), {
|
||||
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
|
||||
&m_BoolArgument, &m_ClampedFloatArgument
|
||||
}) {}
|
||||
~TestArgParser() {}
|
||||
|
||||
YYCC::ArgParser::NumberArgument<int32_t> m_IntArgument;
|
||||
YYCC::ArgParser::NumberArgument<float> m_FloatArgument;
|
||||
YYCC::ArgParser::StringArgument m_StringArgument;
|
||||
|
||||
YYCC::ArgParser::SwitchArgument m_BoolArgument;
|
||||
YYCC::ArgParser::NumberArgument<float> m_ClampedFloatArgument;
|
||||
|
||||
YYCC::ArgParser::OptionContext m_OptionContext;
|
||||
};
|
||||
|
||||
static void ArgParserTestbench(int argc, char* argv[]) {
|
||||
// test command line getter
|
||||
{
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromStd"));
|
||||
auto result = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
|
||||
for (result.Reset(); !result.IsEOF(); result.Next()) {
|
||||
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
|
||||
}
|
||||
}
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
{
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromWin32"));
|
||||
auto result = YYCC::ArgParser::ArgumentList::CreateFromWin32();
|
||||
for (result.Reset(); !result.IsEOF(); result.Next()) {
|
||||
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// test option context
|
||||
// init option context
|
||||
TestArgParser test;
|
||||
|
||||
#define PREPARE_DATA(...) const char* test_argv[] = { __VA_ARGS__ }; \
|
||||
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(sizeof(test_argv) / sizeof(char*), const_cast<char**>(test_argv));
|
||||
|
||||
// normal test
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514");
|
||||
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_FloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_StringArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_ClampedFloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// no argument
|
||||
{
|
||||
PREPARE_DATA("exec");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// error argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-?", "114514");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// lost argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-i");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// dplicated assign
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514" "--int", "114514");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// extra useless argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514" "1919810");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// invalid clamp argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514", "--clamped-float", "114.0");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// full argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514", "-f", "2.0", "--string", "fuck", "-b", "--clamped-float", "0.5");
|
||||
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_FloatArgument.IsCaptured() && test.m_FloatArgument.Get() == 2.0f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_StringArgument.IsCaptured() && test.m_StringArgument.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_ClampedFloatArgument.IsCaptured() && test.m_ClampedFloatArgument.Get() == 0.5f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
|
||||
// Help text
|
||||
test.m_OptionContext.Help();
|
||||
|
||||
#undef PREPARE_DATA
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// common testbench
|
||||
// normal
|
||||
YYCCTestbench::EncodingTestbench();
|
||||
YYCCTestbench::StringTestbench();
|
||||
YYCCTestbench::ParserTestbench();
|
||||
YYCCTestbench::WinFctTestbench();
|
||||
YYCCTestbench::StdPatch();
|
||||
// advanced
|
||||
YYCCTestbench::ConfigManagerTestbench();
|
||||
YYCCTestbench::ArgParserTestbench(argc, argv);
|
||||
|
||||
// testbench which may terminal app or ordering input
|
||||
YYCCTestbench::ConsoleTestbench();
|
||||
YYCCTestbench::DialogTestbench();
|
||||
YYCCTestbench::ExceptionTestbench();
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
735
testbench/main_legacy.cpp
Normal file
735
testbench/main_legacy.cpp
Normal file
@ -0,0 +1,735 @@
|
||||
#include <YYCCommonplace.hpp>
|
||||
#include <cstdio>
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
namespace Console = YYCC::ConsoleHelper;
|
||||
|
||||
namespace YYCCTestbench {
|
||||
|
||||
#pragma region Unicode Test Data
|
||||
|
||||
// UNICODE Test Strings
|
||||
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
|
||||
#define TEST_UNICODE_STR_JAPAN "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"
|
||||
#define TEST_UNICODE_STR_CHINA "\u7B80\u4F53\u4E2D\u6587"
|
||||
#define TEST_UNICODE_STR_KOREA "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"
|
||||
#define TEST_UNICODE_STR_ISRAEL "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"
|
||||
#define TEST_UNICODE_STR_EGYPT "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"
|
||||
#define TEST_UNICODE_STR_GREECE "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"
|
||||
#define TEST_UNICODE_STR_RUSSIA "\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"
|
||||
#define TEST_UNICODE_STR_THAILAND "\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"
|
||||
#define TEST_UNICODE_STR_FRANCE "fran\u00E7ais langue \u00E9trang\u00E8re"
|
||||
#define TEST_UNICODE_STR_SPAIN "ma\u00F1ana ol\u00E9"
|
||||
#define TEST_UNICODE_STR_MATHMATICS "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"
|
||||
#define TEST_UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
|
||||
|
||||
#define CONCAT(prefix, strl) prefix ## strl
|
||||
#define CPP_U8_LITERAL(strl) YYCC_U8(strl)
|
||||
#define CPP_U16_LITERAL(strl) CONCAT(u, strl)
|
||||
#define CPP_U32_LITERAL(strl) CONCAT(U, strl)
|
||||
#define CPP_WSTR_LITERAL(strl) CONCAT(L, strl)
|
||||
|
||||
static std::vector<YYCC::yycc_u8string> c_UTF8TestStrTable {
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::wstring> c_WStrTestStrTable {
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u16string> c_UTF16TestStrTable {
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u32string> c_UTF32TestStrTable {
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
|
||||
#undef CPP_WSTR_LITERAL
|
||||
#undef CPP_U32_LITERAL
|
||||
#undef CPP_U16_LITERAL
|
||||
#undef CPP_U8_LITERAL
|
||||
#undef CONCAT
|
||||
|
||||
#pragma endregion
|
||||
|
||||
static void Assert(bool condition, const YYCC::yycc_char8_t* description) {
|
||||
if (condition) {
|
||||
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("OK: %s")), description);
|
||||
} else {
|
||||
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("Failed: %s\n")), description);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void ConsoleTestbench() {
|
||||
// Color Test
|
||||
Console::EnableColorfulConsole();
|
||||
Console::WriteLine(YYCC_U8("Color Test:"));
|
||||
|
||||
#define TEST_MACRO(col) Console::WriteLine(YYCC_U8("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col ));
|
||||
// U+2588 is full block
|
||||
|
||||
TEST_MACRO(BLACK);
|
||||
TEST_MACRO(RED);
|
||||
TEST_MACRO(GREEN);
|
||||
TEST_MACRO(YELLOW);
|
||||
TEST_MACRO(BLUE);
|
||||
TEST_MACRO(MAGENTA);
|
||||
TEST_MACRO(CYAN);
|
||||
TEST_MACRO(WHITE);
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// UTF8 Output Test
|
||||
Console::WriteLine(YYCC_U8("UTF8 Output Test:"));
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
Console::FormatLine(YYCC_U8("\t%s"), strl.c_str());
|
||||
}
|
||||
|
||||
// UTF8 Input Test
|
||||
Console::WriteLine(YYCC_U8("UTF8 Input Test:"));
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
Console::FormatLine(YYCC_U8("\tPlease type: %s"), strl.c_str());
|
||||
Console::Write(YYCC_U8("\t> "));
|
||||
|
||||
YYCC::yycc_u8string gotten(Console::ReadLine());
|
||||
if (gotten == strl) Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("\tMatched! Got: %s")), gotten.c_str());
|
||||
else Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("\tNOT Matched! Got: %s")), gotten.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void EncodingTestbench() {
|
||||
// get test tuple size
|
||||
size_t count = c_UTF8TestStrTable.size();
|
||||
|
||||
// check the convertion between given string
|
||||
for (size_t i = 0u; i < count; ++i) {
|
||||
// get item
|
||||
const auto& u8str = c_UTF8TestStrTable[i];
|
||||
const auto& u16str = c_UTF16TestStrTable[i];
|
||||
const auto& u32str = c_UTF32TestStrTable[i];
|
||||
|
||||
// create cache variables
|
||||
YYCC::yycc_u8string u8cache;
|
||||
std::u16string u16cache;
|
||||
std::u32string u32cache;
|
||||
|
||||
// do convertion check
|
||||
Assert(YYCC::EncodingHelper::UTF8ToUTF16(u8str, u16cache) && u16cache == u16str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF16"));
|
||||
Assert(YYCC::EncodingHelper::UTF8ToUTF32(u8str, u32cache) && u32cache == u32str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF32"));
|
||||
|
||||
Assert(YYCC::EncodingHelper::UTF16ToUTF8(u16str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF16ToUTF8"));
|
||||
Assert(YYCC::EncodingHelper::UTF32ToUTF8(u32str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF32ToUTF8"));
|
||||
}
|
||||
|
||||
// check wstring convertion on windows
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
for (size_t i = 0u; i < count; ++i) {
|
||||
// get item
|
||||
const auto& u8str = c_UTF8TestStrTable[i];
|
||||
const auto& wstr = c_WStrTestStrTable[i];
|
||||
|
||||
// create cache variables
|
||||
YYCC::yycc_u8string u8cache;
|
||||
std::wstring wcache;
|
||||
|
||||
// do convertion check
|
||||
Assert(YYCC::EncodingHelper::UTF8ToWchar(u8str.c_str(), wcache) && wcache == wstr, YYCC_U8("YYCC::EncodingHelper::UTF8ToWchar"));
|
||||
Assert(YYCC::EncodingHelper::WcharToUTF8(wstr.c_str(), u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::WcharToUTF8"));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
static void StringTestbench() {
|
||||
// Test Printf
|
||||
auto test_printf = YYCC::StringHelper::Printf(YYCC_U8("%s == %s"), YYCC_U8("Hello World"), YYCC_U8("Hello, world"));
|
||||
Assert(test_printf == YYCC_U8("Hello World == Hello, world"), YYCC_U8("YYCC::StringHelper::Printf"));
|
||||
|
||||
// Test Replace
|
||||
auto test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8("bb"), YYCC_U8("dd")); // normal case
|
||||
Assert(test_replace == YYCC_U8("aaddcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8("zz"), YYCC_U8("yy")); // no replace
|
||||
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC::yycc_u8string_view(), YYCC_U8("zz")); // empty finding
|
||||
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaaabbaa"), YYCC_U8("aa"), YYCC_U8("")); // no replaced string
|
||||
Assert(test_replace == YYCC_U8("bb"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaxcc"), YYCC_U8("x"), YYCC_U8("yx")); // nested replacing
|
||||
Assert(test_replace == YYCC_U8("aayxcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
test_replace = YYCC::StringHelper::Replace(YYCC::yycc_u8string_view(), YYCC_U8(""), YYCC_U8("xy")); // empty source string
|
||||
Assert(test_replace == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
|
||||
// Test Upper / Lower
|
||||
auto test_lower = YYCC::StringHelper::Lower(YYCC_U8("LOWER"));
|
||||
Assert(test_lower == YYCC_U8("lower"), YYCC_U8("YYCC::StringHelper::Lower"));
|
||||
auto test_upper = YYCC::StringHelper::Upper(YYCC_U8("upper"));
|
||||
Assert(test_upper == YYCC_U8("UPPER"), YYCC_U8("YYCC::StringHelper::Upper"));
|
||||
|
||||
// Test Join
|
||||
std::vector<YYCC::yycc_u8string> test_join_container {
|
||||
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
|
||||
};
|
||||
auto test_join = YYCC::StringHelper::Join(test_join_container.begin(), test_join_container.end(), YYCC_U8(", "));
|
||||
Assert(test_join == YYCC_U8(", 1, 2, "), YYCC_U8("YYCC::StringHelper::Join"));
|
||||
|
||||
// Test Split
|
||||
auto test_split = YYCC::StringHelper::Split(YYCC_U8(", 1, 2, "), YYCC_U8(", ")); // normal
|
||||
Assert(test_split.size() == 4u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[1] == YYCC_U8("1"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[2] == YYCC_U8("2"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[3] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched delimiter
|
||||
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC::yycc_u8string_view()); // empty delimiter
|
||||
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
test_split = YYCC::StringHelper::Split(YYCC::yycc_u8string_view(), YYCC_U8("")); // empty source string
|
||||
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0].empty(), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
|
||||
}
|
||||
|
||||
static void ParserTestbench() {
|
||||
|
||||
// Test success TryParse
|
||||
#define TEST_MACRO(type_t, value, string_value, ...) { \
|
||||
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
|
||||
type_t cache; \
|
||||
Assert(YYCC::ParserHelper::TryParse<type_t>(cache_string, cache, ##__VA_ARGS__) && cache == value, YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(int8_t, INT8_C(-61), "-61");
|
||||
TEST_MACRO(uint8_t, UINT8_C(200), "200");
|
||||
TEST_MACRO(int16_t, INT16_C(6161), "6161");
|
||||
TEST_MACRO(uint16_t, UINT16_C(32800), "32800");
|
||||
TEST_MACRO(int32_t, INT32_C(61616161), "61616161");
|
||||
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
|
||||
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
|
||||
TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807");
|
||||
TEST_MACRO(uint32_t, UINT32_C(0xffff), "ffff", 16);
|
||||
TEST_MACRO(float, 1.0f, "1.0");
|
||||
TEST_MACRO(double, 1.0, "1.0");
|
||||
TEST_MACRO(bool, true, "true");
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// Test failed TryParse
|
||||
#define TEST_MACRO(type_t, string_value, ...) { \
|
||||
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
|
||||
type_t cache; \
|
||||
Assert(!YYCC::ParserHelper::TryParse<type_t>(cache_string, cache, ##__VA_ARGS__), YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(int8_t, "6161");
|
||||
TEST_MACRO(uint8_t, "32800");
|
||||
TEST_MACRO(int16_t, "61616161");
|
||||
TEST_MACRO(uint16_t, "4294967293");
|
||||
TEST_MACRO(int32_t, "616161616161");
|
||||
TEST_MACRO(uint32_t, "9223372036854775807");
|
||||
TEST_MACRO(int64_t, "616161616161616161616161");
|
||||
TEST_MACRO(uint64_t, "92233720368547758079223372036854775807");
|
||||
TEST_MACRO(float, "1e40");
|
||||
TEST_MACRO(double, "1e114514");
|
||||
TEST_MACRO(bool, "hello, world!");
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// Test ToString
|
||||
#define TEST_MACRO(type_t, value, string_value, ...) { \
|
||||
type_t cache = value; \
|
||||
YYCC::yycc_u8string ret(YYCC::ParserHelper::ToString<type_t>(cache, ##__VA_ARGS__)); \
|
||||
Assert(ret == YYCC_U8(string_value), YYCC_U8("YYCC::StringHelper::ToString<" #type_t ">")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(int8_t, INT8_C(-61), "-61");
|
||||
TEST_MACRO(uint8_t, UINT8_C(200), "200");
|
||||
TEST_MACRO(int16_t, INT16_C(6161), "6161");
|
||||
TEST_MACRO(uint16_t, UINT16_C(32800), "32800");
|
||||
TEST_MACRO(int32_t, INT32_C(61616161), "61616161");
|
||||
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
|
||||
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
|
||||
TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807");
|
||||
TEST_MACRO(uint32_t, UINT32_C(0xffff), "ffff", 16);
|
||||
TEST_MACRO(float, 1.0f, "1.0", std::chars_format::fixed, 1);
|
||||
TEST_MACRO(double, 1.0, "1.0", std::chars_format::fixed, 1);
|
||||
TEST_MACRO(bool, true, "true");
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
}
|
||||
|
||||
static void DialogTestbench() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
YYCC::yycc_u8string ret;
|
||||
std::vector<YYCC::yycc_u8string> rets;
|
||||
|
||||
YYCC::DialogHelper::FileDialog params;
|
||||
auto& filters = params.ConfigreFileTypes();
|
||||
filters.Add(YYCC_U8("Microsoft Word (*.docx; *.doc)"), { YYCC_U8("*.docx"), YYCC_U8("*.doc") });
|
||||
filters.Add(YYCC_U8("Microsoft Excel (*.xlsx; *.xls)"), { YYCC_U8("*.xlsx"), YYCC_U8("*.xls") });
|
||||
filters.Add(YYCC_U8("Microsoft PowerPoint (*.pptx; *.ppt)"), { YYCC_U8("*.pptx"), YYCC_U8("*.ppt") });
|
||||
filters.Add(YYCC_U8("Text File (*.txt)"), { YYCC_U8("*.txt") });
|
||||
filters.Add(YYCC_U8("All Files (*.*)"), { YYCC_U8("*.*") });
|
||||
params.SetDefaultFileTypeIndex(0u);
|
||||
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Open File: %s"), ret.c_str());
|
||||
}
|
||||
if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) {
|
||||
Console::WriteLine(YYCC_U8("Open Multiple Files:"));
|
||||
for (const auto& item : rets) {
|
||||
Console::FormatLine(YYCC_U8("\t%s"), item.c_str());
|
||||
}
|
||||
}
|
||||
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Save File: %s"), ret.c_str());
|
||||
}
|
||||
params.Clear();
|
||||
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Open Folder: %s"), ret.c_str());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ExceptionTestbench() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
YYCC::ExceptionHelper::Register([](const YYCC::yycc_u8string& log_path, const YYCC::yycc_u8string& coredump_path) -> void {
|
||||
MessageBoxW(
|
||||
NULL,
|
||||
YYCC::EncodingHelper::UTF8ToWchar(
|
||||
YYCC::StringHelper::Printf(YYCC_U8("Log generated:\nLog path: %s\nCore dump path: %s"), log_path.c_str(), coredump_path.c_str())
|
||||
).c_str(),
|
||||
L"Fatal Error", MB_OK + MB_ICONERROR
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Perform a div zero exception.
|
||||
#if defined (YYCC_DEBUG_UE_FILTER)
|
||||
// Reference: https://stackoverflow.com/questions/20981982/is-it-possible-to-debug-unhandledexceptionfilters-with-a-debugger
|
||||
__try {
|
||||
// all of code normally inside of main or WinMain here...
|
||||
int i = 1, j = 0;
|
||||
int k = i / j;
|
||||
} __except (YYCC::ExceptionHelper::DebugCallUExceptionImpl(GetExceptionInformation())) {
|
||||
OutputDebugStringW(L"executed filter function\n");
|
||||
}
|
||||
#else
|
||||
int i = 1, j = 0;
|
||||
int k = i / j;
|
||||
#endif
|
||||
|
||||
YYCC::ExceptionHelper::Unregister();
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void WinFctTestbench() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
HMODULE test_current_module;
|
||||
Assert((test_current_module = YYCC::WinFctHelper::GetCurrentModule()) != nullptr, YYCC_U8("YYCC::WinFctHelper::GetCurrentModule"));
|
||||
Console::FormatLine(YYCC_U8("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR), test_current_module);
|
||||
|
||||
YYCC::yycc_u8string test_temp;
|
||||
Assert(YYCC::WinFctHelper::GetTempDirectory(test_temp), YYCC_U8("YYCC::WinFctHelper::GetTempDirectory"));
|
||||
Console::FormatLine(YYCC_U8("Temp Directory: %s"), test_temp.c_str());
|
||||
|
||||
YYCC::yycc_u8string test_module_name;
|
||||
Assert(YYCC::WinFctHelper::GetModuleFileName(YYCC::WinFctHelper::GetCurrentModule(), test_module_name), YYCC_U8("YYCC::WinFctHelper::GetModuleFileName"));
|
||||
Console::FormatLine(YYCC_U8("Current Module File Name: %s"), test_module_name.c_str());
|
||||
|
||||
YYCC::yycc_u8string test_localappdata_path;
|
||||
Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), YYCC_U8("YYCC::WinFctHelper::GetLocalAppData"));
|
||||
Console::FormatLine(YYCC_U8("Local AppData: %s"), test_localappdata_path.c_str());
|
||||
|
||||
Assert(YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(1252)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
|
||||
Assert(!YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(114514)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
|
||||
|
||||
// MARK: There is no testbench for MoveFile, CopyFile DeleteFile.
|
||||
// Because they can operate file system files.
|
||||
// And may cause test environment entering unstable status.
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void StdPatchTestbench() {
|
||||
|
||||
// Std Path
|
||||
|
||||
std::filesystem::path test_path;
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
test_path /= YYCC::StdPatch::ToStdPath(strl);
|
||||
}
|
||||
YYCC::yycc_u8string test_slashed_path(YYCC::StdPatch::ToUTF8Path(test_path));
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
std::wstring wdelimiter(1u, std::filesystem::path::preferred_separator);
|
||||
YYCC::yycc_u8string delimiter(YYCC::EncodingHelper::WcharToUTF8(wdelimiter));
|
||||
#else
|
||||
YYCC::yycc_u8string delimiter(1u, std::filesystem::path::preferred_separator);
|
||||
#endif
|
||||
YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), delimiter));
|
||||
|
||||
Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::StdPatch::ToStdPath, YYCC::StdPatch::ToUTF8Path"));
|
||||
|
||||
// StartsWith, EndsWith
|
||||
YYCC::yycc_u8string test_starts_ends_with(YYCC_U8("aaabbbccc"));
|
||||
Assert(YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::StartsWith"));
|
||||
Assert(!YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::StartsWith"));
|
||||
Assert(!YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::EndsWith"));
|
||||
Assert(YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::EndsWith"));
|
||||
|
||||
// Contains
|
||||
std::set<int> test_set { 1, 2, 3, 4, 6, 7 };
|
||||
Assert(YYCC::StdPatch::Contains(test_set, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
Assert(!YYCC::StdPatch::Contains(test_set, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
std::map<int, float> test_map { { 1, 1.0f }, { 4, 4.0f } };
|
||||
Assert(YYCC::StdPatch::Contains(test_map, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
Assert(!YYCC::StdPatch::Contains(test_map, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
|
||||
|
||||
}
|
||||
|
||||
enum class TestFlagEnum : uint8_t {
|
||||
Test1 = 0b00000000,
|
||||
Test2 = 0b00000001,
|
||||
Test3 = 0b00000010,
|
||||
Test4 = 0b00000100,
|
||||
Test5 = 0b00001000,
|
||||
Test6 = 0b00010000,
|
||||
Test7 = 0b00100000,
|
||||
Test8 = 0b01000000,
|
||||
Test9 = 0b10000000,
|
||||
Inverted = 0b01111111,
|
||||
Merged = Test3 + Test5,
|
||||
};
|
||||
|
||||
static void EnumHelperTestbench() {
|
||||
TestFlagEnum val;
|
||||
|
||||
Assert(YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5) == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Merge"));
|
||||
|
||||
Assert(YYCC::EnumHelper::Invert(TestFlagEnum::Test9) == TestFlagEnum::Inverted, YYCC_U8("YYCC::EnumHelper::Invert"));
|
||||
|
||||
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
|
||||
YYCC::EnumHelper::Mask(val, TestFlagEnum::Test3);
|
||||
Assert(YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
|
||||
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
|
||||
YYCC::EnumHelper::Mask(val, TestFlagEnum::Test4);
|
||||
Assert(!YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
|
||||
|
||||
val = TestFlagEnum::Test3;
|
||||
YYCC::EnumHelper::Add(val, TestFlagEnum::Test5);
|
||||
Assert(val == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Add"));
|
||||
|
||||
val = TestFlagEnum::Merged;
|
||||
YYCC::EnumHelper::Remove(val, TestFlagEnum::Test5);
|
||||
Assert(val == TestFlagEnum::Test3, YYCC_U8("YYCC::EnumHelper::Remove"));
|
||||
|
||||
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
|
||||
Assert(YYCC::EnumHelper::Has(val, TestFlagEnum::Test3), YYCC_U8("YYCC::EnumHelper::Has"));
|
||||
Assert(!YYCC::EnumHelper::Has(val, TestFlagEnum::Test4), YYCC_U8("YYCC::EnumHelper::Has"));
|
||||
|
||||
Assert(!YYCC::EnumHelper::Bool(TestFlagEnum::Test1), YYCC_U8("YYCC::EnumHelper::Bool"));
|
||||
Assert(YYCC::EnumHelper::Bool(TestFlagEnum::Test2), YYCC_U8("YYCC::EnumHelper::Bool"));
|
||||
|
||||
}
|
||||
|
||||
static void VersionMacroTestbench() {
|
||||
Assert(YYCC_VERCMP_E(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_E"));
|
||||
Assert(!YYCC_VERCMP_NE(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_NE"));
|
||||
Assert(YYCC_VERCMP_G(1, 2, 3, 0, 2, 5), YYCC_U8("YYCC_VERCMP_G"));
|
||||
Assert(YYCC_VERCMP_GE(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_GE"));
|
||||
Assert(YYCC_VERCMP_NL(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_NL"));
|
||||
Assert(YYCC_VERCMP_L(0, 2, 5, 1, 2, 3), YYCC_U8("YYCC_VERCMP_L"));
|
||||
Assert(YYCC_VERCMP_LE(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_LE"));
|
||||
Assert(YYCC_VERCMP_NG(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_NG"));
|
||||
}
|
||||
|
||||
enum class TestEnum : int8_t {
|
||||
Test1, Test2, Test3
|
||||
};
|
||||
|
||||
class TestConfigManager {
|
||||
public:
|
||||
TestConfigManager() :
|
||||
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
|
||||
m_FloatSetting(YYCC_U8("float-setting"), 0.0f),
|
||||
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
|
||||
m_BoolSetting(YYCC_U8("bool-setting"), false),
|
||||
m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
|
||||
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
|
||||
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
|
||||
&m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting
|
||||
}) {}
|
||||
~TestConfigManager() {}
|
||||
|
||||
void PrintSettings() {
|
||||
Console::WriteLine(YYCC_U8("Config Manager Settings:"));
|
||||
|
||||
Console::FormatLine(YYCC_U8("\tint-setting: %" PRIi32), m_IntSetting.Get());
|
||||
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_FloatSetting.Get());
|
||||
Console::FormatLine(YYCC_U8("\tstring-setting: %s"), m_StringSetting.Get().c_str());
|
||||
|
||||
Console::FormatLine(YYCC_U8("\tbool-setting: %s"), m_BoolSetting.Get() ? YYCC_U8("true") : YYCC_U8("false"));
|
||||
Console::FormatLine(YYCC_U8("\tfloat-setting: %f"), m_ClampedFloatSetting.Get());
|
||||
Console::FormatLine(YYCC_U8("\tenum-setting: %" PRIi8), static_cast<std::underlying_type_t<TestEnum>>(m_EnumSetting.Get()));
|
||||
}
|
||||
|
||||
YYCC::ConfigManager::NumberSetting<int32_t> m_IntSetting;
|
||||
YYCC::ConfigManager::NumberSetting<float> m_FloatSetting;
|
||||
YYCC::ConfigManager::StringSetting m_StringSetting;
|
||||
|
||||
YYCC::ConfigManager::NumberSetting<bool> m_BoolSetting;
|
||||
YYCC::ConfigManager::NumberSetting<float> m_ClampedFloatSetting;
|
||||
YYCC::ConfigManager::NumberSetting<TestEnum> m_EnumSetting;
|
||||
|
||||
YYCC::ConfigManager::CoreManager m_CoreManager;
|
||||
};
|
||||
|
||||
static void ConfigManagerTestbench() {
|
||||
// init cfg manager
|
||||
TestConfigManager test;
|
||||
|
||||
// test constraint works
|
||||
Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::Constraints::Constraint"));
|
||||
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::Constraints::Constraint"));
|
||||
|
||||
// test modify settings
|
||||
#define TEST_MACRO(member_name, set_val) { \
|
||||
Assert(test.member_name.Set(set_val), YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
|
||||
Assert(test.member_name.Get() == set_val, YYCC_U8("YYCC::ConfigManager::AbstractSetting::Set")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(m_IntSetting, INT32_C(114));
|
||||
TEST_MACRO(m_FloatSetting, 2.0f);
|
||||
TEST_MACRO(m_StringSetting, YYCC_U8("fuck"));
|
||||
TEST_MACRO(m_BoolSetting, true);
|
||||
TEST_MACRO(m_ClampedFloatSetting, 0.5f);
|
||||
TEST_MACRO(m_EnumSetting, TestEnum::Test2);
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// test save
|
||||
test.PrintSettings();
|
||||
Assert(test.m_CoreManager.Save(), YYCC_U8("YYCC::ConfigManager::CoreManager::Save"));
|
||||
|
||||
// test reset
|
||||
test.m_CoreManager.Reset();
|
||||
test.PrintSettings();
|
||||
Assert(test.m_IntSetting.Get() == INT32_C(0), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_FloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_StringSetting.Get() == YYCC_U8(""), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_BoolSetting.Get() == false, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
Assert(test.m_EnumSetting.Get() == TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
|
||||
|
||||
// test load
|
||||
YYCC::ConfigManager::ConfigLoadResult wrong_result = YYCC::EnumHelper::Merge(
|
||||
YYCC::ConfigManager::ConfigLoadResult::ItemError,
|
||||
YYCC::ConfigManager::ConfigLoadResult::BrokenFile
|
||||
);
|
||||
Assert(!YYCC::EnumHelper::Has(test.m_CoreManager.Load(), wrong_result), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
test.PrintSettings();
|
||||
Assert(test.m_IntSetting.Get() == INT32_C(114), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_FloatSetting.Get() == 2.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_StringSetting.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_BoolSetting.Get() == true, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_ClampedFloatSetting.Get() == 0.5f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
Assert(test.m_EnumSetting.Get() == TestEnum::Test2, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
|
||||
|
||||
}
|
||||
|
||||
class TestArgParser {
|
||||
public:
|
||||
TestArgParser() :
|
||||
m_IntArgument(YYCC_U8("int"), YYCC_U8_CHAR('i'), YYCC_U8("integral argument"), YYCC_U8("114514")),
|
||||
m_FloatArgument(nullptr, YYCC_U8_CHAR('f'), nullptr, nullptr, true),
|
||||
m_StringArgument(YYCC_U8("string"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true),
|
||||
m_BoolArgument(nullptr, YYCC_U8_CHAR('b'), nullptr),
|
||||
m_ClampedFloatArgument(YYCC_U8("clamped-float"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
|
||||
m_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the testbench of argument parser."), {
|
||||
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
|
||||
&m_BoolArgument, &m_ClampedFloatArgument
|
||||
}) {}
|
||||
~TestArgParser() {}
|
||||
|
||||
YYCC::ArgParser::NumberArgument<int32_t> m_IntArgument;
|
||||
YYCC::ArgParser::NumberArgument<float> m_FloatArgument;
|
||||
YYCC::ArgParser::StringArgument m_StringArgument;
|
||||
|
||||
YYCC::ArgParser::SwitchArgument m_BoolArgument;
|
||||
YYCC::ArgParser::NumberArgument<float> m_ClampedFloatArgument;
|
||||
|
||||
YYCC::ArgParser::OptionContext m_OptionContext;
|
||||
};
|
||||
|
||||
static void ArgParserTestbench(int argc, char* argv[]) {
|
||||
// test command line getter
|
||||
{
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromStd"));
|
||||
auto result = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
|
||||
for (result.Reset(); !result.IsEOF(); result.Next()) {
|
||||
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
|
||||
}
|
||||
}
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
{
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromWin32"));
|
||||
auto result = YYCC::ArgParser::ArgumentList::CreateFromWin32();
|
||||
for (result.Reset(); !result.IsEOF(); result.Next()) {
|
||||
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// test option context
|
||||
// init option context
|
||||
TestArgParser test;
|
||||
|
||||
#define PREPARE_DATA(...) const char* test_argv[] = { __VA_ARGS__ }; \
|
||||
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(sizeof(test_argv) / sizeof(char*), const_cast<char**>(test_argv));
|
||||
|
||||
// normal test
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514");
|
||||
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_FloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_StringArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(!test.m_ClampedFloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// no argument
|
||||
{
|
||||
PREPARE_DATA("exec");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// error argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-?", "114514");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// lost argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-i");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// dplicated assign
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514" "--int", "114514");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// extra useless argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514" "1919810");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// invalid clamp argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514", "--clamped-float", "114.0");
|
||||
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
// full argument
|
||||
{
|
||||
PREPARE_DATA("exec", "-i", "114514", "-f", "2.0", "--string", "fuck", "-b", "--clamped-float", "0.5");
|
||||
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_FloatArgument.IsCaptured() && test.m_FloatArgument.Get() == 2.0f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_StringArgument.IsCaptured() && test.m_StringArgument.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
Assert(test.m_ClampedFloatArgument.IsCaptured() && test.m_ClampedFloatArgument.Get() == 0.5f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
|
||||
test.m_OptionContext.Reset();
|
||||
}
|
||||
|
||||
// Help text
|
||||
test.m_OptionContext.Help();
|
||||
|
||||
#undef PREPARE_DATA
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
||||
#if YYCC_VERCMP_NE(YYCC_VER_MAJOR, YYCC_VER_MINOR, YYCC_VER_PATCH, 1, 3 ,0)
|
||||
#error "The YYCC library used when compiling is not match code expected, this may cause build error."
|
||||
#error "If you trust it, please annotate these preprocessor statement, otherwise please contact developer."
|
||||
#endif
|
||||
|
||||
// common testbench
|
||||
// normal
|
||||
YYCCTestbench::EncodingTestbench();
|
||||
YYCCTestbench::StringTestbench();
|
||||
YYCCTestbench::ParserTestbench();
|
||||
YYCCTestbench::WinFctTestbench();
|
||||
YYCCTestbench::StdPatchTestbench();
|
||||
YYCCTestbench::EnumHelperTestbench();
|
||||
YYCCTestbench::VersionMacroTestbench();
|
||||
// advanced
|
||||
YYCCTestbench::ConfigManagerTestbench();
|
||||
YYCCTestbench::ArgParserTestbench(argc, argv);
|
||||
|
||||
// testbench which may terminal app or ordering input
|
||||
YYCCTestbench::ConsoleTestbench();
|
||||
YYCCTestbench::DialogTestbench();
|
||||
YYCCTestbench::ExceptionTestbench();
|
||||
}
|
49
testbench/yycc/constraint.cpp
Normal file
49
testbench/yycc/constraint.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/constraint.hpp>
|
||||
#include <yycc/prelude/rust.hpp>
|
||||
|
||||
#define CONSTRAINT ::yycc::constraint::Constraint
|
||||
|
||||
namespace yycctest::constraint {
|
||||
|
||||
template<typename T>
|
||||
bool check(const T& value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T clamp(const T& value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
TEST(Constraint, Normal) {
|
||||
CONSTRAINT<u32> instance(check<u32>, clamp<u32>);
|
||||
EXPECT_TRUE(instance.support_check());
|
||||
EXPECT_TRUE(instance.support_clamp());
|
||||
EXPECT_FALSE(instance.check(0));
|
||||
EXPECT_EQ(instance.clamp(0), 0);
|
||||
}
|
||||
|
||||
TEST(Constraint, SomeNone) {
|
||||
{
|
||||
CONSTRAINT<u32> instance(check<u32>, nullptr);
|
||||
EXPECT_TRUE(instance.support_check());
|
||||
EXPECT_FALSE(instance.support_clamp());
|
||||
EXPECT_FALSE(instance.check(0));
|
||||
}
|
||||
{
|
||||
CONSTRAINT<u32> instance(nullptr, clamp<u32>);
|
||||
EXPECT_FALSE(instance.support_check());
|
||||
EXPECT_TRUE(instance.support_clamp());
|
||||
EXPECT_EQ(instance.clamp(0), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Constraint, AllNone) {
|
||||
CONSTRAINT<u32> instance(nullptr, nullptr);
|
||||
EXPECT_FALSE(instance.support_check());
|
||||
EXPECT_FALSE(instance.support_clamp());
|
||||
}
|
||||
|
||||
}
|
5
testbench/yycc/constraint/builder.cpp
Normal file
5
testbench/yycc/constraint/builder.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace yycctest::constraint::builder {
|
||||
|
||||
}
|
87
testbench/yycc/rust/parse.cpp
Normal file
87
testbench/yycc/rust/parse.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/rust/parse.hpp>
|
||||
|
||||
#include <yycc/prelude/rust.hpp>
|
||||
|
||||
#define PARSE ::yycc::rust::parse
|
||||
|
||||
namespace yycctest::rust::parse {
|
||||
|
||||
// We only want to test it if C++ support it.
|
||||
#if defined(YYCC_CPPFEAT_EXPECTED)
|
||||
|
||||
// This namespace is just a wrapper for legacy "parse" module.
|
||||
// So the test is just a copy of original implementation.
|
||||
// Please update this if original test was updated.
|
||||
|
||||
#define TEST_SUCCESS(type_t, expected_value, string_value, ...) \
|
||||
{ \
|
||||
u8string cache_string(YYCC_U8(string_value)); \
|
||||
auto rv = PARSE::parse<type_t>(cache_string, ##__VA_ARGS__); \
|
||||
ASSERT_TRUE(rv.has_value()); \
|
||||
EXPECT_EQ(rv.value(), expected_value); \
|
||||
}
|
||||
|
||||
#define TEST_FAIL(type_t, string_value, ...) \
|
||||
{ \
|
||||
u8string cache_string(YYCC_U8(string_value)); \
|
||||
auto rv = PARSE::parse<type_t>(cache_string, ##__VA_ARGS__); \
|
||||
EXPECT_FALSE(rv.has_value()); \
|
||||
}
|
||||
|
||||
TEST(RustParse, Common) {
|
||||
TEST_SUCCESS(i8, INT8_C(-61), "-61");
|
||||
TEST_SUCCESS(u8, UINT8_C(200), "200");
|
||||
TEST_SUCCESS(i16, INT16_C(6161), "6161");
|
||||
TEST_SUCCESS(u16, UINT16_C(32800), "32800");
|
||||
TEST_SUCCESS(i32, INT32_C(61616161), "61616161");
|
||||
TEST_SUCCESS(u32, UINT32_C(4294967293), "4294967293");
|
||||
TEST_SUCCESS(i64, INT64_C(616161616161), "616161616161");
|
||||
TEST_SUCCESS(u64, UINT64_C(9223372036854775807), "9223372036854775807");
|
||||
|
||||
TEST_SUCCESS(float, 1.0f, "1.0");
|
||||
TEST_SUCCESS(double, 1.0, "1.0");
|
||||
|
||||
TEST_SUCCESS(bool, true, "true");
|
||||
TEST_SUCCESS(bool, false, "false");
|
||||
}
|
||||
|
||||
TEST(RustParse, Radix) {
|
||||
TEST_SUCCESS(u32, UINT32_C(0xffff), "ffff", 16);
|
||||
TEST_SUCCESS(u32, UINT32_C(032), "032", 8);
|
||||
TEST_SUCCESS(u32, UINT32_C(0B1011), "1011", 2);
|
||||
}
|
||||
|
||||
TEST(RustParse, CaseInsensitive) {
|
||||
TEST_SUCCESS(bool, true, "tRUE");
|
||||
}
|
||||
|
||||
TEST(RustParse, Overflow) {
|
||||
TEST_FAIL(i8, "6161");
|
||||
TEST_FAIL(u8, "32800");
|
||||
TEST_FAIL(i16, "61616161");
|
||||
TEST_FAIL(u16, "4294967293");
|
||||
TEST_FAIL(i32, "616161616161");
|
||||
TEST_FAIL(u32, "9223372036854775807");
|
||||
TEST_FAIL(i64, "616161616161616161616161");
|
||||
TEST_FAIL(u64, "92233720368547758079223372036854775807");
|
||||
|
||||
TEST_FAIL(float, "1e40");
|
||||
TEST_FAIL(double, "1e114514");
|
||||
}
|
||||
|
||||
TEST(RustParse, BadRadix) {
|
||||
TEST_FAIL(u32, "fghj", 16);
|
||||
TEST_FAIL(u32, "099", 8);
|
||||
TEST_FAIL(u32, "12345", 2);
|
||||
}
|
||||
|
||||
TEST(RustParse, InvalidWords) {
|
||||
TEST_FAIL(u32, "hello, world!");
|
||||
TEST_FAIL(bool, "hello, world!");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace yycctest::rust::parse
|
14
testbench/yycc/rust/stringify.cpp
Normal file
14
testbench/yycc/rust/stringify.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/rust/stringify.hpp>
|
||||
|
||||
#include <yycc/prelude/rust.hpp>
|
||||
|
||||
#define STRINGIFY ::yycc::string::stringify
|
||||
|
||||
namespace yycctest::rust::stringify {
|
||||
|
||||
// There is not testbench for this
|
||||
// because it just a map to original implementation without any modification.
|
||||
|
||||
}
|
96
testbench/yycc/string/op.cpp
Normal file
96
testbench/yycc/string/op.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/op.hpp>
|
||||
#include <yycc/string/reinterpret.hpp>
|
||||
|
||||
#include <yycc/prelude/core.hpp>
|
||||
|
||||
#define OP ::yycc::string::op
|
||||
|
||||
namespace yycctest::string::op {
|
||||
|
||||
TEST(StringOp, Printf) {
|
||||
auto rv = OP::printf(YYCC_U8("%s == %s"), YYCC_U8("Hello World"), YYCC_U8("Hello, world"));
|
||||
EXPECT_EQ(rv, YYCC_U8("Hello World == Hello, world"));
|
||||
}
|
||||
|
||||
TEST(StringOp, Replace) {
|
||||
// Normal case
|
||||
{
|
||||
auto rv = OP::replace(YYCC_U8("aabbcc"), YYCC_U8("bb"), YYCC_U8("dd"));
|
||||
EXPECT_EQ(rv, YYCC_U8("aaddcc"));
|
||||
}
|
||||
// No matched expected string
|
||||
{
|
||||
auto rv = OP::replace(YYCC_U8("aabbcc"), YYCC_U8("zz"), YYCC_U8("yy"));
|
||||
EXPECT_EQ(rv, YYCC_U8("aabbcc"));
|
||||
}
|
||||
// Empty expected string
|
||||
{
|
||||
auto rv = OP::replace(YYCC_U8("aabbcc"), u8string_view(), YYCC_U8("zz"));
|
||||
EXPECT_EQ(rv, YYCC_U8("aabbcc"));
|
||||
}
|
||||
// Empty replace string
|
||||
{
|
||||
auto rv = OP::replace(YYCC_U8("aaaabbaa"), YYCC_U8("aa"), YYCC_U8(""));
|
||||
EXPECT_EQ(rv, YYCC_U8("bb"));
|
||||
}
|
||||
// Nested replacing
|
||||
{
|
||||
auto rv = OP::replace(YYCC_U8("aaxcc"), YYCC_U8("x"), YYCC_U8("yx"));
|
||||
EXPECT_EQ(rv, YYCC_U8("aayxcc"));
|
||||
}
|
||||
// Empty source string
|
||||
{
|
||||
auto rv = OP::replace(u8string_view(), YYCC_U8(""), YYCC_U8("xy"));
|
||||
EXPECT_EQ(rv, YYCC_U8(""));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StringOp, Lower) {
|
||||
auto rv = OP::to_lower(YYCC_U8("LOWER"));
|
||||
EXPECT_EQ(rv, YYCC_U8("lower"));
|
||||
}
|
||||
|
||||
TEST(StringOp, Upper) {
|
||||
auto rv = OP::to_upper(YYCC_U8("upper"));
|
||||
EXPECT_EQ(rv, YYCC_U8("UPPER"));
|
||||
}
|
||||
|
||||
TEST(StringOp, Join) {
|
||||
std::vector<u8string> datas{YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")};
|
||||
auto rv = OP::join(datas.begin(), datas.end(), YYCC_U8(", "));
|
||||
EXPECT_EQ(rv, YYCC_U8(", 1, 2, "));
|
||||
}
|
||||
|
||||
TEST(StringOp, Split) {
|
||||
// Normal
|
||||
{
|
||||
auto rv = OP::split(YYCC_U8(", 1, 2, "), YYCC_U8(", "));
|
||||
ASSERT_EQ(rv.size(), 4u);
|
||||
EXPECT_EQ(rv[0], YYCC_U8(""));
|
||||
EXPECT_EQ(rv[1], YYCC_U8("1"));
|
||||
EXPECT_EQ(rv[2], YYCC_U8("2"));
|
||||
EXPECT_EQ(rv[3], YYCC_U8(""));
|
||||
}
|
||||
// No matched delimiter
|
||||
{
|
||||
auto rv = OP::split(YYCC_U8("test"), YYCC_U8("-"));
|
||||
ASSERT_EQ(rv.size(), 1u);
|
||||
EXPECT_EQ(rv[0], YYCC_U8("test"));
|
||||
}
|
||||
// Empty delimiter
|
||||
{
|
||||
auto rv = OP::split(YYCC_U8("test"), u8string_view());
|
||||
ASSERT_EQ(rv.size(), 1u);
|
||||
EXPECT_EQ(rv[0], YYCC_U8("test"));
|
||||
}
|
||||
// Empty source string
|
||||
{
|
||||
auto rv = OP::split(u8string_view(), YYCC_U8(""));
|
||||
ASSERT_EQ(rv.size(), 1u);
|
||||
EXPECT_TRUE(rv[0].empty());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace yycctest::string::op
|
84
testbench/yycc/string/parse.cpp
Normal file
84
testbench/yycc/string/parse.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/parse.hpp>
|
||||
#include <yycc/string/reinterpret.hpp>
|
||||
|
||||
#include <yycc/prelude/rust.hpp>
|
||||
|
||||
#define PARSE ::yycc::string::parse
|
||||
|
||||
namespace yycctest::string::parse {
|
||||
|
||||
// These 2 test macros build string container via given string.
|
||||
// Check `try_parse` first, and then check `parse`.
|
||||
|
||||
#define TEST_SUCCESS(type_t, value, string_value, ...) \
|
||||
{ \
|
||||
u8string cache_string(YYCC_U8(string_value)); \
|
||||
type_t cache; \
|
||||
ASSERT_TRUE(PARSE::try_parse<type_t>(cache_string, cache, ##__VA_ARGS__)); \
|
||||
EXPECT_EQ(cache, value); \
|
||||
EXPECT_EQ(PARSE::parse<type_t>(cache_string, ##__VA_ARGS__), value); \
|
||||
}
|
||||
|
||||
#define TEST_FAIL(type_t, string_value, ...) \
|
||||
{ \
|
||||
u8string cache_string(YYCC_U8(string_value)); \
|
||||
type_t cache; \
|
||||
EXPECT_FALSE(PARSE::try_parse<type_t>(cache_string, cache, ##__VA_ARGS__)); \
|
||||
EXPECT_ANY_THROW(PARSE::parse<type_t>(cache_string, ##__VA_ARGS__)); \
|
||||
}
|
||||
|
||||
TEST(StringParse, Common) {
|
||||
TEST_SUCCESS(i8, INT8_C(-61), "-61");
|
||||
TEST_SUCCESS(u8, UINT8_C(200), "200");
|
||||
TEST_SUCCESS(i16, INT16_C(6161), "6161");
|
||||
TEST_SUCCESS(u16, UINT16_C(32800), "32800");
|
||||
TEST_SUCCESS(i32, INT32_C(61616161), "61616161");
|
||||
TEST_SUCCESS(u32, UINT32_C(4294967293), "4294967293");
|
||||
TEST_SUCCESS(i64, INT64_C(616161616161), "616161616161");
|
||||
TEST_SUCCESS(u64, UINT64_C(9223372036854775807), "9223372036854775807");
|
||||
|
||||
TEST_SUCCESS(float, 1.0f, "1.0");
|
||||
TEST_SUCCESS(double, 1.0, "1.0");
|
||||
|
||||
TEST_SUCCESS(bool, true, "true");
|
||||
TEST_SUCCESS(bool, false, "false");
|
||||
}
|
||||
|
||||
TEST(StringParse, Radix) {
|
||||
TEST_SUCCESS(u32, UINT32_C(0xffff), "ffff", 16);
|
||||
TEST_SUCCESS(u32, UINT32_C(032), "032", 8);
|
||||
TEST_SUCCESS(u32, UINT32_C(0B1011), "1011", 2);
|
||||
}
|
||||
|
||||
TEST(StringParse, CaseInsensitive) {
|
||||
TEST_SUCCESS(bool, true, "tRUE");
|
||||
}
|
||||
|
||||
TEST(StringParse, Overflow) {
|
||||
TEST_FAIL(i8, "6161");
|
||||
TEST_FAIL(u8, "32800");
|
||||
TEST_FAIL(i16, "61616161");
|
||||
TEST_FAIL(u16, "4294967293");
|
||||
TEST_FAIL(i32, "616161616161");
|
||||
TEST_FAIL(u32, "9223372036854775807");
|
||||
TEST_FAIL(i64, "616161616161616161616161");
|
||||
TEST_FAIL(u64, "92233720368547758079223372036854775807");
|
||||
|
||||
TEST_FAIL(float, "1e40");
|
||||
TEST_FAIL(double, "1e114514");
|
||||
}
|
||||
|
||||
TEST(StringParse, BadRadix) {
|
||||
TEST_FAIL(u32, "fghj", 16);
|
||||
TEST_FAIL(u32, "099", 8);
|
||||
TEST_FAIL(u32, "12345", 2);
|
||||
}
|
||||
|
||||
TEST(StringParse, InvalidWords) {
|
||||
TEST_FAIL(u32, "hello, world!");
|
||||
TEST_FAIL(bool, "hello, world!");
|
||||
}
|
||||
|
||||
} // namespace yycctest::string::parse
|
60
testbench/yycc/string/reinterpret.cpp
Normal file
60
testbench/yycc/string/reinterpret.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <cstring>
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/reinterpret.hpp>
|
||||
|
||||
#include <yycc/prelude/core.hpp>
|
||||
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
#define CONST_VOID_PTR(p) reinterpret_cast<const void*>(p)
|
||||
#define VOID_PTR(p) reinterpret_cast<void*>(p)
|
||||
|
||||
namespace yycctest::string::reinterpret {
|
||||
|
||||
static u8string PROBE(YYCC_U8("Test"));
|
||||
|
||||
TEST(StringReinterpret, ConstPointer) {
|
||||
const auto* src = PROBE.data();
|
||||
const auto* dst = REINTERPRET::as_ordinary(src);
|
||||
const auto* new_src = REINTERPRET::as_utf8(dst);
|
||||
|
||||
// Pointer should point to the same address after casting.
|
||||
EXPECT_EQ(CONST_VOID_PTR(src), CONST_VOID_PTR(dst));
|
||||
EXPECT_EQ(CONST_VOID_PTR(src), CONST_VOID_PTR(new_src));
|
||||
}
|
||||
|
||||
TEST(StringReinterpret, Pointer) {
|
||||
auto* src = PROBE.data();
|
||||
auto* dst = REINTERPRET::as_ordinary(src);
|
||||
auto* new_src = REINTERPRET::as_utf8(dst);
|
||||
|
||||
// Pointer should point to the same address after casting.
|
||||
EXPECT_EQ(VOID_PTR(src), VOID_PTR(dst));
|
||||
EXPECT_EQ(VOID_PTR(src), VOID_PTR(new_src));
|
||||
}
|
||||
|
||||
TEST(StringReinterpret, String) {
|
||||
auto src = u8string(PROBE);
|
||||
auto dst = REINTERPRET::as_ordinary(src);
|
||||
auto new_src = REINTERPRET::as_utf8(dst);
|
||||
|
||||
// Check memory length and data.
|
||||
ASSERT_EQ(src.length(), dst.length());
|
||||
EXPECT_TRUE(std::memcmp(src.data(), dst.data(), src.length()) == 0);
|
||||
ASSERT_EQ(src.length(), new_src.length());
|
||||
EXPECT_TRUE(std::memcmp(src.data(), new_src.data(), src.length()) == 0);
|
||||
}
|
||||
|
||||
TEST(StringReinterpret, StringView) {
|
||||
auto src = u8string_view(PROBE);
|
||||
auto dst = REINTERPRET::as_ordinary_view(src);
|
||||
auto new_src = REINTERPRET::as_utf8_view(dst);
|
||||
|
||||
// Check memory length and data.
|
||||
ASSERT_EQ(src.length(), dst.length());
|
||||
EXPECT_TRUE(std::memcmp(src.data(), dst.data(), src.length()) == 0);
|
||||
ASSERT_EQ(src.length(), new_src.length());
|
||||
EXPECT_TRUE(std::memcmp(src.data(), new_src.data(), src.length()) == 0);
|
||||
}
|
||||
|
||||
} // namespace yycctest::string::reinterpret
|
42
testbench/yycc/string/stringify.cpp
Normal file
42
testbench/yycc/string/stringify.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/reinterpret.hpp>
|
||||
#include <yycc/string/stringify.hpp>
|
||||
|
||||
#include <yycc/prelude/rust.hpp>
|
||||
|
||||
#define STRINGIFY ::yycc::string::stringify
|
||||
|
||||
namespace yycctest::string::stringify {
|
||||
|
||||
#define TEST_SUCCESS(type_t, value, string_value, ...) \
|
||||
{ \
|
||||
type_t cache = value; \
|
||||
u8string ret = STRINGIFY::stringify<type_t>(cache, ##__VA_ARGS__); \
|
||||
EXPECT_EQ(ret, YYCC_U8(string_value)); \
|
||||
}
|
||||
|
||||
TEST(StringStringify, Common) {
|
||||
TEST_SUCCESS(i8, INT8_C(-61), "-61");
|
||||
TEST_SUCCESS(u8, UINT8_C(200), "200");
|
||||
TEST_SUCCESS(i16, INT16_C(6161), "6161");
|
||||
TEST_SUCCESS(u16, UINT16_C(32800), "32800");
|
||||
TEST_SUCCESS(i32, INT32_C(61616161), "61616161");
|
||||
TEST_SUCCESS(u32, UINT32_C(4294967293), "4294967293");
|
||||
TEST_SUCCESS(i64, INT64_C(616161616161), "616161616161");
|
||||
TEST_SUCCESS(u64, UINT64_C(9223372036854775807), "9223372036854775807");
|
||||
|
||||
TEST_SUCCESS(float, 1.0f, "1.0", std::chars_format::fixed, 1);
|
||||
TEST_SUCCESS(double, 1.0, "1.0", std::chars_format::fixed, 1);
|
||||
|
||||
TEST_SUCCESS(bool, true, "true");
|
||||
TEST_SUCCESS(bool, false, "false");
|
||||
}
|
||||
|
||||
TEST(StringStringify, Radix) {
|
||||
TEST_SUCCESS(u32, UINT32_C(0xffff), "ffff", 16);
|
||||
TEST_SUCCESS(u32, UINT32_C(032), "32", 8);
|
||||
TEST_SUCCESS(u32, UINT32_C(0B1011), "1011", 2);
|
||||
}
|
||||
|
||||
} // namespace yycctest::string::stringify
|
Reference in New Issue
Block a user