3 /* disable unused functions so we don't
4 get compiler warnings about them */
6 %option noyywrap nounput noinput
8 /* change our prefix from yy to ical */
12 /* use the pure parser calling convention */
14 %option reentrant bison-bridge bison-locations
19 #define YY_EXIT_FAILURE ((void)yyscanner, EXIT_FAILURE)
21 /* XOPEN for strdup */
22 #define _XOPEN_SOURCE 600
27 /* seems like a bug that I have to do this, since flex
28 should know prefix=ical and match bison's ICALSTYPE */
29 #define YYSTYPE ICALSTYPE
30 #define YYLTYPE ICALLTYPE
32 /* Helper function to convert string to uppercase for case-insensitive matching */
33 static char *str_toupper(const char *str) {
34 char *result = strdup(str);
35 if (!result) return NULL;
36 for (int i = 0; result[i]; i++) {
37 result[i] = toupper(result[i]);
44 /* Lexer states for RFC 5545 content line parsing */
45 %x PARAM_STATE VALUE_STATE COMPONENT_STATE
47 /* Define patterns for RFC 5545 tokens */
52 IANA_TOKEN ({ALPHA}|{DIGIT}|"-")+
53 X_NAME "X-"[A-Za-z0-9-]*"-"?{IANA_TOKEN}
54 SAFE_CHAR [ !#-+\--9<-~]
59 /* Line folding - continuation lines start with whitespace (all states) */
61 /* Update location - treat folded line as continuation */
62 yylloc->first_line = yylloc->last_line;
63 yylloc->first_column = yylloc->last_column;
65 yylloc->last_column = yyleng;
67 /* Folded lines are treated as a single space */
68 yylval->str = strdup(" ");
72 /* INITIAL state: expecting property name at start of content line */
74 /* Component BEGIN/END keywords - case insensitive */
77 yylloc->first_line = yylloc->last_line;
78 yylloc->first_column = yylloc->last_column;
79 yylloc->last_column += yyleng;
81 /* After BEGIN, we expect a component name */
82 BEGIN(COMPONENT_STATE);
88 yylloc->first_line = yylloc->last_line;
89 yylloc->first_column = yylloc->last_column;
90 yylloc->last_column += yyleng;
92 /* After END, we expect a component name */
93 BEGIN(COMPONENT_STATE);
97 /* Component names - case insensitive */
100 yylloc->first_line = yylloc->last_line;
101 yylloc->first_column = yylloc->last_column;
102 yylloc->last_column += yyleng;
104 yylval->str = str_toupper(yytext);
105 return COMPONENT_NAME;
109 /* Update location */
110 yylloc->first_line = yylloc->last_line;
111 yylloc->first_column = yylloc->last_column;
112 yylloc->last_column += yyleng;
114 yylval->str = str_toupper(yytext);
115 return COMPONENT_NAME;
119 /* Update location */
120 yylloc->first_line = yylloc->last_line;
121 yylloc->first_column = yylloc->last_column;
122 yylloc->last_column += yyleng;
124 yylval->str = str_toupper(yytext);
125 return COMPONENT_NAME;
129 /* Update location */
130 yylloc->first_line = yylloc->last_line;
131 yylloc->first_column = yylloc->last_column;
132 yylloc->last_column += yyleng;
134 yylval->str = str_toupper(yytext);
135 return COMPONENT_NAME;
139 /* Update location */
140 yylloc->first_line = yylloc->last_line;
141 yylloc->first_column = yylloc->last_column;
142 yylloc->last_column += yyleng;
144 yylval->str = str_toupper(yytext);
145 return COMPONENT_NAME;
149 /* Update location */
150 yylloc->first_line = yylloc->last_line;
151 yylloc->first_column = yylloc->last_column;
152 yylloc->last_column += yyleng;
154 yylval->str = str_toupper(yytext);
155 return COMPONENT_NAME;
159 /* Update location */
160 yylloc->first_line = yylloc->last_line;
161 yylloc->first_column = yylloc->last_column;
162 yylloc->last_column += yyleng;
164 yylval->str = str_toupper(yytext);
165 return COMPONENT_NAME;
168 /* Property names - only in INITIAL state at start of content line */
169 /* X_NAME must come before IANA_TOKEN since it's more specific */
171 /* Update location */
172 yylloc->first_line = yylloc->last_line;
173 yylloc->first_column = yylloc->last_column;
174 yylloc->last_column += yyleng;
176 yylval->str = str_toupper(yytext);
177 return PROPERTY_NAME;
181 /* Update location */
182 yylloc->first_line = yylloc->last_line;
183 yylloc->first_column = yylloc->last_column;
184 yylloc->last_column += yyleng;
186 yylval->str = str_toupper(yytext);
187 return PROPERTY_NAME;
190 /* Semicolon starts parameter parsing */
192 /* Update location */
193 yylloc->first_line = yylloc->last_line;
194 yylloc->first_column = yylloc->last_column;
195 yylloc->last_column++;
201 /* Colon starts value parsing */
203 /* Update location */
204 yylloc->first_line = yylloc->last_line;
205 yylloc->first_column = yylloc->last_column;
206 yylloc->last_column++;
212 /* PARAM_STATE: parsing parameters between ; and : */
214 /* Parameter names - X_NAME must come before IANA_TOKEN */
216 /* Update location */
217 yylloc->first_line = yylloc->last_line;
218 yylloc->first_column = yylloc->last_column;
219 yylloc->last_column += yyleng;
221 yylval->str = str_toupper(yytext);
222 return PROPERTY_NAME; /* Reuse token for param names */
226 /* Update location */
227 yylloc->first_line = yylloc->last_line;
228 yylloc->first_column = yylloc->last_column;
229 yylloc->last_column += yyleng;
231 yylval->str = str_toupper(yytext);
232 return PROPERTY_NAME; /* Reuse token for param names */
235 /* Quoted parameter values */
237 /* Update location */
238 yylloc->first_line = yylloc->last_line;
239 yylloc->first_column = yylloc->last_column;
240 yylloc->last_column += yyleng;
242 /* Remove quotes and handle escaped quotes */
243 size_t len = strlen(yytext);
244 char *unquoted = malloc(len - 1);
245 if (!unquoted) return PARAM_VALUE;
247 strncpy(unquoted, yytext + 1, len - 2);
248 unquoted[len - 2] = '\0';
250 /* Handle escaped quotes - replace \" with " */
251 char *src = unquoted, *dst = unquoted;
253 if (*src == '\\' && *(src + 1) == '"') {
262 yylval->str = unquoted;
266 /* Unquoted parameter values - SAFE-CHAR per RFC excluding = ; : , " */
267 [ !#-+\--9<>?@A-Z\[\\\]^_`a-z{|}~]+ {
268 /* Update location */
269 yylloc->first_line = yylloc->last_line;
270 yylloc->first_column = yylloc->last_column;
271 yylloc->last_column += yyleng;
273 yylval->str = strdup(yytext);
278 /* Update location */
279 yylloc->first_line = yylloc->last_line;
280 yylloc->first_column = yylloc->last_column;
281 yylloc->last_column++;
287 /* Update location */
288 yylloc->first_line = yylloc->last_line;
289 yylloc->first_column = yylloc->last_column;
290 yylloc->last_column++;
296 /* Update location */
297 yylloc->first_line = yylloc->last_line;
298 yylloc->first_column = yylloc->last_column;
299 yylloc->last_column++;
305 /* Update location */
306 yylloc->first_line = yylloc->last_line;
307 yylloc->first_column = yylloc->last_column;
308 yylloc->last_column++;
315 /* COMPONENT_STATE: parsing component name after BEGIN: or END: */
318 /* Update location */
319 yylloc->first_line = yylloc->last_line;
320 yylloc->first_column = yylloc->last_column;
321 yylloc->last_column++;
323 /* Stay in COMPONENT_STATE - we expect a component name next */
327 /* Component names in COMPONENT_STATE */
329 /* Update location */
330 yylloc->first_line = yylloc->last_line;
331 yylloc->first_column = yylloc->last_column;
332 yylloc->last_column += yyleng;
334 yylval->str = str_toupper(yytext);
335 return COMPONENT_NAME;
339 /* Update location */
340 yylloc->first_line = yylloc->last_line;
341 yylloc->first_column = yylloc->last_column;
342 yylloc->last_column += yyleng;
344 yylval->str = str_toupper(yytext);
345 return COMPONENT_NAME;
349 /* Update location */
350 yylloc->first_line = yylloc->last_line;
351 yylloc->first_column = yylloc->last_column;
352 yylloc->last_column += yyleng;
354 yylval->str = str_toupper(yytext);
355 return COMPONENT_NAME;
359 /* Update location */
360 yylloc->first_line = yylloc->last_line;
361 yylloc->first_column = yylloc->last_column;
362 yylloc->last_column += yyleng;
364 yylval->str = str_toupper(yytext);
365 return COMPONENT_NAME;
369 /* Update location */
370 yylloc->first_line = yylloc->last_line;
371 yylloc->first_column = yylloc->last_column;
372 yylloc->last_column += yyleng;
374 yylval->str = str_toupper(yytext);
375 return COMPONENT_NAME;
379 /* Update location */
380 yylloc->first_line = yylloc->last_line;
381 yylloc->first_column = yylloc->last_column;
382 yylloc->last_column += yyleng;
384 yylval->str = str_toupper(yytext);
385 return COMPONENT_NAME;
389 /* Update location */
390 yylloc->first_line = yylloc->last_line;
391 yylloc->first_column = yylloc->last_column;
392 yylloc->last_column += yyleng;
394 yylval->str = str_toupper(yytext);
395 return COMPONENT_NAME;
399 /* VALUE_STATE: parsing property value after : until CRLF */
401 /* Property value - VALUE-CHAR per RFC (any textual character) */
403 /* Update location */
404 yylloc->first_line = yylloc->last_line;
405 yylloc->first_column = yylloc->last_column;
406 yylloc->last_column += yyleng;
408 yylval->str = strdup(yytext);
409 return PARAM_VALUE; /* Reuse token for property values */
413 /* CRLF - end of content line, reset to INITIAL state */
415 /* Update location - newline resets column and increments line */
416 yylloc->first_line = yylloc->last_line;
417 yylloc->first_column = yylloc->last_column;
419 yylloc->last_column = 1;
421 /* Reset to INITIAL state for next content line */
426 /* Whitespace within lines - ignore in all states */
428 /* Update location */
429 yylloc->first_line = yylloc->last_line;
430 yylloc->first_column = yylloc->last_column;
431 yylloc->last_column += yyleng;
433 /* Ignore whitespace within content lines */
436 /* Everything else */
438 /* Update location */
439 yylloc->first_line = yylloc->last_line;
440 yylloc->first_column = yylloc->last_column;
441 yylloc->last_column++;