1 /* ical.y - iCalendar parser using Bison */
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 */
9 /* change prefix of symbols from yy to "ical" to avoid
10 clashes with any other parsers we may want to link */
12 %define api.prefix {ical}
14 /* generate much more meaningful errors rather than the
15 uninformative string "syntax error" */
17 %define parse.error verbose
19 /* enable location tracking for better error reporting */
22 /* Bison offers different %code insertion locations in
23 addition to yacc's %{ %} construct.
25 The "top" location is good for headers and feature
26 flags like the _XOPEN_SOURCE we use here */
29 /* XOPEN for strdup */
30 #define _XOPEN_SOURCE 600
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.
42 The following conditional define allows us to use
43 the functionality in earlier versions too. */
46 #define YYNOMEM goto yyexhaustedlab
56 /* Use vector from collection library instead of custom linked lists */
57 #include <derp/common.h>
58 #include <derp/vector.h>
60 /* Structure for property parameters */
64 } property_parameter_t;
66 /* Structure for properties */
69 vector *parameters; /* vector of property_parameter_t* */
73 /* Structure for components */
76 vector *properties; /* vector of property_t* */
77 vector *subcomponents; /* vector of component_t* */
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 */
87 %parse-param {void *parser_state}
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 */
94 %param {void *scanner}
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);
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);
106 /* Helper functions */
107 static char *concatenate_value_parts(vector *parts);
108 static void free_property_parameter(void *param, void *aux);
115 property_parameter_t *param;
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
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
136 /* Bison memory management - automatically free memory on errors */
137 %destructor { free($$); } <str>
138 %destructor { if ($$) v_free($$); } <list>
149 if ($$->parameters) v_free($$->parameters);
157 if ($$->properties) v_free($$->properties);
158 if ($$->subcomponents) v_free($$->subcomponents);
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");
173 /* Create the main calendar component and send to parser */
174 ical_parser_add_component(parser_state, $3, $5, $6);
176 /* Free component name strings */
186 v_dtor($$, derp_free, NULL);
188 | components component {
189 if (!v_append($1, $2)) {
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");
204 /* Create component structure */
205 $$ = malloc(sizeof(component_t));
208 $$->type = $3; /* Transfer ownership */
209 $$->properties = $5; /* Transfer ownership */
210 $$->subcomponents = $6; /* Transfer ownership */
212 free($9); /* Free duplicate component name */
220 v_dtor($$, derp_free, NULL);
222 | properties property {
223 if (!v_append($1, $2)) {
231 PROPERTY_NAME parameters_opt ':' property_value CRLF {
232 $$ = malloc(sizeof(property_t));
235 $$->name = $1; /* Transfer ownership */
236 $$->parameters = $2; /* Transfer ownership */
237 $$->value = $4; /* Transfer ownership */
245 v_dtor($$, free_property_parameter, NULL);
256 v_dtor($$, free_property_parameter, NULL);
257 if (!v_append($$, $2)) {
261 | parameters ';' parameter {
262 if (!v_append($1, $3)) {
270 PROPERTY_NAME '=' parameter_values {
271 $$ = malloc(sizeof(property_parameter_t));
274 $$->name = $1; /* Transfer ownership */
276 /* Concatenate parameter values with commas */
277 if (v_length($3) == 1) {
278 $$->value = strdup((char*)v_at($3, 0));
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 */
287 $$->value = malloc(total_len + 1);
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));
301 v_free($3); /* Free the parameter values vector */
309 v_dtor($$, derp_free, NULL);
310 if (!v_append($$, $1)) {
314 | parameter_values ',' PARAM_VALUE {
315 if (!v_append($1, $3)) {
323 property_value_parts {
324 $$ = concatenate_value_parts($1);
330 property_value_parts :
334 v_dtor($$, derp_free, NULL);
335 if (!v_append($$, $1)) {
339 | property_value_parts value_part {
340 if (!v_append($1, $2)) {
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 */
350 free($2); /* Free the folding space since we don't use it */
363 /* Helper function to concatenate value parts into a single string */
364 static char *concatenate_value_parts(vector *parts)
366 if (!parts || v_length(parts) == 0) {
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);
377 /* Allocate and concatenate */
378 char *result = malloc(total_len + 1);
379 if (!result) return NULL;
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);
390 /* Helper function to free property parameters */
391 static void free_property_parameter(void *param, void *aux)
393 (void)aux; /* Unused parameter */
395 property_parameter_t *p = (property_parameter_t*)param;
402 int icalerror(ICALLTYPE *locp, void *parser_state, void *scanner, const char *s)
406 /* Use location information if available */
408 ical_parser_set_error_with_location(parser_state, s, locp->first_line, locp->first_column);
410 ical_parser_set_error_with_location(parser_state, s, 0, 0);