diff options
| author | Tyge Lovset <[email protected]> | 2022-12-20 23:31:51 +0100 |
|---|---|---|
| committer | Tyge Lovset <[email protected]> | 2022-12-20 23:31:51 +0100 |
| commit | 5f57d597cd27aef55adbcb3b452973b0c6e33667 (patch) | |
| tree | dfd59c2fd0e36a6ef37912a9d0cc5a65970f1524 /misc/include | |
| parent | 1763be8c8cbbc0896477fcf924edd4180d1345a9 (diff) | |
| download | STC-modified-5f57d597cd27aef55adbcb3b452973b0c6e33667.tar.gz STC-modified-5f57d597cd27aef55adbcb3b452973b0c6e33667.zip | |
Restructured folders: examples, benchmarks, tests into misc folder.
Diffstat (limited to 'misc/include')
| -rw-r--r-- | misc/include/alt/csmap.h | 512 | ||||
| -rw-r--r-- | misc/include/alt/cstr.h | 385 | ||||
| -rw-r--r-- | misc/include/fmt.h | 253 | ||||
| -rw-r--r-- | misc/include/stctest.h | 203 |
4 files changed, 1353 insertions, 0 deletions
diff --git a/misc/include/alt/csmap.h b/misc/include/alt/csmap.h new file mode 100644 index 00000000..4b4e764c --- /dev/null +++ b/misc/include/alt/csmap.h @@ -0,0 +1,512 @@ +/* MIT License + * + * Copyright (c) 2022 Tyge Løvset, NORCE, www.norceresearch.no + * + * 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. + */ + +// Sorted/Ordered set and map - implemented as an AA-tree. +/* +#include <stdio.h> +#include <stc/cstr.h> + +#define i_tag sx // Sorted map<cstr, double> +#define i_key_str +#define i_val double +#include <stc/csmap.h> + +int main(void) { + c_with (csmap_sx m = csmap_sx_init(), csmap_sx_drop(&m)) + { + csmap_sx_emplace(&m, "Testing one", 1.234); + csmap_sx_emplace(&m, "Testing two", 12.34); + csmap_sx_emplace(&m, "Testing three", 123.4); + + csmap_sx_value *v = csmap_sx_get(&m, "Testing five"); // NULL + double num = *csmap_sx_at(&m, "Testing one"); + csmap_sx_emplace_or_assign(&m, "Testing three", 1000.0); // update + csmap_sx_erase(&m, "Testing two"); + + c_foreach (i, csmap_sx, m) + printf("map %s: %g\n", cstr_str(&i.ref->first), i.ref->second); + } +} +*/ +#include <stc/ccommon.h> + +#ifndef CSMAP_H_INCLUDED +#define STC_CSMAP_V1 1 +#include <stc/forward.h> +#include <stdlib.h> +#include <string.h> +#endif // CSMAP_H_INCLUDED + +#ifndef _i_prefix +#define _i_prefix csmap_ +#endif +#ifdef _i_isset + #define _i_MAP_ONLY c_false + #define _i_SET_ONLY c_true + #define _i_keyref(vp) (vp) +#else + #define _i_ismap + #define _i_MAP_ONLY c_true + #define _i_SET_ONLY c_false + #define _i_keyref(vp) (&(vp)->first) +#endif +#include <stc/priv/template.h> + +#if !c_option(c_is_forward) +_cx_deftypes(_c_aatree_types, _cx_self, i_key, i_val, i_size, _i_MAP_ONLY, _i_SET_ONLY); +#endif + +_i_MAP_ONLY( struct _cx_value { + _cx_key first; + _cx_mapped second; +}; ) +struct _cx_node { + struct _cx_node *link[2]; + uint8_t level; + _cx_value value; +}; + +typedef i_keyraw _cx_rawkey; +typedef i_valraw _cx_memb(_rawmapped); +typedef _i_SET_ONLY( i_keyraw ) + _i_MAP_ONLY( struct { i_keyraw first; i_valraw second; } ) + _cx_raw; + +#if !defined i_no_clone +STC_API _cx_self _cx_memb(_clone)(_cx_self cx); +#if !defined i_no_emplace +STC_API _cx_result _cx_memb(_emplace)(_cx_self* self, i_keyraw rkey _i_MAP_ONLY(, i_valraw rmapped)); +#endif // !i_no_emplace +#endif // !i_no_clone +STC_API _cx_self _cx_memb(_init)(void); +STC_API _cx_result _cx_memb(_insert)(_cx_self* self, i_key key _i_MAP_ONLY(, i_val mapped)); +STC_API _cx_result _cx_memb(_push)(_cx_self* self, _cx_value _val); +STC_API void _cx_memb(_drop)(_cx_self* self); +STC_API _cx_value* _cx_memb(_find_it)(const _cx_self* self, i_keyraw rkey, _cx_iter* out); +STC_API _cx_iter _cx_memb(_lower_bound)(const _cx_self* self, i_keyraw rkey); +STC_API _cx_value* _cx_memb(_front)(const _cx_self* self); +STC_API _cx_value* _cx_memb(_back)(const _cx_self* self); +STC_API int _cx_memb(_erase)(_cx_self* self, i_keyraw rkey); +STC_API _cx_iter _cx_memb(_erase_at)(_cx_self* self, _cx_iter it); +STC_API _cx_iter _cx_memb(_erase_range)(_cx_self* self, _cx_iter it1, _cx_iter it2); +STC_API void _cx_memb(_next)(_cx_iter* it); + +STC_INLINE bool _cx_memb(_empty)(_cx_self cx) { return cx.size == 0; } +STC_INLINE size_t _cx_memb(_size)(_cx_self cx) { return cx.size; } +STC_INLINE void _cx_memb(_swap)(_cx_self* a, _cx_self* b) { c_swap(_cx_self, *a, *b); } +STC_INLINE _cx_iter _cx_memb(_find)(const _cx_self* self, i_keyraw rkey) + { _cx_iter it; _cx_memb(_find_it)(self, rkey, &it); return it; } +STC_INLINE bool _cx_memb(_contains)(const _cx_self* self, i_keyraw rkey) + { _cx_iter it; return _cx_memb(_find_it)(self, rkey, &it) != NULL; } +STC_INLINE const _cx_value* _cx_memb(_get)(const _cx_self* self, i_keyraw rkey) + { _cx_iter it; return _cx_memb(_find_it)(self, rkey, &it); } +STC_INLINE _cx_value* _cx_memb(_get_mut)(_cx_self* self, i_keyraw rkey) + { _cx_iter it; return _cx_memb(_find_it)(self, rkey, &it); } + +STC_INLINE void +_cx_memb(_clear)(_cx_self* self) + { _cx_memb(_drop)(self); *self = _cx_memb(_init)(); } + +STC_INLINE _cx_raw +_cx_memb(_value_toraw)(_cx_value* val) { + return _i_SET_ONLY( i_keyto(val) ) + _i_MAP_ONLY( c_INIT(_cx_raw){i_keyto((&val->first)), + i_valto((&val->second))} ); +} + +STC_INLINE int +_cx_memb(_value_cmp)(const _cx_value* x, const _cx_value* y) { + _cx_rawkey rx = i_keyto(_i_keyref(x)), ry = i_keyto(_i_keyref(y)); + return i_cmp((&rx), (&ry)); +} + +STC_INLINE void +_cx_memb(_value_drop)(_cx_value* val) { + i_keydrop(_i_keyref(val)); + _i_MAP_ONLY( i_valdrop((&val->second)); ) +} + +#if !defined i_no_clone +STC_INLINE _cx_value +_cx_memb(_value_clone)(_cx_value _val) { + *_i_keyref(&_val) = i_keyclone((*_i_keyref(&_val))); + _i_MAP_ONLY( _val.second = i_valclone(_val.second); ) + return _val; +} + +STC_INLINE void +_cx_memb(_copy)(_cx_self *self, const _cx_self* other) { + if (self->root == other->root) + return; + _cx_memb(_drop)(self); + *self = _cx_memb(_clone)(*other); +} +#endif // !i_no_clone + +#ifndef _i_isset + #if !defined i_no_clone && !defined i_no_emplace + STC_API _cx_result _cx_memb(_emplace_or_assign)(_cx_self* self, i_keyraw rkey, i_valraw rmapped); + #endif + STC_API _cx_result _cx_memb(_insert_or_assign)(_cx_self* self, i_key key, i_val mapped); + + STC_INLINE const _cx_mapped* + _cx_memb(_at)(const _cx_self* self, i_keyraw rkey) + { _cx_iter it; return &_cx_memb(_find_it)(self, rkey, &it)->second; } + STC_INLINE _cx_mapped* + _cx_memb(_at_mut)(_cx_self* self, i_keyraw rkey) + { _cx_iter it; return &_cx_memb(_find_it)(self, rkey, &it)->second; } +#endif // !_i_isset + +STC_INLINE _cx_iter +_cx_memb(_begin)(const _cx_self* self) { + _cx_iter it; + it.ref = NULL, it._top = 0, it._tn = self->root; + _cx_memb(_next)(&it); + return it; +} + +STC_INLINE _cx_iter +_cx_memb(_end)(const _cx_self* self) { + (void)self; + _cx_iter it; it.ref = NULL, it._top = 0, it._tn = NULL; + return it; +} + +STC_INLINE _cx_iter +_cx_memb(_advance)(_cx_iter it, size_t n) { + while (n-- && it.ref) + _cx_memb(_next)(&it); + return it; +} + +/* -------------------------- IMPLEMENTATION ------------------------- */ +#if defined(i_implement) + +#ifndef CSMAP_H_INCLUDED +static struct { void *link[2]; uint8_t level; } +_csmap_sentinel = {{&_csmap_sentinel, &_csmap_sentinel}, 0}; +#endif + +static _cx_result _cx_memb(_insert_entry_)(_cx_self* self, i_keyraw rkey); + +STC_DEF _cx_self +_cx_memb(_init)(void) { + _cx_self cx = {(_cx_node *)&_csmap_sentinel, 0}; + return cx; +} + +STC_DEF _cx_value* +_cx_memb(_front)(const _cx_self* self) { + _cx_node *tn = self->root; + while (tn->link[0]->level) + tn = tn->link[0]; + return &tn->value; +} + +STC_DEF _cx_value* +_cx_memb(_back)(const _cx_self* self) { + _cx_node *tn = self->root; + while (tn->link[1]->level) + tn = tn->link[1]; + return &tn->value; +} + +STC_DEF _cx_result +_cx_memb(_insert)(_cx_self* self, i_key key _i_MAP_ONLY(, i_val mapped)) { + _cx_result res = _cx_memb(_insert_entry_)(self, i_keyto((&key))); + if (res.inserted) + { *_i_keyref(res.ref) = key; _i_MAP_ONLY( res.ref->second = mapped; )} + else + { i_keydrop((&key)); _i_MAP_ONLY( i_valdrop((&mapped)); )} + return res; +} + +STC_DEF _cx_result +_cx_memb(_push)(_cx_self* self, _cx_value _val) { + _cx_result _res = _cx_memb(_insert_entry_)(self, i_keyto(_i_keyref(&_val))); + if (_res.inserted) + *_res.ref = _val; + else + _cx_memb(_value_drop)(&_val); + return _res; +} + +#ifndef _i_isset + STC_DEF _cx_result + _cx_memb(_insert_or_assign)(_cx_self* self, i_key key, i_val mapped) { + _cx_result res = _cx_memb(_insert_entry_)(self, i_keyto((&key))); + if (res.inserted) + res.ref->first = key; + else + { i_keydrop((&key)); i_valdrop((&res.ref->second)); } + res.ref->second = mapped; + return res; + } + #if !defined i_no_clone && !defined i_no_emplace + STC_DEF _cx_result + _cx_memb(_emplace_or_assign)(_cx_self* self, i_keyraw rkey, i_valraw rmapped) { + _cx_result res = _cx_memb(_insert_entry_)(self, rkey); + if (res.inserted) + res.ref->first = i_keyfrom(rkey); + else + { i_valdrop((&res.ref->second)); } + res.ref->second = i_valfrom(rmapped); + return res; + } + #endif // !i_no_clone && !i_no_emplace +#endif // !_i_isset + +STC_DEF _cx_value* +_cx_memb(_find_it)(const _cx_self* self, _cx_rawkey rkey, _cx_iter* out) { + _cx_node *tn = self->root; + out->_top = 0; + while (tn->level) { + int c; _cx_rawkey raw = i_keyto(_i_keyref(&tn->value)); + if ((c = i_cmp((&raw), (&rkey))) < 0) + tn = tn->link[1]; + else if (c > 0) + { out->_st[out->_top++] = tn; tn = tn->link[0]; } + else + { out->_tn = tn->link[1]; return (out->ref = &tn->value); } + } + return (out->ref = NULL); +} + +STC_DEF _cx_iter +_cx_memb(_lower_bound)(const _cx_self* self, i_keyraw rkey) { + _cx_iter it; + _cx_memb(_find_it)(self, rkey, &it); + if (!it.ref && it._top) { + _cx_node *tn = it._st[--it._top]; + it._tn = tn->link[1]; + it.ref = &tn->value; + } + return it; +} + +STC_DEF void +_cx_memb(_next)(_cx_iter *it) { + _cx_node *tn = it->_tn; + if (it->_top || tn->level) { + while (tn->level) { + it->_st[it->_top++] = tn; + tn = tn->link[0]; + } + tn = it->_st[--it->_top]; + it->_tn = tn->link[1]; + it->ref = &tn->value; + } else + it->ref = NULL; +} + +static _cx_node * +_cx_memb(_skew_)(_cx_node *tn) { + if (tn && tn->link[0]->level == tn->level && tn->level) { + _cx_node *tmp = tn->link[0]; + tn->link[0] = tmp->link[1]; + tmp->link[1] = tn; + tn = tmp; + } + return tn; +} + +static _cx_node * +_cx_memb(_split_)(_cx_node *tn) { + if (tn->link[1]->link[1]->level == tn->level && tn->level) { + _cx_node *tmp = tn->link[1]; + tn->link[1] = tmp->link[0]; + tmp->link[0] = tn; + tn = tmp; + ++tn->level; + } + return tn; +} + +static _cx_node* +_cx_memb(_insert_entry_i_)(_cx_node* tn, const _cx_rawkey* rkey, _cx_result* res) { + _cx_node *up[64], *tx = tn; + int c, top = 0, dir = 0; + while (tx->level) { + up[top++] = tx; + _cx_rawkey r = i_keyto(_i_keyref(&tx->value)); + if (!(c = (i_cmp((&r), rkey)))) + { res->ref = &tx->value; return tn; } + dir = (c < 0); + tx = tx->link[dir]; + } + tn = c_alloc(_cx_node); + tn->link[0] = tn->link[1] = (_cx_node*)&_csmap_sentinel; + tn->level = 1; + res->ref = &tn->value, res->inserted = true; + if (top == 0) + return tn; + up[top - 1]->link[dir] = tn; + while (top--) { + if (top) + dir = (up[top - 1]->link[1] == up[top]); + up[top] = _cx_memb(_skew_)(up[top]); + up[top] = _cx_memb(_split_)(up[top]); + if (top) + up[top - 1]->link[dir] = up[top]; + } + return up[0]; +} + +STC_DEF _cx_result +_cx_memb(_insert_entry_)(_cx_self* self, i_keyraw rkey) { + _cx_result res = {NULL}; + self->root = _cx_memb(_insert_entry_i_)(self->root, &rkey, &res); + self->size += res.inserted; + return res; +} + +static _cx_node* +_cx_memb(_erase_r_)(_cx_node *tn, const _cx_rawkey* rkey, int *erased) { + if (tn->level == 0) + return tn; + _cx_rawkey raw = i_keyto(_i_keyref(&tn->value)); + _cx_node *tx; int c = (i_cmp((&raw), rkey)); + if (c != 0) + tn->link[c < 0] = _cx_memb(_erase_r_)(tn->link[c < 0], rkey, erased); + else { + if (!*erased) + { _cx_memb(_value_drop)(&tn->value); *erased = 1; } + if (tn->link[0]->level && tn->link[1]->level) { + tx = tn->link[0]; + while (tx->link[1]->level) + tx = tx->link[1]; + tn->value = tx->value; + raw = i_keyto(_i_keyref(&tn->value)); + tn->link[0] = _cx_memb(_erase_r_)(tn->link[0], &raw, erased); + } else { /* unlink node */ + tx = tn; + tn = tn->link[tn->link[0]->level == 0]; + c_free(tx); + } + } + if (tn->link[0]->level < tn->level - 1 || tn->link[1]->level < tn->level - 1) { + if (tn->link[1]->level > --tn->level) + tn->link[1]->level = tn->level; + tn = _cx_memb(_skew_)(tn); + tx = tn->link[0] = _cx_memb(_skew_)(tn->link[0]); + tx->link[0] = _cx_memb(_skew_)(tx->link[0]); + tn = _cx_memb(_split_)(tn); + tn->link[0] = _cx_memb(_split_)(tn->link[0]); + } + return tn; +} + +STC_DEF int +_cx_memb(_erase)(_cx_self* self, i_keyraw rkey) { + int erased = 0; + self->root = _cx_memb(_erase_r_)(self->root, &rkey, &erased); + self->size -= erased; + return erased; +} + +STC_DEF _cx_iter +_cx_memb(_erase_at)(_cx_self* self, _cx_iter it) { + _cx_rawkey raw = i_keyto(_i_keyref(it.ref)), nxt; + _cx_memb(_next)(&it); + if (it.ref) + nxt = i_keyto(_i_keyref(it.ref)); + _cx_memb(_erase)(self, raw); + if (it.ref) + _cx_memb(_find_it)(self, nxt, &it); + return it; +} + +STC_DEF _cx_iter +_cx_memb(_erase_range)(_cx_self* self, _cx_iter it1, _cx_iter it2) { + if (!it2.ref) { + while (it1.ref) + it1 = _cx_memb(_erase_at)(self, it1); + return it1; + } + _cx_key k1 = *_i_keyref(it1.ref), k2 = *_i_keyref(it2.ref); + _cx_rawkey r1 = i_keyto((&k1)); + for (;;) { + if (memcmp(&k1, &k2, sizeof k1) == 0) + return it1; + _cx_memb(_next)(&it1); + k1 = *_i_keyref(it1.ref); + _cx_memb(_erase)(self, r1); + r1 = i_keyto((&k1)); + _cx_memb(_find_it)(self, r1, &it1); + } +} + +#if !defined i_no_clone +static _cx_node* +_cx_memb(_clone_r_)(_cx_node *tn) { + if (! tn->level) + return tn; + _cx_node *cn = c_alloc(_cx_node); + cn->level = tn->level; + cn->value = _cx_memb(_value_clone)(tn->value); + cn->link[0] = _cx_memb(_clone_r_)(tn->link[0]); + cn->link[1] = _cx_memb(_clone_r_)(tn->link[1]); + return cn; +} + +STC_DEF _cx_self +_cx_memb(_clone)(_cx_self cx) { + return c_INIT(_cx_self){_cx_memb(_clone_r_)(cx.root), cx.size}; +} +#endif // !i_no_clone + +#if !defined i_no_emplace +STC_DEF _cx_result +_cx_memb(_emplace)(_cx_self* self, i_keyraw rkey _i_MAP_ONLY(, i_valraw rmapped)) { + _cx_result res = _cx_memb(_insert_entry_)(self, rkey); + if (res.inserted) { + *_i_keyref(res.ref) = i_keyfrom(rkey); + _i_MAP_ONLY(res.ref->second = i_valfrom(rmapped);) + } + return res; +} +#endif // i_no_emplace + +static void +_cx_memb(_drop_r_)(_cx_node* tn) { + if (tn->level != 0) { + _cx_memb(_drop_r_)(tn->link[0]); + _cx_memb(_drop_r_)(tn->link[1]); + _cx_memb(_value_drop)(&tn->value); + c_free(tn); + } +} + +STC_DEF void +_cx_memb(_drop)(_cx_self* self) { + _cx_memb(_drop_r_)(self->root); +} + +#endif // i_implement +#undef _i_isset +#undef _i_ismap +#undef _i_keyref +#undef _i_MAP_ONLY +#undef _i_SET_ONLY +#define CSMAP_H_INCLUDED +#include <stc/priv/template.h> diff --git a/misc/include/alt/cstr.h b/misc/include/alt/cstr.h new file mode 100644 index 00000000..ecb9c9fb --- /dev/null +++ b/misc/include/alt/cstr.h @@ -0,0 +1,385 @@ +/* MIT License + * + * Copyright (c) 2022 Tyge Løvset, NORCE, www.norceresearch.no + * + * 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. + */ +#ifndef CSTR_H_INCLUDED +#define CSTR_H_INCLUDED + +#include <stc/ccommon.h> +#include <stc/forward.h> +#include <stdlib.h> /* malloc */ +#include <string.h> +#include <stdarg.h> +#include <stdio.h> /* vsnprintf */ +#include <ctype.h> + +#define c_unchecked_container_of(ptr, type, member) \ + ((type*)((char*)(ptr) - offsetof(type, member))) + +typedef char cstr_value; +typedef struct { cstr_value* str; } cstr; +typedef struct { size_t size, cap; char chr[1]; } cstr_priv; +#define _cstr_p(self) c_unchecked_container_of((self)->str, cstr_priv, chr) +#ifdef i_static + static cstr_priv _cstr_nullrep = {0, 0, {0}}; + static const cstr cstr_NULL = {_cstr_nullrep.chr}; +#else + extern const cstr cstr_NULL; +#endif +/* optimal memory: based on malloc_usable_size() sequence: 24, 40, 56, ... */ +#define _cstr_opt_mem(cap) ((((offsetof(cstr_priv, chr) + (cap) + 8)>>4)<<4) + 8) +/* optimal string capacity: 7, 23, 39, ... */ +#define _cstr_opt_cap(cap) (_cstr_opt_mem(cap) - offsetof(cstr_priv, chr) - 1) + +STC_API cstr cstr_from_n(const char* str, size_t n); +STC_API cstr cstr_from_fmt(const char* fmt, ...); +STC_API char* cstr_reserve(cstr* self, size_t cap); +STC_API void cstr_resize(cstr* self, size_t len, char fill); +STC_API cstr* cstr_assign_n(cstr* self, const char* str, size_t n); +STC_API int cstr_printf(cstr* self, const char* fmt, ...); +STC_API cstr* cstr_append_n(cstr* self, const char* str, size_t n); +STC_API cstr cstr_replace_sv(csview str, csview find, csview repl, unsigned count); +STC_DEF void cstr_replace_at_sv(cstr* self, const size_t pos, size_t len, csview repl); +STC_API void cstr_erase(cstr* self, size_t pos, size_t n); +STC_API size_t cstr_find(const cstr* self, const char* needle); +STC_API size_t cstr_find_at(const cstr* self, size_t pos, const char* needle); +STC_API bool cstr_getdelim(cstr *self, int delim, FILE *stream); + +STC_INLINE cstr cstr_init() { return cstr_NULL; } +STC_INLINE const char* cstr_str(const cstr* self) { return self->str; } +#define cstr_toraw(self) (self)->str +STC_INLINE csview cstr_sv(const cstr* self) + { return c_INIT(csview){self->str, _cstr_p(self)->size}; } +#define cstr_lit(literal) \ + cstr_from_n(literal, c_strlen_lit(literal)) +STC_INLINE cstr cstr_from(const char* str) + { return cstr_from_n(str, strlen(str)); } +STC_INLINE char* cstr_data(cstr* self) { return self->str; } +STC_INLINE size_t cstr_size(const cstr* self) { return _cstr_p(self)->size; } +STC_INLINE size_t cstr_capacity(cstr s) { return _cstr_p(&s)->cap; } +STC_INLINE bool cstr_empty(cstr s) { return _cstr_p(&s)->size == 0; } +STC_INLINE void cstr_drop(cstr* self) + { if (_cstr_p(self)->cap) c_free(_cstr_p(self)); } +STC_INLINE cstr cstr_clone(cstr s) + { return cstr_from_n(s.str, _cstr_p(&s)->size); } +STC_INLINE void cstr_clear(cstr* self) + { self->str[_cstr_p(self)->size = 0] = '\0'; } +STC_INLINE cstr* cstr_assign(cstr* self, const char* str) + { return cstr_assign_n(self, str, strlen(str)); } +STC_INLINE cstr* cstr_copy(cstr* self, cstr s) + { return cstr_assign_n(self, s.str, _cstr_p(&s)->size); } +STC_INLINE cstr* cstr_append(cstr* self, const char* str) + { return cstr_append_n(self, str, strlen(str)); } +STC_INLINE cstr* cstr_append_s(cstr* self, cstr s) + { return cstr_append_n(self, s.str, _cstr_p(&s)->size); } +STC_INLINE void cstr_push_back(cstr* self, char value) + { cstr_append_n(self, &value, 1); } +STC_INLINE void cstr_pop_back(cstr* self) + { self->str[ --_cstr_p(self)->size ] = '\0'; } +STC_INLINE void cstr_insert_n(cstr* self, const size_t pos, const char* str, const size_t n) + { cstr_replace_at_sv(self, pos, 0, c_SV(str, n)); } +STC_INLINE void cstr_insert(cstr* self, const size_t pos, const char* str) + { cstr_replace_at_sv(self, pos, 0, c_SV(str, strlen(str))); } +STC_INLINE void cstr_insert_s(cstr* self, const size_t pos, cstr s) + { cstr_replace_at_sv(self, pos, 0, c_SV(s.str, _cstr_p(&s)->size)); } +STC_INLINE void cstr_replace_at(cstr* self, const size_t pos, const size_t len, const char* str) + { cstr_replace_at_sv(self, pos, len, c_SV(str, strlen(str))); } +STC_INLINE void cstr_replace_s(cstr* self, const size_t pos, const size_t len, cstr s) + { cstr_replace_at_sv(self, pos, len, c_SV(s.str, _cstr_p(&s)->size)); } +STC_INLINE char* cstr_front(cstr* self) { return self->str; } +STC_INLINE char* cstr_back(cstr* self) + { return self->str + _cstr_p(self)->size - 1; } +STC_INLINE bool cstr_equals(const cstr* self, const char* str) + { return strcmp(self->str, str) == 0; } +STC_INLINE bool cstr_equals_s(const cstr* self, cstr s) + { return strcmp(self->str, s.str) == 0; } +STC_INLINE bool cstr_contains(const cstr* self, const char* needle) + { return strstr(self->str, needle) != NULL; } +STC_INLINE bool cstr_getline(cstr *self, FILE *stream) + { return cstr_getdelim(self, '\n', stream); } + +STC_INLINE cstr_buf cstr_buffer(cstr* s) { + cstr_priv* p = _cstr_p(s); + return c_INIT(cstr_buf){s->str, p->size, p->cap}; +} + +STC_INLINE cstr cstr_with_capacity(const size_t cap) { + cstr s = cstr_NULL; + cstr_reserve(&s, cap); + return s; +} + +STC_INLINE cstr cstr_with_size(const size_t len, const char fill) { + cstr s = cstr_NULL; + cstr_resize(&s, len, fill); + return s; +} + +STC_INLINE char* cstr_append_uninit(cstr *self, size_t n) { + size_t len = cstr_size(self); char* d; + if (!(d = cstr_reserve(self, len + n))) return NULL; + _cstr_p(self)->size += n; + return d + len; +} + +STC_INLINE cstr* cstr_take(cstr* self, cstr s) { + if (self->str != s.str && _cstr_p(self)->cap) + c_free(_cstr_p(self)); + self->str = s.str; + return self; +} + +STC_INLINE cstr cstr_move(cstr* self) { + cstr tmp = *self; + *self = cstr_NULL; + return tmp; +} + +STC_INLINE bool cstr_starts_with(const cstr* self, const char* sub) { + const char* p = self->str; + while (*sub && *p == *sub) ++p, ++sub; + return *sub == 0; +} + +STC_INLINE bool cstr_ends_with(const cstr* self, const char* sub) { + const size_t n = strlen(sub), sz = _cstr_p(self)->size; + return n <= sz && !memcmp(self->str + sz - n, sub, n); +} + +STC_INLINE int c_strncasecmp(const char* s1, const char* s2, size_t nmax) { + int ret = 0; + while (nmax-- && (ret = tolower(*s1++) - tolower(*s2)) == 0 && *s2++) + ; + return ret; +} + +/* container adaptor functions: */ +#define cstr_cmp(xp, yp) strcmp((xp)->str, (yp)->str) + +STC_INLINE bool cstr_eq(const cstr* x, const cstr* y) { + size_t xs = _cstr_p(x)->size, ys = _cstr_p(y)->size; + return xs == ys && !memcmp(x->str, y->str, xs); +} +STC_INLINE uint64_t cstr_hash(const cstr *self) { + return cfasthash(self->str, _cstr_p(self)->size); +} + +STC_INLINE void +cstr_replace(cstr* self, const char* find, const char* repl, unsigned count) { + csview in = cstr_sv(self); + cstr_take(self, cstr_replace_sv(in, c_SV(find, strlen(find)), + c_SV(repl, strlen(repl)), count)); +} + +/* -------------------------- IMPLEMENTATION ------------------------- */ +#if defined(i_implement) + +#ifndef i_static +static cstr_priv _cstr_nullrep = {0, 0, {0}}; +const cstr cstr_NULL = {_cstr_nullrep.chr}; +#endif + +STC_DEF char* +cstr_reserve(cstr* self, const size_t cap) { + cstr_priv *p = _cstr_p(self); + const size_t oldcap = p->cap; + if (cap > oldcap) { + p = (cstr_priv*) c_realloc(((oldcap != 0) & (p != &_cstr_nullrep)) ? p : NULL, _cstr_opt_mem(cap)); + if (!p) return NULL; + self->str = p->chr; + if (oldcap == 0) self->str[p->size = 0] = '\0'; + p->cap = _cstr_opt_cap(cap); + } + return self->str; +} + +STC_DEF void +cstr_resize(cstr* self, const size_t len, const char fill) { + const size_t n = _cstr_p(self)->size; + cstr_reserve(self, len); + if (len > n) memset(self->str + n, fill, len - n); + if (len | n) self->str[_cstr_p(self)->size = len] = '\0'; +} + +STC_DEF cstr +cstr_from_n(const char* str, const size_t n) { + if (n == 0) return cstr_NULL; + cstr_priv* prv = (cstr_priv*) c_malloc(_cstr_opt_mem(n)); + cstr s = {(char *) memcpy(prv->chr, str, n)}; + s.str[prv->size = n] = '\0'; + prv->cap = _cstr_opt_cap(n); + return s; +} + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable: 4996) +#endif + +STC_DEF int +cstr_vfmt(cstr* self, const char* fmt, va_list args) { + va_list args2; + va_copy(args2, args); + int len = vsnprintf(NULL, (size_t)0, fmt, args); + cstr_reserve(self, len); + vsprintf(self->str, fmt, args2); + va_end(args2); + return _cstr_p(self)->size = len; +} + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif + +STC_DEF cstr +cstr_from_fmt(const char* fmt, ...) { + cstr ret = cstr_NULL; + va_list args; va_start(args, fmt); + cstr_vfmt(&ret, fmt, args); + va_end(args); + return ret; +} + +STC_DEF int +cstr_printf(cstr* self, const char* fmt, ...) { + cstr ret = cstr_NULL; + va_list args; + va_start(args, fmt); + int n = cstr_vfmt(&ret, fmt, args); + va_end(args); + cstr_drop(self); + *self = ret; + return n; +} + +STC_DEF cstr* +cstr_assign_n(cstr* self, const char* str, const size_t n) { + if (n || _cstr_p(self)->cap) { + cstr_reserve(self, n); + memmove(self->str, str, n); + self->str[_cstr_p(self)->size = n] = '\0'; + } + return self; +} + +STC_DEF cstr* +cstr_append_n(cstr* self, const char* str, const size_t n) { + if (n == 0) return self; + const size_t oldlen = _cstr_p(self)->size, newlen = oldlen + n; + if (newlen > _cstr_p(self)->cap) { + const size_t off = (size_t) (str - self->str); /* handle self append */ + cstr_reserve(self, (oldlen*3 >> 1) + n); + if (off <= oldlen) str = self->str + off; + } + memcpy(&self->str[oldlen], str, n); + self->str[_cstr_p(self)->size = newlen] = '\0'; + return self; +} + +STC_INLINE void _cstr_internal_move(cstr* self, const size_t pos1, const size_t pos2) { + if (pos1 == pos2) + return; + const size_t len = _cstr_p(self)->size, newlen = len + pos2 - pos1; + if (newlen > _cstr_p(self)->cap) + cstr_reserve(self, (len*3 >> 1) + pos2 - pos1); + memmove(&self->str[pos2], &self->str[pos1], len - pos1); + self->str[_cstr_p(self)->size = newlen] = '\0'; +} + +STC_DEF void +cstr_replace_at_sv(cstr* self, const size_t pos, size_t len, csview repl) { + const size_t sz = cstr_size(self); + if (len > sz - pos) len = sz - pos; + char buf[256], *xstr = repl.size > 256 ? c_malloc(repl.size) : buf; + memcpy(xstr, repl.str, repl.size); + _cstr_internal_move(self, pos + len, pos + repl.size); + memcpy(&self->str[pos], xstr, repl.size); + if (repl.size > 256) c_free(xstr); +} + +STC_DEF cstr +cstr_replace_sv(csview str, csview find, csview repl, unsigned count) { + cstr out = cstr_NULL; + size_t from = 0; char* res; + if (count == 0) count = ~0; + if (find.size) + while (count-- && (res = cstrnstrn(str.str + from, find.str, str.size - from, find.size))) { + const size_t pos = res - str.str; + cstr_append_n(&out, str.str + from, pos - from); + cstr_append_n(&out, repl.str, repl.size); + from = pos + find.size; + } + cstr_append_n(&out, str.str + from, str.size - from); + return out; +} + +STC_DEF void +cstr_erase(cstr* self, const size_t pos, size_t n) { + const size_t len = _cstr_p(self)->size; + if (n > len - pos) n = len - pos; + if (len) { + memmove(&self->str[pos], &self->str[pos + n], len - (pos + n)); + self->str[_cstr_p(self)->size -= n] = '\0'; + } +} + +STC_DEF bool +cstr_getdelim(cstr *self, const int delim, FILE *fp) { + size_t pos = 0, cap = _cstr_p(self)->cap; + char* d = self->str; + int c = fgetc(fp); + if (c == EOF) + return false; + for (;;) { + if (c == delim || c == EOF) { + if (cap) d[_cstr_p(self)->size = pos] = '\0'; + return true; + } + if (pos == cap) { + d = cstr_reserve(self, (cap*3 >> 1) + 16); + cap = cstr_capacity(*self); + } + d[pos++] = (char) c; + c = fgetc(fp); + } +} + +STC_DEF size_t +cstr_find(const cstr* self, const char* needle) { + char* res = strstr(self->str, needle); + return res ? res - self->str : c_NPOS; +} + +STC_DEF size_t +cstr_find_at(const cstr* self, const size_t pos, const char* needle) { + if (pos > _cstr_p(self)->size) return c_NPOS; + char* res = strstr(self->str + pos, needle); + return res ? res - self->str : c_NPOS; +} + +#endif +#endif // CSTR_H_INCLUDED +#undef i_opt diff --git a/misc/include/fmt.h b/misc/include/fmt.h new file mode 100644 index 00000000..df1fe990 --- /dev/null +++ b/misc/include/fmt.h @@ -0,0 +1,253 @@ +#ifndef FMT_INCLUDED
+#define FMT_INCLUDED
+/*
+void fmt_print(dst, fmt, ...);
+void fmt_destroy(fmt_buffer* buf);
+
+ dst - destination
+ int 1=stdout, 2=stderr
+ FILE* file Write to a file
+ char* strbuf Write to a pre-allocated string buffer
+ fmt_buffer* b Auto realloc the needed memory. Set b->stream=1 for stream-mode.
+ b->data must be freed after usage.
+
+ fmt - format string
+ {} Auto-detected format. If :MOD is not specified,
+ float will use ".8g" format, and double ".16g".
+ {:MOD} Format modifiers: < left align (replaces -), default for char*, char.
+ > right align, default for numbers.
+ Other than that MOD can be normal printf format modifiers.
+ {{, }} Print chars {, and }. (note: a single % prints %).
+
+* C11 or higher required.
+* MAX 255 chars fmt string by default. MAX 12 arguments after fmt string.
+* Static linking by default, shared symbols by defining FMT_HEADER / FMT_IMPLEMENT.
+* (c) operamint, 2022, MIT License.
+-----------------------------------------------------------------------------------
+#include "fmt.h"
+
+int main() {
+ const double pi = 3.141592653589793;
+ const size_t x = 1234567890;
+ const char* string = "Hello world";
+ const char z = 'z';
+ _Bool flag = 1;
+ unsigned char r = 123, g = 214, b = 90, w = 110;
+ char buffer[64];
+
+ fmt_print(1, "Color: ({} {} {}), {}\n", r, g, b, flag);
+ fmt_print(1, "{:10} {:10} {:10.2f}\n", 42ull, 43, pi);
+ fmt_print(stdout, "{:>10} {:>10} {:>10}\n", z, z, w);
+ fmt_print(stdout, "{:10} {:10} {:10}\n", "Hello", "Mad", "World");
+ fmt_print(stderr, "100%: {:<20} {:.*} {}\n", string, 4, pi, x);
+ fmt_print(buffer, "Precision: {} {:.10} {}", string, pi, x);
+ fmt_print(1, "{}\n", buffer);
+ fmt_print(1, "Vector: ({}, {}, {})\n", 3.2, 3.3, pi);
+
+ fmt_buffer out[1] = {{.stream=1}};
+ fmt_print(out, "{} {}", "Pi is:", pi);
+ fmt_print(1, "{}, len={}, cap={}\n", out->data, out->len, out->cap);
+ fmt_print(out, "{} {}", ", Pi squared is:", pi*pi);
+ fmt_print(1, "{}, len={}, cap={}\n", out->data, out->len, out->cap);
+ fmt_destroy(out);
+}
+*/
+#include <stdio.h>
+#include <assert.h>
+
+#define fmt_OVERLOAD(name, ...) \
+ fmt_JOIN(name, fmt_NUMARGS(__VA_ARGS__))(__VA_ARGS__)
+#define fmt_CONCAT(a, b) a ## b
+#define fmt_JOIN(a, b) fmt_CONCAT(a, b)
+#define fmt_EXPAND(...) __VA_ARGS__
+#define fmt_NUMARGS(...) _fmt_APPLY_ARG_N((__VA_ARGS__, _fmt_RSEQ_N))
+
+#define _fmt_APPLY_ARG_N(args) fmt_EXPAND(_fmt_ARG_N args)
+#define _fmt_RSEQ_N 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
+#define _fmt_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
+ _14, _15, _16, N, ...) N
+
+#if defined FMT_HEADER || defined FMT_IMPLEMENT
+# define FMT_API
+#else
+# define FMT_API static inline
+#endif
+
+typedef struct {
+ char* data;
+ size_t cap, len;
+ _Bool stream;
+} fmt_buffer;
+
+FMT_API void fmt_destroy(fmt_buffer* buf);
+FMT_API int _fmt_parse(char* p, int nargs, const char *fmt, ...);
+FMT_API void _fmt_iprint(int fd, const char* fmt, ...);
+FMT_API void _fmt_bprint(fmt_buffer*, const char* fmt, ...);
+
+#ifndef FMT_MAX
+#define FMT_MAX 256
+#endif
+
+/* Primary function. */
+#define fmt_print(...) fmt_OVERLOAD(fmt_print, __VA_ARGS__)
+#define fmt_print2(to, fmt) \
+ do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 0, fmt); \
+ assert(_n == 0); _fmt_fn(to)(to, fmt); } while (0)
+#define fmt_print3(to, fmt, c) \
+ do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 1, fmt, _fc(c)); \
+ assert(_n == 1); _fmt_fn(to)(to, _fs, c); } while (0)
+#define fmt_print4(to, fmt, c, d) \
+ do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 2, fmt, _fc(c), _fc(d)); \
+ assert(_n == 2); _fmt_fn(to)(to, _fs, c, d); } while (0)
+#define fmt_print5(to, fmt, c, d, e) \
+ do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 3, fmt, _fc(c), _fc(d), _fc(e)); \
+ assert(_n == 3); _fmt_fn(to)(to, _fs, c, d, e); } while (0)
+#define fmt_print6(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)); \
+ assert(_n == 4); _fmt_fn(to)(to, _fs, c, d, e, f); } while (0)
+#define fmt_print7(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)); \
+ assert(_n == 5); _fmt_fn(to)(to, _fs, c, d, e, f, g); } while (0)
+#define fmt_print8(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)); \
+ assert(_n == 6); _fmt_fn(to)(to, _fs, c, d, e, f, g, h); } while (0)
+#define fmt_print9(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)); \
+ assert(_n == 7); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i); } while (0)
+#define fmt_print10(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)); \
+ assert(_n == 8); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j); } while (0)
+#define fmt_print11(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)); \
+ assert(_n == 9); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k); } while (0)
+#define fmt_print12(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)); \
+ assert(_n == 10); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m); } while (0)
+#define fmt_print13(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)); \
+ assert(_n == 11); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m, n); } while (0)
+#define fmt_print14(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)); \
+ assert(_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, \
+ int: _fmt_iprint, \
+ fmt_buffer*: _fmt_bprint)
+
+#if defined(_MSC_VER) && !defined(__clang__)
+# define _signed_char_hhd
+#else
+# define _signed_char_hhd signed char: "hhd",
+#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", \
+ void*: "p", \
+ const char*: "s", \
+ const void*: "p")
+
+#if defined FMT_IMPLEMENT || !(defined FMT_HEADER || defined FMT_IMPLEMENT)
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+FMT_API void fmt_destroy(fmt_buffer* buf) {
+ free(buf->data);
+}
+
+FMT_API void _fmt_iprint(int fd, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(fd == 1 ? stdout : stderr, fmt, args);
+ va_end(args);
+}
+
+FMT_API void _fmt_bprint(fmt_buffer* buf, const char* fmt, ...) {
+ va_list args, args2;
+ va_start(args, fmt);
+ va_copy(args2, args);
+ const int n = vsnprintf(NULL, 0U, fmt, args);
+ const size_t pos = buf->stream ? buf->len : 0U;
+ buf->len = pos + (size_t)n;
+ if (buf->len > buf->cap) {
+ buf->cap = buf->len + buf->cap/2;
+ buf->data = (char*)realloc(buf->data, buf->cap + 1);
+ }
+ vsprintf(buf->data + pos, fmt, args2);
+ va_end(args2);
+ va_end(args);
+}
+
+FMT_API 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; /* nobreak */
+ case '}': goto done;
+ case '<': *p++ = '-', ++fmt; break;
+ case '>': p0 = NULL; /* nobreak */
+ case '-': ++fmt; break;
+ case '*': if (++n <= nargs) arg = va_arg(args, char *); /* nobreak */
+ 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]))
+ while (*arg) *p++ = *arg++;
+ if (p0 && (p[-1] == 's' || p[-1] == 'c')) /* left-align str */
+ memmove(p0 + 1, p0, p++ - p0), *p0 = '-';
+ fmt += *fmt == '}';
+ continue;
+ }
+ *p++ = *fmt++;
+ } while (ch);
+ va_end(args);
+ return n;
+}
+#endif
+#endif
\ No newline at end of file diff --git a/misc/include/stctest.h b/misc/include/stctest.h new file mode 100644 index 00000000..9718af7f --- /dev/null +++ b/misc/include/stctest.h @@ -0,0 +1,203 @@ +/* MIT License
+ *
+ * Copyright (c) 2022 Tyge Løvset, NORCE, www.norceresearch.no
+ *
+ * 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.
+ */
+
+/* stctest: A small and simple C11 unit-testing framework.
+
+ Features:
+ - Requires C11. Should work with any C compiler supporting _Generic.
+ - No library dependencies. Not even itself. Just a header file.
+ - Reports assertion failures, including expressions and line numbers.
+ - ANSI color output for maximum visibility.
+ - Easy to embed in apps for runtime tests (e.g. environment tests).
+
+ Example:
+
+ #include "stctest.h"
+ #include "mylib.h"
+
+ void test_sheep()
+ {
+ EXPECT_EQ("Sheep are cool", are_sheep_cool());
+ EXPECT_EQ(4, sheep.legs);
+ }
+
+ void test_cheese()
+ {
+ EXPECT_GT(cheese.tanginess, 0);
+ EXPECT_EQ("Wensleydale", cheese.name);
+ }
+
+ int main()
+ {
+ RUN_TEST(test_sheep);
+ RUN_TEST(test_cheese);
+ return REPORT_TESTS();
+ }
+ */
+
+#ifndef STCTEST_INCLUDED
+#define STCTEST_INCLUDED
+
+#include <stdio.h>
+#include <time.h>
+#include <string.h>
+#include <float.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#define STC_FLOAT_EPSILON 1e-6
+#define STC_DOUBLE_EPSILON 1e-13
+
+
+#define EXPECT_TRUE(expr) \
+ do { if (!_stctest_assert(__FILE__, __LINE__, #expr, (expr) != 0)) puts(""); } while (0)
+
+#define EXPECT_TRUE1(expr, v) \
+ do { if (!_stctest_assert(__FILE__, __LINE__, #expr, (expr) != 0)) { \
+ char _fmt[32]; sprintf(_fmt, " ; %%s --> %s\n", _stctest_FMT(v)); \
+ printf(_fmt, #v, v); \
+ }} while (0)
+
+#define EXPECT_FALSE(expr) EXPECT_TRUE(!(expr))
+#define EXPECT_FALSE1(expr, v) EXPECT_TRUE1(!(expr), v)
+
+/* NB! (char*) are compared as strings. Cast to (void*) to compare pointers only */
+#define EXPECT_EQ(a, b) _stctest_CHECK(a, ==, b, -STC_DOUBLE_EPSILON)
+#define EXPECT_NE(a, b) _stctest_CHECK(a, !=, b, -STC_DOUBLE_EPSILON)
+#define EXPECT_GT(a, b) _stctest_CHECK(a, >, b, -STC_DOUBLE_EPSILON)
+#define EXPECT_LT(a, b) _stctest_CHECK(a, <, b, -STC_DOUBLE_EPSILON)
+#define EXPECT_LE(a, b) _stctest_CHECK(a, <=, b, -STC_DOUBLE_EPSILON)
+#define EXPECT_GE(a, b) _stctest_CHECK(a, >=, b, -STC_DOUBLE_EPSILON)
+#define EXPECT_FLOAT_EQ(a, b) _stctest_CHECK((float)(a), ==, (float)(b), -STC_FLOAT_EPSILON)
+#define EXPECT_DOUBLE_EQ(a, b) _stctest_CHECK((double)(a), ==, (double)(b), -STC_DOUBLE_EPSILON)
+#define EXPECT_NEAR(a, b, abs_error) _stctest_CHECK((double)(a), ==, (double)(b), abs_error)
+
+/* Run a test() function */
+#define RUN_TEST(test, ...) do { \
+ puts(#test "(" #__VA_ARGS__ "):"); \
+ const int ps = _stctest_s.passes; \
+ const int fs = _stctest_s.fails; \
+ const clock_t _start = clock(); \
+ test(__VA_ARGS__); \
+ const int _sum = (clock() - _start)*1000 / CLOCKS_PER_SEC; \
+ _stctest_s.total_ms += _sum; \
+ printf(" passed: %d/%d. duration: %d ms\n", \
+ _stctest_s.passes - ps, _stctest_s.passes + _stctest_s.fails - (ps + fs), _sum); \
+} while (0)
+
+#define REPORT_TESTS() stctest_report()
+
+/* ----------------------------------------------------------------------------- */
+
+#define _stctest_CHECK(a, OP, b, e) \
+ do { if (!_stctest_assert(__FILE__, __LINE__, #a " " #OP " " #b, _stctest_CMP(a, OP, b, e))) { \
+ char _fmt[32]; sprintf(_fmt, " ; %s %s %s\n", _stctest_FMT(a), #OP, _stctest_FMT(b)); \
+ printf(_fmt, a, b); \
+ }} while (0)
+
+#define _stctest_CMP(a, OP, b, e) _Generic((a), \
+ const char*: _stctest_strcmp, char*: _stctest_strcmp, \
+ double: _Generic((b), double: _stctest_dblcmp, float: _stctest_dblcmp, default: _stctest_valcmp), \
+ float: _Generic((b), double: _stctest_dblcmp, float: _stctest_dblcmp, default: _stctest_valcmp), \
+ default: _stctest_valcmp)((a) OP (b), #OP, a, b, (double)(e))
+
+#define _stctest_FMT(a) _Generic((a), \
+ float: "%.8gf", double: "%.15g", \
+ int64_t: "%" PRId64, int32_t: "%" PRId32, int16_t: "%" PRId16, int8_t: "%" PRId8, \
+ uint64_t: "%" PRIu64 "u", uint32_t: "%" PRIu32 "u", uint16_t: "%" PRIu16 "u", uint8_t: "%" PRIu8 "u", \
+ char*: "`%s`", const char*: "`%s`", \
+ default: "%p")
+
+static int _stctest_strcmp(int res, const char* OP, ...) {
+ va_list ap;
+ va_start(ap, OP);
+ const char* a = va_arg(ap, const char *);
+ const char* b = va_arg(ap, const char *);
+ va_end(ap);
+ int c = strcmp(a, b);
+ if (OP[0] == '<') return OP[1] == '=' ? c <= 0 : c < 0;
+ if (OP[0] == '>') return OP[1] == '=' ? c >= 0 : c > 0;
+ return (OP[0] == '!') ^ (c == 0);
+}
+
+// Knuth:
+static int approximately_equal(double a, double b, double epsilon) {
+ double d = a - b; if (d < 0) d = -d;
+ if (a < 0) a = -a; if (b < 0) b = -b;
+ return d <= ((a < b ? b : a) * epsilon); // (a > b ? b : a) => essentially_equal:
+}
+
+static int _stctest_dblcmp(int res, const char* OP, ...) {
+ va_list ap;
+ va_start(ap, OP);
+ double a = va_arg(ap, double);
+ double b = va_arg(ap, double);
+ double e = va_arg(ap, double);
+ double c = a - b;
+ va_end(ap);
+ if (OP[0] == '<') return OP[1] == '=' ? c <= 0 : c < 0;
+ if (OP[0] == '>') return OP[1] == '=' ? c >= 0 : c > 0;
+ return (OP[0] == '!') ^ (e < 0 ? approximately_equal(a, b, -e) : (c < 0 ? -c : c) <= e);
+}
+
+static int _stctest_valcmp(int res, const char* OP, ...)
+ { return res; }
+
+#define _stctest_COLOR_CODE 0x1B
+#define _stctest_COLOR_RED "[1;31m"
+#define _stctest_COLOR_GREEN "[1;32m"
+#define _stctest_COLOR_RESET "[0m"
+
+static struct stctest_s {
+ int passes;
+ int fails;
+ const char* current_file;
+ int total_ms;
+} _stctest_s = {0};
+
+static int _stctest_assert(const char* file, int line, const char* expression, int pass) {
+ if (pass)
+ _stctest_s.passes++;
+ else {
+ _stctest_s.fails++;
+ printf(" failed \"%s:%d\": (%s)", file, line, expression);
+ }
+ _stctest_s.current_file = file;
+ return pass;
+}
+
+static int stctest_report(void) {
+ if (_stctest_s.fails) {
+ printf("%c%sFAILED%c%s: \"%s\": (failed %d, passed %d, total %d)\n",
+ _stctest_COLOR_CODE, _stctest_COLOR_RED, _stctest_COLOR_CODE, _stctest_COLOR_RESET,
+ _stctest_s.current_file, _stctest_s.fails, _stctest_s.passes, _stctest_s.passes + _stctest_s.fails);
+ } else {
+ printf("%c%sPASSED%c%s: \"%s\": (total %d)\n",
+ _stctest_COLOR_CODE, _stctest_COLOR_GREEN, _stctest_COLOR_CODE, _stctest_COLOR_RESET,
+ _stctest_s.current_file, _stctest_s.passes);
+ }
+ printf(" duration: %d ms\n", _stctest_s.total_ms); \
+ return -_stctest_s.fails;
+}
+
+#endif
|
