15 Commits

Author SHA1 Message Date
e166dc41ac refactor: finish rust parse and add testbench for it. 2025-06-26 10:27:33 +08:00
a6382d6a22 refactor: add document for some namespaces 2025-06-25 10:40:05 +08:00
adc99274f4 refactor: add testbench for parse and stringify 2025-06-24 11:29:01 +08:00
3abd0969c0 refactor: add Rust infrastructure: Option, Result and panic 2025-06-23 16:22:55 +08:00
28ff7008a8 add parse and stringify 2025-06-22 19:53:49 +08:00
ab8d74efe6 test: add testbench for string module 2025-06-22 17:14:49 +08:00
df3b602110 refactor: start to refactor project 2025-06-20 23:38:34 +08:00
bec36b4b3c fix: fix install path in CMake script.
- replace some CMake variables to our custom variables in install path, however it does nothing because they are equal in default.
2024-12-23 09:30:39 +08:00
0b7e58c8e8 feat: use CMake to generate library version info.
- use CMake to produce YYCC version header when configuring.
2024-11-03 18:52:02 +08:00
831fa130bc feat: improve EnumHelper
- allow multiple enum flags in EnumHelper::Has to check whether given flags contains specified flags.
- update version in CMake script.
2024-11-03 18:06:36 +08:00
7adac00035 doc: fix document about recent changes
- fix build commandline introduction in documentation.
- update build script template.
- only install document in any Release-like build type.
- now testbench will be installed in any Release-like build type, not only Release.
2024-11-03 17:29:34 +08:00
0cd9582757 refactor: refactor project layout
- move header files into an individual directory to prevent the possibility that file name is conflict in Linux include directory.
- update build script generator. use jinja2 template engine to get better view, rather than python code written by hand.
- add version number and version comparation macros in core library.
2024-11-03 14:51:18 +08:00
2206825223 doc: add document for the change of loading function of ConfigManager. 2024-11-02 17:31:19 +08:00
21f7e7f786 feat: add new helper for scoped enum type.
- Add EnumHelper for bitwise operation of scoped enum type (copied from libcmo21)
- Enrich the return value of ConfigManager load function to present more infomation of loading.
- update testbench for new added feature and modification.
- add document for new added feature.
2024-11-02 17:10:55 +08:00
50dd086b53 fix: fix linux build issue 2024-08-27 17:35:57 +08:00
94 changed files with 3717 additions and 1315 deletions

314
.clang-format Normal file
View File

@ -0,0 +1,314 @@
# yaml-language-server: $schema=https://json.schemastore.org/clang-format.json
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseArrows: false
AlignCaseColons: false
AlignConsecutiveTableGenBreakingDAGArgColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenCondOperatorColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveTableGenDefinitionColons:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignEscapedNewlines: DontAlign
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseExpressionOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AttributeMacros:
- __capability
BinPackArguments: false
BinPackParameters: false
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakAfterReturnType: None
BreakArrays: true
BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakFunctionDefinitionParameters: false
BreakInheritanceList: BeforeColon
BreakStringLiterals: false
BreakTemplateDeclarations: Yes
ColumnLimit: 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
View File

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

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
# -------------------- Output --------------------
out/
src/YYCC/YYCCVersion.hpp
CMakeSettings.json
# -------------------- VSCode --------------------

View File

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

View File

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

View File

@ -86,12 +86,66 @@ so it must be initialized after initializing all settings.
When initializing core manager, you need assign config file path first.
Then you need specify a version number.
Version number will be used when reading config file.
If the version of config file is higher than your given number,
core manager will assume you are trying to read a config file created by a higher version program.
Core manager will reject reading and use default value for all settings.
Otherwise, core manager will try to read config file and do proper migration if possible.
Version number is important.
It will be used when reading config file and only can be increased if needed (version can not downgrade).
The last argument is an initializer list which contain the \b pointer to all settings this manager managed.
When executing YYCC::ConfigManager::CoreManager::Load to load configs, it will perform following steps one by one:
<UL>
<LI>
Open given config file.
<UL>
<LI>
If given file is not existing, loading function will simply return and all configs will be reset to its default value.
</LI>
<LI>
Success to open file, go to next step.
</LI>
</UL>
</LI>
<LI>
Fetch version number from file.
<UL>
<LI>
If fail to read version number from file, loading function will simply return and all configs will be reset to its default value.
</LI>
<LI>
If the version of config file is higher than your specified version number when constructing this class,
core manager will assume you are trying to read a config file created by a higher version program,
and will reject reading and use default value for all settings.
</LI>
<LI>
If the version of config file is lower than your specified version number,
core manager will try to read config file and do proper migration (set default value for configs which do not existing) if possible.
</LI>
<LI>
If the version of config file is equal than your specified version number,
core manager will read config file normally.
</LI>
</UL>
</LI>
<LI>
Read config file body.
<UL>
<LI>
If any IO error occurs when reading, loading function will simply return.
All read config will keep their read value and all configs which has not been read will keep their default value.
</LI>
<LI>
If some config can not parse binary data to its type,
this config will be skipped and core manager will process next config.
This config will keep its default value.
</LI>
</UL>
</LI>
</UL>
All of these scenarios can be found by the return value of loading function.
The return type of loading function, ConfigLoadResult is a flag enum.
You can find whether loading process happend specified issue by using bitwise operation on it.
*/
}

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

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

