74e33108ad154d7be47309db306e915b864f0697
[external/ragel.git] / examples / mailbox / mailbox.rl
1 /*
2  * Parses unix mail boxes into headers and bodies.
3  */
4
5 #include <iostream>
6 #include <stdlib.h>
7 #include <stdio.h>
8
9 using namespace std;
10
11 #define BUFSIZE 2048
12
13 /* A growable buffer for collecting headers. */
14 struct Buffer
15 {
16         Buffer() : data(0), allocated(0), length(0) { }
17         ~Buffer() { empty(); }
18
19         void append( char p ) {
20                 if ( ++length > allocated )
21                         upAllocate( length*2 );
22                 data[length-1] = p;
23         }
24                 
25         void clear() { length = 0; }
26         void upAllocate( int len );
27         void empty();
28
29         char *data;
30         int allocated;
31         int length;
32 };
33
34
35 struct MailboxScanner
36 {
37         Buffer headName;
38         Buffer headContent;
39
40         int cs, top, stack[1];
41
42         int init( );
43         int execute( const char *data, int len );
44         int finish( );
45 };
46
47 %%{
48         machine MailboxScanner;
49
50         # Buffer the header names.
51         action bufHeadName { headName.append(fc); }
52
53         # Prints a blank line after the end of the headers of each message.
54         action blankLine { cout << endl; }
55         
56         # Helpers we will use in matching the date section of the from line.
57         day = /[A-Z][a-z][a-z]/;
58         month = /[A-Z][a-z][a-z]/;
59         year = /[0-9][0-9][0-9][0-9]/;
60         time = /[0-9][0-9]:[0-9][0-9]/ . ( /:[0-9][0-9]/ | '' );
61         letterZone = /[A-Z][A-Z][A-Z]/;
62         numZone = /[+\-][0-9][0-9][0-9][0-9]/;
63         zone = letterZone | numZone;
64         dayNum = /[0-9 ][0-9]/;
65
66         # These are the different formats of the date minus an obscure
67         # type that has a funny string 'remote from xxx' on the end. Taken
68         # from c-client in the imap-2000 distribution.
69         date = day . ' ' . month . ' ' . dayNum . ' ' . time . ' ' .
70                 ( year | year . ' ' . zone | zone . ' ' . year );
71
72         # From lines separate messages. We will exclude fromLine from a message
73         # body line.  This will cause us to stay in message line up until an
74         # entirely correct from line is matched.
75         fromLine = 'From ' . (any-'\n')* . ' ' . date . '\n';
76
77         # The types of characters that can be used as a header name.
78         hchar = print - [ :];
79
80         # Simply eat up an uninteresting header. Return at the first non-ws
81         # character following a newline.
82         consumeHeader := ( 
83                         [^\n] | 
84                         '\n' [ \t] |
85                         '\n' [^ \t] @{fhold; fret;}
86                 )*;
87
88         action hchar {headContent.append(fc);}
89         action hspace {headContent.append(' ');}
90
91         action hfinish {
92                 headContent.append(0);
93                 cout << headContent.data << endl;
94                 headContent.clear();
95                 fhold;
96                 fret;
97         }
98
99         # Display the contents of a header as it is consumed. Collapses line
100         # continuations to a single space. 
101         printHeader := ( 
102                 [^\n] @hchar  | 
103                 ( '\n' ( [ \t]+ '\n' )* [ \t]+ ) %hspace
104         )** $!hfinish;
105
106         action onHeader 
107         {
108                 headName.append(0);
109                 if ( strcmp( headName.data, "From" ) == 0 ||
110                                 strcmp( headName.data, "To" ) == 0 ||
111                                 strcmp( headName.data, "Subject" ) == 0 )
112                 {
113                         /* Print the header name, then jump to a machine the will display
114                          * the contents. */
115                         cout << headName.data << ":";
116                         headName.clear();
117                         fcall printHeader;
118                 }
119
120                 headName.clear();
121                 fcall consumeHeader;
122         }
123
124         header = hchar+ $bufHeadName ':' @onHeader;
125
126         # Exclude fromLine from a messageLine, otherwise when encountering a
127         # fromLine we will be simultaneously matching the old message and a new
128         # message.
129         messageLine = ( [^\n]* '\n' - fromLine );
130
131         # An entire message.
132         message = ( fromLine .  header* .  '\n' @blankLine .  messageLine* );
133
134         # File is a series of messages.
135         main := message*;
136 }%%
137
138 %% write data;
139
140 int MailboxScanner::init( )
141 {
142         %% write init;
143         return 1;
144 }
145
146 int MailboxScanner::execute( const char *data, int len )
147 {
148         const char *p = data;
149         const char *pe = data + len;
150
151         %% write exec;
152
153         if ( cs == MailboxScanner_error )
154                 return -1;
155         if ( cs >= MailboxScanner_first_final )
156                 return 1;
157         return 0;
158 }
159
160 int MailboxScanner::finish( )
161 {
162         %% write eof;
163         if ( cs == MailboxScanner_error )
164                 return -1;
165         if ( cs >= MailboxScanner_first_final )
166                 return 1;
167         return 0;
168 }
169
170
171 void Buffer::empty()
172 {
173         if ( data != 0 ) {
174                 free( data );
175
176                 data = 0;
177                 length = 0;
178                 allocated = 0;
179         }
180 }
181
182 void Buffer::upAllocate( int len )
183 {
184         if ( data == 0 )
185                 data = (char*) malloc( len );
186         else
187                 data = (char*) realloc( data, len );
188         allocated = len;
189 }
190
191 MailboxScanner mailbox;
192 char buf[BUFSIZE];
193
194 int main()
195 {
196         mailbox.init();
197         while ( 1 ) {
198                 int len = fread( buf, 1, BUFSIZE, stdin );
199                 mailbox.execute( buf, len );
200                 if ( len != BUFSIZE )
201                         break;
202         }
203         if ( mailbox.finish() <= 0 )
204                 cerr << "mailbox: error parsing input" << endl;
205         return 0;
206 }