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