]> begriffs open source - sa-parse/blob - src/ical.l
ical parser
[sa-parse] / src / ical.l
1 /* ical.l */
2
3 /* disable unused functions so we don't
4    get compiler warnings about them */
5
6 %option noyywrap nounput noinput
7
8 /* change our prefix from yy to ical */
9
10 %option prefix="ical"
11
12 /* use the pure parser calling convention */
13
14 %option reentrant bison-bridge bison-locations
15
16 %{
17 #include "ical.tab.h"
18
19 #define YY_EXIT_FAILURE ((void)yyscanner, EXIT_FAILURE)
20
21 /* XOPEN for strdup */
22 #define _XOPEN_SOURCE 600
23 #include <stdlib.h>
24 #include <string.h>
25 #include <ctype.h>
26
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
31
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]);
38     }
39     return result;
40 }
41
42 %}
43
44 /* Lexer states for RFC 5545 content line parsing */
45 %x PARAM_STATE VALUE_STATE COMPONENT_STATE
46
47 /* Define patterns for RFC 5545 tokens */
48 ALPHA           [A-Za-z]
49 DIGIT           [0-9]
50 CRLF            \r?\n
51 WSP             [ \t]
52 IANA_TOKEN      ({ALPHA}|{DIGIT}|"-")+
53 X_NAME          "X-"[A-Za-z0-9-]*"-"?{IANA_TOKEN}
54 SAFE_CHAR       [ !#-+\--9<-~]
55 VALUE_CHAR      [ !-~]
56
57 %%
58
59     /* Line folding - continuation lines start with whitespace (all states) */
60 <*>{CRLF}{WSP}+ {
61     /* Update location - treat folded line as continuation */
62     yylloc->first_line = yylloc->last_line;
63     yylloc->first_column = yylloc->last_column;
64     yylloc->last_line++;
65     yylloc->last_column = yyleng;
66     
67     /* Folded lines are treated as a single space */
68     yylval->str = strdup(" ");
69     return FOLDING_WS;
70 }
71
72     /* INITIAL state: expecting property name at start of content line */
73
74     /* Component BEGIN/END keywords - case insensitive */
75 (?i:"BEGIN") {
76     /* Update location */
77     yylloc->first_line = yylloc->last_line;
78     yylloc->first_column = yylloc->last_column;
79     yylloc->last_column += yyleng;
80     
81     /* After BEGIN, we expect a component name */
82     BEGIN(COMPONENT_STATE);
83     return BEGIN_TOKEN;
84 }
85
86 (?i:"END") {
87     /* Update location */
88     yylloc->first_line = yylloc->last_line;
89     yylloc->first_column = yylloc->last_column;
90     yylloc->last_column += yyleng;
91     
92     /* After END, we expect a component name */
93     BEGIN(COMPONENT_STATE);
94     return END_TOKEN;
95 }
96
97     /* Component names - case insensitive */
98 (?i:"VCALENDAR") {
99     /* Update location */
100     yylloc->first_line = yylloc->last_line;
101     yylloc->first_column = yylloc->last_column;
102     yylloc->last_column += yyleng;
103     
104     yylval->str = str_toupper(yytext);
105     return COMPONENT_NAME;
106 }
107
108 (?i:"VEVENT") {
109     /* Update location */
110     yylloc->first_line = yylloc->last_line;
111     yylloc->first_column = yylloc->last_column;
112     yylloc->last_column += yyleng;
113     
114     yylval->str = str_toupper(yytext);
115     return COMPONENT_NAME;
116 }
117
118 (?i:"VTODO") {
119     /* Update location */
120     yylloc->first_line = yylloc->last_line;
121     yylloc->first_column = yylloc->last_column;
122     yylloc->last_column += yyleng;
123     
124     yylval->str = str_toupper(yytext);
125     return COMPONENT_NAME;
126 }
127
128 (?i:"VJOURNAL") {
129     /* Update location */
130     yylloc->first_line = yylloc->last_line;
131     yylloc->first_column = yylloc->last_column;
132     yylloc->last_column += yyleng;
133     
134     yylval->str = str_toupper(yytext);
135     return COMPONENT_NAME;
136 }
137
138 (?i:"VFREEBUSY") {
139     /* Update location */
140     yylloc->first_line = yylloc->last_line;
141     yylloc->first_column = yylloc->last_column;
142     yylloc->last_column += yyleng;
143     
144     yylval->str = str_toupper(yytext);
145     return COMPONENT_NAME;
146 }
147
148 (?i:"VTIMEZONE") {
149     /* Update location */
150     yylloc->first_line = yylloc->last_line;
151     yylloc->first_column = yylloc->last_column;
152     yylloc->last_column += yyleng;
153     
154     yylval->str = str_toupper(yytext);
155     return COMPONENT_NAME;
156 }
157
158 (?i:"VALARM") {
159     /* Update location */
160     yylloc->first_line = yylloc->last_line;
161     yylloc->first_column = yylloc->last_column;
162     yylloc->last_column += yyleng;
163     
164     yylval->str = str_toupper(yytext);
165     return COMPONENT_NAME;
166 }
167
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 */
170 {X_NAME} {
171     /* Update location */
172     yylloc->first_line = yylloc->last_line;
173     yylloc->first_column = yylloc->last_column;
174     yylloc->last_column += yyleng;
175     
176     yylval->str = str_toupper(yytext);
177     return PROPERTY_NAME;
178 }
179
180 {IANA_TOKEN} {
181     /* Update location */
182     yylloc->first_line = yylloc->last_line;
183     yylloc->first_column = yylloc->last_column;
184     yylloc->last_column += yyleng;
185     
186     yylval->str = str_toupper(yytext);
187     return PROPERTY_NAME;
188 }
189
190     /* Semicolon starts parameter parsing */
191 ";" {
192     /* Update location */
193     yylloc->first_line = yylloc->last_line;
194     yylloc->first_column = yylloc->last_column;
195     yylloc->last_column++;
196     
197     BEGIN(PARAM_STATE);
198     return ';';
199 }
200
201     /* Colon starts value parsing */
202 ":" {
203     /* Update location */
204     yylloc->first_line = yylloc->last_line;
205     yylloc->first_column = yylloc->last_column;
206     yylloc->last_column++;
207     
208     BEGIN(VALUE_STATE);
209     return ':';
210 }
211
212     /* PARAM_STATE: parsing parameters between ; and : */
213 <PARAM_STATE>{
214     /* Parameter names - X_NAME must come before IANA_TOKEN */
215     {X_NAME} {
216         /* Update location */
217         yylloc->first_line = yylloc->last_line;
218         yylloc->first_column = yylloc->last_column;
219         yylloc->last_column += yyleng;
220         
221         yylval->str = str_toupper(yytext);
222         return PROPERTY_NAME;  /* Reuse token for param names */
223     }
224     
225     {IANA_TOKEN} {
226         /* Update location */
227         yylloc->first_line = yylloc->last_line;
228         yylloc->first_column = yylloc->last_column;
229         yylloc->last_column += yyleng;
230         
231         yylval->str = str_toupper(yytext);
232         return PROPERTY_NAME;  /* Reuse token for param names */
233     }
234
235     /* Quoted parameter values */
236     \"([^"]|\\\")*\" {
237         /* Update location */
238         yylloc->first_line = yylloc->last_line;
239         yylloc->first_column = yylloc->last_column;
240         yylloc->last_column += yyleng;
241         
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;
246         
247         strncpy(unquoted, yytext + 1, len - 2);
248         unquoted[len - 2] = '\0';
249         
250         /* Handle escaped quotes - replace \" with " */
251         char *src = unquoted, *dst = unquoted;
252         while (*src) {
253             if (*src == '\\' && *(src + 1) == '"') {
254                 *dst++ = '"';
255                 src += 2;
256             } else {
257                 *dst++ = *src++;
258             }
259         }
260         *dst = '\0';
261         
262         yylval->str = unquoted;
263         return PARAM_VALUE;
264     }
265
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;
272         
273         yylval->str = strdup(yytext);
274         return PARAM_VALUE;
275     }
276     
277     "=" {
278         /* Update location */
279         yylloc->first_line = yylloc->last_line;
280         yylloc->first_column = yylloc->last_column;
281         yylloc->last_column++;
282         
283         return '=';
284     }
285     
286     "," {
287         /* Update location */
288         yylloc->first_line = yylloc->last_line;
289         yylloc->first_column = yylloc->last_column;
290         yylloc->last_column++;
291         
292         return ',';
293     }
294     
295     ";" {
296         /* Update location */
297         yylloc->first_line = yylloc->last_line;
298         yylloc->first_column = yylloc->last_column;
299         yylloc->last_column++;
300         
301         return ';';
302     }
303     
304     ":" {
305         /* Update location */
306         yylloc->first_line = yylloc->last_line;
307         yylloc->first_column = yylloc->last_column;
308         yylloc->last_column++;
309         
310         BEGIN(VALUE_STATE);
311         return ':';
312     }
313 }
314
315     /* COMPONENT_STATE: parsing component name after BEGIN: or END: */
316 <COMPONENT_STATE>{
317     ":" {
318         /* Update location */
319         yylloc->first_line = yylloc->last_line;
320         yylloc->first_column = yylloc->last_column;
321         yylloc->last_column++;
322         
323         /* Stay in COMPONENT_STATE - we expect a component name next */
324         return ':';
325     }
326     
327     /* Component names in COMPONENT_STATE */
328     (?i:"VCALENDAR") {
329         /* Update location */
330         yylloc->first_line = yylloc->last_line;
331         yylloc->first_column = yylloc->last_column;
332         yylloc->last_column += yyleng;
333         
334         yylval->str = str_toupper(yytext);
335         return COMPONENT_NAME;
336     }
337
338     (?i:"VEVENT") {
339         /* Update location */
340         yylloc->first_line = yylloc->last_line;
341         yylloc->first_column = yylloc->last_column;
342         yylloc->last_column += yyleng;
343         
344         yylval->str = str_toupper(yytext);
345         return COMPONENT_NAME;
346     }
347
348     (?i:"VTODO") {
349         /* Update location */
350         yylloc->first_line = yylloc->last_line;
351         yylloc->first_column = yylloc->last_column;
352         yylloc->last_column += yyleng;
353         
354         yylval->str = str_toupper(yytext);
355         return COMPONENT_NAME;
356     }
357
358     (?i:"VJOURNAL") {
359         /* Update location */
360         yylloc->first_line = yylloc->last_line;
361         yylloc->first_column = yylloc->last_column;
362         yylloc->last_column += yyleng;
363         
364         yylval->str = str_toupper(yytext);
365         return COMPONENT_NAME;
366     }
367
368     (?i:"VFREEBUSY") {
369         /* Update location */
370         yylloc->first_line = yylloc->last_line;
371         yylloc->first_column = yylloc->last_column;
372         yylloc->last_column += yyleng;
373         
374         yylval->str = str_toupper(yytext);
375         return COMPONENT_NAME;
376     }
377
378     (?i:"VTIMEZONE") {
379         /* Update location */
380         yylloc->first_line = yylloc->last_line;
381         yylloc->first_column = yylloc->last_column;
382         yylloc->last_column += yyleng;
383         
384         yylval->str = str_toupper(yytext);
385         return COMPONENT_NAME;
386     }
387
388     (?i:"VALARM") {
389         /* Update location */
390         yylloc->first_line = yylloc->last_line;
391         yylloc->first_column = yylloc->last_column;
392         yylloc->last_column += yyleng;
393         
394         yylval->str = str_toupper(yytext);
395         return COMPONENT_NAME;
396     }
397 }
398
399     /* VALUE_STATE: parsing property value after : until CRLF */
400 <VALUE_STATE>{
401     /* Property value - VALUE-CHAR per RFC (any textual character) */
402     [ !-~]+ {
403         /* Update location */
404         yylloc->first_line = yylloc->last_line;
405         yylloc->first_column = yylloc->last_column;
406         yylloc->last_column += yyleng;
407         
408         yylval->str = strdup(yytext);
409         return PARAM_VALUE;  /* Reuse token for property values */
410     }
411 }
412
413     /* CRLF - end of content line, reset to INITIAL state */
414 <*>{CRLF} {
415     /* Update location - newline resets column and increments line */
416     yylloc->first_line = yylloc->last_line;
417     yylloc->first_column = yylloc->last_column;
418     yylloc->last_line++;
419     yylloc->last_column = 1;
420     
421     /* Reset to INITIAL state for next content line */
422     BEGIN(INITIAL);
423     return CRLF;
424 }
425
426     /* Whitespace within lines - ignore in all states */
427 <*>{WSP}+ {
428     /* Update location */
429     yylloc->first_line = yylloc->last_line;
430     yylloc->first_column = yylloc->last_column;
431     yylloc->last_column += yyleng;
432     
433     /* Ignore whitespace within content lines */
434 }
435
436     /* Everything else */
437 <*>. {
438     /* Update location */
439     yylloc->first_line = yylloc->last_line;
440     yylloc->first_column = yylloc->last_column;
441     yylloc->last_column++;
442     
443     return *yytext;
444 }
445
446 %%