]> begriffs open source - sa-parse/blob - src/ical.y
ical parser
[sa-parse] / src / ical.y
1 /* ical.y - iCalendar 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 "ical" to avoid
10    clashes with any other parsers we may want to link */
11
12 %define api.prefix {ical}
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         #include <ctype.h>
35
36         /* Bison versions 3.7.5 and above provide the YYNOMEM
37            macro to allow our actions to signal the unlikely
38            event that they couldn't allocate memory. Thanks
39            to the Bison team for adding this feature at my
40            request. :) YYNOMEM causes yyparse() to return 2.
41
42            The following conditional define allows us to use
43            the functionality in earlier versions too. */
44
45         #ifndef YYNOMEM
46         #define YYNOMEM goto yyexhaustedlab
47         #endif
48 }
49
50 %code requires {
51         #include <stdbool.h>
52         #include <stdio.h>
53         #include <stdlib.h>
54         #include <string.h>
55         
56         /* Use vector from collection library instead of custom linked lists */
57         #include <derp/common.h>
58         #include <derp/vector.h>
59         
60         /* Structure for property parameters */
61         typedef struct {
62                 char *name;
63                 char *value;
64         } property_parameter_t;
65
66         /* Structure for properties */
67         typedef struct {
68                 char *name;
69                 vector *parameters;  /* vector of property_parameter_t* */
70                 char *value;
71         } property_t;
72
73         /* Structure for components */
74         typedef struct {
75                 char *type;
76                 vector *properties;      /* vector of property_t* */
77                 vector *subcomponents;   /* vector of component_t* */
78         } component_t;
79 }
80
81 /* Add another argument in yyparse() so that we
82    can communicate any parser state to the caller.
83    We can't return the result directly, since the
84    return value is already reserved as an int, with
85    0=success, 1=error, 2=nomem */
86
87 %parse-param {void *parser_state}
88
89 /* param adds an extra param to yyparse (like parse-param)
90    but also causes yyparse to send the value to yylex.
91    In our case the caller will initialize their own scanner
92    instance and pass it through */
93
94 %param {void *scanner}
95
96 %code {
97         /* Function declarations - updated for pure API */
98         int icalerror(ICALLTYPE *locp, void *parser_state, void *scanner, const char *s);
99         int icallex(void *lval, ICALLTYPE *locp, void *scanner);
100         
101         /* Interface with the iCalendar parser library */
102         void ical_parser_add_component(void *parser_state, const char *type, vector *properties, vector *subcomponents);
103         void ical_parser_add_property(void *parser_state, const char *name, vector *parameters, const char *value);
104         void ical_parser_set_error_with_location(void *parser_state, const char *error, int line, int column);
105         
106         /* Helper functions */
107         static char *concatenate_value_parts(vector *parts);
108         static void free_property_parameter(void *param, void *aux);
109 }
110
111 %union
112 {
113         char *str;
114         vector *list;
115         property_parameter_t *param;
116         property_t *prop;
117         component_t *comp;
118 }
119
120 %token CRLF
121 %token BEGIN_TOKEN END_TOKEN
122 %token <str> FOLDING_WS
123 %token <str> COMPONENT_NAME
124 %token <str> PROPERTY_NAME
125 %token <str> PARAM_VALUE
126
127 %type <str> property_value value_part
128 %type <list> property_value_parts parameter_values
129 %type <param> parameter
130 %type <list> parameters parameters_opt
131 %type <prop> property
132 %type <list> properties
133 %type <comp> component
134 %type <list> components
135
136 /* Bison memory management - automatically free memory on errors */
137 %destructor { free($$); } <str>
138 %destructor { if ($$) v_free($$); } <list>
139 %destructor { 
140         if ($$) {
141                 free($$->name);
142                 free($$->value);
143                 free($$);
144         }
145 } <param>
146 %destructor {
147         if ($$) {
148                 free($$->name);
149                 if ($$->parameters) v_free($$->parameters);
150                 free($$->value);
151                 free($$);
152         }
153 } <prop>
154 %destructor {
155         if ($$) {
156                 free($$->type);
157                 if ($$->properties) v_free($$->properties);
158                 if ($$->subcomponents) v_free($$->subcomponents);
159                 free($$);
160         }
161 } <comp>
162
163 %%
164
165 icalendar_object :
166   BEGIN_TOKEN ':' COMPONENT_NAME CRLF properties components END_TOKEN ':' COMPONENT_NAME CRLF {
167         /* Verify matching component names */
168         if (strcmp($3, $9) != 0) {
169                 icalerror(&@1, parser_state, scanner, "Mismatched BEGIN/END component names");
170                 YYERROR;
171         }
172         
173         /* Create the main calendar component and send to parser */
174         ical_parser_add_component(parser_state, $3, $5, $6);
175         
176         /* Free component name strings */
177         free($3);
178         free($9);
179   }
180 ;
181
182 components :
183   %empty {
184         $$ = v_new();
185         if (!$$) YYNOMEM;
186         v_dtor($$, derp_free, NULL);
187   }
188 | components component {
189         if (!v_append($1, $2)) {
190                 YYNOMEM;
191         }
192         $$ = $1;
193   }
194 ;
195
196 component :
197   BEGIN_TOKEN ':' COMPONENT_NAME CRLF properties components END_TOKEN ':' COMPONENT_NAME CRLF {
198         /* Verify matching component names */
199         if (strcmp($3, $9) != 0) {
200                 icalerror(&@1, parser_state, scanner, "Mismatched BEGIN/END component names");
201                 YYERROR;
202         }
203         
204         /* Create component structure */
205         $$ = malloc(sizeof(component_t));
206         if (!$$) YYNOMEM;
207         
208         $$->type = $3;  /* Transfer ownership */
209         $$->properties = $5;  /* Transfer ownership */
210         $$->subcomponents = $6;  /* Transfer ownership */
211         
212         free($9);  /* Free duplicate component name */
213   }
214 ;
215
216 properties :
217   %empty {
218         $$ = v_new();
219         if (!$$) YYNOMEM;
220         v_dtor($$, derp_free, NULL);
221   }
222 | properties property {
223         if (!v_append($1, $2)) {
224                 YYNOMEM;
225         }
226         $$ = $1;
227   }
228 ;
229
230 property :
231   PROPERTY_NAME parameters_opt ':' property_value CRLF {
232         $$ = malloc(sizeof(property_t));
233         if (!$$) YYNOMEM;
234         
235         $$->name = $1;  /* Transfer ownership */
236         $$->parameters = $2;  /* Transfer ownership */
237         $$->value = $4;  /* Transfer ownership */
238   }
239 ;
240
241 parameters_opt :
242   %empty {
243         $$ = v_new();
244         if (!$$) YYNOMEM;
245         v_dtor($$, free_property_parameter, NULL);
246   }
247 | parameters {
248         $$ = $1;
249   }
250 ;
251
252 parameters :
253   ';' parameter {
254         $$ = v_new();
255         if (!$$) YYNOMEM;
256         v_dtor($$, free_property_parameter, NULL);
257         if (!v_append($$, $2)) {
258                 YYNOMEM;
259         }
260   }
261 | parameters ';' parameter {
262         if (!v_append($1, $3)) {
263                 YYNOMEM;
264         }
265         $$ = $1;
266   }
267 ;
268
269 parameter :
270   PROPERTY_NAME '=' parameter_values {
271         $$ = malloc(sizeof(property_parameter_t));
272         if (!$$) YYNOMEM;
273         
274         $$->name = $1;  /* Transfer ownership */
275         
276         /* Concatenate parameter values with commas */
277         if (v_length($3) == 1) {
278                 $$->value = strdup((char*)v_at($3, 0));
279         } else {
280                 /* Join multiple values with commas */
281                 size_t total_len = 0;
282                 for (size_t i = 0; i < v_length($3); i++) {
283                         total_len += strlen((char*)v_at($3, i));
284                         if (i > 0) total_len++;  /* For comma */
285                 }
286                 
287                 $$->value = malloc(total_len + 1);
288                 if (!$$->value) {
289                         free($$->name);
290                         free($$);
291                         YYNOMEM;
292                 }
293                 
294                 $$->value[0] = '\0';
295                 for (size_t i = 0; i < v_length($3); i++) {
296                         if (i > 0) strcat($$->value, ",");
297                         strcat($$->value, (char*)v_at($3, i));
298                 }
299         }
300         
301         v_free($3);  /* Free the parameter values vector */
302   }
303 ;
304
305 parameter_values :
306   PARAM_VALUE {
307         $$ = v_new();
308         if (!$$) YYNOMEM;
309         v_dtor($$, derp_free, NULL);
310         if (!v_append($$, $1)) {
311                 YYNOMEM;
312         }
313   }
314 | parameter_values ',' PARAM_VALUE {
315         if (!v_append($1, $3)) {
316                 YYNOMEM;
317         }
318         $$ = $1;
319   }
320 ;
321
322 property_value :
323   property_value_parts {
324         $$ = concatenate_value_parts($1);
325         v_free($1);
326         if (!$$) YYNOMEM;
327   }
328 ;
329
330 property_value_parts :
331   value_part {
332         $$ = v_new();
333         if (!$$) YYNOMEM;
334         v_dtor($$, derp_free, NULL);
335         if (!v_append($$, $1)) {
336                 YYNOMEM;
337         }
338   }
339 | property_value_parts value_part {
340         if (!v_append($1, $2)) {
341                 YYNOMEM;
342         }
343         $$ = $1;
344   }
345 | property_value_parts FOLDING_WS value_part {
346         /* Handle line folding - concatenate without adding folding space */
347         if (!v_append($1, $3)) {  /* Add the continued content directly */
348                 YYNOMEM;
349         }
350         free($2);  /* Free the folding space since we don't use it */
351         $$ = $1;
352   }
353 ;
354
355 value_part :
356   PARAM_VALUE {
357         $$ = $1;
358   }
359 ;
360
361 %%
362
363 /* Helper function to concatenate value parts into a single string */
364 static char *concatenate_value_parts(vector *parts)
365 {
366         if (!parts || v_length(parts) == 0) {
367                 return strdup("");
368         }
369         
370         /* Calculate total length needed */
371         size_t total_len = 0;
372         for (size_t i = 0; i < v_length(parts); i++) {
373                 char *part = (char*)v_at(parts, i);
374                 if (part) total_len += strlen(part);
375         }
376         
377         /* Allocate and concatenate */
378         char *result = malloc(total_len + 1);
379         if (!result) return NULL;
380         
381         result[0] = '\0';
382         for (size_t i = 0; i < v_length(parts); i++) {
383                 char *part = (char*)v_at(parts, i);
384                 if (part) strcat(result, part);
385         }
386         
387         return result;
388 }
389
390 /* Helper function to free property parameters */
391 static void free_property_parameter(void *param, void *aux)
392 {
393         (void)aux;  /* Unused parameter */
394         if (param) {
395                 property_parameter_t *p = (property_parameter_t*)param;
396                 free(p->name);
397                 free(p->value);
398                 free(p);
399         }
400 }
401
402 int icalerror(ICALLTYPE *locp, void *parser_state, void *scanner, const char *s)
403 {
404         (void)scanner;
405         
406         /* Use location information if available */
407         if (locp) {
408                 ical_parser_set_error_with_location(parser_state, s, locp->first_line, locp->first_column);
409         } else {
410                 ical_parser_set_error_with_location(parser_state, s, 0, 0);
411         }
412         return 0;
413 }