1
0

15 Commits

Author SHA1 Message Date
ab8489c377 doc: add doc for moved files 2025-10-07 18:49:02 +08:00
c48e79753d refactor: cancel the namespace Rust.
- all sub-functions are put into respective position.
2025-10-07 18:15:17 +08:00
eda801d3c7 refactor: move env outside from rust namespace 2025-10-07 18:03:40 +08:00
64045b1d48 doc: update license date 2025-10-03 22:52:31 +08:00
8e0865384d fix: do misc work
- add macos compile note in compile manual.
- add DoNotOptimize in benchmark.
2025-10-03 22:47:30 +08:00
c6c450f6fa feat: move my homemade fft module into this project.
- move homemade fft module into this project.
- also migrate test and benchmark.
2025-10-03 21:01:37 +08:00
3dd0c85995 feat: finish clap manual namespace 2025-10-02 18:19:11 +08:00
5859264eca feat: add current_exe in rust env 2025-10-02 18:06:24 +08:00
d69563b5df feat: finish clap manual table filler
- finish clap manual table fill functions.
- fix iterator object for the requirements of std::ranges::end().
2025-10-02 16:57:15 +08:00
446f880df4 feat: add trim in string op opposited with strip 2025-10-01 20:53:43 +08:00
05a80268ab chore: update build manual and script. 2025-09-29 22:43:28 +08:00
19d0a5bb4d feat: add benchmark for string strip 2025-09-29 21:20:44 +08:00
e7a05b3488 refactor: rename testbench to test.
- rename testbench to test.
- add benchmark for future development.
2025-09-29 13:34:02 +08:00
82c3ed5b32 fix: fix compile error in gcc.
- remove __attribute__(format) checker for UTF8 char type printf becuase it forcely require the type of format string is const char*, rather than const char8_t*, and I can not use any switches to remove this.
- delete useless macro and header in clap manual.
2025-09-28 22:38:09 +08:00
d6be8a11ac fix: use more clear format spec in format test.
- this is designed to avoid different behavior between msvc and gcc.
	- gcc output char in default.
	- msvc output it as integer in default.
2025-09-28 22:31:08 +08:00
74 changed files with 1210 additions and 495 deletions

18
.github/linux_build.sh vendored Normal file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# Create build directory and enter it
mkdir bin
cd bin
# Create internal build and install directory, then enter it
mkdir build
mkdir install
cd build
# Build in Release mode
cmake -DCMAKE_BUILD_TYPE=Release ../..
cmake --build .
cmake --install . --prefix=../install
# Back to root directory
cd ..
cd ..

18
.github/windows_build.bat vendored Normal file
View File

@ -0,0 +1,18 @@
@ECHO OFF
:: Create build directory and enter it
MKDIR bin
CD bin
:: Create internal build and install directory, then enter it
MKDIR build
MKDIR install
CD build
:: Build with x64 architecture in Release mode
cmake -A x64 ../..
cmake --build . --config Release
cmake --install . --prefix=../install --config Relese
:: Back to root directory
CD ..
CD ..

View File

@ -10,7 +10,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Provide options
option(YYCC_BUILD_TESTBENCH "Build testbench of YYCCommonplace." OFF)
option(YYCC_BUILD_TEST "Build test of YYCCommonplace." OFF)
option(YYCC_BUILD_BENCHMARK "Build benchmark of YYCCommonplace." OFF)
option(YYCC_BUILD_DOC "Build document of YYCCommonplace." OFF)
option(YYCC_ENFORCE_ICONV "Enforce iconv support for this library (e.g. in MSYS2 environment)." OFF)
@ -26,21 +27,28 @@ 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)
# GTest is required if we build test
if (YYCC_BUILD_TEST)
# 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 ()
# Google Benchmark is required if we build benchmark
if (YYCC_BUILD_BENCHMARK)
find_package(benchmark REQUIRED)
endif ()
# Iconv is required if we are not in Windows or user request it
if (YYCC_ENFORCE_ICONV OR (NOT WIN32))
find_package(Iconv REQUIRED)
endif ()
# Import 3 build targets
# Import 4 build targets
add_subdirectory(src)
if (YYCC_BUILD_TESTBENCH)
add_subdirectory(testbench)
if (YYCC_BUILD_TEST)
add_subdirectory(test)
endif ()
if (YYCC_BUILD_BENCHMARK)
add_subdirectory(benchmark)
endif ()
if (YYCC_BUILD_DOC)
add_subdirectory(doc)

View File

