Skip to content

Commit

Permalink
- Added introduction text in README.md
Browse files Browse the repository at this point in the history
- Some smaller docs updates.
- Updated Makefile to work with TinyC,gcc,clang Linux + Windows.
- Minor update in sumtype.h
  • Loading branch information
tylov committed Jan 5, 2025
1 parent 54e330a commit a49c878
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 76 deletions.
71 changes: 41 additions & 30 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
# GNU Makefile for STC. Linux/Mac + Windows
# On Windows, the makefile requires mkdir, rm, and printf
# which are all found in C:\Program Files\Git\usr\bin
# GNU Makefile for STC. Linux/Mac + Windows for gcc, clang and tcc (TinyC).
# On Windows, makefile requires mkdir, rm, and printf, which are all
# found in the C:\Program Files\Git\usr\bin folder.
# Use 'make CC=gcc' (or clang or tcc), or set CC in the environment.
# Currently only release build.

ifeq ($(origin CC),default)
CC := gcc
endif
ifeq ($(origin CXX),default)
CXX := g++
endif
ifeq ($(CC),cl)
CFLAGS ?= -nologo -std:c11 -Iinclude -MD -O2 -W3 -wd4003
CXXFLAGS ?= -nologo -std:c++20 -EHsc -Iinclude -O2 -MD
LDFLAGS ?=
AR_RCS ?=

CFLAGS ?= -std=c11 -Iinclude -MMD -O3 -Wpedantic -Wall -Wextra -Werror -Wno-missing-field-initializers
CXXFLAGS ?= -std=c++20 -Iinclude -O3 -MMD -Wall
LDFLAGS ?=
ifeq ($(CC:.exe=),tcc)
AR_RCS ?= tcc -ar rcs
else
CFLAGS ?= -std=c11 -Iinclude -MMD -O3 -Wpedantic -Wall -Wextra -Werror -Wno-missing-field-initializers
CXXFLAGS ?= -std=c++20 -Iinclude -O3 -MMD -Wall
LDFLAGS ?=
AR_RCS ?= ar rcs
AR_RCS ?= ar rcs
endif
MKDIR_P ?= mkdir -p
RM_F ?= rm -f

ifeq ($(OS),Windows_NT)
EXE := .exe
BUILDDIR := build_Windows
DOTEXE := .exe
BUILDDIR := bld_Windows/$(CC:.exe=)
else
BUILDDIR := build_$(shell uname)
BUILDDIR := bld_$(shell uname)/$(CC)
LDFLAGS += -lm
CFLAGS += -Wno-clobbered
endif

