/* csv.y - CSV parser using Bison */ %code requires { #include #include #include #include /* Simple linked list structures for CSV parsing */ typedef struct field_node { char *content; struct field_node *next; } field_node_t; typedef struct record_node { field_node_t *fields; struct record_node *next; } record_node_t; } %code { /* Function declarations */ int yylex(void); int yyerror(const char *s); bool one_empty_field(field_node_t *fields); field_node_t *create_field(char *content); void append_field_to_list(field_node_t **head, char *content); void free_field_list(field_node_t *fields); size_t count_fields(field_node_t *fields); /* Interface with the CSV parser library */ void csv_parser_add_record(field_node_t *fields); void csv_parser_set_error(const char *error); } %union { char *str; field_node_t *fields; } %token CRLF %token FIELD %type field.opt %type record /* Bison memory management - automatically free memory on errors */ %destructor { free($$); } %destructor { free_field_list($$); } %% file : consumed_record | file CRLF consumed_record ; /* A record can be constructed in two ways, but we want to run the same side effect for either case. We add an intermediate non-terminal symbol "consumed_record" just to perform the action. In library code, this would be a good place to send the the record to a callback function. */ consumed_record : record { /* a record comprised of exactly one blank field is a blank record, which we can skip */ if (!one_empty_field($1)) { /* Send the record to the parser library */ csv_parser_add_record($1); } /* Memory is automatically freed by %destructor */ } ; record : field.opt { /* Create first field node */ $$ = create_field($1); } | record ',' field.opt { /* Append field to existing list */ append_field_to_list(&$1, $3); $$ = $1; } ; field.opt : %empty { $$ = calloc(1,1); } | FIELD ; %% field_node_t *create_field(char *content) { field_node_t *field = malloc(sizeof(field_node_t)); if (!field) { free(content); return NULL; } field->content = content; /* Take ownership of the string */ field->next = NULL; return field; } void append_field_to_list(field_node_t **head, char *content) { field_node_t *new_field = create_field(content); if (!new_field) return; if (!*head) { *head = new_field; return; } /* Find the last field in the list */ field_node_t *current = *head; while (current->next) { current = current->next; } current->next = new_field; } void free_field_list(field_node_t *fields) { field_node_t *current = fields; while (current) { field_node_t *next = current->next; free(current->content); free(current); current = next; } } size_t count_fields(field_node_t *fields) { size_t count = 0; field_node_t *current = fields; while (current) { count++; current = current->next; } return count; } bool one_empty_field(field_node_t *fields) { return fields && !fields->next && fields->content && fields->content[0] == '\0'; } int yyerror(const char *s) { csv_parser_set_error(s); return 0; }