@ -11,40 +11,65 @@ It means that it is not stable and work in progress.
* CMake 3.23 at least.
* The common compiler supporting C++ 23 (GCC / Clang / MSVC).
* Iconv (Optional on Windows. Required on other systems).
* [GoogleTest](https://github.com/google/googletest) (Required if you build testbench).
* [Google Test](https://github.com/google/googletest) (Required if you build test).
* [Google Benchmark](https://github.com/google/benchmark) (Required if you build benchmark).
* Doxygen (Required if you build documentation).
* Python and Astral UV (Required if you use "User Build" method)
If you are just want to build this project to make something works, or build other project, rather than code with it,
you commonly do not need build test, benchmark and documentation.
So you actually do not need Google Test, Google Benchmark and Doxygen.
## Preparing
### Compiler
> [!WARNING]
> You may face some issues when building on macOS with Clang. That's not your fault.
> Clang used libc++ library lacks some essential features used by this project.
> You may try other solutions for compiling this project on macOS or with Clang.
> A possible solution is that use GCC and libstdc++ on macOS instead of default Clang and libc++.
> Build issue may be resolved until libc++ finish these features: `std::stacktrace` and `std::views::enumerate`.
## Preparing
### Google Test
### GoogleTest
GoogleTest is required if you need to build testbench.
Google Test is required if you need to build test.
If you don't need this please skip this chapter.
We use GoogleTest v1.17.0.
We use Google Test v1.17.0.
It would be okey use other versions but I have not test on them.
> [!WARNING]
> When building this project, you may face link error with GoogleTest, especially on Linux.
> When building this project, you may face link error with Google Test, especially on Linux.
> This issue is caused by that the binary provided by your package manager is built in C++17 and its ABI is incompatible with C++23.
> See this [GitHub Issue](https://github.com/google/googletest/issues/4591) for more infomation.
> The solution is that download GoogleTest source code and build it in C++23 on your own.
> The solution is that download Google Test source code and build it in C++23 on your own.
> Following content tell you how to do this.
There are the steps instructing you how to compile GoogleTest manually.
There are the steps instructing you how to compile Google Test manually.
1. Download GoogleTest source code with given version in GitHub Release page.
1. Download Google Test source code with given version in GitHub Release page.
1. Extract it into a directory.
1. Enter this directory and create 2 subdirectory `build` and `install` for CMake build and install respectively.
1. Enter `build` directory and configure CMake with extra `-DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON` parameters.
1. Use CMake to build GoogleTest
1. Use CMake to install GoogleTest into previous we created `install` directory.
1. Use CMake to build Google Test
1. Use CMake to install Google Test into previous we created `install` directory.
### Google Benchmark
Google Benchmark is required if you need to build benchmark.
If you don't need this please skip this chapter.
We use Google Benchmark v1.9.4.
It would be okey use other versions but I have not test on them.
There are the steps instructing you how to compile Google Benchmark manually.
1. Download Google Benchmark source code with given version in GitHub Release page.
1. Extract it into a directory.
1. Enter this directory and create link named `googletest` to previous fetched Google Test root directory. This is instructed by official manual because Google Benchmark rely on Google Test. Link can be create by executing `mklink /D googletest <path-to-googletest-root-dir>` on Windows or `ln -s <path-to-googletest-root-dir> googletest` on POSIX-like OS.
1. Keep stay in this directory and create 2 subdirectory `build` and `install` for CMake build and install respectively.
1. Enter `build` directory and configure CMake with extra `-DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF` parameters.
1. Use CMake to build Google Benchmark
1. Use CMake to install Google Benchmark into previous we created `install` directory.
### Iconv
@ -71,33 +96,33 @@ So before compiling, you must make sure `doxygen` are presented in your environm
## Build and Install
There are 2 different ways to build this project.
If you are the user of this project (just want this project to make something work), please choose "User Build".
If you are the user of this project (just want this project to make something works, or build other projects), please choose "User Build".
If you are a developer (developer of this project, or use this project as dependency to develop your project), please choose "Developer Build".
### User Build
"User Build" is basically how GitHub Action build this project.
We use Python 3.11 and UV 0.7.17 to manage our build script generator.
It would be okey use other versions but I have not test on them.
TODO...
Execute `.github/windows_build.bat` on Windows or `.github/linux_build.sh` on POSIX-like OS (Linux and macOS) under **the root directory** of this project. The final built artifact is under `bin/install` directory.
### Developer Build
TODO...
First, there is a list listing all variables you may configure during compiling.
There is a list listing all variables you may configure during compiling.
* `YYCC_BUILD_TESTBENCH`: Set it to `ON` to build testbench. `OFF` in default.
* `YYCC_BUILD_TEST`: Set it to `ON` to build test. `OFF` in default.
It is useful for the developer of this project.
It also suit for the user who has runtime issues on their platforms to check whether this project works as expected.
If you are debugging this project to find bug, I suggest that you build this project under Debug mode and use this test project for debugging.
* `YYCC_BUILD_BENCHMARK`: Set it to `ON` to build benchmark. `OFF` in default.
It is useful for the developer of this project to checking the performace for those homemade functions.
It is highly suggested build this project with Release mode to have real benchmark result.
* `YYCC_BUILD_DOC`: Set it to `ON` to build documentation. `OFF` in default.
It may be useful for the developer who firstly use this project in their own projects.
Please note that generated documentation is different in different platforms.
* `YYCC_ENFORCE_ICONV`: Set it to `ON` to enable Iconv feature forcely. `OFF` in default.
The usage of this option has been introduced in previous "Iconv" chapter.
* `GTest_ROOT`: TODO
* `benchmark_ROOT`: TODO
* `Iconv_ROOT`: TODO
* `CMAKE_CXX_STANDARD`: Set C++ standard version of project.
`23` in default and this version can not be lower than C++23.

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2024-2024 yyc12345
Copyright (c) 2024-2025 yyc12345
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

28
benchmark/CMakeLists.txt Normal file
View File

@ -0,0 +1,28 @@
# Create executable benchmark
add_executable(YYCCBenchmark "")
# Setup test sources
target_sources(YYCCBenchmark
PRIVATE
main.cpp
yycc/string/op.cpp
yycc/carton/fft.cpp
)
# target_sources(YYCCBenchmark
# PRIVATE
# FILE_SET HEADERS
# FILES
# shared/literals.hpp
# )
# Setup headers
target_include_directories(YYCCBenchmark
PUBLIC
"${CMAKE_CURRENT_LIST_DIR}"
)
# Setup libraries
target_link_libraries(YYCCBenchmark
PRIVATE
YYCCommonplace
benchmark::benchmark
)

3
benchmark/main.cpp Normal file
View File

@ -0,0 +1,3 @@
#include <benchmark/benchmark.h>
BENCHMARK_MAIN();

View File

@ -0,0 +1,40 @@
#include <benchmark/benchmark.h>
#include <yycc.hpp>
#include <yycc/carton/fft.hpp>
#include <random>
#include <chrono>
#define FFT ::yycc::carton::fft
namespace yyccbench::carton::fft {
using TIndex = size_t;
using TFloat = float;
using TComplex = std::complex<TFloat>;
template<size_t N>
using TFft = FFT::Fft<TIndex, TFloat, N>;
constexpr TIndex FFT_POINTS = 1024u;
static void BM_FftCompute(benchmark::State& state) {
// prepare random buffer
constexpr TIndex RND_BUF_CNT = 8u;
std::random_device rnd_device;
std::default_random_engine rnd_engine(rnd_device());
std::uniform_real_distribution<TFloat> rnd_dist(0.0f, 1.0f);
std::vector<std::vector<TComplex>> buffer_collection(RND_BUF_CNT);
for (auto& buf : buffer_collection) {
buf.resize(FFT_POINTS);
std::generate(buf.begin(), buf.end(), [&rnd_engine, &rnd_dist]() mutable -> TComplex { return TComplex(rnd_dist(rnd_engine)); });
}
// prepare FFT engine
TFft<FFT_POINTS> fft;
// do benchmark
for (auto _ : state) {
fft.compute(buffer_collection[state.iterations() % RND_BUF_CNT].data());
}
}
BENCHMARK(BM_FftCompute)->Name("FftCompute");
}

View File

@ -0,0 +1,28 @@
#include <benchmark/benchmark.h>
#include <yycc.hpp>
#include <yycc/string/op.hpp>
#define OP ::yycc::string::op
using namespace std::literals::string_view_literals;
namespace yyccbench::string::op {
static void BM_StringStrip(benchmark::State& state) {
std::u8string_view strl = u8" \thello\r\n"sv, words = u8" \t\r\n"sv;
for (auto _ : state) {
auto rv = OP::strip(strl, words);
benchmark::DoNotOptimize(rv);
}
}
BENCHMARK(BM_StringStrip)->Name("StringStrip");
static void BM_StringTrim(benchmark::State& state) {
std::u8string_view strl = u8" \thello\r\n"sv, words = u8" \t\r\n"sv;
for (auto _ : state) {
auto rv = OP::trim(strl, words);
benchmark::DoNotOptimize(rv);
}
}
BENCHMARK(BM_StringTrim)->Name("StringTrim");
}

View File

@ -1031,7 +1031,7 @@ EXCLUDE_SYMBOLS =
# that contain example code fragments that are included (see the \include
# command).
EXAMPLE_PATH = @CMAKE_CURRENT_LIST_DIR@/../testbench
EXAMPLE_PATH = @CMAKE_CURRENT_LIST_DIR@/../test
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and

View File

@ -21,7 +21,7 @@ public:
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_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the test of argument parser."), {
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
&m_BoolArgument, &m_ClampedFloatArgument
}) {}

View File

@ -29,7 +29,7 @@ In short words, this library use UTF8 encoding everywhere except some special ca
\li Traditional format function in yycc::string::op.
Traditional format function provide some overloads for ordinary string formatting.
That's because this feature is so common to use in some cases.
\li The message of Rust panic in yycc::rust::panic.
\li The message of Rust panic in yycc::panic.
Due to the limitation of \c std::format, we only can use ordinary string as its message content.
\li The message of standard library exception.
For the compatibility with C++ standard library exception,

4
script/.gitignore vendored
View File

@ -2,10 +2,6 @@
# Exclude VSCode
.vscode/
# Exclude generated files
win_build.bat
linux_build.sh
## ===== Python =====
# Byte-compiled / optimized / DLL files
__pycache__/

View File

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

View File

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

View File

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

@ -15,8 +15,8 @@ PRIVATE
yycc/string/op.cpp
yycc/patch/fopen.cpp
yycc/patch/stream.cpp
yycc/rust/panic.cpp
yycc/rust/env.cpp
yycc/panic.cpp
yycc/env.cpp
yycc/windows/com.cpp
yycc/windows/dialog.cpp
yycc/windows/winfct.cpp
@ -53,6 +53,7 @@ FILES
yycc/macro/class_copy_move.hpp
yycc/macro/printf_checker.hpp
yycc/flag_enum.hpp
yycc/string.hpp
yycc/string/reinterpret.hpp
yycc/string/op.hpp
yycc/patch/ptr_pad.hpp
@ -64,12 +65,12 @@ FILES
yycc/num/safe_cast.hpp
yycc/num/safe_op.hpp
yycc/num/op.hpp
yycc/rust/prelude.hpp
yycc/rust/primitive.hpp
yycc/rust/panic.hpp
yycc/rust/option.hpp
yycc/rust/result.hpp
yycc/rust/env.hpp
yycc/primitive.hpp
yycc/option.hpp
yycc/result.hpp
yycc/prelude.hpp
yycc/panic.hpp
yycc/env.hpp
yycc/windows/import_guard_head.hpp
yycc/windows/import_guard_tail.hpp
yycc/windows/com.hpp
@ -95,6 +96,7 @@ FILES
yycc/carton/clap/summary.hpp
yycc/carton/clap/application.hpp
yycc/carton/clap/manual.hpp
yycc/carton/fft.hpp
)
# Setup header infomations
target_include_directories(YYCCommonplace

View File

@ -3,12 +3,15 @@
#include "../../patch/stream.hpp"
#include "../../patch/format.hpp"
#include "../../string/op.hpp"
#include "../../env.hpp"
#include <ranges>
#define CLAP ::yycc::carton::clap
#define TABULATE ::yycc::carton::tabulate
#define TERMCOLOR ::yycc::carton::termcolor
#define OP ::yycc::string::op
#define FORMAT ::yycc::patch::format
#define OP ::yycc::carton::op
#define ENV ::yycc::env
using namespace ::yycc::patch::stream;
@ -53,10 +56,45 @@ namespace yycc::carton::clap::manual {
const auto &options = app.get_options();
for (const auto &reg_opt : options.all_options()) {
const auto &opt = reg_opt.get_option();
auto desc_by_line = OP::lazy_split(opt.get_description(), u8"\n");
for (const auto [index, item] : std::views::enumerate(desc_by_line)) {
if (index == 0) {
auto full_name = TERMCOLOR::colored(opt.to_showcase_name(),
TERMCOLOR::Color::LightYellow,
TERMCOLOR::Color::Default,
TERMCOLOR::Attribute::Default);
auto value_hint = TERMCOLOR::colored(opt.to_showcase_value(),
TERMCOLOR::Color::LightGreen,
TERMCOLOR::Color::Default,
TERMCOLOR::Attribute::Default);
this->opt_printer.add_row({full_name, value_hint, item});
} else {
this->opt_printer.add_row({u8"", u8"", item});
}
}
}
}
void Manual::fill_var_table() {}
void Manual::fill_var_table() {
const auto &variables = app.get_variables();
for (const auto &reg_var : variables.all_variables()) {
const auto &var = reg_var.get_variable();
auto desc_by_line = OP::lazy_split(var.get_description(), u8"\n");
for (const auto [index, item] : std::views::enumerate(desc_by_line)) {
if (index == 0) {
auto name = TERMCOLOR::colored(var.get_name(),
TERMCOLOR::Color::LightYellow,
TERMCOLOR::Color::Default,
TERMCOLOR::Attribute::Default);
this->var_printer.add_row({name, item});
} else {
this->var_printer.add_row({u8"", item});
}
}
}
}
void Manual::print_version(std::ostream &dst) const {
const auto &summary = this->app.get_summary();
@ -71,8 +109,12 @@ namespace yycc::carton::clap::manual {
void Manual::print_help(std::ostream &dst) const {
this->print_version();
TERMCOLOR::cprintln(trctx.usage_title, TERMCOLOR::Color::Yellow, TERMCOLOR::Color::Default, TERMCOLOR::Attribute::Default, dst);
dst << INDENT << FORMAT::format(trctx.usage_body, app.get_summary().get_bin_name()) << std::endl;
// only print usage if we can fetch the name of executable
auto executable = ENV::current_exe();
if (executable.has_value()) {
TERMCOLOR::cprintln(trctx.usage_title, TERMCOLOR::Color::Yellow, TERMCOLOR::Color::Default, TERMCOLOR::Attribute::Default, dst);
dst << INDENT << FORMAT::format(trctx.usage_body, executable.value()) << std::endl;
}
const auto &variables = app.get_variables();
if (!variables.empty()) {

View File

@ -3,11 +3,9 @@
namespace yycc::carton::clap::summary {
Summary::Summary(const std::u8string_view &name,
const std::u8string_view &bin_name,
const std::u8string_view &author,
const std::u8string_view &version,
const std::u8string_view &description) :
name(name), bin_name(bin_name), author(author), version(version), description(description) {}
const std::u8string_view &description) : name(name), author(author), version(version), description(description) {}
Summary::~Summary() {}
@ -15,10 +13,6 @@ namespace yycc::carton::clap::summary {
return this->name;
}
std::u8string_view Summary::get_bin_name() const {
return this->bin_name;
}
std::u8string_view Summary::get_author() const {
return this->author;
}

View File

@ -4,11 +4,10 @@
#include <string_view>
namespace yycc::carton::clap::summary {
class Summary {
public:
Summary(const std::u8string_view& name,
const std::u8string_view& bin_name,
const std::u8string_view& author,
const std::u8string_view& version,
const std::u8string_view& description);
@ -17,13 +16,12 @@ namespace yycc::carton::clap::summary {
public:
std::u8string_view get_name() const;
std::u8string_view get_bin_name() const;
std::u8string_view get_author() const;
std::u8string_view get_version() const;
std::u8string_view get_description() const;
private:
std::u8string name, bin_name, author, version, description;
std::u8string name, author, version, description;
};
}
} // namespace yycc::carton::clap::summary

307
src/yycc/carton/fft.hpp Normal file
View File

@ -0,0 +1,307 @@
#pragma once
#include <concepts>
#include <type_traits>
#include <numbers>
#include <bit>
#include <cstdint>
#include <complex>
#include <vector>
#include <cmath>
#include <memory>
#include <stdexcept>
#include <algorithm>
namespace yycc::carton::fft {
/// @private
/// @brief Meta-programming utilities for FFT modules.
namespace util {
template<std::floating_point TFloat>
inline constexpr TFloat tau_v = static_cast<TFloat>(2) * std::numbers::pi_v<TFloat>;
// NOTE:
// We use std::has_single_bit() to check whether given number is an integral power of 2.
// And use (std::bit_width() - 1) to get the exponent of given number based on 2.
template<typename TIndex, typename TFloat, size_t N>
struct validate_args {
private:
static constexpr bool is_unsigned_int = std::is_unsigned_v<TIndex> && std::is_integral_v<TIndex>;
static constexpr bool is_float_point = std::is_floating_point_v<TFloat>;
static constexpr bool n_is_pow_2 = std::has_single_bit<TIndex>(static_cast<TIndex>(N)) && N >= static_cast<TIndex>(2);
public:
static constexpr bool value = is_unsigned_int && is_float_point && n_is_pow_2;
};
template<typename TIndex, typename TFloat, size_t N>
inline constexpr bool validate_args_v = validate_args<TIndex, TFloat, N>::value;
} // namespace util
#pragma region Window
enum class WindowType { HanningWindow };
template<typename TIndex, typename TFloat, size_t N>
requires util::validate_args_v<TIndex, TFloat, N>
class Window {
private:
static constexpr TIndex N = N;
public:
Window(WindowType win_type) : window_type(win_type), window_data(nullptr) {
// Pre-compute window data
// Allocate window buffer
window_data = std::make_unique<TFloat[]>(N);
// Assign window data
switch (win_type) {
case WindowType::HanningWindow:
for (TIndex i = 0u; i < N; ++i) {
window_data[i] = static_cast<TFloat>(0.5)
* (static_cast<TFloat>(1)
- std::cos(util::tau_v<TFloat>
* static_cast<TFloat>(i) / static_cast<TFloat>(N - static_cast<TIndex>(1))));
}
break;
default:
throw std::invalid_argument("invalid window function type");
}
}
private:
WindowType window_type;
std::unique_ptr<TFloat[]> window_data;
public:
/**
* @brief Apply window function to given data sequence.
* @param[in,out] data
* The float-point data sequence for applying window function.
* The length of this sequence must be N.
*/
void apply_window(TFloat* data) const {
if (data == nullptr) [[unlikely]] {
throw std::invalid_argument("nullptr data is not allowed for applying window.");
}
for (TIndex i = static_cast<TIndex>(0); i < N; ++i) {
data[i] *= window_data[i];
}
}
/**
* @brief Get underlying window function data for custom applying.
* @return
* The pointer to the start address of underlying window function data sequence.
* The length of this sequence is N.
*/
const TFloat* get_window_data() const { return window_data.get(); }
};
#pragma endregion
#pragma region FFT
template<typename TIndex, typename TFloat, size_t N>
requires util::validate_args_v<TIndex, TFloat, N>
struct FftProperties {
public:
using TComplex = std::complex<TFloat>;
static constexpr TIndex N = static_cast<TIndex>(N);
static constexpr TIndex M = static_cast<TIndex>(std::bit_width<TIndex>(N) - 1);
static constexpr TIndex HALF_POINT = N >> static_cast<TIndex>(1);
};
/**
* @brief The core FFT class.
* @details The core class implementing FFT algorithm (base-2 version).
* @tparam TIndex
* @tparam TFloat
* @tparam N
*/
template<typename TIndex, typename TFloat, size_t N>
requires util::validate_args_v<TIndex, TFloat, N>
class Fft {
private:
using TProperties = FftProperties<TIndex, TFloat, N>;
using TComplex = TProperties::TComplex;
static constexpr TIndex N = TProperties::N;
static constexpr TIndex M = TProperties::M;
static constexpr TIndex HALF_POINT = TProperties::HALF_POINT;
public:
Fft() : wnp_cache(nullptr) {
// Generate WNP cache
wnp_cache = std::make_unique<TComplex[]>(N);
for (TIndex P = static_cast<TIndex>(0); P < N; ++P) {
TFloat angle = util::tau_v<TFloat> * static_cast<TFloat>(P) / static_cast<TFloat>(N);
// e^(-jx) = cosx - j sinx
wnp_cache[P] = TComplex(std::cos(angle), -std::sin(angle));
}
}
private:
std::unique_ptr<TComplex[]> wnp_cache;
public:
/**
* @brief Compute FFT for given complex sequence.
* @details
* This is FFT core compute function but not suit for common user
* because it order that you have enough FFT knowledge to understand what is input data and what is output data.
* For convenient use, see also easy_compute().
* @param[in,out] data
* The complex sequence for computing.
* The length of this sequence must be N.
*/
void compute(TComplex* data) const {
if (data == nullptr) [[unlikely]] {
throw std::invalid_argument("nullptr data is not allowed for FFT computing.");
}
TIndex LH, J, K, B, P;
LH = J = HALF_POINT;
// Construct butterfly structure
for (TIndex I = static_cast<TIndex>(1); I <= N - static_cast<TIndex>(2); ++I) {
if (I < J) std::swap(data[I], data[J]);
K = LH;
while (J >= K) {
J -= K;
K >>= static_cast<TIndex>(1);
}
J += K;
}
// Calculate butterfly
TComplex temp, temp2;
for (TIndex L = static_cast<TIndex>(1); L <= M; ++L) {
B = static_cast<TIndex>(1u) << (L - static_cast<TIndex>(1));
for (J = static_cast<TIndex>(0); J <= B - static_cast<TIndex>(1); ++J) {
P = J * (static_cast<TIndex>(1) << (M - L));
// Use pre-computed cache instead of real-time computing
for (TIndex KK = J; KK <= N - static_cast<TIndex>(1); KK += (static_cast<TIndex>(1) << L)) {
temp2 = (data[KK + B] * this->wnp_cache[P]);
temp = temp2 + data[KK];
data[KK + B] = data[KK] - temp2;
data[KK] = temp;
}
}
}
}
};
/**
* @brief User friendly FFT computation class.
* @details
* @tparam TIndex
* @tparam TFloat
* @tparam N
* @warning This class is \b NOT thread safe. Please use different instance in different thread.
*/
template<typename TIndex, typename TFloat, size_t N>
requires util::validate_args_v<TIndex, TFloat, N>
class FriendlyFft {
private:
using UnderlyingFft = Fft<TIndex, TFloat, N>;
using TProperties = FftProperties<TIndex, TFloat, N>;
using TComplex = TProperties::TComplex;
static constexpr TIndex N = TProperties::N;
static constexpr TIndex M = TProperties::M;
static constexpr TIndex HALF_POINT = TProperties::HALF_POINT;
public:
FriendlyFft() : compute_cache(N) {
// Initialize computation used buffer.
compute_cache = std::vector<TComplex>();
}
private:
UnderlyingFft underlying_fft;
std::vector<TComplex> compute_cache;
public:
/**
* @brief Get the maximum frequency by given sample rate.
* @param[in] sample_rate
* The sample rate of input stream.
* Unit is Hz or SPS (sample point per second).
* @return
* The last data in computed FFT drequency data represented frequency.
* Unit is Hz.
*/
TFloat get_max_freq(TFloat sample_rate) {
// Following sample priniciple
return sample_rate / static_cast<TFloat>(2);
}
/**
* @brief Compute FFT for given time scope data.
* @details
* This is convenient FFT compute function, comparing with compute().
* This function accepts time scope data and output frequency scope data automatically.
* Additionally, it order a window function instance to apply to time scope data before computing.
* @param[in] time_scope The length of this data must be N.
* For the time order of data, the first data should be the oldest data and the last data should be the newest data.
* @param[out] freq_scope The length of this data must be N / 2.
* The first data is 0Hz and the frequency of last data is decided by sample rate which can be computed by get_max_freq() function in this class.
* @param[in] window The window instance applied to data.
* @warnings
* This function is \b NOT thread-safe.
* Please do NOT call this function in different thread for one instance.
*/
void easy_compute(const TFloat* time_scope, TFloat* freq_scope, const Window<TIndex, TFloat, N>& window) {
if (time_scope == nullptr || freq_scope == nullptr) [[unlikely]] {
throw std::invalid_argument("nullptr data is not allowed for easy FFT computing.");
}
// First, we copy time scope data into cache with reversed order.
// because FFT order the first item should be the latest data.
// At the same time we multiple it with window function.
std::generate(compute_cache.begin(),
compute_cache.end(),
[data = &(time_scope[N]), win_data = window.get_window_data()]() mutable -> TComplex {
return TComplex(*(data--) * *(win_data++));
});
// Do FFT compute
underlying_fft.compute(compute_cache.data());
// Compute amplitude
for (TIndex i = static_cast<TIndex>(0); i < HALF_POINT; ++i) {
freq_scope[i] = static_cast<TFloat>(10) * std::log10(std::abs(compute_cache[i + HALF_POINT]));
}
}
};
#pragma endregion
#pragma region Pre-defined FFT Types
using Fft4F = Fft<size_t, float, 4u>;
using Fft8F = Fft<size_t, float, 8u>;
using Fft16F = Fft<size_t, float, 16u>;
using Fft32F = Fft<size_t, float, 32u>;
using Fft64F = Fft<size_t, float, 64u>;
using Fft128F = Fft<size_t, float, 128u>;
using Fft256F = Fft<size_t, float, 256u>;
using Fft512F = Fft<size_t, float, 512u>;
using Fft1024F = Fft<size_t, float, 1024u>;
using Fft2048F = Fft<size_t, float, 2048u>;
using Fft4 = Fft<size_t, double, 4u>;
using Fft8 = Fft<size_t, double, 8u>;
using Fft16 = Fft<size_t, double, 16u>;
using Fft32 = Fft<size_t, double, 32u>;
using Fft64 = Fft<size_t, double, 64u>;
using Fft128 = Fft<size_t, double, 128u>;
using Fft256 = Fft<size_t, double, 256u>;
using Fft512 = Fft<size_t, double, 512u>;
using Fft1024 = Fft<size_t, double, 1024u>;
using Fft2048 = Fft<size_t, double, 2048u>;
#pragma endregion
} // namespace yycc::carton::fft

View File

@ -1,27 +1,39 @@
#include "env.hpp"
#include "../macro/os_detector.hpp"
#include "macro/os_detector.hpp"
// Environment variable required
#if defined(YYCC_OS_WINDOWS)
#include "../encoding/windows.hpp"
#include "../num/safe_op.hpp"
#include "../num/safe_cast.hpp"
#include "encoding/windows.hpp"
#include "num/safe_op.hpp"
#include "num/safe_cast.hpp"
#include <Windows.h>
#include <winbase.h>
#else
#include "../string/reinterpret.hpp"
#include "string/reinterpret.hpp"
#include <cstdlib>
#include <cerrno>
#include <stdexcept>
#endif
// Path related functions required
#if defined(YYCC_OS_WINDOWS)
#include "windows/winfct.hpp"
#else
#include <unistd.h>
#include <sys/stat.h>
#endif
#define SAFECAST ::yycc::num::safe_cast
#define SAFEOP ::yycc::num::safe_op
#define ENC ::yycc::encoding::windows
#define REINTERPRET ::yycc::string::reinterpret
#define WINFCT ::yycc::windows::winfct
namespace yycc::rust::env {
namespace yycc::env {
EnvResult<std::u8string> get_var(const std::u8string_view &name) {
#pragma region Environment Variable
VarResult<std::u8string> get_var(const std::u8string_view &name) {
#if defined(YYCC_OS_WINDOWS)
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew
// Convert to wchar
@ -38,22 +50,22 @@ namespace yycc::rust::env {
// the size passed to this function must include NULL terminal.
// So we forcely use checked add and sub for this bad behavior.
auto fct_size = SAFEOP::checked_add<size_t>(wvalue.size(), 1);
if (!fct_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
if (!fct_size.has_value()) return std::unexpected(VarError::BadArithmetic);
auto rv = ::GetEnvironmentVariableW(wname.c_str(), wvalue.data(), fct_size.value());
// Check the return value
if (rv == 0) {
// Function failed. Extract error reason.
auto ec = GetLastError();
if (ec == ERROR_ENVVAR_NOT_FOUND) return std::unexpected(EnvError::NoSuchName);
else return std::unexpected(EnvError::BadCall);
if (ec == ERROR_ENVVAR_NOT_FOUND) return std::unexpected(VarError::NoSuchName);
else return std::unexpected(VarError::BadCall);
} else {
// Function okey. Check the size.
// Fetch function expected size.
auto rv_size = SAFECAST::try_to<size_t>(rv);
if (!rv_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
if (!rv_size.has_value()) return std::unexpected(VarError::BadArithmetic);
auto exp_size = SAFEOP::checked_sub<size_t>(rv_size.value(), 1);
if (!exp_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
if (!exp_size.has_value()) return std::unexpected(VarError::BadArithmetic);
// YYC MARK:
// According to Microsoft, the return value of this function is just a bullshit.
@ -75,7 +87,7 @@ namespace yycc::rust::env {
}
// Convert back to UTF8 string and return.
return ENC::to_utf8(wvalue).transform_error([](auto err) { return EnvError::BadEncoding; });
return ENC::to_utf8(wvalue).transform_error([](auto err) { return VarError::BadEncoding; });
#else
// String view is not NULL-terminal-guaranted,
// so we solve this when casting its type.
@ -83,18 +95,18 @@ namespace yycc::rust::env {
// Fetch variable
auto finder = std::getenv(ordinary_name.c_str());
if (finder == nullptr) return std::unexpected(EnvError::NoSuchName);
if (finder == nullptr) return std::unexpected(VarError::NoSuchName);
else return REINTERPRET::as_utf8(finder);
#endif
}
EnvResult<void> set_var(const std::u8string_view &name, const std::u8string_view &value) {
VarResult<void> set_var(const std::u8string_view &name, const std::u8string_view &value) {
#if defined(YYCC_OS_WINDOWS)
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
// Convert to wchar, set variable, and check result.
auto rv = ::SetEnvironmentVariableW(ENC::to_wchar(name).value().c_str(), ENC::to_wchar(value).value().c_str());
if (!rv) return std::unexpected(EnvError::BadCall);
if (!rv) return std::unexpected(VarError::BadCall);
else return {};
#else
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html
@ -106,19 +118,19 @@ namespace yycc::rust::env {
if (rv == 0) return {};
// Check error type
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
else if (errno == ENOMEM) return std::unexpected(EnvError::NoMemory);
if (errno == EINVAL) return std::unexpected(VarError::BadName);
else if (errno == ENOMEM) return std::unexpected(VarError::NoMemory);
else throw std::runtime_error("impossible errno");
#endif
}
EnvResult<void> del_var(const std::u8string_view &name) {
VarResult<void> del_var(const std::u8string_view &name) {
#if defined(YYCC_OS_WINDOWS)
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
// Convert to wchar, delete variable, and check result.
auto rv = ::SetEnvironmentVariableW(ENC::to_wchar(name).value().c_str(), NULL);
if (!rv) return std::unexpected(EnvError::BadCall);
if (!rv) return std::unexpected(VarError::BadCall);
else return {};
#else
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html
@ -129,9 +141,70 @@ namespace yycc::rust::env {
if (rv == 0) return {};
// Check error type
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
if (errno == EINVAL) return std::unexpected(VarError::BadName);
else throw std::runtime_error("impossible errno");
#endif
}
} // namespace yycc::rust::env
#pragma endregion
#pragma region Environment
PathResult<std::u8string> current_exe() {
#if defined(YYCC_OS_WINDOWS)
return WINFCT::get_module_file_name(NULL).transform_error([](auto e) { return PathError::Win32; });
#else
// TODO:
// "/proc/self/exe" is Linux specific, not in POSIX standard.
// This method may need further patch when running on macOS.
// Reference: https://www.man7.org/linux/man-pages/man2/readlink.2.html
// specify the path
constexpr char path[] = "/proc/self/exe";
// get the expected size
struct stat sb;
if (lstat(path, &sb) != 0) {
}
auto expected_size = SAFECAST::try_to<size_t>(sb.st_size);
if (!expected_size.has_value()) {
return std::unexpected(PathError::BadCast);
}
auto buf_size = expected_size.value();
// Some magic symlinks under (for example) /proc and /sys report 'st_size' as zero.
// In that case, take PATH_MAX as a "good enough" estimate.
if (buf_size == 0) {
buf_size = PATH_MAX;
}
// prepare buffer and resize it;
std::u8string rv(u8'\0', buf_size);
// write data
auto passed_size = SAFEOP::checked_add<size_t>(buf_size, 1);
if (!passed_size.has_value()) {
return std::unexpected(PathError::Overflow);
}
ssize_t nbytes = readlink(path, REINTERPRET::as_ordinary(rv.data()), passed_size.value());
if (nbytes < 0) {
return std::unexpected(PathError::Posix);
}
// check written size
auto written_size = SAFECAST::try_to<size_t>(nbytes);
if (!written_size.has_value()) {
return std::unexpected(PathError::BadCast);
}
if (written_size.value() != buf_size) {
return std::unexpected(PathError::BadSize);
}
// okey
return rv;
#endif
}
#pragma endregion
} // namespace yycc::env

89
src/yycc/env.hpp Normal file
View File

@ -0,0 +1,89 @@
#pragma once
#include <string>
#include <string_view>
#include <expected>
/**
* @brief The namespace providing runtime environment operations.
* @details
* When I programming with Rust, I was astonished that
* Rust standard library have so much robust environment-related operations,
* such as environment variable operations, current program infos and etc.
* Oppositly, C++ STL are still lack in this even in today.
* So I create this namespace to glue all these things up,
* according to different operating systems, and make a uniform interface.
*/
namespace yycc::env {
#pragma region Environment Variable
/// @brief The error occurs in environment variable operations.
enum class VarError {
NoSuchName, ///< The variable with given name is not presented.
BadEncoding, ///< Error when performing encoding convertion.
BadArithmetic, ///< Error when performing arithmetic operations.
BadCall, ///< Error occurs when calling backend functions.
BadName, ///< Given name is ill-formated (empty string or has "=" character).
NoMemory, ///< No enough memory to finish this operation.
};
/// @brief The result type in environment variable operations.
template<typename T>
using VarResult = std::expected<T, VarError>;
/**
* @brief Get the value of given environment variable name.
* @param[in] name The name of environment variable
* @return Gotten value, or error occurs.
*/
VarResult<std::u8string> get_var(const std::u8string_view& name);
/**
* @brief Set the value of given environment variable name.
* @details
* If there is no such name variable presented in environment,
* a new variable will be created,
* otherwise, new value will overwrite old value.
* @param[in] name The name of environment variable
* @param[in] value The value to be written into.
* @return Nothing or error occurs.
*/
VarResult<void> set_var(const std::u8string_view& name, const std::u8string_view& value);
/**
* @brief Delete environment variable with given name.
* @details
* If given variable is not presented in environment,
* this function will NOT return error.
* @param[in] name The name of environment variable
* @return Nothing, or error occurs.
*/
VarResult<void> del_var(const std::u8string_view& name);
#pragma endregion
#pragma region Environment Path
/// @brief Error occurs when operating path related functions.
enum class PathError {
Win32, ///< Underlying Win32 function error.
Posix, ///< Underlying POSIX failed.
BadSize, ///< Written size if not matched with expected size.
BadCast, ///< Error occurs when casting values.
Overflow, ///< Some arithmetic operation overflow.
};
/// @brief The result type used for path related functions;
template<typename T>
using PathResult = std::expected<T, PathError>;
/**
* @brief Get the path of the current running executable.
* @return Gotten path (no absolute path guaranteed) or error occurs.
*/
PathResult<std::u8string> current_exe();
#pragma endregion
} // namespace yycc::env

View File

@ -7,7 +7,7 @@
* 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 {
namespace yycc::option {
template<typename T>
using Option = std::optional<T>;
@ -22,4 +22,4 @@ namespace yycc::rust::option {
return OptionType(std::nullopt);
}
} // namespace yycc::rust::option
} // namespace yycc::option

View File

@ -1,6 +1,6 @@
#include "panic.hpp"
#include "../carton/termcolor.hpp"
#include "../patch/stream.hpp"
#include "carton/termcolor.hpp"
#include "patch/stream.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
@ -10,7 +10,7 @@
using namespace yycc::patch::stream;
namespace yycc::rust::panic {
namespace yycc::panic {
void panic(const char* file, int line, const std::u8string_view& msg) {
// Output message in stderr.
@ -36,4 +36,4 @@ namespace yycc::rust::panic {
std::abort();
}
} // namespace yycc::rust::panic
} // namespace yycc::panic

View File

@ -1,5 +1,5 @@
#pragma once
#include "../patch/format.hpp"
#include "patch/format.hpp"
#include <string_view>
#include <format>
@ -15,13 +15,16 @@
*
* 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.
* Therefore, I suggest a good practice call "exception is error",
* 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 strictly prohibited to prevent any unexpected recovery from exceptions.
* For code your written, you should use the macros provided in this file instead of throwing exceptions.
* In this way, unexpected behavior in code will cause immediate exit, shown error information and stack traces.
* Standard library exceptions will also cause the program to exit, but without stack information.
* However, if you are following "exception is error" rule, you still can throw exceptions instead.
*/
namespace yycc::rust::panic {
namespace yycc::panic {
/**
* @brief Immediately crashes the entire program like Rust's \c panic! macro.
@ -31,7 +34,7 @@ namespace yycc::rust::panic {
* However, this format function is specially modified that it can accept UTF8 format string and UTF8 string argument.
* More preciously, it is "format" in \c yycc::patch::format namespace.
*/
#define RS_PANIC(msg, ...) ::yycc::rust::panic::panic(__FILE__, __LINE__, ::yycc::patch::format::format(msg __VA_OPT__(, ) __VA_ARGS__))
#define RS_PANIC(msg, ...) ::yycc::panic::panic(__FILE__, __LINE__, ::yycc::patch::format::format(msg __VA_OPT__(, ) __VA_ARGS__))
/**
* @brief Immediately crashes the entire program like Rust's \c panic! macro.
@ -44,4 +47,4 @@ namespace yycc::rust::panic {
*/
[[noreturn]] void panic(const char* file, int line, const std::u8string_view& msg);
} // namespace yycc::rust::panic
} // namespace yycc::panic

68
src/yycc/prelude.hpp Normal file
View File

@ -0,0 +1,68 @@
#pragma once
/**
* @file
* @brief The Rust-like prelude header for C++.
* @details
* When I writting with Rust, I notice Rust add types for all files in default.
* This default imported types are called "prelude".
* This is very convenient for programming so I decide to introduce it in C++.
*
* I create this file, organize all types, which I think should be exposed for programmer, in to an independent namespace,
* and expose them into global namesoace.
* By simply include this file at the top of your C++ code, you can get Rust-like prelude effect in C++.
* These exposed types including primitive types, string types, basic Option and Result utilities, panic mechanisim and etc.
*/
// Rust prelude section
#include "primitive.hpp"
#include "result.hpp"
#include "option.hpp"
#include "panic.hpp"
#include <vector>
/**
* @brief The namespace including all types presented in prelude.
* @details
* By including this file, all of these types are automaticalling exposed to global namespace.
* There is no need to refer this namespace anymore.
* This namespace is just a container for types which need to be exposed.
*/
namespace yycc::prelude {
// Include primitive types
#define NS_YYCC_PRIMITIVE ::yycc::primitive
using i8 = NS_YYCC_PRIMITIVE::i8;
using i16 = NS_YYCC_PRIMITIVE::i16;
using i32 = NS_YYCC_PRIMITIVE::i32;
using i64 = NS_YYCC_PRIMITIVE::i64;
using u8 = NS_YYCC_PRIMITIVE::u8;
using u16 = NS_YYCC_PRIMITIVE::u16;
using u32 = NS_YYCC_PRIMITIVE::u32;
using u64 = NS_YYCC_PRIMITIVE::u64;
using isize = NS_YYCC_PRIMITIVE::isize;
using usize = NS_YYCC_PRIMITIVE::usize;
using f32 = NS_YYCC_PRIMITIVE::f32;
using f64 = NS_YYCC_PRIMITIVE::f64;
#undef NS_YYCC_PRIMITIVE
// Other types
//using str = std::u8string_view;
//using String = std::u8string;
template<typename T>
using Vec = std::vector<T>;
// Expose Result and Option
using namespace ::yycc::option;
using namespace ::yycc::result;
// Panic are introduced by including header file
// so we do not need re-expose it.
} // namespace yycc::prelude
// Expose all members
using namespace ::yycc::prelude;

40
src/yycc/primitive.hpp Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <string_view>
/**
* @brief The namespace providing primitive types.
* @details
* When I writing with Rust, I notice that most pf primitive types has explicit and exact size, and their names are short and clear.
* Hoeever, in C++, most primitive types are variable based on system, due to the legacy of C era.
* All primitive types with explicit size are only can be fetched in a specific header file,
* and, their names are too long because they can not be registered as reserved keywords or names
* due to the name conflict with so much code written in past years.
* However, STL can't do this but I can do this.
* I invent this namespace providing primitive types, such as integers, floating-point numbers, and strings,
* in Rust style, their names areshort and clear.
*/
namespace yycc::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 String = std::u8string;
// using str = std::u8string_view;
}

View File

@ -36,7 +36,7 @@
* 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 {
namespace yycc::result {
/**
* @brief Equivalent Rust \c Result in C++
@ -74,4 +74,4 @@ namespace yycc::rust::result {
return ResultType(std::unexpect, std::forward<Args>(args)...);
}
} // namespace yycc::rust::result
} // namespace yycc::result

View File

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

View File

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

View File

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

9
src/yycc/string.hpp Normal file
View File

@ -0,0 +1,9 @@
#pragma once
// TODO:
// Add content safe, Rust-like string container "String"
// and string view "str" in there.
// Once we add it, all string process function can be migrated as their class member,
// or keep them independendly but change the type of parameter into our string types.
namespace yycc::string {}

View File

@ -181,12 +181,12 @@ namespace yycc::string::op {
std::u8string_view next_str;
public:
CodePointIterator() : CodePointIterator(std::u8string_view()) {}
CodePointIterator(const std::u8string_view& strl) : current_str(), next_str(strl) { ++(*this); }
YYCC_DEFAULT_COPY_MOVE(CodePointIterator)
reference operator*() const { return this->current_str; }
pointer operator->() const { return &this->current_str; }
CodePointIterator& operator++() {
// move next string to current string and analyse it
current_str = next_str;
@ -214,17 +214,14 @@ namespace yycc::string::op {
// return self
return *this;
}
CodePointIterator operator++(int) {
CodePointIterator temp = *this;
++(*this);
return temp;
}
bool operator==(const CodePointIterator& other) const {
return this->current_str == other.current_str && this->next_str == other.next_str;
}
bool operator!=(const CodePointIterator& other) const { return !(*this == other); }
private:
@ -263,9 +260,9 @@ namespace yycc::string::op {
public:
explicit CodePoint(std::u8string_view u8str) : u8str(u8str) {}
YYCC_DEFAULT_COPY_MOVE(CodePoint)
CodePointIterator begin() const { return CodePointIterator(u8str); }
CodePointIterator end() const {
// Pass empty string view indicate end.
return CodePointIterator(std::u8string_view());
@ -295,7 +292,7 @@ namespace yycc::string::op {
// Do not accept root element always (no empty string).
root->is_end = false;
}
/**
* @brief Insert new words in trie tree.
* @details
@ -352,18 +349,18 @@ namespace yycc::string::op {
// YYC MARK:
// There is a fatal bug for Trie Tree, but it doesn't matter with our usage scenario.
//
//
// Assume there is two string "ab" and "abcd". If user give "abc",
// we should match it with "ab" prefix, but this function will return there is no match.
// However, this is impossible for UTF8 sequence.
// There is no possibility that two UTF8 sequence, indicating two different Unicode code point respectively,
// has the same prefix and different length. Because their first byte must be different,
// the first byte indicate the length of sequence.
//
// This result also can be proven for suffix,
//
// This result also can be proven for suffix,
// because first byte must not be equal to any other continuation bytes.
// It is impossible that they have same "ab".
//
//
// So it is safe for our usage scenario although this bug is presented.
// check whether current is valid end.
@ -379,7 +376,7 @@ namespace yycc::string::op {
#pragma endregion
template<bool bDoLeft, bool bDoRight>
std::u8string_view internal_strip(const std::u8string_view& strl, const std::u8string_view& words) {
static std::u8string_view internal_strip(const std::u8string_view& strl, const std::u8string_view& words) {
std::optional<TrieTree> prefix, suffix;
if constexpr (bDoLeft) prefix = TrieTree();
if constexpr (bDoRight) suffix = TrieTree();
@ -419,6 +416,60 @@ namespace yycc::string::op {
#pragma endregion
#pragma region Trim
template<bool bDoLeft, bool bDoRight>
std::u8string_view internal_trim(const std::u8string_view& strl, const std::u8string_view& words) {
// check words
if (!std::ranges::none_of(words, [](auto c) { return static_cast<uint8_t>(c) & 0x80; })) {
throw std::invalid_argument("given words are not all ASCII (<= 0x7F) only");
}
// prepare return value
std::u8string_view rv = strl;
// remove left first
if constexpr (bDoLeft) {
auto finder = rv.find_first_not_of(words);
if (finder == std::u8string_view::npos) {
// all string are in given words
rv = std::u8string_view();
} else {
// remove by offset
rv = rv.substr(finder);
}
}
// remove right
if constexpr (bDoRight) {
auto finder = rv.find_last_not_of(words);
if (finder == std::u8string_view::npos) {
// all string are in given words
rv = std::u8string_view();
} else {
// remove by offset
rv = rv.substr(0, finder + 1);
}
}
// return value
return rv;
}
std::u8string_view trim(const std::u8string_view& strl, const std::u8string_view& words) {
return internal_trim<true, true>(strl, words);
}
std::u8string_view ltrim(const std::u8string_view& strl, const std::u8string_view& words) {
return internal_trim<true, false>(strl, words);
}
std::u8string_view rtrim(const std::u8string_view& strl, const std::u8string_view& words) {
return internal_trim<false, true>(strl, words);
}
#pragma endregion
#pragma region Split
// Reference:
@ -426,6 +477,8 @@ namespace yycc::string::op {
#pragma region Lazy Split Iterator
LazySplitIterator::LazySplitIterator() : LazySplitIterator(std::nullopt, std::u8string_view()) {}
LazySplitIterator::LazySplitIterator(std::optional<std::u8string_view> strl, const std::u8string_view& delimiter) :
m_current_str(std::nullopt), m_next_str(strl), m_delimiter(delimiter) {
// We can archive result by assign string into next string,
@ -486,8 +539,8 @@ namespace yycc::string::op {
}
bool LazySplitIterator::operator==(const LazySplitIterator& other) const {
return (this->m_current_str == other.m_current_str) && (this->m_next_str == other.m_next_str)
&& (this->m_delimiter == other.m_delimiter);
// YYC MARK: do not compare the delimiter
return (this->m_current_str == other.m_current_str) && (this->m_next_str == other.m_next_str);
}
bool LazySplitIterator::operator!=(const LazySplitIterator& other) const {

View File

@ -1,5 +1,6 @@
#pragma once
#include "../macro/printf_checker.hpp"
#include "../macro/class_copy_move.hpp"
#include <string>
#include <string_view>
#include <cstdarg>
@ -12,13 +13,19 @@ namespace yycc::string::op {
#pragma region Printf
// YYC MARK:
// Shitty __attribute__((format(gnu_printf, (A), (B)))) force the type of format string is const char*.
// My function signature will cause compile error which can not be removed by any switches.
// I guess Clang may have same issue.
// So I sadly disable format string check for printf in UTF8 char type.
/**
* @brief Perform an UTF8 string formatting operation.
* @param[in] format The format string.
* @param[in] ... Argument list of format string.
* @return The formatted result.
*/
std::u8string printf(YYCC_PRINTF_CHECK_FMTSTR const char8_t* format, ...) YYCC_PRINTF_CHECK_ATTR(1, 2);
std::u8string printf(/*YYCC_PRINTF_CHECK_FMTSTR*/ const char8_t* format, ...) /*YYCC_PRINTF_CHECK_ATTR(1, 2)*/;
/**
* @brief Perform an UTF8 string formatting operation.
* @param[in] format The format string.
@ -138,32 +145,75 @@ namespace yycc::string::op {
#pragma endregion
#pragma region Strip
#pragma region Strip and Trim
/**
* @brief Remove leading and trailing whitespace from the string.
* @param[in,out] strl The string to be stripped.
* @details
* This "strip" function is full Unicode supported.
* It means that it is different with all other ordinary implementations,
* that treat each UTF8 code unit as an invididual chars when stripping.
* This function will break given words by UTF8 code point first,
* and try to strip these code points in given string.
* So it can strip Unicode whitespace or any other characters correctly.
* However, obviously, it is slower than ASCII-only version "trim".
* If you only need to strip ASCII whitespace (space, tab, newline) or any other code point lower than \c 0x7F,
* please consider using trim() for better performance.
* @param[in] strl The string to be stripped.
* @param[in] words The characters to be stripped.
* @return The string view with leading and trailing whitespace removed.
* @see See trim() for ASCII-only version "strip".
*/
std::u8string_view strip(const std::u8string_view& strl, const std::u8string_view& words);
/**
* @brief Remove leading whitespace from the string.
* @param[in,out] strl The string to be stripped.
* @param[in] strl The string to be stripped.
* @param[in] words The characters to be stripped.
* @return The string view with leading whitespace removed.
* @see See strip() for more info.
*/
std::u8string_view lstrip(const std::u8string_view& strl, const std::u8string_view& words);
/**
* @brief Remove trailing whitespace from the string.
* @param[in,out] strl The string to be stripped.
* @param[in] strl The string to be stripped.
* @param[in] words The characters to be stripped.
* @return The string view with trailing whitespace removed.
* @see See strip() for more info.
*/
std::u8string_view rstrip(const std::u8string_view& strl, const std::u8string_view& words);
/**
* @brief Remove leading and trailing whitespace from the string.
* @details
* This function is limited "trim" function.
* It brutely think each code unit in given words are invididual chars during stripping.
* So it can only trim ASCII whitespace (space, tab, newline) or any other code point lower than \c 0x7F.
* If you need to trim Unicode whitespace or any other characters,
* please consider using strip() for correct behavior.
* @param[in] strl The view of string to be trimmed.
* @param[in] words The characters to be trimmed.
* @return The string view with leading and trailing whitespace removed.
* @see See strip() for full Unicode supported version "trim".
*/
std::u8string_view trim(const std::u8string_view& strl, const std::u8string_view& words);
/**
* @brief Remove leading whitespace from the string.
* @param[in] strl The view of string to be trimmed.
* @param[in] words The characters to be trimmed.
* @return The string view with leading whitespace removed.
* @see See trim() for more info.
*/
std::u8string_view ltrim(const std::u8string_view& strl, const std::u8string_view& words);
/**
* @brief Remove trailing whitespace from the string.
* @param[in] strl The view of string to be trimmed.
* @param[in] words The characters to be trimmed.
* @return The string view with trailing whitespace removed.
* @see See trim() for more info.
*/
std::u8string_view rtrim(const std::u8string_view& strl, const std::u8string_view& words);
#pragma endregion
#pragma region Split
@ -194,7 +244,9 @@ namespace yycc::string::op {
std::u8string_view m_delimiter; ///< Delimiter
public:
LazySplitIterator();
LazySplitIterator(std::optional<std::u8string_view> strl, const std::u8string_view& delimiter);
YYCC_DEFAULT_COPY_MOVE(LazySplitIterator)
reference operator*() const;
pointer operator->() const;
@ -214,6 +266,8 @@ namespace yycc::string::op {
public:
LazySplit(const std::u8string_view& strl, const std::u8string_view& delimiter);
YYCC_DEFAULT_COPY_MOVE(LazySplit)
LazySplitIterator begin() const;
LazySplitIterator end() const;
};

View File

@ -1,7 +1,7 @@
# Create executable testbench
add_executable(YYCCTestbench "")
# Setup testbench sources
target_sources(YYCCTestbench
# Create executable test
add_executable(YYCCTest "")
# Setup test sources
target_sources(YYCCTest
PRIVATE
main.cpp
@ -20,7 +20,7 @@ PRIVATE
yycc/patch/fopen.cpp
yycc/patch/stream.cpp
yycc/patch/format.cpp
yycc/rust/env.cpp
yycc/env.cpp
yycc/string/reinterpret.cpp
yycc/string/op.cpp
yycc/num/parse.cpp
@ -41,20 +41,21 @@ PRIVATE
yycc/carton/wcwidth.cpp
yycc/carton/tabulate.cpp
yycc/carton/clap.cpp
yycc/carton/fft.cpp
)
target_sources(YYCCTestbench
target_sources(YYCCTest
PRIVATE
FILE_SET HEADERS
FILES
shared/literals.hpp
)
# Setup headers
target_include_directories(YYCCTestbench
target_include_directories(YYCCTest
PUBLIC
"${CMAKE_CURRENT_LIST_DIR}"
)
# Setup libraries
target_link_libraries(YYCCTestbench
target_link_libraries(YYCCTest
PRIVATE
YYCCommonplace
GTest::gtest_main
@ -62,4 +63,4 @@ PRIVATE
# Discover all test
include(GoogleTest)
gtest_discover_tests(YYCCTestbench)
gtest_discover_tests(YYCCTest)

116
test/yycc/carton/fft.cpp Normal file
View File

@ -0,0 +1,116 @@
#include <gtest/gtest.h>
#include <yycc.hpp>
#include <yycc/carton/fft.hpp>
#include <initializer_list>
#define FFT ::yycc::carton::fft
namespace yycctest::carton::fft {
using TIndex = size_t;
using TFloat = float;
using TComplex = std::complex<TFloat>;
template<size_t N>
using TFft = FFT::Fft<TIndex, TFloat, N>;
// YYC MARK:
// It seems that default epsilon can not fulfill our test (too small).
constexpr TFloat TOLERANCE = static_cast<TFloat>(0.0003);
//constexpr TFloat tolerance = std::numeric_limits<TFloat>::epsilon();
template<size_t N>
static void test_fft(const std::vector<TFloat>& real_src, const std::vector<TComplex>& dst) {
// check given data size
ASSERT_EQ(real_src.size(), N);
ASSERT_EQ(dst.size(), N);
// convert real-number source into complex-number source
std::vector<TComplex> src(real_src.size());
std::generate(src.begin(), src.end(), [data = real_src.begin()]() mutable -> TComplex { return TComplex(*data++); });
// create FFT instance and compute data
TFft<N> fft;
fft.compute(src.data());
// check result with tolerance
for (TIndex i = 0u; i < src.size(); ++i) {
EXPECT_NEAR(src[i].real(), dst[i].real(), TOLERANCE);
EXPECT_NEAR(src[i].imag(), dst[i].imag(), TOLERANCE);
}
}
TEST(CartonFft, Test1) {
std::vector<TFloat> src = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
std::vector<TComplex> expected = {{+3.6000e+01f, +0.0000e+00f},
{-4.0000e+00f, +9.6569e+00f},
{-4.0000e+00f, +4.0000e+00f},
{-4.0000e+00f, +1.6569e+00f},
{-4.0000e+00f, +0.0000e+00f},
{-4.0000e+00f, -1.6569e+00f},
{-4.0000e+00f, -4.0000e+00f},
{-4.0000e+00f, -9.6569e+00f}};
test_fft<8>(src, expected);
}
TEST(CartonFft, Test2) {
std::vector<TFloat> src = {6.0f, 1.0f, 7.0f, 2.0f, 7.0f, 4.0f, 8.0f, 7.0f};
std::vector<TComplex> expected = {{+4.2000e+01f, +0.0000e+00f},
{+4.1421e-01f, +6.6569e+00f},
{-2.0000e+00f, +4.0000e+00f},
{-2.4142e+00f, +4.6569e+00f},
{+1.4000e+01f, +0.0000e+00f},
{-2.4142e+00f, -4.6569e+00f},
{-2.0000e+00f, -4.0000e+00f},
{+4.1421e-01f, -6.6569e+00f}};
test_fft<8>(src, expected);
}
TEST(CartonFft, Test3) {
std::vector<TFloat> src = {1.0f, 2.0f, 3.0f, 4.0f};
std::vector<TComplex> expected = {{+1.0000e+01f, +0.0000e+00f},
{-2.0000e+00f, +2.0000e+00f},
{-2.0000e+00f, +0.0000e+00f},
{-2.0000e+00f, -2.0000e+00f}};
test_fft<4>(src, expected);
}
TEST(CartonFft, Test4) {
std::vector<TFloat> src = {6.0f, 1.0f, 7.0f, 2.0f};
std::vector<TComplex> expected = {{+1.6000e+01f, +0.0000e+00f},
{-1.0000e+00f, +1.0000e+00f},
{+1.0000e+01f, +0.0000e+00f},
{-1.0000e+00f, -1.0000e+00f}};
test_fft<4>(src, expected);
}
TEST(CartonFft, Test5) {
std::vector<TFloat> src = {4.0f, 4.0f, 4.0f, 4.0f};
std::vector<TComplex> expected = {{+1.6000e+01f, +0.0000e+00f},
{+0.0000e+00f, +0.0000e+00f},
{+0.0000e+00f, +0.0000e+00f},
{+0.0000e+00f, +0.0000e+00f}};
test_fft<4>(src, expected);
}
TEST(CartonFft, Test6) {
std::vector<TFloat> src = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f};
std::vector<TComplex> expected = {{+1.3600e+02f, +0.0000e+00f},
{-8.0000e+00f, +4.0219e+01f},
{-8.0000e+00f, +1.9314e+01f},
{-8.0000e+00f, +1.1973e+01f},
{-8.0000e+00f, +8.0000e+00f},
{-8.0000e+00f, +5.3454e+00f},
{-8.0000e+00f, +3.3137e+00f},
{-8.0000e+00f, +1.5913e+00f},
{-8.0000e+00f, +0.0000e+00f},
{-8.0000e+00f, -1.5913e+00f},
{-8.0000e+00f, -3.3137e+00f},
{-8.0000e+00f, -5.3454e+00f},
{-8.0000e+00f, -8.0000e+00f},
{-8.0000e+00f, -1.1973e+01f},
{-8.0000e+00f, -1.9314e+01f},
{-8.0000e+00f, -4.0219e+01f}};
test_fft<16>(src, expected);
}
} // namespace yycctest::carton::fft

View File

@ -2,7 +2,7 @@
#include <yycc.hpp>
#include <yycc/constraint.hpp>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define CONSTRAINT ::yycc::constraint::Constraint

View File

@ -2,7 +2,7 @@
#include <yycc.hpp>
#include <yycc/constraint/builder.hpp>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define BUILDER ::yycc::constraint::builder
using namespace std::literals::string_view_literals;

View File

@ -1,15 +1,17 @@
#include <gtest/gtest.h>
#include <yycc.hpp>
#include <yycc/rust/env.hpp>
#include <yycc/env.hpp>
#include <yycc/macro/os_detector.hpp>
#include <filesystem>
#define ENV ::yycc::rust::env
#define ENV ::yycc::env
namespace yycctest::rust::env {
namespace yycctest::env {
constexpr char8_t VAR_NAME[] = u8"HOMER";
constexpr char8_t VAR_VALUE[] = u8"doh";
TEST(RustEnv, All) {
TEST(Env, EnvVar) {
// Write a new variable should okey
{
auto rv = ENV::set_var(VAR_NAME, VAR_VALUE);
@ -42,4 +44,19 @@ namespace yycctest::rust::env {
}
}
}
TEST(Env, CurrentExe) {
auto rv = ENV::current_exe();
ASSERT_TRUE(rv.has_value());
std::filesystem::path p(rv.value());
auto filename = p.filename().u8string();
#if defined(YYCC_OS_WINDOWS)
// Only Windows has special ext.
EXPECT_EQ(filename, u8"YYCCTest.exe");
#else
// Executable in other system are all in plain name.
EXPECT_EQ(filename, u8"YYCCTest");
#endif
}
} // namespace yycctest::env

View File

@ -3,7 +3,7 @@
#include <yycc/flag_enum.hpp>
#include <cinttypes>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define FLAG_ENUM ::yycc::flag_enum

View File

@ -2,7 +2,7 @@
#include <yycc.hpp>
#include <yycc/num/op.hpp>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define OP ::yycc::num::op

View File

@ -2,7 +2,7 @@
#include <yycc.hpp>
#include <yycc/num/parse.hpp>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define PARSE ::yycc::num::parse

View File

@ -3,7 +3,7 @@
#include <yycc/num/safe_cast.hpp>
#include <yycc/macro/ptr_size_detector.hpp>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define CAST ::yycc::num::safe_cast

View File

@ -4,7 +4,7 @@
#include <cstdint>
#include <limits>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define OP ::yycc::num::safe_op

View File

@ -2,7 +2,7 @@
#include <yycc.hpp>
#include <yycc/num/stringify.hpp>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define STRINGIFY ::yycc::num::stringify

View File

@ -11,25 +11,25 @@ namespace yycctest::patch::format {
static constexpr std::u8string_view PROBE_STRING_VIEW(PROBE);
TEST(PatchFormat, OrdinaryFormat) {
auto rv = FORMAT::format("{}{}{}{}{}{} world!",
auto rv = FORMAT::format("{:c}{}{}{}{}{} world!",
PROBE[0],
PROBE_STRING.data(),
PROBE_STRING.c_str(),
PROBE,
PROBE_STRING,
PROBE_STRING_VIEW);
EXPECT_EQ(rv, "104hellohellohellohellohello world!");
EXPECT_EQ(rv, "hhellohellohellohellohello world!");
}
TEST(PatchFormat, Utf8Format) {
auto rv = FORMAT::format(u8"{}{}{}{}{}{} world!",
auto rv = FORMAT::format(u8"{:c}{}{}{}{}{} world!",
PROBE[0],
PROBE_STRING.data(),
PROBE_STRING.c_str(),
PROBE,
PROBE_STRING,
PROBE_STRING_VIEW);
EXPECT_EQ(rv, u8"104hellohellohellohellohello world!");
EXPECT_EQ(rv, u8"hhellohellohellohellohello world!");
}
}

View File

@ -2,7 +2,7 @@
#include <yycc.hpp>
#include <yycc/string/op.hpp>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define OP ::yycc::string::op
using namespace std::literals::string_view_literals;
@ -86,6 +86,12 @@ namespace yycctest::string::op {
EXPECT_EQ(rv, u8" \taaa");
}
// Full strip
{
auto rv = OP::strip(u8" ", u8" ");
EXPECT_TRUE(rv.empty());
}
// Special strip
{
auto rv = OP::strip(u8"啊啊啊aaaあああ", u8"啊あ");
@ -110,6 +116,32 @@ namespace yycctest::string::op {
}
}
TEST(StringOp, Trim) {
// Normal trim
{
auto rv = OP::trim(u8" \taaa\n", u8" \t\r\n");
EXPECT_EQ(rv, u8"aaa");
}
{
auto rv = OP::ltrim(u8" \taaa\n", u8" \t\r\n");
EXPECT_EQ(rv, u8"aaa\n");
}
{
auto rv = OP::rtrim(u8" \taaa\n", u8" \t\r\n");
EXPECT_EQ(rv, u8" \taaa");
}
// Bad words
{
EXPECT_ANY_THROW(OP::trim(u8"q啊啊啊aaaあああp", u8"p啊q"));
}
// Full trim
{
auto rv = OP::trim(u8" ", u8" ");
EXPECT_TRUE(rv.empty());
}
}
TEST(StringOp, Split) {
// Normal
{

View File

@ -3,7 +3,7 @@
#include <yycc.hpp>
#include <yycc/string/reinterpret.hpp>
#include <yycc/rust/prelude.hpp>
#include <yycc/prelude.hpp>
#define REINTERPRET ::yycc::string::reinterpret
#define AS_UINT8(p) static_cast<u8>(p)