thecodingidiot.com

The ReaderSplitting Lines

Splitting Lines

tci_getline returns a line. The line is often structured — fields separated by a delimiter character. A quiz question stored in a text file might look like this:

What does the & operator do in C?|Bitwise AND|Logical OR|Address-of|Dereference|1

One field for the question, four for the options, one for the answer index. tciu_split(line, '|') turns that line into an array:

words[0] = "What does the & operator do in C?"
words[1] = "Bitwise AND"
words[2] = "Logical OR"
words[3] = "Address-of"
words[4] = "Dereference"
words[5] = "1"
words[6] = NULL

tciu_split is also needed in c05 for splitting the PATH environment variable on ':' to locate executables. Introducing it here, while parsing a text file, gives it a natural first use.

split is not part of libc or POSIX — there is no standard function to mirror. That is exactly why it lives in libtciutil rather than libtci. libtci reimplements what the standard library provides; libtciutil holds original utilities that the standard library does not. tciu_split is libtciutil's first function — the tciu_ prefix identifies everything in the library. Two callers across the curriculum is enough to earn it a slot as a named public function, not a static helper.

The algorithm

Three passes over the input string:

  1. Count — scan s to count the number of non-empty fields
  2. Allocatetci_calloc(count + 1, sizeof(char *)) for the array; the +1 reserves the NULL terminator slot
  3. Fill — scan again, copying each field with tci_strndup

Pass 1: counting fields

static int  count_fields(char const *s, char sep)
{
    int     count;
    int     in_field;
 
    count = 0;
    in_field = 0;
    while (*s) {
        if (*s != sep && !in_field) {
            count++;            /* first non-sep character opens a new field */
            in_field = 1;
        } else if (*s == sep) {
            in_field = 0;       /* separator closes the current field */
        }
        s++;
    }
    return (count);
}

The in_field flag prevents consecutive separators from counting as multiple fields. "one||two" counts as 2 fields, not 3.

Pass 2 and 3: allocating and filling

static char **fill_words(char const *s, char sep, int count)
{
    char    **words;
    int     i;
    int     len;
 
    words = tci_calloc(count + 1, sizeof(char *)); /* +1 for NULL terminator */
    if (!words)
        return (NULL);
    i = 0;
    while (*s) {
        if (*s != sep) {
            len = 0;
            while (s[len] && s[len] != sep)       /* measure the field */
                len++;
            words[i++] = tci_strndup(s, len);      /* copy exactly len chars */
            s += len;
        } else {
            s++;                                   /* skip separator */
        }
    }
    words[i] = NULL;                               /* NULL-terminate the array */
    return (words);
}

tci_strndup is in libtci — it was introduced in c01/05 alongside tci_strdup. Include "libtciutil.h" — it pulls in "libtci.h" — and call it directly.

The complete tciu_split

#include "libtciutil.h"
 
char    **tciu_split(char const *s, char sep)
{
    int  count;
 
    if (!s)                              /* NULL input: nothing to split */
        return (NULL);
    count = count_fields(s, sep);
    return (fill_words(s, sep, count));  /* caller owns the returned array */
}

Freeing the result

The caller owns the array and all strings in it. The standard free pattern:

char    **words;
int     i;
 
words = tciu_split(line, '|');
i = 0;
while (words[i])
    free(words[i++]);   /* free each string */
free(words);            /* free the array itself */

tci_calloc zero-initialises the array, so words[count] is NULL even if fill_words exits early — the free loop terminates safely.

Edge cases

  • NULL inputtciu_split(NULL, '|') returns NULL. The guard at the top of tciu_split handles this.
  • Empty stringcount_fields("", sep) returns 0. fill_words allocates a single-slot array containing only NULL.
  • Separator-only string"|||" has no non-empty fields. Returns {NULL}.
  • Leading or trailing separator"|one|two|" counts 2 fields. Leading and trailing separators are ignored by count_fields.

Run the complete tester

make re
bash test.sh

Both suites — tci_getline and tciu_split — should now pass in full. libtci can read a file one line at a time; libtciutil can parse each line into fields. That is enough to drive a game loop from a text file. The next chapter puts those tools to use.

up next

Who Wants to Be a Game Developer?

Who Wants to Be a Game Developer?