/* ical.y - iCalendar parser using Bison */ /* a "pure" api means communication variables like yylval won't be global variables, and yylex is assumed to have a different signature */ %define api.pure true /* change prefix of symbols from yy to "ical" to avoid clashes with any other parsers we may want to link */ %define api.prefix {ical} /* generate much more meaningful errors rather than the uninformative string "syntax error" */ %define parse.error verbose /* enable location tracking for better error reporting */ %locations /* Bison offers different %code insertion locations in addition to yacc's %{ %} construct. The "top" location is good for headers and feature flags like the _XOPEN_SOURCE we use here */ %code top { /* XOPEN for strdup */ #define _XOPEN_SOURCE 600 #include #include #include #include /* Bison versions 3.7.5 and above provide the YYNOMEM macro to allow our actions to signal the unlikely event that they couldn't allocate memory. Thanks to the Bison team for adding this feature at my request. :) YYNOMEM causes yyparse() to return 2. The following conditional define allows us to use the functionality in earlier versions too. */ #ifndef YYNOMEM #define YYNOMEM goto yyexhaustedlab #endif } %code requires { #include #include #include #include /* Use vector from collection library instead of custom linked lists */ #include #include /* Structure for property parameters */ typedef struct { char *name; char *value; } property_parameter_t; /* Structure for properties */ typedef struct { char *name; vector *parameters; /* vector of property_parameter_t* */ char *value; } property_t; /* Structure for components */ typedef struct { char *type; vector *properties; /* vector of property_t* */ vector *subcomponents; /* vector of component_t* */ } component_t; } /* Add another argument in yyparse() so that we can communicate any parser state to the caller. We can't return the result directly, since the return value is already reserved as an int, with 0=success, 1=error, 2=nomem */ %parse-param {void *parser_state} /* param adds an extra param to yyparse (like parse-param) but also causes yyparse to send the value to yylex. In our case the caller will initialize their own scanner instance and pass it through */ %param {void *scanner} %code { /* Function declarations - updated for pure API */ int icalerror(ICALLTYPE *locp, void *parser_state, void *scanner, const char *s); int icallex(void *lval, ICALLTYPE *locp, void *scanner); /* Interface with the iCalendar parser library */ void ical_parser_add_component(void *parser_state, const char *type, vector *properties, vector *subcomponents); void ical_parser_add_property(void *parser_state, const char *name, vector *parameters, const char *value); void ical_parser_set_error_with_location(void *parser_state, const char *error, int line, int column); /* Helper functions */ static char *concatenate_value_parts(vector *parts); static void free_property_parameter(void *param, void *aux); } %union { char *str; vector *list; property_parameter_t *param; property_t *prop; component_t *comp; } %token CRLF %token BEGIN_TOKEN END_TOKEN %token FOLDING_WS %token COMPONENT_NAME %token PROPERTY_NAME %token PARAM_VALUE %type property_value value_part %type property_value_parts parameter_values %type parameter %type parameters parameters_opt %type property %type properties %type component %type components /* Bison memory management - automatically free memory on errors */ %destructor { free($$); } %destructor { if ($$) v_free($$); } %destructor { if ($$) { free($$->name); free($$->value); free($$); } } %destructor { if ($$) { free($$->name); if ($$->parameters) v_free($$->parameters); free($$->value); free($$); } } %destructor { if ($$) { free($$->type); if ($$->properties) v_free($$->properties); if ($$->subcomponents) v_free($$->subcomponents); free($$); } } %% icalendar_object : BEGIN_TOKEN ':' COMPONENT_NAME CRLF properties components END_TOKEN ':' COMPONENT_NAME CRLF { /* Verify matching component names */ if (strcmp($3, $9) != 0) { icalerror(&@1, parser_state, scanner, "Mismatched BEGIN/END component names"); YYERROR; } /* Create the main calendar component and send to parser */ ical_parser_add_component(parser_state, $3, $5, $6); /* Free component name strings */ free($3); free($9); } ; components : %empty { $$ = v_new(); if (!$$) YYNOMEM; v_dtor($$, derp_free, NULL); } | components component { if (!v_append($1, $2)) { YYNOMEM; } $$ = $1; } ; component : BEGIN_TOKEN ':' COMPONENT_NAME CRLF properties components END_TOKEN ':' COMPONENT_NAME CRLF { /* Verify matching component names */ if (strcmp($3, $9) != 0) { icalerror(&@1, parser_state, scanner, "Mismatched BEGIN/END component names"); YYERROR; } /* Create component structure */ $$ = malloc(sizeof(component_t)); if (!$$) YYNOMEM; $$->type = $3; /* Transfer ownership */ $$->properties = $5; /* Transfer ownership */ $$->subcomponents = $6; /* Transfer ownership */ free($9); /* Free duplicate component name */ } ; properties : %empty { $$ = v_new(); if (!$$) YYNOMEM; v_dtor($$, derp_free, NULL); } | properties property { if (!v_append($1, $2)) { YYNOMEM; } $$ = $1; } ; property : PROPERTY_NAME parameters_opt ':' property_value CRLF { $$ = malloc(sizeof(property_t)); if (!$$) YYNOMEM; $$->name = $1; /* Transfer ownership */ $$->parameters = $2; /* Transfer ownership */ $$->value = $4; /* Transfer ownership */ } ; parameters_opt : %empty { $$ = v_new(); if (!$$) YYNOMEM; v_dtor($$, free_property_parameter, NULL); } | parameters { $$ = $1; } ; parameters : ';' parameter { $$ = v_new(); if (!$$) YYNOMEM; v_dtor($$, free_property_parameter, NULL); if (!v_append($$, $2)) { YYNOMEM; } } | parameters ';' parameter { if (!v_append($1, $3)) { YYNOMEM; } $$ = $1; } ; parameter : PROPERTY_NAME '=' parameter_values { $$ = malloc(sizeof(property_parameter_t)); if (!$$) YYNOMEM; $$->name = $1; /* Transfer ownership */ /* Concatenate parameter values with commas */ if (v_length($3) == 1) { $$->value = strdup((char*)v_at($3, 0)); } else { /* Join multiple values with commas */ size_t total_len = 0; for (size_t i = 0; i < v_length($3); i++) { total_len += strlen((char*)v_at($3, i)); if (i > 0) total_len++; /* For comma */ } $$->value = malloc(total_len + 1); if (!$$->value) { free($$->name); free($$); YYNOMEM; } $$->value[0] = '\0'; for (size_t i = 0; i < v_length($3); i++) { if (i > 0) strcat($$->value, ","); strcat($$->value, (char*)v_at($3, i)); } } v_free($3); /* Free the parameter values vector */ } ; parameter_values : PARAM_VALUE { $$ = v_new(); if (!$$) YYNOMEM; v_dtor($$, derp_free, NULL); if (!v_append($$, $1)) { YYNOMEM; } } | parameter_values ',' PARAM_VALUE { if (!v_append($1, $3)) { YYNOMEM; } $$ = $1; } ; property_value : property_value_parts { $$ = concatenate_value_parts($1); v_free($1); if (!$$) YYNOMEM; } ; property_value_parts : value_part { $$ = v_new(); if (!$$) YYNOMEM; v_dtor($$, derp_free, NULL); if (!v_append($$, $1)) { YYNOMEM; } } | property_value_parts value_part { if (!v_append($1, $2)) { YYNOMEM; } $$ = $1; } | property_value_parts FOLDING_WS value_part { /* Handle line folding - concatenate without adding folding space */ if (!v_append($1, $3)) { /* Add the continued content directly */ YYNOMEM; } free($2); /* Free the folding space since we don't use it */ $$ = $1; } ; value_part : PARAM_VALUE { $$ = $1; } ; %% /* Helper function to concatenate value parts into a single string */ static char *concatenate_value_parts(vector *parts) { if (!parts || v_length(parts) == 0) { return strdup(""); } /* Calculate total length needed */ size_t total_len = 0; for (size_t i = 0; i < v_length(parts); i++) { char *part = (char*)v_at(parts, i); if (part) total_len += strlen(part); } /* Allocate and concatenate */ char *result = malloc(total_len + 1); if (!result) return NULL; result[0] = '\0'; for (size_t i = 0; i < v_length(parts); i++) { char *part = (char*)v_at(parts, i); if (part) strcat(result, part); } return result; } /* Helper function to free property parameters */ static void free_property_parameter(void *param, void *aux) { (void)aux; /* Unused parameter */ if (param) { property_parameter_t *p = (property_parameter_t*)param; free(p->name); free(p->value); free(p); } } int icalerror(ICALLTYPE *locp, void *parser_state, void *scanner, const char *s) { (void)scanner; /* Use location information if available */ if (locp) { ical_parser_set_error_with_location(parser_state, s, locp->first_line, locp->first_column); } else { ical_parser_set_error_with_location(parser_state, s, 0, 0); } return 0; }