OBJ_DIR := $(BUILDDIR)
Expand All @@ -41,44 +43,53 @@ LIB_PATH := $(BUILDDIR)/lib$(LIB_NAME).a
EX_SRCS := $(wildcard examples/*/*.c)
EX_OBJS := $(EX_SRCS:%.c=$(OBJ_DIR)/%.o)
EX_DEPS := $(EX_SRCS:%.c=$(OBJ_DIR)/%.d)
EX_EXES := $(EX_SRCS:%.c=$(OBJ_DIR)/%$(EXE))
EX_EXES := $(EX_SRCS:%.c=$(OBJ_DIR)/%$(DOTEXE))

TEST_SRCS := $(wildcard tests/*_test.c) tests/main.c
TEST_OBJS := $(TEST_SRCS:%.c=$(OBJ_DIR)/%.o)
TEST_DEPS := $(TEST_SRCS:%.c=$(OBJ_DIR)/%.d)
TEST_EXE := $(OBJ_DIR)/tests/test_all$(DOTEXE)

fast:
@$(MAKE) -j --no-print-directory all

all: $(LIB_PATH) $(EX_EXES)
@echo
all: $(LIB_PATH) $(EX_EXES) $(TEST_EXE)

clean:
@$(RM_F) $(LIB_OBJS) $(EX_OBJS) $(LIB_DEPS) $(EX_DEPS) $(LIB_PATH) $(EX_EXES)
@echo Cleaned
@$(RM_F) $(LIB_OBJS) $(TEST_OBJS) $(EX_OBJS) $(LIB_DEPS) $(EX_DEPS) $(LIB_PATH) $(EX_EXES) $(TEST_EXE)
@echo "Cleaned"

distclean:
$(RM_F) $(OBJ_DIR)

lib: $(LIB_PATH)

$(LIB_PATH): $(LIB_OBJS)
@printf "\n%s" "$(AR_RCS) $@"
@printf "\r\e[2K%s\n" "$(AR_RCS) $@"
@$(AR_RCS) $@ $(LIB_OBJS)
@echo ""
# @echo ""

$(OBJ_DIR)/%.o: %.c
@$(MKDIR_P) $(@D)
@printf "\r\e[2K%s" "$(CC) $< -o $@ -c"
@$(CC) $< -o $@ -c $(CFLAGS)
@printf "\r\e[2K%s" "$(CC) $< -c -o $@"
@$(CC) $< -c -o $@ $(CFLAGS)

$(OBJ_DIR)/%.o: %.cpp
@$(MKDIR_P) $(@D)
@printf "\r\e[2K%s" "$(CXX) $< -c $(CXXFLAGS) -o $@"
@$(CXX) $< -o $@ -c $(CXXFLAGS)
@printf "\r\e[2K%s" "$(CXX) $< -c -o $@"
@$(CXX) $< -c -o $@ $(CXXFLAGS)

$(OBJ_DIR)/%$(EXE): %.c $(LIB_PATH)
$(OBJ_DIR)/%$(DOTEXE): %.c $(LIB_PATH)
@$(MKDIR_P) $(@D)
@printf "\r\e[2K%s" "$(CC) $< -o $(@F) -s $(LDFLAGS) -L$(BUILDDIR) -l$(LIB_NAME) -lm"
@$(CC) $< $(CFLAGS) -o $@ -s $(LDFLAGS) -L$(BUILDDIR) -l$(LIB_NAME) -lm
@printf "\r\e[2K%s" "$(CC) -o $(@F) $< -s $(LDFLAGS) -L$(BUILDDIR) -l$(LIB_NAME)"
@$(CC) -o $@ $(CFLAGS) -s $< $(LDFLAGS) -L$(BUILDDIR) -l$(LIB_NAME)

$(TEST_EXE): $(TEST_OBJS)
@printf "\r\e[2K%s" "$(CC) -o $@ $(notdir $(TEST_OBJS))"
@$(CC) -o $@ $(TEST_OBJS) -s $(LDFLAGS) -L$(BUILDDIR) -l$(LIB_NAME)


.SECONDARY: $(EX_OBJS) # Prevent deleting objs after building
.PHONY: all clean distclean
.PHONY: fast all clean distclean lib

-include $(LIB_DEPS) $(EX_DEPS)
44 changes: 39 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,49 @@

# STC - Smart Template Containers

### Version 5.0 RC5
*This library is a tribute to the C language and its creator, Dennis M. Ritchie.*

## Version 5.0 RC6
STC is a *modern*, *typesafe* and *fast* templated general purpose container and algorithms
library for C99. It aims to elevate C-programming to be even more fun, more productive, and
substantially safer due to the encouragement of broad usage of loop abstractions, typesafe
high-level generic datatypes and algorithms, and through consistent handling of
substantially safer due to the encouragement of broad usage of loop abstractions,
high-level generic datatypes, iterators and algorithms, and through a consistent handling of
object ownership and lifetimes.

<details>
<summary><b>The big picture: Why we need a modern generic library for C</b></summary>
C is still among the most popular programming languages, despite the fact that it was created
as early as in 1972. It is a manifestation of how well the language was designed for its time,
and still is. However, times are rapidly changing, and C among others is starting to lag
behind many of the new system languages like Zig, Odin and Rust with regard to features in
the standard library, but also when it comes to safety and vulnerabilities. Both those issues
are addressed with the STC library.

#### A. Missing features in the C standard library, which STC provides
* Large set of high performance, generic/templated typesafe container types, including smart pointers and bitsets.
* String type with utf8 support and short string optimization (sso), and two string-view types.
* Typesafe and ergonomic **sum type** implementation, aka. tagged union or variant.
* Flexible **coroutine** implementation with excellent ergonomics, error recovery and cleanup support.
* Fast, modern **regular expressions** with full utf8 and a subset of unicode character classes support.
* Ranges algorithms like *iota* and filter views like *take, skip, take-while, skip-while, map*.
* Generic algorithms, iterators and loop abstactions. Blazing fast *sort, binary search* and *lower bound*.
* Single/multi-dimensional generic **span view** with arbitrary array dimensions (numpy array-like slicing).

#### B. Improved safety by using STC
* Abstractions for raw loops, ranged iteration over containers, and generic ranges algorithms. All this
reduces the chance of creating bugs, as user code with raw loops and ad-hoc implementation of
common algorithms and containers is minimized/eliminated.
* STC is inherently **type safe**. Essentially, there are no opaque pointers or casting away of type information.
Only where neccesary, generic code will use some macros to do compile-time type-checking before types are casted.
Examples are `c_static_assert`, `c_const_cast`, `c_safe_cast` and macros for safe integer type casting.
* Containers and algorithms all use **signed integers** for indices and sizes, and it encourange to use
signed integers for quantities in general (unsigned integers have valid usages as bitsets and in bit operations).
This could remove a wide range of bugs related to mixed unsigned-signed calculations and comparisons, which
intuitively gives the wrong answer in many cases.
* Tagged unions in C are common, but normally unsafely implemented. Traditionally, it leaves the inactive payload
data readily accesible to user code, and there is no general way to ensure that the payload is assigned along with
the tag, or that they match. STC **sum type** is a typesafe version of tagged unions which eliminates all those
safety concerns.
</details>

Containers
----------
- [***arc*** - (atomic) reference counted shared pointer`](docs/arc_api.md)
Expand Down
23 changes: 14 additions & 9 deletions docs/algorithm_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,24 +236,29 @@ c_sumtype (SumType,
(VariantEnumN, VariantTypeN)
);
// Sum type variant constructor
SumType c_variant(VariantEnum tag, VariantType value);
SumType c_variant(VariantEnum tag, VariantType value); // Sum type constructor
bool c_holds(const SumType* obj, VariantEnum tag); // does obj hold VariantType?
int c_tag_index(SumType* obj); // 1-based index (mostly for debug)
// Use a sum type (1)
c_when (SumType*) {
c_is(VariantEnum1, VariantType1* v) <body>;
c_is(VariantEnumX) c_or_is(VariantEnumY) <body>;
c_when (SumType* obj) {
c_is(VariantEnum1, VariantType1* x) <body>;
c_is(VariantEnum2) c_or_is(VariantEnum3) <body>;
...
c_is(VariantEnumN, VariantTypeN* v) <body>;
c_otherwise <body>;
}
// Use a sum type (2)
c_if_is (SumType*, VariantEnum, VariantType* v) <body>;
c_if_is(SumType* obj, VariantEnum1, VariantType1* x) <body>;
...
else <body>;
```
The **c_when** statement is exhaustive. The compiler will give a warning if not all variants are
covered by **c_is** (requires `-Wall` or `-Wswitch` gcc/clang compiler flag). Note also that the
first enum value is deliberately set to 1 in order to easier detect non/zero-initialized variants.
covered by **c_is** (requires `-Wall` or `-Wswitch` gcc/clang compiler flag). The first enum value
is deliberately set to 1 in order to easier detect non/zero-initialized variants.

* Note 1: The `x` variables in the synopsis are "auto" type declared/defined - see examples.
* Note 2: Sum types will not work in coroutines (i.e. if `cco_yield..` or `cco_await..` are used within `c_when` / `c_if_is` blocks).

### Example 1

Expand Down
8 changes: 3 additions & 5 deletions docs/coroutine_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This is small and portable implementation of coroutines.
* Allows "throwing" errors. To be handled in a `cco_finally:` during the immediate unwinding of the call stack.
Unhandled errors will exit program with an error message including the offendig throw's line number.

Because these coroutines are stackful, all variables used within the coroutine scope (where usage crosses `cco_yield*` or `cco_await*`) must be stored in a struct which is passed as pointer to the coroutine. This has the advantages that they become extremely lightweight and therefore useful on severely memory constrained systems like small microcontrollers where other solutions are impractical.
Because these coroutines are stackful, all variables used within the coroutine scope (where usage crosses `cco_yield..` or `cco_await..`) must be stored in a struct which is passed as pointer to the coroutine. This has the advantages that they become extremely lightweight and therefore useful on severely memory constrained systems like small microcontrollers where other solutions are impractical.

## Methods and statements

Expand Down Expand Up @@ -45,8 +45,6 @@ void cco_recover_error(cco_runtime* rt); // Reset err
void cco_resume_task(cco_task* task, cco_runtime* rt); // Resume suspended task, return value in `rt->result`.
cco_run_task(cco_task* task) {} // Run blocking until task is finished.
cco_run_task(cco_task* task, <Environment> *env) {} // Run blocking with env data
cco_run_task(cco_task* task, <Environment> *env, runnername) {} // Run blocking with env data and
// name of the cco_taskrunner.
```
#### Timers and time functions
```c++
Expand Down Expand Up @@ -92,7 +90,7 @@ cco_semaphore cco_make_semaphore(long value); // Create se
|`cco_runtime` | Struct type | Runtime object to manage cco_taskrunner states |
## Rules
1. Avoid declaring local variables within a `cco_routine` scope. They are only alive until next `cco_yield*` or `cco_await*`
1. Avoid declaring local variables within a `cco_routine` scope. They are only alive until next `cco_yield..` or `cco_await..`
suspension point. Normally place them in the coroutine struct. Be particularly careful with control variables in loops.
2. Do not call ***cco_yield\**** or ***cco_await\**** inside a `switch` statement within a
`cco_routine` scope. In general, use `if-else-if` to avoid this trap.
Expand All @@ -103,7 +101,7 @@ A regular coroutine may have any signature, however this implementation has spec
coroutines which returns `int`, indicating CCO_DONE, CCO_AWAIT, CCO_YIELD, or a custom value.
It should take a struct pointer as one of the parameter which must contains a cco-state member named `cco`.
The coroutine struct should normally store all *local* variables to be used within the coroutine
(technically those which lifetime crosses a `cco_yield*` or `cco_await*()` statement), along with
(technically those which lifetime crosses a `cco_yield..` or a `cco_await..` statement), along with
*input* and *output* data for the coroutine.
Both asymmetric and symmetric coroutine control flow transfer are supported when using ***tasks***
Expand Down
47 changes: 22 additions & 25 deletions include/stc/sys/sumtype.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,34 +106,34 @@ int main(void) {
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)
#if defined STC_HAS_TYPEOF && STC_HAS_TYPEOF
#define c_when(varptr) \
for (__typeof__(varptr) _vp1 = (varptr); _vp1; _vp1 = NULL) \
switch (_vp1->_any_.tag)

#define c_is_2(Tag, x) \
break; case Tag: \
for (__typeof__(_match->Tag.var)* x = &_match->Tag.var; x; x = NULL)
for (__typeof__(_vp1->Tag.var)* x = &_vp1->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)
#define c_if_is(varptr, Tag, x) \
for (__typeof__(varptr) _vp2 = (varptr); _vp2; _vp2 = NULL) \
if (c_holds(_vp2, Tag)) \
for (__typeof__(_vp2->Tag.var) *x = &_vp2->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_when(varptr) \
for (_c_any_variant* _vp1 = (_c_any_variant *)(varptr) + 0*sizeof((varptr)->_any_.tag); \
_vp1; _vp1 = NULL) \
switch (_vp1->_any_.tag)

#define c_is_2(Tag, x) \
break; case Tag: \
for (Tag##_type *x = &((Tag##_sumtype *)_match)->Tag.var; x; x = NULL)
for (Tag##_type *x = &((Tag##_sumtype *)_vp1)->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)
#define c_if_is(varptr, Tag, x) \
for (Tag##_sumtype* _vp2 = c_const_cast(Tag##_sumtype*, varptr); _vp2; _vp2 = NULL) \
if (c_holds(_vp2, Tag)) \
for (Tag##_type *x = &_vp2->Tag.var; x; x = NULL)
#endif

#define c_is(...) c_MACRO_OVERLOAD(c_is, __VA_ARGS__)
Expand All @@ -149,13 +149,10 @@ int main(void) {
#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(varptr) \
((int)(varptr)->_any_.tag)

#define c_tag_index(var_ptr) \
((var_ptr)->_any_.tag)

#define c_holds(var_ptr, Tag) \
(c_tag_index(var_ptr) == Tag)
#define c_holds(varptr, Tag) \
((varptr)->_any_.tag == Tag)

#endif // STC_SUMTYPE_H_INCLUDED
2 changes: 1 addition & 1 deletion tests/ctest.h
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ static void sighandler(int signum)
* so it can terminate as expected */
signal(signum, SIG_DFL);
#if !defined(_WIN32) || defined(__CYGWIN__)
kill(getpid(), signum);
//kill(getpid(), signum);
#endif
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion tests/hmap_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ TEST(hmap, mapdemo3)
EXPECT_TRUE(hmap_cstr_eq(&res2, &map));

c_drop(hmap_cstr, &map, &res1, &res2);
}
}

0 comments on commit a49c878

Please sign in to comment.