Introduction

Currently we have a generic list container which uses void pointers to allow anything to be stored. Previously, we created a type safe string hashtable wrapping the generic hashtable. We want to do the same thing for our list and so now we’re going to create a type safe string list wrapping our generic list.

Design

Unlike the string hashtable we want to extend the our string list to have some string specific functionality. We can do more than just wrapping with our type safe wrappers. Since the wrapper is what knows the data type it an provide data type specific functions outside of the generic base.

Header

#ifndef __LIST_STR_H__
#define __LIST_STR_H__

#include <stdbool.h>
#include <stddef.h>

struct list_str;
typedef struct list_str list_str_t;

typedef enum {
    LIST_STR_NONE    = 0,
    LIST_STR_SORT    = 1 << 0,
    LIST_STR_CASECMP = 1 << 1
} list_str_flags_t;

list_str_t *list_str_create(list_str_flags_t flags);
void list_str_destroy(list_str_t *l);

size_t list_str_len(list_str_t *l);

bool list_str_append(list_str_t *l, const char *v);
bool list_str_insert(list_str_t *l, const char *v, size_t idx);
bool list_str_remove(list_str_t *l, size_t idx);

bool list_str_index_of(list_str_t *l, const char *v, size_t *idx);
const char *list_str_get(list_str_t *l, size_t idx);
char *list_str_take(list_str_t *l, size_t idx);

char *list_str_join(list_str_t *l, const char *sep);

#endif /* __LIST_STR_H__ */

The header is a near carbon copy of the generic list base but with two differences (aside from the typeness). There is an additional string specific flag and there is a function that isn’t from the generic base list.

Implementation

One thing we’ll be using is a string builder. More on this later.

Includes

#include <stdlib.h>
#include <string.h>

#include "list.h"
#include "list_str.h"
#include "str_builder.h"

Object Data

As with the other wrapper we made, there isn’t any object data because we’re using forward declared types which we never implement. We just do a bunch of casting because we’re just adding type safe function prototypes on top of the void pointer implementation.

Equality

int str_eq_int(const char *a, const char *b, bool casecmp)
{
    const char *sa = *(const char **)a;
    const char *sb = *(const char **)b;

    if (casecmp)
        return strcasecmp(sa, sb);
    return strcmp(sa, sb);
}

int str_eq(const void *a, const void *b)
{
    return str_eq_int(a, b, false);
}

int str_caseeq(const void *a, const void *b)
{
    return str_eq_int(a, b, true);
}

Okay, we need a string specific comparison. Two actually so we can handle case insensitive comparison. Now would be a good time go back and put all the various comparison functions into a common file. Especially since the string hashtable uses functions too.

Create

list_str_t *list_str_create(list_str_flags_t flags)
{
    list_flags_t lflags = LIST_NONE;
    list_cbs_t   cbs    = {
        str_eq,
        (list_copy_cb)strdup,
        free
    };
    if (flags & LIST_STR_CASECMP)
        cbs.leq = str_eq;
    if (flags & LIST_STR_SORT)
        lflags != LIST_SORT;
    return (list_str_t *)list_create(&cbs, lflags);
}

Really all we need to do for create is setup our callbacks and handle the flags. When working with flags a wrapper should never do pass through of it’s flags to the base. Each wrapper should have it’s own flags because wrappers will often (in this case) have their own wrapper specific flags. We don’t want to tie a wrapper to whatever flags the base implements.

Wrappers

void list_str_destroy(list_str_t *l)
{
    list_destroy((list_t *)l);
}

size_t list_str_len(list_str_t *l)
{
    return list_len((list_t *)l);    
}

bool list_str_append(list_str_t *l, const char *v)
{
    return list_append((list_t *)l, (void *)v);
}

bool list_str_insert(list_str_t *l, const char *v, size_t idx)
{
    return list_insert((list_t *)l, (void *)v, idx);
}

bool list_str_remove(list_str_t *l, size_t idx)
{
    return list_remove((list_t *)l, idx);
}

bool list_str_index_of(list_str_t *l, const char *v, size_t *idx)
{
    return list_index_of((list_t *)l, (void *)v, idx);
}

const char *list_str_get(list_str_t *l, size_t idx)
{
    return (const char *)list_get((list_t *)l, idx);
}

char *list_str_take(list_str_t *l, size_t idx)
{
    return (char *)list_take((list_t *)l, idx);
}

Pretty much everything else in this is just a wrapper. This is pretty much no different than what we did with the string hashtable.

Join

char *list_str_join(list_str_t *l, const char *sep)
{
    str_builder_t *sb;
    char          *out;
    size_t         len;
    size_t         i;

    if (l == NULL)
        return NULL;

    len = list_str_len(l);
    if (len == 0)
        return NULL;

    sb = str_builder_create();
    for (i=0; i<len; i++) {
        str_builder_add_str(sb, list_str_get(l, i));
        if (sep != NULL && *sep != '\0') {
            str_builder_add_str(sb, sep);
        }
    }

    if (sep != NULL && *sep != '\0') {
        str_builder_truncate(sb, strlen(sep));
    }

    out = str_builder_dump(sb, NULL);
    str_builder_destroy(sb);
    return out;
}

Now for this wrapper’s data specific function. We’re going to use the string builder to allow joining all elements in the list into a single string.

Testing

Testing a wrapper has the benefit of testing the generic base list too.

#include <stdio.h>
#include <stdlib.h>
#include "list_str.h"

int main(int argc, char **argv)
{
    const char *s1 = "s1";
    const char *s2 = "s2";
    const char *s3 = "s3";
    char       *out;
    list_str_t *ls;

    printf("create\n");
    ls = list_str_create(LIST_STR_NONE);
    printf("\tlen = %zu\n", list_str_len(ls));

    printf("append s1\n");
    list_str_append(ls, s1);
    printf("\tidx 0 = %s\n", list_str_get(ls, 0));
    printf("\tlen = %zu\n", list_str_len(ls));

    printf("append s2\n");
    list_str_append(ls, s2);
    printf("\tidx 0 = %s\n", list_str_get(ls, 0));
    printf("\tidx 1 = %s\n", list_str_get(ls, 1));
    printf("\tlen = %zu\n", list_str_len(ls));

    printf("insert s3 at 1\n");
    list_str_insert(ls, s3, 1);
    printf("\tidx 0 = %s\n", list_str_get(ls, 0));
    printf("\tidx 1 = %s\n", list_str_get(ls, 1));
    printf("\tidx 2 = %s\n", list_str_get(ls, 2));
    printf("\tlen = %zu\n", list_str_len(ls));

    printf("hash s3? %s\n", list_str_index_of(ls, s3, NULL)?"YES":"NO");

    out = list_str_join(ls, ", ");
    printf("join:\n%s\n", out);
    free(out);

    list_str_destroy(ls);
    return 0;
}

Of course our test app is going to put things into a list, check if they’re there and print what’s in it. Looks to me like everything works like it should but don’t take my word for it, run the test yourself.

Next steps

A very handy function to add is a split function that returns a string list. It would be a different type of create that takes in a string and returns a string list.