Zápočtový úkol z Jazyka C (KMI/XJC)

Naprogramujte modul pro práci s řetězci. Tento modul exportuje strukturu pro řetězec.

typedef struct
{
    char *data;
    int   len;
} String;

Dále struktury String_Builder a String_Iterator, jejichž položky studenti navrhnou sami.

String_Builder

String_Builder (dále SB) si lze přestavit jako řetězec, který možno prodlužovat tiskem na jeho konec. (Funkce s ním pracující sami zajišťují, aby měl vždy alokován dostatek paměti.) Pro práci s SB implementujte následující funkce.

//
//  Inicializuje sb. Nealokuje žádnou paměť pro uchovávání obsahu.
//  Po inicialize SB vypadá, jakoby obsahoval prázdný řetězec.
//  (Po převodu na String, viz níže, je výsledek prázdný řetězec.)
void sb_init(String_Builder* sb); 

//
//  Vytvoří SB jako kopii existujícího řetězce
//
String_Builder sb_from_string(String s);

//
//  Neuvolňuje žádnou paměť SB, pouze nastaví jeho položky tak, aby obsah
//  SB odpovídal prázdnému řetězci.
//
void sb_clear(String_Builder* sb);

//
//  Uvolní paměť, kterou sb používá pro uchování řetězce.
//
void sb_destroy(String_Builder* sb);

//
//  Funkce analogická printf ze stdio.h, která tiskne do SB.
//  Ve formátovacím řetězci podporuje následující direktivy a typy
//  - #i     typ int,
//  - #S     řetězec typu String,
//  - #s     řetězec typu char*, 
//  - #c     znak typu char,
//
//  Dále podporuje speciální znaky '\t', '\n', '\"' jako printf.
//
void sb_printf(String_Builder*, String format, ...);

//
// Transformuje SB na String, jehož data jsou nově alokována (tj. SB lze pořád používat).
//
String sb_clone_to_string(String_Builder sb); 

Příklad použití

String_Builder sb;
sb_init(&sb);

sb_printf(&sb, "#i #i #s\ndalsi radek", 10, 3, "ahoj svete");
String first = sb_clone_to_string(sb);

sb_clear(&sb);
sb_printf(&sb, "predtim bylo %S", first);
 
String second = sb_clone_to_string(sb);

sb_destroy(&sb);

// 
// předpokládejme, že funkce str_print vytiskne String
// Potom následující
//
str_print(second);
//
// vede k tisku
//
predtim bylo 10 3 ahoj svete
dalsi radek

String

Implementujte následující funkce pro práci s řetězci.

//
// Následující čtyři funkce manuálně ALOKUJÍ paměť pro datové položky řetězců,
// vytváří tak nové kopie.
//

// transformace C řetězce na String a zpět
String  str_clone_from_cstring(char *cstring);
char*   str_clone_to_cstring(String *);

// vytvoření klonu řetězce
String  str_clone(String src);

// spojí řetězce obsažené v poli array (s n prvky) do jednoho, 
// přitom je od sebe oddělí pomocí řetězce sep;
String  str_join(String array[], int n, String sep);

//
// Funkce uvolní vnitřní paměť řetězce a nastaví jej tak,
// aby vypadal jako prázdný řetězec.
//
void str_destroy(String* src);

//
// Následující tři funkce NEALOKUJÍ paměť pro `data` řetězce, ale jejich položka `data`
// ukazuje do této položky jiného řetězce (nebo na C řetězec).
//

// Vytvoří String z C řetězce
String  str_wrap(char* cstring);

// Rozdělí `src` na části oddělené oddělovačem `sep`, a vrátí je jako pole.
// Jejich počet zapíše do n. 
String* str_split(String src, String sep, int *n);

// Zepředu a zezadu src odstraní všechny znaky obsažené v cutset
// a výsledný řetězec vrátí jako výsledek.
String  str_trim(String src, String cutset);

String_Iterator

Slouží pro procházení částí řetězce, které jsou odděleny oddělovačem. Lze předpokládat, že během procházení se procházený řetězec nemění. Jsou-li vedle sebe dva oddělovače, bereme to tak, že je mezi nimi prázdný řetězec. Je-li oddělovač na konci nebo začátku řetězce, bereme to tak, že je za nebo před ním prázdný řetězec. Neobsahuje-li řetězec oddělovač, skládá se pouze z jedné části.

//
// inicializuje iterátor pro řetězec src a oddělovač delimiter.
//
String_Iterator si_create(Strint src, String delimiter);

//
// Vrátí 1 a nastaví slice na další část řetězce. 
// Pokud už byly projity všechny části řetězce, vrátí 0.
// 
int si_next(String_Iterator* s, String *slice);

//
// Restartuje iteraci od začátku
//
void si_restart(String_Iterator* s);

Příklad použití. Následující kód

//
// předpokládejme, že funkce str_println vytiskne String a potom znak nového řádku
//

String foo = str_clone_from_cstring("resim-*-domaci-*-ulohu");
String delim = str_clone_from_cstring("-*-");

String_Iterator si = si_create(foo, delim);
String s;
while( si_next(&si, &s) )
{
    str_println(s);
}
// tady NENÍ nutné volat str_destroy(&s)

vytiskne následující.

resim
domaci
ulohu

Modul s testy

Součástí řešení bude i modul s testy, které otestují funkčnost části modulu pro práci s řetězci.

Spouštění testů půjde ovládat pomocí funkce.

//
// spustí testy a vypíše vhodné informace [test prosel/neprosel atd.]
//
void run_tests();

Očekává se, že student navrhne rozumné testy, které pokryjí i okrajové případy.

Knihovní funkce

Ze string.h jsou povoleny pouze funkce pro práci s pamětí (začínají mem); z stdio.h pouze printf; z stdlib.h funkce pro alokaci a dealokaci paměti. Dále je povolena assert.h, math.h a stdarg.h.

Jiné knihovní funkce povoleny nejsou.

Struktura projektu

Odevzdejte adresář se soubory strings.h, strings.c (modul pro práci s řetězci), test.c, test.h (modul s testy), volitelně i soubor README (textový soubor s případnými dalšími informacemi). Při nedodržení předchozího je úkol považován za nesplněný.

Moduly musí být z pohledu jazyka C správně strukturovány (např. soukromé funkce a data jsou static).

Úkol budu kontrolovat s pomocí gcc, sanitizeru a programu valgrind.