]> begriffs open source - sa-parse/blob - src/csv.y
Convert to libderp
[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         /* Use vector from collection library instead of custom linked lists */
56         #include <derp/common.h>
57         #include <derp/vector.h>
58 }
59
60 /* Add another argument in yyparse() so that we
61    can communicate any parser state to the caller.
62    We can't return the result directly, since the
63    return value is already reserved as an int, with
64    0=success, 1=error, 2=nomem */
65
66 %parse-param {void *parser_state}
67
68 /* param adds an extra param to yyparse (like parse-param)
69    but also causes yyparse to send the value to yylex.
70    In our case the caller will initialize their own scanner
71    instance and pass it through */
72
73 %param {void *scanner}
74
75 %code {
76         /* Function declarations - updated for pure API */
77         int csverror(CSVLTYPE *locp, void *parser_state, void *scanner, const char *s);
78         int csvlex(void *lval, CSVLTYPE *locp, void *scanner);
79         bool one_empty_field(vector *fields);
80         
81         /* Interface with the CSV parser library */
82         void csv_parser_add_record(void *parser_state, vector *fields);
83         void csv_parser_set_error_with_location(void *parser_state, const char *error, int line, int column);
84 }
85
86 %union
87 {
88         char *str;
89         vector *fields;
90 }
91
92 %token CRLF
93 %token <str> FIELD
94 %type <str> field.opt
95 %type <fields> record
96
97 /* Bison memory management - automatically free memory on errors */
98 %destructor { free($$); } <str>
99 %destructor { if ($$) v_free($$); } <fields>
100
101 %%
102
103 file :
104   consumed_record
105 | file CRLF consumed_record
106 ;
107
108 /* A record can be constructed in two ways, but we want to
109    run the same side effect for either case. We add an
110    intermediate non-terminal symbol "consumed_record" just
111    to perform the action. In library code, this would be a
112    good place to send the the record to a callback function. */
113
114 consumed_record :
115   record {
116         /* a record comprised of exactly one blank field is a
117            blank record, which we can skip */
118         if (!one_empty_field($1))
119         {
120                 /* Send the record to the parser library */
121                 csv_parser_add_record(parser_state, $1);
122         }
123         /* Memory is automatically freed by %destructor */
124   }
125 ;
126
127 record :
128   field.opt {
129         /* Create vector and add first field */
130         $$ = v_new();
131         if (!$$) YYNOMEM;
132         /* Set destructor for automatic cleanup */
133         v_dtor($$, derp_free, NULL);
134         if (!v_append($$, $1)) {
135                 free($1);
136                 v_free($$);
137                 YYNOMEM;
138         }
139   }
140 | record ',' field.opt {
141         /* Append field to existing vector */
142         if (!v_append($1, $3)) {
143                 free($3);
144                 YYNOMEM;
145         }
146         $$ = $1;
147   }
148 ;
149
150 field.opt :
151   %empty { 
152         $$ = calloc(1,1);
153         if (!$$) YYNOMEM;
154   }
155 | FIELD
156 ;
157
158 %%
159
160 bool one_empty_field(vector *fields)
161 {
162         return fields && v_length(fields) == 1 && 
163                v_at(fields, 0) && ((char*)v_at(fields, 0))[0] == '\0';
164 }
165
166 int csverror(CSVLTYPE *locp, void *parser_state, void *scanner, const char *s)
167 {
168         (void)scanner;
169         
170         /* Use location information if available */
171         if (locp) {
172                 csv_parser_set_error_with_location(parser_state, s, locp->first_line, locp->first_column);
173         } else {
174                 csv_parser_set_error_with_location(parser_state, s, 0, 0);
175         }
176         return 0;
177 }