From df97c9066ace8d51aac361a91bf9c0c7adfcc468 Mon Sep 17 00:00:00 2001 From: Zephyr Lykos Date: Thu, 2 Jan 2025 08:26:35 +0800 Subject: [PATCH 1/6] build: move subproject check to root --- examples/meson.build | 1 - meson.build | 15 +++++++++------ tests/meson.build | 1 - 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/meson.build b/examples/meson.build index 01b7979b..e38d5188 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,4 +1,3 @@ -root = not meson.is_subproject() examples = get_option('examples').enable_auto_if(root) if examples.enabled() diff --git a/meson.build b/meson.build index f104b558..604fd5c7 100644 --- a/meson.build +++ b/meson.build @@ -131,13 +131,16 @@ if flex_exe.found() meson.override_find_program('checkscoped', checkscoped_exe) endif +root = not meson.is_subproject() subdir('tests') subdir('examples') -datadir = get_option('datadir') +if root + datadir = get_option('datadir') -install_subdir( - 'docs', - install_dir: datadir / 'doc' / meson.project_name(), - strip_directory: true, -) + install_subdir( + 'docs', + install_dir: datadir / 'doc' / meson.project_name(), + strip_directory: true, + ) +endif diff --git a/tests/meson.build b/tests/meson.build index e933dbf4..02c70113 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,4 +1,3 @@ -root = not meson.is_subproject() tests = get_option('tests').enable_auto_if(root) if tests.enabled() From ea952e4be7d35eea62e588494c86c57f6cf0ed2c Mon Sep 17 00:00:00 2001 From: Zephyr Lykos Date: Thu, 2 Jan 2025 08:27:44 +0800 Subject: [PATCH 2/6] build: only add warning flags if supported --- meson.build | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/meson.build b/meson.build index 604fd5c7..ea05bfde 100644 --- a/meson.build +++ b/meson.build @@ -3,18 +3,30 @@ project( 'c', version: '5.0-dev', license: 'MIT', - default_options: ['c_std=c11', 'warning_level=3', 'default_library=static'] + default_options: ['c_std=c11', 'warning_level=3', 'default_library=static'], + meson_version: '>=1.1', ) stcversion = 5 cc = meson.get_compiler('c') -if cc.get_id() == 'gcc' - add_project_arguments('-Wno-missing-field-initializers', '-Wno-stringop-overflow', '-Wno-clobbered', language: ['c', 'cpp']) -elif cc.get_id() == 'clang' - add_project_arguments('-Wno-missing-field-initializers', language: ['c', 'cpp']) -elif cc.get_id() == 'msvc' - add_project_arguments('-wd4996', language: ['c', 'cpp']) # no "unsecure" funcs warnings +if cc.get_argument_syntax() == 'gcc' + add_project_arguments( + cc.get_supported_arguments( + '-Wno-missing-field-initializers', + '-Wno-stringop-overflow', + '-Wno-clobbered', + ), + language: ['c', 'cpp'], + ) +elif cc.get_argument_syntax() == 'msvc' + # no "unsecure" funcs warnings + add_project_arguments( + cc.get_supported_arguments( + '-wd4996', + ), + language: ['c', 'cpp'], + ) endif libsrc = files( From 48b50c823163ca2b9bd3e941ae883ac2c2552c8a Mon Sep 17 00:00:00 2001 From: Zephyr Lykos Date: Thu, 2 Jan 2025 08:34:45 +0800 Subject: [PATCH 3/6] build: support explict docdir specification --- meson.build | 6 +++++- meson_options.txt | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index ea05bfde..9633c63c 100644 --- a/meson.build +++ b/meson.build @@ -149,10 +149,14 @@ subdir('examples') if root datadir = get_option('datadir') + docdir = get_option('docdir') + if docdir == '' + docdir = datadir / 'doc' / meson.project_name() + endif install_subdir( 'docs', - install_dir: datadir / 'doc' / meson.project_name(), + install_dir: docdir, strip_directory: true, ) endif diff --git a/meson_options.txt b/meson_options.txt index 4c93ee0e..e5a737ef 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -16,3 +16,5 @@ option( value: 'auto', description: 'Build examples', ) + +option('docdir', type: 'string', description: 'documentation directory') From b3b5f65d1a1d379e494e73ec98cd55e41932751e Mon Sep 17 00:00:00 2001 From: Zephyr Lykos Date: Thu, 2 Jan 2025 09:20:07 +0800 Subject: [PATCH 4/6] build: remove unused CMakeLists --- .github/workflows/cmake.yml | 23 ------------------- CMakeLists.txt | 45 ------------------------------------- 2 files changed, 68 deletions(-) delete mode 100644 .github/workflows/cmake.yml delete mode 100644 CMakeLists.txt diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml deleted file mode 100644 index 3a453b2c..00000000 --- a/.github/workflows/cmake.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: "CMake CI" -on: - pull_request: - push: - -jobs: - cmake-build: - runs-on: ubuntu-latest - env: - CFLAGS: -DSTC_STATIC -Wall -Wno-unused-function -ggdb3 -fsanitize=address -fsanitize=undefined -fsanitize=pointer-compare -fsanitize=pointer-subtract - CXXFLAGS: -DSTC_STATIC -Wall -Wno-unused-function -ggdb3 -fsanitize=address -fsanitize=undefined -fsanitize=pointer-compare -fsanitize=pointer-subtract - steps: - - name: 'Checkout' - uses: actions/checkout@v4 - - name: 'Build & Test' - uses: ashutoshvarma/action-cmake-build@master - with: - cc: gcc - cxx: g++ - build-type: Release - configure-options: -DBUILD_TESTING=1 - ctest-options: --output-on-failure - run-test: true diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index cc98aaa6..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -cmake_minimum_required(VERSION 3.12) -project(stclib) -#[===[ -add_library(stcinc INTERFACE) -#target_include_directories(stcinc INTERFACE include) - -file(GLOB sources "src/*.c") -add_library(stc STATIC ${sources}) - -if (UNIX) - find_package(FLEX) - flex_target(checkscoped src/checkscoped.l ${CMAKE_CURRENT_BINARY_DIR}/lex.yy.c) - add_executable(checkscoped ${FLEX_checkscoped_OUTPUTS}) -endif() - - -include(CTest) - -if (BUILD_TESTING) - file(GLOB examples examples/*.c) - foreach (file IN LISTS examples) - get_filename_component(name "${file}" NAME_WE) - add_executable(${name} ${file}) - target_compile_options(${name} PRIVATE "-DSTC_STATIC") - if (CMAKE_THREAD_LIBS_INIT) - target_link_libraries(${name} PRIVATE "${CMAKE_THREAD_LIBS_INIT}") - endif() - target_link_libraries(${name} PRIVATE stc m) - add_test(NAME ${name} COMMAND ${name}) - endforeach() - - file(GLOB test_files tests/*_test.c) - add_executable(stctest ${test_files} tests/main.c) - target_include_directories(stctest PRIVATE "include") - target_link_libraries(stctest PRIVATE stc m) - add_test(NAME stctest COMMAND stctest) - - # foreach (name IN ITEMS deque list hmap smap vec) - # add_executable(${name} benchmarks/plotbench/${name}_benchmark.cpp) - # target_link_libraries(${name} PRIVATE stc m) - # add_test(NAME ${name} COMMAND ${name}) - # endforeach() -endif() - -]===] From c0a25f3df97c050183730edc6e5500f25a18ba97 Mon Sep 17 00:00:00 2001 From: Zephyr Lykos Date: Thu, 2 Jan 2025 09:28:17 +0800 Subject: [PATCH 5/6] ci: additional targets and comments --- .github/workflows/meson.yml | 109 +++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 39 deletions(-) diff --git a/.github/workflows/meson.yml b/.github/workflows/meson.yml index dc9a2061..5ab91914 100644 --- a/.github/workflows/meson.yml +++ b/.github/workflows/meson.yml @@ -16,73 +16,93 @@ jobs: - release mode: - name: default + args: -Dtests=enabled + extra_envs: {} + + # Alternative compiler setups + - name: gcc args: -Dtests=enabled extra_envs: CC: gcc CXX: g++ + - name: clang + args: -Dtests=enabled + extra_envs: + CC: clang + CXX: clang++ + - name: sanitize args: >- "-Db_sanitize=address,undefined" - extra_envs: - CC: gcc - CXX: g++ - # For Visual Studio + extra_envs: {} + + # This is for MSVC, which only supports AddressSanitizer. + # https://learn.microsoft.com/en-us/cpp/sanitizers/ - name: sanitize+asanonly args: -Db_sanitize=address extra_envs: ASAN_OPTIONS: report_globals=0:halt_on_error=1:abort_on_error=1:print_summary=1 + - name: clang+sanitize args: >- "-Db_sanitize=address,undefined" extra_envs: CC: clang CXX: clang++ - #- name: clang-cl+sanitize - # args: >- - # "-Db_sanitize=address,undefined" - # extra_envs: - # CC: clang-cl - # CXX: clang-cl - #- name: clang+msan - # args: -Db_sanitize=memory - # extra_envs: - # CC: clang - # CXX: clang++ - #- name: clang-cl+msan - # args: -Db_sanitize=memory - # extra_envs: - # CC: clang-cl - # CXX: clang-cl + - name: clang+msan + args: -Db_sanitize=memory + extra_envs: + CC: clang + CXX: clang++ + + # default clang on GitHub hosted runners is from MSYS2. + # Use Visual Studio supplied clang-cl instead. + - name: clang-cl+sanitize + args: >- + "-Db_sanitize=address,undefined" + extra_envs: + CC: clang-cl + CXX: clang-cl + - name: clang-cl+msan + args: -Db_sanitize=memory + extra_envs: + CC: clang-cl + CXX: clang-cl platform: - ubuntu-22.04 - windows-2022 - #- macos-latest + - macos-latest exclude: - # clang-cl + # clang-cl only makes sense on windows. - platform: ubuntu-22.04 mode: name: clang-cl+sanitize - platform: macos-latest mode: name: clang-cl+sanitize - - platform: windows-2022 - mode: - name: clang+sanitize - - platform: windows-2022 - mode: - name: sanitize+asanonly - flavor: release - platform: ubuntu-22.04 mode: name: clang-cl+msan - platform: macos-latest mode: name: clang-cl+msan + + # Use clang-cl instead of MSYS2 clang. + # + # we already tested clang+sanitize on linux, + # if this doesn't work, it should be an issue for MSYS2 team to consider. + - platform: windows-2022 + mode: + name: clang + - platform: windows-2022 + mode: + name: clang+sanitize - platform: windows-2022 mode: name: clang+msan - # msvc + + # MSVC-only sanitizers - platform: ubuntu-22.04 mode: name: sanitize+asanonly @@ -92,24 +112,35 @@ jobs: - platform: windows-2022 mode: name: sanitize - # default is clang + + # clang is the default on macos + # also gcc is an alias to clang - platform: macos-latest mode: - name: clang+sanitize - # msan unsupported + name: clang - platform: macos-latest mode: - name: clang+msan + name: gcc + + # gcc is the default on linux + - platform: ubuntu-22.04 + mode: + name: gcc + + # only run sanitizer tests on linux + # + # gcc/clang's codegen shouldn't massively change across platforms, + # and linux supports most of the sanitizers. - platform: macos-latest mode: - name: sanitize - flavor: release + name: clang+sanitize - platform: macos-latest mode: + # macos does not support msan name: clang+msan - flavor: release - - + - platform: macos-latest + mode: + name: sanitize steps: - name: Setup meson run: | From c1372c7f2aff2fe0bf11f743b1878b8e3230a14e Mon Sep 17 00:00:00 2001 From: Zephyr Lykos Date: Thu, 2 Jan 2025 09:57:26 +0800 Subject: [PATCH 6/6] repo: make line endings unix by default --- .gitattributes | 6 +- include/c11/fmt.h | 574 +++++++++++++++++++------------------- include/stc/sys/sumtype.h | 322 ++++++++++----------- tests/algorithm_test.c | 186 ++++++------ 4 files changed, 545 insertions(+), 543 deletions(-) diff --git a/.gitattributes b/.gitattributes index 768ef2aa..8626e4b5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ -*.h linguist-language=C -*.c linguist-language=C +* text=auto + +*.h linguist-language=C +*.c linguist-language=C diff --git a/include/c11/fmt.h b/include/c11/fmt.h index 4659c4d1..70c90eff 100644 --- a/include/c11/fmt.h +++ b/include/c11/fmt.h @@ -1,287 +1,287 @@ -#ifndef FMT_H_INCLUDED -#define FMT_H_INCLUDED -/* -VER 2.3 API: -void fmt_print(fmt, ...); -void fmt_println(fmt, ...); -void fmt_printd(dst, fmt, ...); -const char* fmt_time(fmt, const struct tm* tm, char *buf, int len); -void fmt_close(fmt_stream* ss); - - dst - destination, one of: - FILE* fp Write to a file - char* strbuf Write to a pre-allocated string buffer - fmt_stream* ss Write to a string-stream (auto allocated). - Set ss->overwrite=1 for overwrite-mode. - Call fmt_close(ss) after usage. - - fmt - format string (const char*) - {} Auto-detected format. If :MOD is not specified, - float will use ".8g" format, and double ".16g". - {:MODS} Format modifiers: '<' left align (replaces '-'). Default for char* and char. - '>' right align. Default for numbers. - Other than that MODS can be regular printf() format modifiers. - {{ }} % Print the '{', '}', and '%' characters. - -* C11 or higher required. -* MAX 127 chars fmt string by default. MAX 12 arguments after fmt string. -* Define FMT_IMPLEMENT, STC_IMPLEMENT or i_implement prior to #include in one translation unit. -* (c) operamint, 2022, MIT License. ------------------------------------------------------------------------------------ -#define i_implement -#include "c11/fmt.h" - -int main(void) { - const double pi = 3.141592653589793; - const size_t x = 1234567890; - const char* string = "Hello world"; - const wchar_t* wstr = L"The whole"; - const char z = 'z'; - _Bool flag = 1; - unsigned char r = 123, g = 214, b = 90, w = 110; - char buffer[64]; - - fmt_print("Color: ({} {} {}), {}\n", r, g, b, flag); - fmt_println("Wide: {}, {}", wstr, L"wide world"); - fmt_println("{:10} {:10} {:10.2f}", 42ull, 43, pi); - fmt_println("{:>10} {:>10} {:>10}", z, z, w); - fmt_printd(stdout, "{:10} {:10} {:10}\n", "Hello", "Mad", "World"); - fmt_printd(stderr, "100%: {:<20} {:.*} {}\n", string, 4, pi, x); - fmt_printd(buffer, "Precision: {} {:.10} {}", string, pi, x); - fmt_println("{}", buffer); - fmt_println("Vector: ({}, {}, {})", 3.2, 3.3, pi); - - fmt_stream ss[1] = {0}; - fmt_printd(ss, "{} {}", "Pi is:", pi); - fmt_print("{}, len={}, cap={}\n", ss->data, ss->len, ss->cap); - fmt_printd(ss, "{} {}", ", Pi squared is:", pi*pi); - fmt_print("{}, len={}, cap={}\n", ss->data, ss->len, ss->cap); - fmt_close(ss); - - time_t now = time(NULL); - struct tm t1 = *localtime(&now), t2 = t1; - t2.tm_hour += 48; - mktime(&t2); - char ts[2][64]; - fmt_print("Dates: {} and {}\n", fmt_time("%Y-%m-%d %X %Z", &t1, ts[0], 63), - fmt_time("%Y-%m-%d %X %Z", &t2, ts[1], 63)); -} -*/ -#include // IWYU pragma: keep -#include -#include -#include "../stc/common.h" - -#if defined FMT_NDEBUG || defined STC_NDEBUG || defined NDEBUG -# define fmt_OK(exp) (void)(exp) -#else -# define fmt_OK(exp) assert(exp) -#endif - -typedef struct { - char* data; - intptr_t cap, len; - _Bool overwrite; -} fmt_stream; - -#if defined FMT_STATIC || defined STC_STATIC || defined i_static - #define FMT_API static - #define FMT_DEF static -#elif defined FMT_IMPLEMENT || defined STC_IMPLEMENT || defined i_implement - #define FMT_API extern - #define FMT_DEF -#else - #define FMT_API -#endif - -struct tm; -FMT_API const char* fmt_time(const char *fmt, const struct tm* tm, char* buf, int len); -FMT_API void fmt_close(fmt_stream* ss); -FMT_API int _fmt_parse(char* p, int nargs, const char *fmt, ...); -FMT_API void _fmt_sprint(fmt_stream*, const char* fmt, ...); - -#ifndef FMT_MAX -#define FMT_MAX 128 -#endif - -#define fmt_print(...) fmt_printd(stdout, __VA_ARGS__) -#define fmt_println(...) fmt_printd((fmt_stream*)0, __VA_ARGS__) -#define fmt_printd(...) c_MACRO_OVERLOAD(fmt_printd, __VA_ARGS__) -#define fmt_sv "{:.*s}" -#define fmt_svarg(sv) (int)(sv).size, (sv).buf - -/* Primary function. */ -#define fmt_printd_2(to, fmt) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 0, fmt); \ - fmt_OK(_n == 0); _fmt_fn(to)(to, fmt); } while (0) -#define fmt_printd_3(to, fmt, c) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 1, fmt, _fc(c)); \ - fmt_OK(_n == 1); _fmt_fn(to)(to, _fs, c); } while (0) -#define fmt_printd_4(to, fmt, c, d) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 2, fmt, _fc(c), _fc(d)); \ - fmt_OK(_n == 2); _fmt_fn(to)(to, _fs, c, d); } while (0) -#define fmt_printd_5(to, fmt, c, d, e) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 3, fmt, _fc(c), _fc(d), _fc(e)); \ - fmt_OK(_n == 3); _fmt_fn(to)(to, _fs, c, d, e); } while (0) -#define fmt_printd_6(to, fmt, c, d, e, f) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 4, fmt, _fc(c), _fc(d), _fc(e), _fc(f)); \ - fmt_OK(_n == 4); _fmt_fn(to)(to, _fs, c, d, e, f); } while (0) -#define fmt_printd_7(to, fmt, c, d, e, f, g) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 5, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g)); \ - fmt_OK(_n == 5); _fmt_fn(to)(to, _fs, c, d, e, f, g); } while (0) -#define fmt_printd_8(to, fmt, c, d, e, f, g, h) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 6, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h)); \ - fmt_OK(_n == 6); _fmt_fn(to)(to, _fs, c, d, e, f, g, h); } while (0) -#define fmt_printd_9(to, fmt, c, d, e, f, g, h, i) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 7, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), _fc(i)); \ - fmt_OK(_n == 7); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i); } while (0) -#define fmt_printd_10(to, fmt, c, d, e, f, g, h, i, j) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 8, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ - _fc(i), _fc(j)); \ - fmt_OK(_n == 8); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j); } while (0) -#define fmt_printd_11(to, fmt, c, d, e, f, g, h, i, j, k) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 9, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ - _fc(i), _fc(j), _fc(k)); \ - fmt_OK(_n == 9); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k); } while (0) -#define fmt_printd_12(to, fmt, c, d, e, f, g, h, i, j, k, m) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 10, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ - _fc(i), _fc(j), _fc(k), _fc(m)); \ - fmt_OK(_n == 10); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m); } while (0) -#define fmt_printd_13(to, fmt, c, d, e, f, g, h, i, j, k, m, n) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 11, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ - _fc(i), _fc(j), _fc(k), _fc(m), _fc(n)); \ - fmt_OK(_n == 11); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m, n); } while (0) -#define fmt_printd_14(to, fmt, c, d, e, f, g, h, i, j, k, m, n, o) \ - do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 12, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ - _fc(i), _fc(j), _fc(k), _fc(m), _fc(n), _fc(o)); \ - fmt_OK(_n == 12); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m, n, o); } while (0) - -#define _fmt_fn(x) _Generic ((x), \ - FILE*: fprintf, \ - char*: sprintf, \ - fmt_stream*: _fmt_sprint) - -#if defined(_MSC_VER) && !defined(__clang__) - #define _signed_char_hhd -#else - #define _signed_char_hhd signed char: "c", -#endif -#ifdef __GNUC__ - #define FMT_UNUSED __attribute__((unused)) -#else - #define FMT_UNUSED -#endif - -#define _fc(x) _Generic (x, \ - _Bool: "d", \ - unsigned char: "hhu", \ - _signed_char_hhd \ - char: "c", \ - short: "hd", \ - unsigned short: "hu", \ - int: "d", \ - unsigned: "u", \ - long: "ld", \ - unsigned long: "lu", \ - long long: "lld", \ - unsigned long long: "llu", \ - float: "g", \ - double: "@g", \ - long double: "@Lg", \ - char*: "s", \ - wchar_t*: "ls", \ - void*: "p", \ - const char*: "s", \ - const wchar_t*: "ls", \ - const void*: "p") - -#if defined FMT_DEF - -#include -#include -#include -#include - -FMT_DEF FMT_UNUSED void fmt_close(fmt_stream* ss) { - free(ss->data); -} - -FMT_DEF FMT_UNUSED -const char* fmt_time(const char *fmt, const struct tm* tm, char* buf, int len) { - strftime(buf, (size_t)len, fmt, tm); - return buf; -} - -FMT_DEF void _fmt_sprint(fmt_stream* ss, const char* fmt, ...) { - va_list args, args2; - va_start(args, fmt); - if (ss == NULL) { - vprintf(fmt, args); putchar('\n'); - goto done1; - } - va_copy(args2, args); - const int n = vsnprintf(NULL, 0U, fmt, args); - if (n < 0) goto done2; - const intptr_t pos = ss->overwrite ? 0 : ss->len; - ss->len = pos + n; - if (ss->len > ss->cap) { - ss->cap = ss->len + ss->cap/2; - ss->data = (char*)realloc(ss->data, (size_t)ss->cap + 1U); - } - vsnprintf(ss->data + pos, (size_t)n+1, fmt, args2); - done2: va_end(args2); - done1: va_end(args); -} - -FMT_DEF int _fmt_parse(char* p, int nargs, const char *fmt, ...) { - char *arg, *p0, ch; - int n = 0, empty; - va_list args; - va_start(args, fmt); - do { - switch ((ch = *fmt)) { - case '%': - *p++ = '%'; - break; - case '}': - if (*++fmt == '}') break; /* ok */ - n = 99; - continue; - case '{': - if (*++fmt == '{') break; /* ok */ - if (++n > nargs) continue; - if (*fmt != ':' && *fmt != '}') n = 99; - fmt += (*fmt == ':'); - empty = *fmt == '}'; - arg = va_arg(args, char *); - *p++ = '%', p0 = p; - while (1) switch (*fmt) { - case '\0': n = 99; /* FALLTHRU */ - case '}': goto done; - case '<': *p++ = '-', ++fmt; break; - case '>': p0 = NULL; /* FALLTHRU */ - case '-': ++fmt; break; - case '*': if (++n <= nargs) arg = va_arg(args, char *); /* FALLTHRU */ - default: *p++ = *fmt++; - } - done: - switch (*arg) { - case 'g': if (empty) memcpy(p, ".8", 2), p += 2; break; - case '@': ++arg; if (empty) memcpy(p, ".16", 3), p += 3; break; - } - if (strchr("csdioxXufFeEaAgGnp", fmt[-1]) == NULL) - while (*arg) *p++ = *arg++; - if (p0 && (p[-1] == 's' || p[-1] == 'c')) /* left-align str */ - memmove(p0 + 1, p0, (size_t)(p++ - p0)), *p0 = '-'; - fmt += *fmt == '}'; - continue; - } - *p++ = *fmt++; - } while (ch); - va_end(args); - return n; -} -#endif -#endif -#undef i_implement -#undef i_static +#ifndef FMT_H_INCLUDED +#define FMT_H_INCLUDED +/* +VER 2.3 API: +void fmt_print(fmt, ...); +void fmt_println(fmt, ...); +void fmt_printd(dst, fmt, ...); +const char* fmt_time(fmt, const struct tm* tm, char *buf, int len); +void fmt_close(fmt_stream* ss); + + dst - destination, one of: + FILE* fp Write to a file + char* strbuf Write to a pre-allocated string buffer + fmt_stream* ss Write to a string-stream (auto allocated). + Set ss->overwrite=1 for overwrite-mode. + Call fmt_close(ss) after usage. + + fmt - format string (const char*) + {} Auto-detected format. If :MOD is not specified, + float will use ".8g" format, and double ".16g". + {:MODS} Format modifiers: '<' left align (replaces '-'). Default for char* and char. + '>' right align. Default for numbers. + Other than that MODS can be regular printf() format modifiers. + {{ }} % Print the '{', '}', and '%' characters. + +* C11 or higher required. +* MAX 127 chars fmt string by default. MAX 12 arguments after fmt string. +* Define FMT_IMPLEMENT, STC_IMPLEMENT or i_implement prior to #include in one translation unit. +* (c) operamint, 2022, MIT License. +----------------------------------------------------------------------------------- +#define i_implement +#include "c11/fmt.h" + +int main(void) { + const double pi = 3.141592653589793; + const size_t x = 1234567890; + const char* string = "Hello world"; + const wchar_t* wstr = L"The whole"; + const char z = 'z'; + _Bool flag = 1; + unsigned char r = 123, g = 214, b = 90, w = 110; + char buffer[64]; + + fmt_print("Color: ({} {} {}), {}\n", r, g, b, flag); + fmt_println("Wide: {}, {}", wstr, L"wide world"); + fmt_println("{:10} {:10} {:10.2f}", 42ull, 43, pi); + fmt_println("{:>10} {:>10} {:>10}", z, z, w); + fmt_printd(stdout, "{:10} {:10} {:10}\n", "Hello", "Mad", "World"); + fmt_printd(stderr, "100%: {:<20} {:.*} {}\n", string, 4, pi, x); + fmt_printd(buffer, "Precision: {} {:.10} {}", string, pi, x); + fmt_println("{}", buffer); + fmt_println("Vector: ({}, {}, {})", 3.2, 3.3, pi); + + fmt_stream ss[1] = {0}; + fmt_printd(ss, "{} {}", "Pi is:", pi); + fmt_print("{}, len={}, cap={}\n", ss->data, ss->len, ss->cap); + fmt_printd(ss, "{} {}", ", Pi squared is:", pi*pi); + fmt_print("{}, len={}, cap={}\n", ss->data, ss->len, ss->cap); + fmt_close(ss); + + time_t now = time(NULL); + struct tm t1 = *localtime(&now), t2 = t1; + t2.tm_hour += 48; + mktime(&t2); + char ts[2][64]; + fmt_print("Dates: {} and {}\n", fmt_time("%Y-%m-%d %X %Z", &t1, ts[0], 63), + fmt_time("%Y-%m-%d %X %Z", &t2, ts[1], 63)); +} +*/ +#include // IWYU pragma: keep +#include +#include +#include "../stc/common.h" + +#if defined FMT_NDEBUG || defined STC_NDEBUG || defined NDEBUG +# define fmt_OK(exp) (void)(exp) +#else +# define fmt_OK(exp) assert(exp) +#endif + +typedef struct { + char* data; + intptr_t cap, len; + _Bool overwrite; +} fmt_stream; + +#if defined FMT_STATIC || defined STC_STATIC || defined i_static + #define FMT_API static + #define FMT_DEF static +#elif defined FMT_IMPLEMENT || defined STC_IMPLEMENT || defined i_implement + #define FMT_API extern + #define FMT_DEF +#else + #define FMT_API +#endif + +struct tm; +FMT_API const char* fmt_time(const char *fmt, const struct tm* tm, char* buf, int len); +FMT_API void fmt_close(fmt_stream* ss); +FMT_API int _fmt_parse(char* p, int nargs, const char *fmt, ...); +FMT_API void _fmt_sprint(fmt_stream*, const char* fmt, ...); + +#ifndef FMT_MAX +#define FMT_MAX 128 +#endif + +#define fmt_print(...) fmt_printd(stdout, __VA_ARGS__) +#define fmt_println(...) fmt_printd((fmt_stream*)0, __VA_ARGS__) +#define fmt_printd(...) c_MACRO_OVERLOAD(fmt_printd, __VA_ARGS__) +#define fmt_sv "{:.*s}" +#define fmt_svarg(sv) (int)(sv).size, (sv).buf + +/* Primary function. */ +#define fmt_printd_2(to, fmt) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 0, fmt); \ + fmt_OK(_n == 0); _fmt_fn(to)(to, fmt); } while (0) +#define fmt_printd_3(to, fmt, c) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 1, fmt, _fc(c)); \ + fmt_OK(_n == 1); _fmt_fn(to)(to, _fs, c); } while (0) +#define fmt_printd_4(to, fmt, c, d) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 2, fmt, _fc(c), _fc(d)); \ + fmt_OK(_n == 2); _fmt_fn(to)(to, _fs, c, d); } while (0) +#define fmt_printd_5(to, fmt, c, d, e) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 3, fmt, _fc(c), _fc(d), _fc(e)); \ + fmt_OK(_n == 3); _fmt_fn(to)(to, _fs, c, d, e); } while (0) +#define fmt_printd_6(to, fmt, c, d, e, f) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 4, fmt, _fc(c), _fc(d), _fc(e), _fc(f)); \ + fmt_OK(_n == 4); _fmt_fn(to)(to, _fs, c, d, e, f); } while (0) +#define fmt_printd_7(to, fmt, c, d, e, f, g) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 5, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g)); \ + fmt_OK(_n == 5); _fmt_fn(to)(to, _fs, c, d, e, f, g); } while (0) +#define fmt_printd_8(to, fmt, c, d, e, f, g, h) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 6, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h)); \ + fmt_OK(_n == 6); _fmt_fn(to)(to, _fs, c, d, e, f, g, h); } while (0) +#define fmt_printd_9(to, fmt, c, d, e, f, g, h, i) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 7, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), _fc(i)); \ + fmt_OK(_n == 7); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i); } while (0) +#define fmt_printd_10(to, fmt, c, d, e, f, g, h, i, j) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 8, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ + _fc(i), _fc(j)); \ + fmt_OK(_n == 8); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j); } while (0) +#define fmt_printd_11(to, fmt, c, d, e, f, g, h, i, j, k) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 9, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ + _fc(i), _fc(j), _fc(k)); \ + fmt_OK(_n == 9); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k); } while (0) +#define fmt_printd_12(to, fmt, c, d, e, f, g, h, i, j, k, m) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 10, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ + _fc(i), _fc(j), _fc(k), _fc(m)); \ + fmt_OK(_n == 10); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m); } while (0) +#define fmt_printd_13(to, fmt, c, d, e, f, g, h, i, j, k, m, n) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 11, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ + _fc(i), _fc(j), _fc(k), _fc(m), _fc(n)); \ + fmt_OK(_n == 11); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m, n); } while (0) +#define fmt_printd_14(to, fmt, c, d, e, f, g, h, i, j, k, m, n, o) \ + do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 12, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \ + _fc(i), _fc(j), _fc(k), _fc(m), _fc(n), _fc(o)); \ + fmt_OK(_n == 12); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m, n, o); } while (0) + +#define _fmt_fn(x) _Generic ((x), \ + FILE*: fprintf, \ + char*: sprintf, \ + fmt_stream*: _fmt_sprint) + +#if defined(_MSC_VER) && !defined(__clang__) + #define _signed_char_hhd +#else + #define _signed_char_hhd signed char: "c", +#endif +#ifdef __GNUC__ + #define FMT_UNUSED __attribute__((unused)) +#else + #define FMT_UNUSED +#endif + +#define _fc(x) _Generic (x, \ + _Bool: "d", \ + unsigned char: "hhu", \ + _signed_char_hhd \ + char: "c", \ + short: "hd", \ + unsigned short: "hu", \ + int: "d", \ + unsigned: "u", \ + long: "ld", \ + unsigned long: "lu", \ + long long: "lld", \ + unsigned long long: "llu", \ + float: "g", \ + double: "@g", \ + long double: "@Lg", \ + char*: "s", \ + wchar_t*: "ls", \ + void*: "p", \ + const char*: "s", \ + const wchar_t*: "ls", \ + const void*: "p") + +#if defined FMT_DEF + +#include +#include +#include +#include + +FMT_DEF FMT_UNUSED void fmt_close(fmt_stream* ss) { + free(ss->data); +} + +FMT_DEF FMT_UNUSED +const char* fmt_time(const char *fmt, const struct tm* tm, char* buf, int len) { + strftime(buf, (size_t)len, fmt, tm); + return buf; +} + +FMT_DEF void _fmt_sprint(fmt_stream* ss, const char* fmt, ...) { + va_list args, args2; + va_start(args, fmt); + if (ss == NULL) { + vprintf(fmt, args); putchar('\n'); + goto done1; + } + va_copy(args2, args); + const int n = vsnprintf(NULL, 0U, fmt, args); + if (n < 0) goto done2; + const intptr_t pos = ss->overwrite ? 0 : ss->len; + ss->len = pos + n; + if (ss->len > ss->cap) { + ss->cap = ss->len + ss->cap/2; + ss->data = (char*)realloc(ss->data, (size_t)ss->cap + 1U); + } + vsnprintf(ss->data + pos, (size_t)n+1, fmt, args2); + done2: va_end(args2); + done1: va_end(args); +} + +FMT_DEF int _fmt_parse(char* p, int nargs, const char *fmt, ...) { + char *arg, *p0, ch; + int n = 0, empty; + va_list args; + va_start(args, fmt); + do { + switch ((ch = *fmt)) { + case '%': + *p++ = '%'; + break; + case '}': + if (*++fmt == '}') break; /* ok */ + n = 99; + continue; + case '{': + if (*++fmt == '{') break; /* ok */ + if (++n > nargs) continue; + if (*fmt != ':' && *fmt != '}') n = 99; + fmt += (*fmt == ':'); + empty = *fmt == '}'; + arg = va_arg(args, char *); + *p++ = '%', p0 = p; + while (1) switch (*fmt) { + case '\0': n = 99; /* FALLTHRU */ + case '}': goto done; + case '<': *p++ = '-', ++fmt; break; + case '>': p0 = NULL; /* FALLTHRU */ + case '-': ++fmt; break; + case '*': if (++n <= nargs) arg = va_arg(args, char *); /* FALLTHRU */ + default: *p++ = *fmt++; + } + done: + switch (*arg) { + case 'g': if (empty) memcpy(p, ".8", 2), p += 2; break; + case '@': ++arg; if (empty) memcpy(p, ".16", 3), p += 3; break; + } + if (strchr("csdioxXufFeEaAgGnp", fmt[-1]) == NULL) + while (*arg) *p++ = *arg++; + if (p0 && (p[-1] == 's' || p[-1] == 'c')) /* left-align str */ + memmove(p0 + 1, p0, (size_t)(p++ - p0)), *p0 = '-'; + fmt += *fmt == '}'; + continue; + } + *p++ = *fmt++; + } while (ch); + va_end(args); + return n; +} +#endif +#endif +#undef i_implement +#undef i_static diff --git a/include/stc/sys/sumtype.h b/include/stc/sys/sumtype.h index 3a190687..1b7d12a0 100644 --- a/include/stc/sys/sumtype.h +++ b/include/stc/sys/sumtype.h @@ -1,161 +1,161 @@ -/* MIT License - * - * Copyright (c) 2024 Tyge Løvset - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -/* -// https://stackoverflow.com/questions/70935435/how-to-create-variants-in-rust -#include -#include "stc/cstr.h" -#include "stc/algorithm.h" - -c_sumtype (Action, - (ActionSpeak, cstr), - (ActionQuit, _Bool), - (ActionRunFunc, struct { - int32 (*func)(int32, int32); - int32 v1, v2; - }) -); - -void Action_drop(Action* self) { - c_when (self) { - c_is(ActionSpeak, x) cstr_drop(x); - } -} - -void action(Action* action) { - c_when (action) { - c_is(ActionSpeak, s) { - printf("Asked to speak: %s\n", cstr_str(s)); - } - c_is(ActionQuit) { - printf("Asked to quit!\n"); - } - c_is(ActionRunFunc, r) { - int32 res = r->func(r->v1, r->v2); - printf("v1: %d, v2: %d, res: %d\n", r->v1, r->v2, res); - } - c_otherwise assert(!"no match"); - } -} - -int32 add(int32 a, int32 b) { - return a + b; -} - -int main(void) { - Action act1 = c_variant(ActionSpeak, cstr_from("Hello")); - Action act2 = c_variant(ActionQuit, 1); - Action act3 = c_variant(ActionRunFunc, {add, 5, 6}); - - action(&act1); - action(&act2); - action(&act3); - - c_drop(Action, &act1, &act2, &act3); -} -*/ -#ifndef STC_SUMTYPE_H_INCLUDED -#define STC_SUMTYPE_H_INCLUDED - -#include "../common.h" - -#define _c_EMPTY() -#define _c_LOOP_INDIRECTION() c_LOOP -#define _c_LOOP_END_1 ,_c_LOOP1 -#define _c_LOOP0(f,T,x,...) f c_EXPAND((T, c_EXPAND x)) _c_LOOP_INDIRECTION _c_EMPTY()()(f,T,__VA_ARGS__) -#define _c_LOOP1(...) -#define _c_TUPLE_AT_1(x,y,...) y -#define _c_CHECK(x,...) _c_TUPLE_AT_1 c_EXPAND((__VA_ARGS__,x,)) -#define _c_E0(...) __VA_ARGS__ -#define _c_E1(...) _c_E0(_c_E0(_c_E0(_c_E0(_c_E0(__VA_ARGS__))))) -#define _c_E2(...) _c_E1(_c_E1(_c_E1(_c_E1(_c_E1(__VA_ARGS__))))) -#define c_EVAL(...) _c_E2(_c_E2(_c_E2(_c_E2(__VA_ARGS__)))) -#define c_LOOP(f,T,x,...) _c_CHECK(_c_LOOP0, c_JOIN(_c_LOOP_END_, c_NUMARGS(c_EXPAND x)))(f,T,x,__VA_ARGS__) - - -#define _c_enum_1(x,...) (x=1, __VA_ARGS__) -#define _c_vartuple_tag(T, Tag, ...) Tag, -#define _c_vartuple_type(T, Tag, ...) typedef __VA_ARGS__ Tag##_type; typedef T Tag##_sumtype; -#define _c_vartuple_var(T, Tag, ...) struct { enum enum_##T tag; Tag##_type var; } Tag; - -#define c_sumtype(T, ...) \ - typedef union T T; \ - enum enum_##T { c_EVAL(c_LOOP(_c_vartuple_tag, T, c_EXPAND(_c_enum_1 __VA_ARGS__), (0),)) }; \ - c_EVAL(c_LOOP(_c_vartuple_type, T, __VA_ARGS__, (0),)) \ - union T { \ - struct { enum enum_##T tag; } _any_; \ - c_EVAL(c_LOOP(_c_vartuple_var, T, __VA_ARGS__, (0),)) \ - } - -#if defined __GNUC__ || defined __clang__ || defined __TINYC__ || _MSC_VER >= 1939 - #define c_when(var) \ - for (__typeof__(var) _match = (var); _match; _match = NULL) \ - switch (_match->_any_.tag) - - #define c_is_2(Tag, x) \ - break; case Tag: \ - for (__typeof__(_match->Tag.var)* x = &_match->Tag.var; x; x = NULL) - - #define c_if_is(v, Tag, x) \ - for (__typeof__(v) _var = (v); _var; _var = NULL) \ - if (c_holds(_var, Tag)) \ - for (__typeof__(_var->Tag.var) *x = &_var->Tag.var; x; x = NULL) -#else - typedef union { struct { int tag; } _any_; } _c_any_variant; - #define c_when(var) \ - for (_c_any_variant* _match = (_c_any_variant *)(var) + 0*sizeof((var)->_any_.tag); \ - _match; _match = NULL) \ - switch (_match->_any_.tag) - - #define c_is_2(Tag, x) \ - break; case Tag: \ - for (Tag##_type *x = &((Tag##_sumtype *)_match)->Tag.var; x; x = NULL) - - #define c_if_is(v, Tag, x) \ - for (Tag##_sumtype* _var = c_const_cast(Tag##_sumtype*, v); _var; _var = NULL) \ - if (c_holds(_var, Tag)) \ - for (Tag##_type *x = &_var->Tag.var; x; x = NULL) -#endif - -#define c_is(...) c_MACRO_OVERLOAD(c_is, __VA_ARGS__) -#define c_is_1(Tag) \ - break; case Tag: - -#define c_or_is(Tag) \ - ; case Tag: - -#define c_otherwise \ - break; default: - -#define c_variant(Tag, ...) \ - (c_literal(Tag##_sumtype){.Tag={.tag=Tag, .var=__VA_ARGS__}}) - -#define c_get(Tag, var_ptr) \ - (c_assert((var_ptr)->Tag.tag == Tag), &(var_ptr)->Tag.var) - -#define c_tag_index(var_ptr) \ - ((var_ptr)->_any_.tag) - -#define c_holds(var_ptr, Tag) \ - (c_tag_index(var_ptr) == Tag) - -#endif // STC_SUMTYPE_H_INCLUDED +/* MIT License + * + * Copyright (c) 2024 Tyge Løvset + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/* +// https://stackoverflow.com/questions/70935435/how-to-create-variants-in-rust +#include +#include "stc/cstr.h" +#include "stc/algorithm.h" + +c_sumtype (Action, + (ActionSpeak, cstr), + (ActionQuit, _Bool), + (ActionRunFunc, struct { + int32 (*func)(int32, int32); + int32 v1, v2; + }) +); + +void Action_drop(Action* self) { + c_when (self) { + c_is(ActionSpeak, x) cstr_drop(x); + } +} + +void action(Action* action) { + c_when (action) { + c_is(ActionSpeak, s) { + printf("Asked to speak: %s\n", cstr_str(s)); + } + c_is(ActionQuit) { + printf("Asked to quit!\n"); + } + c_is(ActionRunFunc, r) { + int32 res = r->func(r->v1, r->v2); + printf("v1: %d, v2: %d, res: %d\n", r->v1, r->v2, res); + } + c_otherwise assert(!"no match"); + } +} + +int32 add(int32 a, int32 b) { + return a + b; +} + +int main(void) { + Action act1 = c_variant(ActionSpeak, cstr_from("Hello")); + Action act2 = c_variant(ActionQuit, 1); + Action act3 = c_variant(ActionRunFunc, {add, 5, 6}); + + action(&act1); + action(&act2); + action(&act3); + + c_drop(Action, &act1, &act2, &act3); +} +*/ +#ifndef STC_SUMTYPE_H_INCLUDED +#define STC_SUMTYPE_H_INCLUDED + +#include "../common.h" + +#define _c_EMPTY() +#define _c_LOOP_INDIRECTION() c_LOOP +#define _c_LOOP_END_1 ,_c_LOOP1 +#define _c_LOOP0(f,T,x,...) f c_EXPAND((T, c_EXPAND x)) _c_LOOP_INDIRECTION _c_EMPTY()()(f,T,__VA_ARGS__) +#define _c_LOOP1(...) +#define _c_TUPLE_AT_1(x,y,...) y +#define _c_CHECK(x,...) _c_TUPLE_AT_1 c_EXPAND((__VA_ARGS__,x,)) +#define _c_E0(...) __VA_ARGS__ +#define _c_E1(...) _c_E0(_c_E0(_c_E0(_c_E0(_c_E0(__VA_ARGS__))))) +#define _c_E2(...) _c_E1(_c_E1(_c_E1(_c_E1(_c_E1(__VA_ARGS__))))) +#define c_EVAL(...) _c_E2(_c_E2(_c_E2(_c_E2(__VA_ARGS__)))) +#define c_LOOP(f,T,x,...) _c_CHECK(_c_LOOP0, c_JOIN(_c_LOOP_END_, c_NUMARGS(c_EXPAND x)))(f,T,x,__VA_ARGS__) + + +#define _c_enum_1(x,...) (x=1, __VA_ARGS__) +#define _c_vartuple_tag(T, Tag, ...) Tag, +#define _c_vartuple_type(T, Tag, ...) typedef __VA_ARGS__ Tag##_type; typedef T Tag##_sumtype; +#define _c_vartuple_var(T, Tag, ...) struct { enum enum_##T tag; Tag##_type var; } Tag; + +#define c_sumtype(T, ...) \ + typedef union T T; \ + enum enum_##T { c_EVAL(c_LOOP(_c_vartuple_tag, T, c_EXPAND(_c_enum_1 __VA_ARGS__), (0),)) }; \ + c_EVAL(c_LOOP(_c_vartuple_type, T, __VA_ARGS__, (0),)) \ + union T { \ + struct { enum enum_##T tag; } _any_; \ + c_EVAL(c_LOOP(_c_vartuple_var, T, __VA_ARGS__, (0),)) \ + } + +#if defined __GNUC__ || defined __clang__ || defined __TINYC__ || _MSC_VER >= 1939 + #define c_when(var) \ + for (__typeof__(var) _match = (var); _match; _match = NULL) \ + switch (_match->_any_.tag) + + #define c_is_2(Tag, x) \ + break; case Tag: \ + for (__typeof__(_match->Tag.var)* x = &_match->Tag.var; x; x = NULL) + + #define c_if_is(v, Tag, x) \ + for (__typeof__(v) _var = (v); _var; _var = NULL) \ + if (c_holds(_var, Tag)) \ + for (__typeof__(_var->Tag.var) *x = &_var->Tag.var; x; x = NULL) +#else + typedef union { struct { int tag; } _any_; } _c_any_variant; + #define c_when(var) \ + for (_c_any_variant* _match = (_c_any_variant *)(var) + 0*sizeof((var)->_any_.tag); \ + _match; _match = NULL) \ + switch (_match->_any_.tag) + + #define c_is_2(Tag, x) \ + break; case Tag: \ + for (Tag##_type *x = &((Tag##_sumtype *)_match)->Tag.var; x; x = NULL) + + #define c_if_is(v, Tag, x) \ + for (Tag##_sumtype* _var = c_const_cast(Tag##_sumtype*, v); _var; _var = NULL) \ + if (c_holds(_var, Tag)) \ + for (Tag##_type *x = &_var->Tag.var; x; x = NULL) +#endif + +#define c_is(...) c_MACRO_OVERLOAD(c_is, __VA_ARGS__) +#define c_is_1(Tag) \ + break; case Tag: + +#define c_or_is(Tag) \ + ; case Tag: + +#define c_otherwise \ + break; default: + +#define c_variant(Tag, ...) \ + (c_literal(Tag##_sumtype){.Tag={.tag=Tag, .var=__VA_ARGS__}}) + +#define c_get(Tag, var_ptr) \ + (c_assert((var_ptr)->Tag.tag == Tag), &(var_ptr)->Tag.var) + +#define c_tag_index(var_ptr) \ + ((var_ptr)->_any_.tag) + +#define c_holds(var_ptr, Tag) \ + (c_tag_index(var_ptr) == Tag) + +#endif // STC_SUMTYPE_H_INCLUDED diff --git a/tests/algorithm_test.c b/tests/algorithm_test.c index bc58be61..3397a0fc 100644 --- a/tests/algorithm_test.c +++ b/tests/algorithm_test.c @@ -1,93 +1,93 @@ -// https://mariusbancila.ro/blog/2019/01/20/cpp-code-samples-before-and-after-ranges/ - -#define i_type IVec,int -#include "stc/stack.h" - -#include "stc/algorithm.h" -#include "stc/cstr.h" -#include "stc/cspan.h" -using_cspan(ISpan, int); - -#include "ctest.h" - - -static cstr to_roman(int value) -{ - cstr result = {0}; - struct roman { int d; const char* r; }; - - c_foritems (i, struct roman, { - {1000, "M"}, {900, "CM"}, - {500, "D"}, {400, "CD"}, - {100, "C"}, {90, "XC"}, - {50, "L"}, {40, "XL"}, - {10, "X"}, {9, "IX"}, - {5, "V"}, {4, "IV"}, - {1, "I"} - }){ - while (value >= i.ref->d) { - cstr_append(&result, i.ref->r); - value -= i.ref->d; - } - } - return result; -} - -TEST(algorithm, cstr_append) -{ - c_with (cstr s = to_roman(2024), cstr_drop(&s)) - { - EXPECT_STREQ("MMXXIV", cstr_str(&s)); - } -} - - -TEST(algorithm, c_find_if) -{ - ISpan nums = c_make(ISpan, {1,1,2,3,5,8,13,21,34}); - - ISpan_iter it; - c_find_if(ISpan, nums, &it, *value == 13); - - EXPECT_EQ(13, *it.ref); -} - - -TEST(algorithm, c_filter) -{ - IVec vec = c_make(IVec, {1,1,2,3,5,8,13,21,34,10}); - #define f_is_even(v) ((v & 1) == 0) - #define f_is_odd(v) ((v & 1) == 1) - - isize sum = 0; - c_filter(IVec, vec - , c_flt_skipwhile(f_is_odd(*value)) - && c_flt_skip(1) - && f_is_even(*value) - && c_flt_take(2) - && (sum += *value)); - - EXPECT_EQ(42, sum); - - uint64_t hash = 0; - c_filter(IVec, vec, hash = c_hash_mix(hash, (uint64_t)*value)); - EXPECT_EQ(658, (isize)hash); - - hash = 0; - c_filter_reverse(IVec, vec, hash = c_hash_mix(hash, (uint64_t)*value)); - EXPECT_EQ(10897, (isize)hash); - - sum = 0; - c_filter(IVec, vec, sum += *value); - EXPECT_EQ(98, sum); - - sum = 0; - c_filter_reverse(IVec, vec, sum += *value); - EXPECT_EQ(98, sum); - - sum = 0; - c_filter_zip(IVec, vec, crange, c_iota(-6), sum += *value1 * *value2); - EXPECT_EQ(73, sum); - - IVec_drop(&vec); -} +// https://mariusbancila.ro/blog/2019/01/20/cpp-code-samples-before-and-after-ranges/ + +#define i_type IVec,int +#include "stc/stack.h" + +#include "stc/algorithm.h" +#include "stc/cstr.h" +#include "stc/cspan.h" +using_cspan(ISpan, int); + +#include "ctest.h" + + +static cstr to_roman(int value) +{ + cstr result = {0}; + struct roman { int d; const char* r; }; + + c_foritems (i, struct roman, { + {1000, "M"}, {900, "CM"}, + {500, "D"}, {400, "CD"}, + {100, "C"}, {90, "XC"}, + {50, "L"}, {40, "XL"}, + {10, "X"}, {9, "IX"}, + {5, "V"}, {4, "IV"}, + {1, "I"} + }){ + while (value >= i.ref->d) { + cstr_append(&result, i.ref->r); + value -= i.ref->d; + } + } + return result; +} + +TEST(algorithm, cstr_append) +{ + c_with (cstr s = to_roman(2024), cstr_drop(&s)) + { + EXPECT_STREQ("MMXXIV", cstr_str(&s)); + } +} + + +TEST(algorithm, c_find_if) +{ + ISpan nums = c_make(ISpan, {1,1,2,3,5,8,13,21,34}); + + ISpan_iter it; + c_find_if(ISpan, nums, &it, *value == 13); + + EXPECT_EQ(13, *it.ref); +} + + +TEST(algorithm, c_filter) +{ + IVec vec = c_make(IVec, {1,1,2,3,5,8,13,21,34,10}); + #define f_is_even(v) ((v & 1) == 0) + #define f_is_odd(v) ((v & 1) == 1) + + isize sum = 0; + c_filter(IVec, vec + , c_flt_skipwhile(f_is_odd(*value)) + && c_flt_skip(1) + && f_is_even(*value) + && c_flt_take(2) + && (sum += *value)); + + EXPECT_EQ(42, sum); + + uint64_t hash = 0; + c_filter(IVec, vec, hash = c_hash_mix(hash, (uint64_t)*value)); + EXPECT_EQ(658, (isize)hash); + + hash = 0; + c_filter_reverse(IVec, vec, hash = c_hash_mix(hash, (uint64_t)*value)); + EXPECT_EQ(10897, (isize)hash); + + sum = 0; + c_filter(IVec, vec, sum += *value); + EXPECT_EQ(98, sum); + + sum = 0; + c_filter_reverse(IVec, vec, sum += *value); + EXPECT_EQ(98, sum); + + sum = 0; + c_filter_zip(IVec, vec, crange, c_iota(-6), sum += *value1 * *value2); + EXPECT_EQ(73, sum); + + IVec_drop(&vec); +}