]> begriffs open source - sa-parse/blob - src/csv_parser.c
Pull globals into parser state for thread safety
[sa-parse] / src / csv_parser.c
1 #include "csv_parser.h"
2 #include "csv.tab.h"
3 #include <stdlib.h>
4 #include <string.h>
5
6 /* External declarations from Bison/Flex - updated for reentrant API */
7 typedef void *yyscan_t;
8
9 extern int csvlex_init(yyscan_t *scanner);
10 extern int csvlex_destroy(yyscan_t scanner);
11 extern void csvset_in(FILE *file, yyscan_t scanner);
12 extern int csvparse(void *parser_state, yyscan_t scanner);
13
14 /* CSV parser structure - now contains all the state that was previously global */
15 struct csv_parser {
16     char error_message[256];
17     bool has_error;
18     csv_document_t *current_document;
19     bool parse_error;
20     char parse_error_message[256];
21     int error_line;
22     int error_column;
23 };
24
25 /* Helper functions */
26 static csv_field_t *convert_field_list(field_node_t *field_nodes);
27 static csv_record_t *create_record_from_fields(field_node_t *field_nodes);
28 static void free_document_internal(csv_document_t *doc);
29
30 /* Functions called by the Bison parser */
31 void csv_parser_add_record(void *parser_state, field_node_t *fields);
32 void csv_parser_set_error_with_location(void *parser_state, const char *error, int line, int column);
33
34 /* Implementation */
35
36 csv_parser_t *csv_parser_create(void)
37 {
38     csv_parser_t *parser = malloc(sizeof(csv_parser_t));
39     if (!parser) {
40         return NULL;
41     }
42     
43     parser->error_message[0] = '\0';
44     parser->has_error = false;
45     parser->current_document = NULL;
46     parser->parse_error = false;
47     parser->parse_error_message[0] = '\0';
48     parser->error_line = 0;
49     parser->error_column = 0;
50     
51     return parser;
52 }
53
54 void csv_parser_destroy(csv_parser_t *parser)
55 {
56     if (parser) {
57         free(parser);
58     }
59 }
60
61 csv_error_t csv_parser_parse_string(csv_parser_t *parser, const char *input, csv_document_t **document)
62 {
63     if (!parser || !input || !document) {
64         return CSV_ERROR_INVALID_PARAM;
65     }
66     
67     *document = NULL;
68     
69     /* Create a temporary file from the string input */
70     FILE *temp_file = tmpfile();
71     if (!temp_file) {
72         strncpy(parser->error_message, "Failed to create temporary file", sizeof(parser->error_message) - 1);
73         parser->has_error = true;
74         return CSV_ERROR_IO;
75     }
76     
77     /* Write input to temporary file */
78     if (fputs(input, temp_file) == EOF) {
79         fclose(temp_file);
80         strncpy(parser->error_message, "Failed to write to temporary file", sizeof(parser->error_message) - 1);
81         parser->has_error = true;
82         return CSV_ERROR_IO;
83     }
84     
85     /* Reset file position to beginning */
86     rewind(temp_file);
87     
88     /* Parse from the temporary file */
89     csv_error_t result = csv_parser_parse_file(parser, temp_file, document);
90     fclose(temp_file);
91     
92     return result;
93 }
94
95 csv_error_t csv_parser_parse_file(csv_parser_t *parser, FILE *file, csv_document_t **document)
96 {
97     if (!parser || !file || !document) {
98         return CSV_ERROR_INVALID_PARAM;
99     }
100     
101     *document = NULL;
102     parser->has_error = false;
103     parser->error_message[0] = '\0';
104     
105     /* Initialize parser state */
106     parser->current_document = malloc(sizeof(csv_document_t));
107     if (!parser->current_document) {
108         strncpy(parser->error_message, "Memory allocation failed", sizeof(parser->error_message) - 1);
109         parser->has_error = true;
110         return CSV_ERROR_MEMORY;
111     }
112     
113     parser->current_document->records = NULL;
114     parser->current_document->record_count = 0;
115     parser->current_document->header = NULL;
116     parser->parse_error = false;
117     parser->parse_error_message[0] = '\0';
118     parser->error_line = 0;
119     parser->error_column = 0;
120     
121     /* Initialize the reentrant scanner */
122     yyscan_t scanner;
123     int scanner_init_result = csvlex_init(&scanner);
124     if (scanner_init_result != 0) {
125         free(parser->current_document);
126         parser->current_document = NULL;
127         return CSV_ERROR_MEMORY;
128     }
129     
130     /* Set up the lexer input */
131     csvset_in(file, scanner);
132     
133     /* Parse the input */
134     int parse_result = csvparse(parser, scanner);
135     
136     /* Clean up lexer state */
137     csvlex_destroy(scanner);
138     
139     /* Check for errors */
140     if (parse_result != 0 || parser->parse_error) {
141         csv_error_t error_code;
142         
143         switch (parse_result) {
144             case 2:
145                 /* Memory exhaustion in parser */
146                 strncpy(parser->error_message, "Parser memory exhaustion", sizeof(parser->error_message) - 1);
147                 error_code = CSV_ERROR_MEMORY;
148                 break;
149                 
150             case 1:
151                 /* Parse error (syntax error or YYABORT) */
152                 if (parser->parse_error_message[0] != '\0') {
153                     if (parser->error_line > 0) {
154                         snprintf(parser->error_message, sizeof(parser->error_message) - 1,
155                                 "Line %d, column %d: %s", parser->error_line, parser->error_column, parser->parse_error_message);
156                     } else {
157                         strncpy(parser->error_message, parser->parse_error_message, sizeof(parser->error_message) - 1);
158                     }
159                 } else {
160                     if (parser->error_line > 0) {
161                         snprintf(parser->error_message, sizeof(parser->error_message) - 1,
162                                 "Line %d, column %d: Syntax error", parser->error_line, parser->error_column);
163                     } else {
164                         strncpy(parser->error_message, "Syntax error", sizeof(parser->error_message) - 1);
165                     }
166                 }
167                 error_code = CSV_ERROR_PARSE;
168                 break;
169                 
170             default:
171                 /* Other non-zero return or parser->parse_error flag set */
172                 if (parser->parse_error_message[0] != '\0') {
173                     if (parser->error_line > 0) {
174                         snprintf(parser->error_message, sizeof(parser->error_message) - 1,
175                                 "Line %d, column %d: %s", parser->error_line, parser->error_column, parser->parse_error_message);
176                     } else {
177                         strncpy(parser->error_message, parser->parse_error_message, sizeof(parser->error_message) - 1);
178                     }
179                 } else {
180                     if (parser->error_line > 0) {
181                         snprintf(parser->error_message, sizeof(parser->error_message) - 1,
182                                 "Line %d, column %d: Parse error", parser->error_line, parser->error_column);
183                     } else {
184                         strncpy(parser->error_message, "Parse error", sizeof(parser->error_message) - 1);
185                     }
186                 }
187                 error_code = CSV_ERROR_PARSE;
188                 break;
189         }
190         
191         parser->has_error = true;
192         free_document_internal(parser->current_document);
193         parser->current_document = NULL;
194         return error_code;
195     }
196     
197     /* Success - transfer ownership of document */
198     *document = parser->current_document;
199     parser->current_document = NULL;
200     
201     return CSV_SUCCESS;
202 }
203
204 const char *csv_parser_get_error(csv_parser_t *parser)
205 {
206     if (!parser) {
207         return "Invalid parser";
208     }
209     
210     return parser->has_error ? parser->error_message : "No error";
211 }
212
213 void csv_document_free(csv_document_t *document)
214 {
215     free_document_internal(document);
216 }
217
218 const char *csv_error_string(csv_error_t error)
219 {
220     switch (error) {
221         case CSV_SUCCESS:
222             return "Success";
223         case CSV_ERROR_MEMORY:
224             return "Memory allocation error";
225         case CSV_ERROR_PARSE:
226             return "Parse error";
227         case CSV_ERROR_INVALID_PARAM:
228             return "Invalid parameter";
229         case CSV_ERROR_IO:
230             return "I/O error";
231         default:
232             return "Unknown error";
233     }
234 }
235
236 /* Data access helper functions */
237
238 bool csv_document_has_header(const csv_document_t *document)
239 {
240     return document && document->header != NULL;
241 }
242
243 csv_record_t *csv_document_get_first_record(const csv_document_t *document)
244 {
245     return document ? document->records : NULL;
246 }
247
248 csv_record_t *csv_record_get_next(const csv_record_t *record)
249 {
250     return record ? record->next : NULL;
251 }
252
253 csv_field_t *csv_record_get_first_field(const csv_record_t *record)
254 {
255     return record ? record->fields : NULL;
256 }
257
258 csv_field_t *csv_field_get_next(const csv_field_t *field)
259 {
260     return field ? field->next : NULL;
261 }
262
263 const char *csv_field_get_content(const csv_field_t *field)
264 {
265     return field ? field->content : NULL;
266 }
267
268 /* Internal helper functions */
269
270 static csv_field_t *convert_field_list(field_node_t *field_nodes)
271 {
272     if (!field_nodes) {
273         return NULL;
274     }
275     
276     csv_field_t *first_field = NULL;
277     csv_field_t *last_field = NULL;
278     
279     field_node_t *current_node = field_nodes;
280     while (current_node) {
281         csv_field_t *field = malloc(sizeof(csv_field_t));
282         if (!field) {
283             /* Clean up already allocated fields */
284             while (first_field) {
285                 csv_field_t *next = first_field->next;
286                 free(first_field->content);
287                 free(first_field);
288                 first_field = next;
289             }
290             return NULL;
291         }
292         
293         /* Copy content (take ownership) */
294         field->content = current_node->content;
295         current_node->content = NULL; /* Transfer ownership */
296         field->next = NULL;
297         
298         if (!first_field) {
299             first_field = field;
300             last_field = field;
301         } else {
302             last_field->next = field;
303             last_field = field;
304         }
305         
306         current_node = current_node->next;
307     }
308     
309     return first_field;
310 }
311
312 static csv_record_t *create_record_from_fields(field_node_t *field_nodes)
313 {
314     csv_record_t *record = malloc(sizeof(csv_record_t));
315     if (!record) {
316         return NULL;
317     }
318     
319     record->fields = convert_field_list(field_nodes);
320     record->next = NULL;
321     
322     /* Count fields */
323     record->field_count = 0;
324     csv_field_t *current = record->fields;
325     while (current) {
326         record->field_count++;
327         current = current->next;
328     }
329     
330     return record;
331 }
332
333 static void free_document_internal(csv_document_t *doc)
334 {
335     if (!doc) {
336         return;
337     }
338     
339     /* Free header */
340     if (doc->header) {
341         csv_field_t *field = doc->header->fields;
342         while (field) {
343             csv_field_t *next = field->next;
344             free(field->content);
345             free(field);
346             field = next;
347         }
348         free(doc->header);
349     }
350     
351     /* Free records */
352     csv_record_t *record = doc->records;
353     while (record) {
354         csv_record_t *next_record = record->next;
355         
356         csv_field_t *field = record->fields;
357         while (field) {
358             csv_field_t *next_field = field->next;
359             free(field->content);
360             free(field);
361             field = next_field;
362         }
363         
364         free(record);
365         record = next_record;
366     }
367     
368     free(doc);
369 }
370
371 /* Functions called by the Bison parser */
372
373 void csv_parser_add_record(void *parser_state, field_node_t *fields)
374 {
375     csv_parser_t *parser = (csv_parser_t *)parser_state;
376     if (!parser || !parser->current_document || parser->parse_error) {
377         return;
378     }
379     
380     /* Skip empty records (single empty field) */
381     if (fields && !fields->next && fields->content && fields->content[0] == '\0') {
382         return;
383     }
384     
385     csv_record_t *record = create_record_from_fields(fields);
386     if (!record) {
387         parser->parse_error = true;
388         strncpy(parser->parse_error_message, "Memory allocation failed while creating record", sizeof(parser->parse_error_message) - 1);
389         return;
390     }
391     
392     /* Add to document */
393     if (!parser->current_document->records) {
394         parser->current_document->records = record;
395     } else {
396         /* Find the last record and append */
397         csv_record_t *last = parser->current_document->records;
398         while (last->next) {
399             last = last->next;
400         }
401         last->next = record;
402     }
403     
404     parser->current_document->record_count++;
405 }
406
407 void csv_parser_set_error_with_location(void *parser_state, const char *error, int line, int column)
408 {
409     csv_parser_t *parser = (csv_parser_t *)parser_state;
410     if (!parser) {
411         return;
412     }
413     
414     parser->parse_error = true;
415     parser->error_line = line;
416     parser->error_column = column;
417     if (error) {
418         strncpy(parser->parse_error_message, error, sizeof(parser->parse_error_message) - 1);
419         parser->parse_error_message[sizeof(parser->parse_error_message) - 1] = '\0';
420     }
421 }
422
423 csv_error_t csv_document_set_first_record_as_header(csv_document_t *document)
424 {
425     if (!document || !document->records) {
426         return CSV_ERROR_INVALID_PARAM;
427     }
428     
429     /* Move first record to header */
430     document->header = document->records;
431     document->records = document->records->next;
432     document->header->next = NULL;
433     document->record_count--;
434     
435     return CSV_SUCCESS;
436 }