View File

@ -45,6 +45,8 @@
\li \subpage std_patch
\li \subpage enum_helper
<B>Advanced Features</B>
\li \subpage constraints

View File

@ -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>.

View File

@ -6,6 +6,48 @@ namespace YYCC {
In this page we will introduce the macros defined by this library
which can not be grouped in other topic.
\section library_macros__batch_class_copy_move Library Version and Version Comparison
Version is a important things in modern software development, especially for a library.
In YYCC, we use Semantic Versioning as our version standard.
For more infomations about it, please see: https://semver.org/
First, YYCC has its own version and it can be visited by
\c YYCC_VER_MAJOR, \c YYCC_VER_MINOR, and \c YYCC_VER_PATCH.
Each part of Semantic Versioning is provided individually.
YYCC also provide a bunch of macros to compare 2 versions.
It also provides a way to check YYCC version in program using YYCC,
because some of them rely on a specific version of YYCC.
There is a list of these comparison macros.
\li YYCC_VERCMP_E
\li YYCC_VERCMP_NE
\li YYCC_VERCMP_G
\li YYCC_VERCMP_GE
\li YYCC_VERCMP_NL
\li YYCC_VERCMP_L
\li YYCC_VERCMP_LE
\li YYCC_VERCMP_NG
You may notice all of these macros are starts with \c YYCC_VERCMP_,
and their tails are inspired from x86 ASM comparison jump code.
For example, \c E means "equal" and \c NE means "not equal",
\c G means "greater", \c GE means "greater or equal", and \c NG means "not gretaer".
All of these macros take 6 arguments,
for the first 3 arguments, we call them "left version".
From left to right they are the major part, minor part and patch part of semantic version.
And for the last 3 arguments, we call them "right version".
From left to right they are the major part, minor part and patch part of semantic version.
There is a example about checking whether YYCC library version is exactly what we wanted version.
\code
#if YYCC_VERCMP_NE(YYCC_VER_MAJOR, YYCC_VER_MINOR, YYCC_VER_PATCH, 1, 3 ,0)
#error "Not Matched YYCC Version"
#endif
\endcode
\section library_macros__platform_checker Platform Checker
In many cross platform applications,

View File

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

1
script/.gitignore vendored
View File

@ -1,2 +1,3 @@
# -------------------- Output --------------------
win_build.bat
linux_build.sh

108
script/gen_build_script.py Normal file
View 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()

View File

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

View File

@ -1,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 ..

View File

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

View File

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

View File

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

View File

@ -1,48 +1,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

View File

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

View File

@ -4,6 +4,7 @@
#include "Constraints.hpp"
#include "EncodingHelper.hpp"
#include "ParserHelper.hpp"
#include <cstring>
#include <functional>
#include <vector>
#include <map>

View File

@ -2,6 +2,7 @@
#include "EncodingHelper.hpp"
#include "IOHelper.hpp"
#include "EnumHelper.hpp"
#include <stdexcept>
namespace YYCC::ConfigManager {
@ -33,7 +34,7 @@ namespace YYCC::ConfigManager {
m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() {
// Mark: no need to check cfg file path
// it will be checked at creating file handle
// assign settings
for (auto* setting : settings) {
auto result = m_Settings.try_emplace(setting->GetName(), setting);
@ -44,7 +45,10 @@ namespace YYCC::ConfigManager {
}
}
bool CoreManager::Load() {
ConfigLoadResult CoreManager::Load() {
// prepare result variables
ConfigLoadResult ret = ConfigLoadResult::OK;
// reset all settings first
Reset();
@ -53,20 +57,27 @@ namespace YYCC::ConfigManager {
if (fs.get() == nullptr) {
// if we fail to get, it means that we do not have corresponding cfg file.
// all settings should be reset to default value.
return true;
ret = ConfigLoadResult::Created;
return ret;
}
// fetch version info
uint64_t version_info;
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info))
return false;
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) {
ret = ConfigLoadResult::Created;
return ret;
}
// check version
// if read version is greater than we expected,
// it means that this cfg file is created by the program higer than this.
// we should not read anything from it.
// however, for compaitibility reason, we allow read old cfg data.
if (version_info > m_VersionIdentifier)
return true;
if (version_info > m_VersionIdentifier) {
ret = ConfigLoadResult::ForwardNew;
return ret;
} else if (version_info < m_VersionIdentifier) {
EnumHelper::Add(ret, ConfigLoadResult::Migrated);
}
// fetch setting item from file
yycc_u8string name_cache;
@ -77,37 +88,50 @@ namespace YYCC::ConfigManager {
if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) {
// we also check whether reach EOF at there.
if (std::feof(fs.get())) break;
else return false;
else {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
}
// fetch name body
name_cache.resize(name_length);
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length)
return false;
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get setting data length
size_t data_length;
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length))
return false;
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get matched setting first
const auto& found = m_Settings.find(name_cache);
if (found != m_Settings.end()) {
// found. read data for it
found->second->ResizeData(data_length);
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length)
return false;
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// call user defined load function
// if fail to parse, reset to default value
if (!found->second->UserLoad())
if (!found->second->UserLoad()) {
EnumHelper::Add(ret, ConfigLoadResult::ItemError);
found->second->UserReset();
}
} else {
// fail to find. skip this unknown setting
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0)
return false;
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
}
}
return true;
return ret;
}
bool CoreManager::Save() {

View File

@ -17,6 +17,19 @@
* @details For how to use this namespace, please see \ref config_manager.
*/
namespace YYCC::ConfigManager {
/**
* @brief The load result of loading config.
*/
enum class ConfigLoadResult {
OK = 0, ///< Success load configs.
Created = 1 << 0, ///< Given file is not existing, we create all configs in default values.
ForwardNew = 1 << 1, ///< Detect the config file created by higher version. We create all configs in default values.
Migrated = 1 << 2, ///< Detect the config file created by lower version. We try migrate configs written in it.
BrokenFile = 1 << 3, ///< Given file has bad format. Thus some configs are kept as its default values.
ItemError = 1 << 4 ///< Some config can not be recognized from the data read from file so they are reset to default value.
};
using UnderlyingConfigLoadResult_t = std::underlying_type_t<ConfigLoadResult>;
/// @brief The base class of every setting.
/// @details Programmer can inherit this class and implement essential functions to create custom setting.
@ -75,7 +88,7 @@ namespace YYCC::ConfigManager {
private:
std::vector<uint8_t> m_RawData;
};
/// @brief Settings manager and config file reader writer.
class CoreManager {
public:
@ -96,8 +109,8 @@ namespace YYCC::ConfigManager {
public:
/// @brief Load settings from file.
/// @details Before loading, all settings will be reset to default value first.
/// @return True if success, otherwise false.
bool Load();
/// @return What happend when loading config. This function always success.
ConfigLoadResult Load();
/// @brief Save settings to file.
/// @return True if success, otherwise false.
bool Save();

View File

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

View File

@ -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

View File

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

View File

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

16
src/yycc.hpp Normal file
View 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 {}

View File

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

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

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

0
src/yycc/patch/path.hpp Normal file
View File

View File

18
src/yycc/prelude/core.hpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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

View 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
View 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

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

View File

@ -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 delimiter as string view.
* @param[in] strl The string need to be splitting.
* @param[in] _delimiter The delimiter for splitting.
* @return
* The split result with string view format.
* This will not produce any copy of original string.
* \par
* If given string or delimiter are empty,
* the result container will only contain 1 entry which is equal to given string.
* @see Split(const NS_YYCC_STRING::u8string_view&, const NS_YYCC_STRING::u8char*)
*/
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 decilmer.
* @brief Split given string with specified delimiter.
* @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.
* \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.
*/
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
/**
* @brief Split given string with specified decilmer as string view.
* @param[in] strl The string need to be splitting.
* @param[in] _decilmer The decilmer for splitting.
* @return
* The split result with string view format.
* This will not produce any copy of original string.
* \par
* If given string or decilmer are empty,
* the result container will only contain 1 entry which is equal to given string.
* @see Split(const yycc_u8string_view&, const yycc_char8_t*)
*/
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
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
View 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

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

View 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

View 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
View 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
View 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
View File

0
src/yycc/windows/com.hpp Normal file
View File

View File

View File

View 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
@ -16,4 +16,4 @@
#define NOMINMAX
#endif
#endif
#endif

View 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
@ -20,4 +20,4 @@
#undef MoveFile
#undef DeleteFile
#endif
#endif

View 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

View File

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

View File

@ -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
View 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();
}

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

View File

@ -0,0 +1,5 @@
#include <gtest/gtest.h>
namespace yycctest::constraint::builder {
}

View 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

View 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.
}

View 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

View 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

View 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

View 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