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|1One 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] = NULLtciu_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:
- Count — scan
sto count the number of non-empty fields - Allocate —
tci_calloc(count + 1, sizeof(char *))for the array; the+1reserves the NULL terminator slot - 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 input —
tciu_split(NULL, '|')returns NULL. The guard at the top oftciu_splithandles this. - Empty string —
count_fields("", sep)returns 0.fill_wordsallocates 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 bycount_fields.
Run the complete tester
make re
bash test.shBoth 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.