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