Generics concept of the CPARSEC3 library consists of the following concepts:
- Generic Data Type
- A parametric typed struct/union.
- Trait
- A struct that provides a set of parametric typed functions/constants.
- Generic Macros
- A type-safe macro that provides a functionallity for easy to use traits/containers.
Generic Data Type is a parametric typed struct/union.
Generic Data Type is a similar concept as known as parameterized type, polymorphic type, template class (C++), and so on.
The below shows a example of Generic Data Types :
- Array(T)
- Array of objects of
T
type. - List(T)
- Linked-list of objects of
T
type orNULL
. - Maybe(T)
- Wraps an object of
T
type or nothing. - Result(T, E)
- Wraps an ok value of
T
type or an error value ofE
type. - Tuple(T1, …)
- A tuple of values of various types.
- Tuple(T1)
- Tuple(T1, T2)
- Tuple(T1, T2, T3)
- Tuple(T1, T2, T3, T4)
- Tuple(T1, T2, T3, T4, T5)
- Tuple(T1, T2, T3, T4, T5, T6)
Trait provides a set of functions against a particular concrete type.
Trait is a similar concept as known as type class (Haskell), trait (Rust), interface (Java), and so on.
For example, an Eq(T)
trait provides the following two functions :
bool eq(T, T)
bool neq(T, T)
To use Trait object, call to trait(M)
that creates a concrete trait object.
The parameter M
is the name of the resulting concrete trait type (e.g.
Eq(int)
), or a name of corresponding concrete type (e.g. Array(int)
).
Usecase 1. Use Eq(String)
trait to test equality of two String
values.
/* Eq(String) is a trait type to test equality of two String values */
Eq(String) E = trait(Eq(String));
if (E.eq("foo", "foo")) { printf("foo == foo\n"); }
if (E.neq("foo", "foo")) { printf("foo != foo\n");}
// -> foo == foo
Usecase 2. Use ArrayT(int)
trait to construct, destruct, and manipulate
Array(int)
object.
/* ArrayT(int) is a trait type to construct, destruct, and manipulate Array(int) */
ArrayT(int) m = trait(Array(int));
Array(int) a = m.create(5);
size_t n = m.length(a); /* -> n = 5 */
for (int* it = m.begin(a); it != m.end(a); *it++ = n--)
;
for (int* it = m.begin(a); it != m.end(a); it++) {
printf("%d ", *it); /* -> 5 4 3 2 1 */
}
m.free(&a);
Usecase 3. Use ItrT(Array(int))
trait to iterate each items in Array(int)
container.
/* ItrT(C) is a trait type to iterate each items in container C */
Iterable(Array(int)) J = trait(Iterable(Array(int)));
ItrT(Array(int)) I = trait(Itr(Array(int)));
ArrayT(int) m = trait(Array(int));
Array(int) a = m.create(5);
size_t n = m.length(a); /* -> n = 5 */
for (Itr(Array(int)) it = J.itr(a); !I.null(it); it = I.next(it)) {
I.set(n--, it);
}
for (Itr(Array(int)) it = J.itr(a); !I.null(it); it = I.next(it)) {
printf("%d ", I.get(it)); /* -> 5 4 3 2 1 */
}
m.free(&a);
CPARSEC3 provides also Generic Macros for easy to use various traits and
containers. The name of each Generic Macros starts with prefix g_
, such as
for example g_array(T, ...)
, g_list(T, ...)
, g_eq(a, b)
, g_le(a, b)
, and
so on.
- Pros of Generic Macros
- Makes it easy to use various traits and containers.
- Cons of Generic Macros
- Needs much more compile time / memory.
Usecase 1a. Constructs / initializes an array then iterates for each items in the array.
Array(int) a = g_array(int, 5, 4, 3, 2, 1);
size_t n = g_length(a); /* -> n = 5 */
for (Itr(Array(int)) it = g_itr(a); !g_null(it); it = g_next(it)) {
printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
g_free(a);
Usecase 1b. Constructs / initializes an list then iterates for each items in the list.
List(int) a = g_list(int, 5, 4, 3, 2, 1);
size_t n = g_length(a); /* -> n = 5 */
for (Itr(List(int)) it = g_itr(a); !g_null(it); it = g_next(it)) {
printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
g_free(a);
Usecase 2a. Use __auto_type
and g_for(it, c)
GCC extension to consruct and
iterate an array.
// GCC only
#ifdef __GNUC__
__auto_type a = g_array(int, 5, 4, 3, 2, 1);
size_t n = g_length(a); /* -> n = 5 */
g_for(it, a) {
printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
g_free(a);
#endif
Usecase 2a. Use __auto_type
and g_for(it, c)
GCC extension to construct
and iterate a list.
// GCC only
#ifdef __GNUC__
__auto_type a = g_list(int, 5, 4, 3, 2, 1);
size_t n = g_length(a); /* -> n = 5 */
g_for(it, a) {
printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
g_free(a);
#endif
Usecase 3a. Use g_scoped(T)
GCC extension to deallocate an array
automatically when leaving the current scope ; RAII (Resource Acquisition Is
Initialization).
// GCC only
#ifdef __GNUC__
{
g_scoped(Array(int)) a = g_array(int, 5, 4, 3, 2, 1);
g_for(it, a) {
printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
// No need to call `g_free(a)`
}
#endif
Usecase 3a. Use g_scoped(T)
GCC extension to deallocate a list
automatically when leaving the current scope ; RAII (Resource Acquisition Is
Initialization).
// GCC only
#ifdef __GNUC__
{
g_scoped(List(int)) xs = g_list(int, 5, 4, 3, 2, 1);
g_for(it, xs) {
printf("%d ", g_get(it)); /* -> 5 4 3 2 1 */
}
// No need to call `g_free(xs)`
}
#endif