aboutsummaryrefslogtreecommitdiff
path: root/lib/src
diff options
context:
space:
mode:
authorThomas Schmucker <ts@its1.de>2026-06-21 17:20:13 +0200
committerThomas Schmucker <ts@its1.de>2026-06-21 17:20:13 +0200
commit08a5f3441675c446482b473777cbba53d5191f20 (patch)
treebb462b2439317d8c3be3e542501aa3211357045c /lib/src
parent7d956c533e9fd7d3bbb9a4a8b7f89f0fe54d4bc3 (diff)
downloadlibcsv-08a5f3441675c446482b473777cbba53d5191f20.tar.gz
libcsv-08a5f3441675c446482b473777cbba53d5191f20.tar.bz2
libcsv-08a5f3441675c446482b473777cbba53d5191f20.zip
rework: add testing frameworkrework
Diffstat (limited to 'lib/src')
-rw-r--r--lib/src/csv.c464
1 files changed, 0 insertions, 464 deletions
diff --git a/lib/src/csv.c b/lib/src/csv.c
deleted file mode 100644
index ba78dbd..0000000
--- a/lib/src/csv.c
+++ /dev/null
@@ -1,464 +0,0 @@
1#include "csv.h"
2
3#include <assert.h>
4#include <stddef.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8
9// === Compile time options ===
10
11#ifndef CSV_DEFAULT_DELIMITER
12# define CSV_DEFAULT_DELIMITER '"'
13#endif
14
15#ifndef CSV_DEFAULT_SEPARATOR
16# define CSV_DEFAULT_SEPARATOR ','
17#endif
18
19// === some useful Makros ===
20
21#define UNUSED(x) (void) (x) // mark a parameter as 'unused'
22#define STR(s) #s // Stringify a Makro
23#define XSTR(s) STR(s)
24
25// === Semantic Version Information ===
26
27#define CSV_VER_MAJOR 1
28#define CSV_VER_MINOR 0
29#define CSV_VER_PATCH 0
30#define CSV_VER_APPENDIX "-dev"
31
32const char csv_version[] = XSTR(CSV_VER_MAJOR) "." XSTR(CSV_VER_MINOR) "." XSTR(CSV_VER_PATCH) CSV_VER_APPENDIX "\0" __DATE__ "\0" __TIME__;
33
34// === CSV-MEMORY Interface ===
35
36static void *
37csv_mem_reallocate(void *ptr, size_t num, size_t size, void *cb_arg)
38{
39 UNUSED(cb_arg);
40
41 return reallocarray(ptr, num, size);
42}
43
44static void
45csv_mem_free(void *ptr, size_t num, size_t size, void *cb_arg)
46{
47 UNUSED(num);
48 UNUSED(size);
49 UNUSED(cb_arg);
50
51 free(ptr);
52}
53
54// === CSV-OPTIONS Interface ===
55
56const csv_options_t csv_default_options = {
57 .field_delimiter = CSV_DEFAULT_DELIMITER,
58 .field_separator = CSV_DEFAULT_SEPARATOR,
59 .cb_error = NULL,
60 .cb_reallocate = csv_mem_reallocate,
61 .cb_free = csv_mem_free
62};
63
64// === CSV-ERROR Interface ===
65
66static void
67csv_fatal_error(csv_err_t csv_err, const csv_options_t *const csv_options)
68{
69 if ( csv_options->cb_error != NULL ) {
70 (*csv_options->cb_error)(csv_err, csv_options->cb_error_arg);
71 }
72 (void) fprintf(stderr, "fatal error: %s\n", csv_err_str(csv_err));
73 abort();
74}
75
76// === CSV-STRING Interface ===
77
78static inline void
79csv_string_init(csv_string_t *csv_string)
80{
81 assert(csv_string != NULL);
82
83 csv_string->str = NULL;
84 csv_string->cap = 0;
85 csv_string->pos = 0;
86}
87
88static inline void
89csv_string_reset(csv_string_t *csv_string)
90{
91 assert(csv_string != NULL);
92
93 csv_string->pos = 0;
94}
95
96static inline int
97csv_string_isempty(csv_string_t *csv_string)
98{
99 assert(csv_string != NULL);
100
101 return (csv_string->pos == 0) ? 1 : 0;
102}
103
104static inline size_t
105growth_strategy(size_t current_cap)
106{
107 static const size_t INITIAL_CAP = 16;
108
109 return (current_cap == 0) ? INITIAL_CAP : (current_cap * 3) / 2;
110}
111
112static inline void
113csv_string_grow_if_needed(csv_string_t *csv_string, const csv_options_t *const csv_options)
114{
115 assert(csv_string != NULL);
116 assert(csv_options != NULL);
117
118 if ( csv_string->pos == csv_string->cap ) {
119 size_t cap = growth_strategy(csv_string->cap);
120 char *str = csv_options->cb_reallocate(csv_string->str, cap, 1, csv_options->cb_memory_arg);
121 if ( str == NULL ) {
122 csv_fatal_error(CSV_ERR_OUT_OF_MEMORY, csv_options);
123 return;
124 }
125 csv_string->str = str;
126 csv_string->cap = cap;
127 }
128}
129
130static inline void
131csv_string_append(csv_string_t *csv_string, int ch, const csv_options_t *const csv_options)
132{
133 assert(csv_string != NULL);
134 assert(csv_options != NULL);
135
136 csv_string_grow_if_needed(csv_string, csv_options);
137
138 csv_string->str[csv_string->pos++] = (char) ch; // append char
139}
140
141static inline void
142csv_string_free(csv_string_t *csv_string, const csv_options_t *const csv_options)
143{
144 assert(csv_string != NULL);
145 assert(csv_options != NULL);
146
147 csv_options->cb_free(csv_string->str, csv_string->cap, 1, csv_options->cb_memory_arg);
148
149 // call *_init() for sane default values; prevent possible double-free
150 csv_string_init(csv_string);
151}
152
153// === CSV-FIELD Interface ===
154
155static inline void
156csv_field_init(csv_field_t *csv_field)
157{
158 assert(csv_field != NULL);
159
160 csv_field->fields = NULL;
161 csv_field->cap = 0;
162 csv_field->pos = 0;
163}
164
165static inline void
166csv_field_reset(csv_field_t *csv_field)
167{
168 assert(csv_field != NULL);
169
170 csv_field->pos = 0;
171}
172
173static inline void
174csv_field_grow_if_needed(csv_field_t *csv_field, const csv_options_t *const csv_options)
175{
176 assert(csv_field != NULL);
177 assert(csv_options != NULL);
178
179 if ( csv_field->pos == csv_field->cap ) {
180 size_t cap = growth_strategy(csv_field->cap);
181 size_t *fields = csv_options->cb_reallocate(csv_field->fields, cap, sizeof(csv_field->fields[0]), csv_options->cb_memory_arg);
182 if ( fields == NULL ) {
183 csv_fatal_error(CSV_ERR_OUT_OF_MEMORY, csv_options);
184 return;
185 }
186 csv_field->fields = fields;
187 csv_field->cap = cap;
188 }
189}
190
191static inline void
192csv_field_append(csv_field_t *csv_field, size_t idx, const csv_options_t *const csv_options)
193{
194 assert(csv_field != NULL);
195 assert(csv_options != NULL);
196
197 csv_field_grow_if_needed(csv_field, csv_options);
198
199 csv_field->fields[csv_field->pos++] = idx; // append index
200}
201
202static inline void
203csv_field_free(csv_field_t *csv_field, const csv_options_t *const csv_options)
204{
205 assert(csv_field != NULL);
206 assert(csv_options != NULL);
207
208 csv_options->cb_free(csv_field->fields, csv_field->cap, sizeof(csv_field->fields[0]), csv_options->cb_memory_arg);
209
210 csv_field_init(csv_field);
211}
212
213// === CSV Interface ===
214
215void
216csv_init(csv_t *csv)
217{
218 assert(csv != NULL);
219
220 csv_init_opt(csv, NULL);
221}
222
223void
224csv_init_opt(csv_t *csv, const csv_options_t *const csv_options)
225{
226 assert(csv != NULL);
227
228 if ( csv_options != NULL ) {
229 csv->csv_options = csv_options;
230 }
231 else {
232 csv->csv_options = &csv_default_options;
233 }
234
235 csv_string_init(&csv->csv_string);
236 csv_field_init(&csv->csv_field);
237}
238
239void
240csv_cleanup(csv_t *csv)
241{
242 assert(csv != NULL);
243 assert(csv->csv_options != NULL);
244
245 csv_string_free(&csv->csv_string, csv->csv_options);
246 csv_field_free(&csv->csv_field, csv->csv_options);
247}
248
249size_t
250csv_nfields(const csv_t *const csv)
251{
252 assert(csv != NULL);
253
254 return csv->csv_field.pos;
255}
256
257const char *
258csv_field(const csv_t *const csv, size_t idx)
259{
260 assert(csv != NULL);
261 assert(idx >= 0 && idx < csv->csv_field.pos);
262
263 if ( idx >= csv->csv_field.pos ) {
264 csv_fatal_error(CSV_ERR_OUT_OF_RANGE, csv->csv_options);
265 return NULL;
266 }
267 return &csv->csv_string.str[csv->csv_field.fields[idx]];
268}
269
270size_t
271csv_read(csv_t *csv, FILE *in)
272{
273 assert(csv != NULL);
274 assert(in != NULL);
275
276 // initialize if needed...
277 if ( csv->csv_options == NULL ) {
278 csv_init(csv);
279 }
280
281 if ( ferror(in) ) {
282 csv_fatal_error(CSV_ERR_IO_READ, csv->csv_options);
283 return 0;
284 }
285
286 // do not try to read if EOF has already been seen
287 if ( feof(in) ) {
288 csv_cleanup(csv);
289 return 0;
290 }
291
292 enum {
293 STATE_START_FIELD,
294 STATE_QUOTED_FIELD,
295 STATE_SIMPLE_FIELD,
296 STATE_END_FIELD,
297 STATE_END_LINE,
298 STATE_END_FILE,
299 };
300
301 register const int DELIM = csv->csv_options->field_delimiter;
302 register const int SEP = csv->csv_options->field_separator;
303
304 csv_string_reset(&csv->csv_string);
305 csv_field_reset(&csv->csv_field);
306
307 for ( int state = STATE_START_FIELD;; ) {
308 int chr;
309
310 switch ( state ) {
311 case STATE_START_FIELD:
312 csv_field_append(&csv->csv_field, csv->csv_string.pos, csv->csv_options);
313
314 chr = getc(in);
315 if ( chr == EOF ) {
316 state = STATE_END_FILE;
317 }
318 else if ( chr == '\r' ) { // test for CR..
319 chr = getc(in);
320 if ( chr != '\n' ) { // ..LF
321 (void) ungetc(chr, in);
322 }
323 state = STATE_END_LINE;
324 }
325 else if ( chr == '\n' ) {
326 state = STATE_END_LINE;
327 }
328 else if ( chr == SEP ) {
329 state = STATE_END_FIELD;
330 }
331 else if ( chr == DELIM ) {
332 state = STATE_QUOTED_FIELD;
333 }
334 else {
335 if ( chr != '\0' ) {
336 csv_string_append(&csv->csv_string, chr, csv->csv_options);
337 }
338 state = STATE_SIMPLE_FIELD;
339 }
340 break;
341
342 case STATE_QUOTED_FIELD:
343 do {
344 chr = getc(in);
345 if ( chr == EOF ) {
346 state = STATE_END_FILE;
347 }
348 else if ( chr == DELIM ) {
349 chr = getc(in);
350 if ( chr == EOF ) {
351 state = STATE_END_FILE;
352 }
353 else if ( chr == DELIM ) {
354 csv_string_append(&csv->csv_string, DELIM, csv->csv_options);
355 }
356 else if ( chr == SEP ) {
357 state = STATE_END_FIELD;
358 }
359 else if ( chr == '\r' ) {
360 chr = getc(in);
361 if ( chr != '\n' ) {
362 (void) ungetc(chr, in);
363 }
364 state = STATE_END_LINE;
365 }
366 else if ( chr == '\n' ) {
367 state = STATE_END_LINE;
368 }
369 else {
370 csv_string_append(&csv->csv_string, DELIM, csv->csv_options);
371 (void) ungetc(chr, in); // we have read too far... Put the character back!
372 }
373 }
374 else {
375 if ( chr != '\0' ) {
376 csv_string_append(&csv->csv_string, chr, csv->csv_options);
377 }
378 }
379 } while ( state == STATE_QUOTED_FIELD );
380 break;
381
382 case STATE_SIMPLE_FIELD:
383 do {
384 chr = getc(in);
385 if ( chr == EOF ) {
386 state = STATE_END_FILE;
387 }
388 else if ( chr == SEP ) {
389 state = STATE_END_FIELD;
390 }
391 else if ( chr == '\r' ) {
392 chr = getc(in);
393 if ( chr != '\n' ) {
394 (void) ungetc(chr, in);
395 }
396 state = STATE_END_LINE;
397 }
398 else if ( chr == '\n' ) {
399 state = STATE_END_LINE;
400 }
401 else {
402 if ( chr != '\0' ) {
403 csv_string_append(&csv->csv_string, chr, csv->csv_options);
404 }
405 }
406 } while ( state == STATE_SIMPLE_FIELD );
407 break;
408
409 case STATE_END_FIELD:
410 csv_string_append(&csv->csv_string, '\0', csv->csv_options);
411 state = STATE_START_FIELD;
412 break;
413
414 case STATE_END_LINE:
415 csv_string_append(&csv->csv_string, '\0', csv->csv_options);
416 return csv->csv_field.pos;
417
418 case STATE_END_FILE:
419 if ( ferror(in) ) {
420 csv_fatal_error(CSV_ERR_IO_READ, csv->csv_options);
421 return 0;
422 }
423
424 if ( csv_string_isempty(&csv->csv_string) ) {
425 csv_cleanup(csv);
426 return 0; // EOF reached
427 }
428
429 /*
430 * The last data record was not terminated with a NEWLINE-Symbol.
431 * So we can't signal End-Of-File for now. Terminate the current
432 * field and return the number of fields processed so far.
433 */
434 csv_string_append(&csv->csv_string, '\0', csv->csv_options);
435
436 return csv->csv_field.pos;
437
438 default:
439 assert(!"this should never be happen...");
440 break;
441 }
442 }
443 // NOT REACHED
444}
445
446const char *
447csv_err_str(csv_err_t csv_err)
448{
449 switch ( csv_err ) {
450 case CSV_ERR_OK:
451 return "no error";
452 case CSV_ERR_OUT_OF_MEMORY:
453 return "out of memory";
454 case CSV_ERR_OUT_OF_RANGE:
455 return "index out of range";
456 case CSV_ERR_IO_READ:
457 return "read error";
458 case CSV_ERR_IO_WRITE:
459 return "write error";
460 default:
461 return "unknown error";
462 }
463 // NOT REACHED
464}