]> begriffs open source - sa-parse/blob - src/csv.y
Pull globals into parser state for thread safety
[sa-parse] / src / csv.y
1 /* csv.y - CSV parser using Bison */
2
3 /* a "pure" api means communication variables like yylval
4    won't be global variables, and yylex is assumed to
5    have a different signature */
6
7 %define api.pure true
8
9 /* change prefix of symbols from yy to "csv" to avoid
10    clashes with any other parsers we may want to link */
11
12 %define api.prefix {csv}
13
14 /* generate much more meaningful errors rather than the
15    uninformative string "syntax error" */
16
17 %define parse.error verbose
18
19 /* enable location tracking for better error reporting */
20 %locations
21
22 /* Bison offers different %code insertion locations in
23    addition to yacc's %{ %} construct.
24
25    The "top" location is good for headers and feature
26    flags like the _XOPEN_SOURCE we use here */
27
28 %code top {
29         /* XOPEN for strdup */
30         #define _XOPEN_SOURCE 600
31         #include <stdio.h>
32         #include <stdlib.h>
33         #include <string.h>
34
35         /* Bison versions 3.7.5 and above provide the YYNOMEM
36            macro to allow our actions to signal the unlikely
37            event that they couldn't allocate memory. Thanks
38            to the Bison team for adding this feature at my
39            request. :) YYNOMEM causes yyparse() to return 2.
40
41            The following conditional define allows us to use
42            the functionality in earlier versions too. */
43
44         #ifndef YYNOMEM
45         #define YYNOMEM goto yyexhaustedlab
46         #endif
47 }
48
49 %code requires {
50         #include <stdbool.h>
51         #include <stdio.h>
52         #include <stdlib.h>
53         #include <string.h>
54
55         /* Simple linked list structures for CSV parsing */
56         typedef struct field_node {
57                 char *content;
58                 struct field_node *next;
59         } field_node_t;
60
61         typedef struct record_node {
62                 field_node_t *fields;
63                 struct record_node *next;
64         } record_node_t;
65 }
66
67 /* Add another argument in yyparse() so that we
68    can communicate any parser state to the caller.
69    We can't return the result directly, since the
70    return value is already reserved as an int, with
71    0=success, 1=error, 2=nomem */
72
73 %parse-param {void *parser_state}
74
75 /* param adds an extra param to yyparse (like parse-param)
76    but also causes yyparse to send the value to yylex.
77    In our case the caller will initialize their own scanner
78    instance and pass it through */
79
80 %param {void *scanner}
81
82 %code {
83         /* Function declarations - updated for pure API */
84         int csverror(CSVLTYPE *locp, void *parser_state, void *scanner, const char *s);
85         int csvlex(void *lval, CSVLTYPE *locp, void *scanner);
86         bool one_empty_field(field_node_t *fields);
87         field_node_t *create_field(char *content);
88         void append_field_to_list(field_node_t **head, char *content);
89         void free_field_list(field_node_t *fields);
90         size_t count_fields(field_node_t *fields);
91         
92         /* Interface with the CSV parser library */
93         void csv_parser_add_record(void *parser_state, field_node_t *fields);
94         void csv_parser_set_error_with_location(void *parser_state, const char *error, int line, int column);
95 }
96
97 %union
98 {
99         char *str;
100         field_node_t *fields;
101 }
102
103 %token CRLF
104 %token <str> FIELD
105 %type <str> field.opt
106 %type <fields> record
107
108 /* Bison memory management - automatically free memory on errors */
109 %destructor { free($$); } <str>
110 %destructor { free_field_list($$); } <fields>
111
112 %%
113
114 file :
115   consumed_record
116 | file CRLF consumed_record
117 ;
118
119 /* A record can be constructed in two ways, but we want to
120    run the same side effect for either case. We add an
121    intermediate non-terminal symbol "consumed_record" just
122    to perform the action. In library code, this would be a
123    good place to send the the record to a callback function. */
124
125 consumed_record :
126   record {
127         /* a record comprised of exactly one blank field is a
128            blank record, which we can skip */
129         if (!one_empty_field($1))
130         {
131                 /* Send the record to the parser library */
132                 csv_parser_add_record(parser_state, $1);
133         }
134         /* Memory is automatically freed by %destructor */
135   }
136 ;
137
138 record :
139   field.opt {
140         /* Create first field node */
141         $$ = create_field($1);
142         if (!$$) YYNOMEM;
143   }
144 | record ',' field.opt {
145         /* Append field to existing list */
146         append_field_to_list(&$1, $3);
147         $$ = $1;
148   }
149 ;
150
151 field.opt :
152   %empty { 
153         $$ = calloc(1,1);
154         if (!$$) YYNOMEM;
155   }
156 | FIELD
157 ;
158
159 %%
160
161 field_node_t *create_field(char *content)
162 {
163         field_node_t *field = malloc(sizeof(field_node_t));
164         if (!field) {
165                 free(content);
166                 /* Can't use YYNOMEM here since we're not in a parser action */
167                 return NULL;
168         }
169         
170         field->content = content; /* Take ownership of the string */
171         field->next = NULL;
172         return field;
173 }
174
175 void append_field_to_list(field_node_t **head, char *content)
176 {
177         field_node_t *new_field = create_field(content);
178         if (!new_field) return;
179         
180         if (!*head) {
181                 *head = new_field;
182                 return;
183         }
184         
185         /* Find the last field in the list */
186         field_node_t *current = *head;
187         while (current->next) {
188                 current = current->next;
189         }
190         current->next = new_field;
191 }
192
193 void free_field_list(field_node_t *fields)
194 {
195         field_node_t *current = fields;
196         while (current) {
197                 field_node_t *next = current->next;
198                 free(current->content);
199                 free(current);
200                 current = next;
201         }
202 }
203
204 size_t count_fields(field_node_t *fields)
205 {
206         size_t count = 0;
207         field_node_t *current = fields;
208         while (current) {
209                 count++;
210                 current = current->next;
211         }
212         return count;
213 }
214
215 bool one_empty_field(field_node_t *fields)
216 {
217         return fields && !fields->next && 
218                fields->content && fields->content[0] == '\0';
219 }
220
221 int csverror(CSVLTYPE *locp, void *parser_state, void *scanner, const char *s)
222 {
223         (void)scanner;
224         
225         /* Use location information if available */
226         if (locp) {
227                 csv_parser_set_error_with_location(parser_state, s, locp->first_line, locp->first_column);
228         } else {
229                 csv_parser_set_error_with_location(parser_state, s, 0, 0);
230         }
231         return 0;
232 }