f9a05984ac4111dbecdf58b558c5e98080115a7d
[external/ragel.git] / rlcodegen / main.cpp
1 /*
2  *  Copyright 2001-2005 Adrian Thurston <thurston@cs.queensu.ca>
3  */
4
5 /*  This file is part of Ragel.
6  *
7  *  Ragel is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  * 
12  *  Ragel is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  * 
17  *  You should have received a copy of the GNU General Public License
18  *  along with Ragel; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
20  */
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <iostream>
26 #include <fstream>
27 #include <unistd.h>
28
29 #include "rlcodegen.h"
30 #include "rlcodegen.h"
31 #include "xmlparse.h"
32 #include "pcheck.h"
33 #include "vector.h"
34 #include "version.h"
35
36 #include "common.cpp"
37
38 using std::istream;
39 using std::ifstream;
40 using std::ostream;
41 using std::ios;
42 using std::cin;
43 using std::cout;
44 using std::cerr;
45 using std::endl;
46
47 /* Target language and output style. */
48 OutputFormat outputFormat = OutCode;
49 CodeStyleEnum codeStyle = GenTables;
50
51 /* Io globals. */
52 istream *inStream = 0;
53 ostream *outStream = 0;
54 output_filter *outFilter = 0;
55 char *outputFileName = 0;
56
57 /* Graphviz dot file generation. */
58 bool graphvizDone = false;
59
60 char *gblFileName = "<unknown>";
61
62 int numSplitPartitions = 0;
63
64 bool printPrintables = false;
65
66 /* Print a summary of the options. */
67 void usage()
68 {
69         cout <<
70 "usage: rlcodegen [options] file\n"
71 "general:\n"
72 "   -h, -H, -?, --help    Print this usage and exit\n"
73 "   -v, --version         Print version information and exit\n"
74 "   -o <file>             Write output to <file>\n"
75 "output:\n"
76 "   -V                    Generate a Graphviz dotfile instead of code\n"
77 "   -p                    Print printable characters in Graphviz output\n"
78 "generated code style:\n"
79 "   -T0                   Table driven FSM (default)\n"
80 "   -T1                   Faster table driven FSM\n"
81 "   -F0                   Flat table driven FSM\n"
82 "   -F1                   Faster flat table-driven FSM\n"
83 "   -G0                   Goto-driven FSM\n"
84 "   -G1                   Faster goto-driven FSM\n"
85 "   -G2                   Really fast goto-driven FSM\n"
86 "   -P<N>                 N-Way Split really fast goto-driven FSM\n"
87         ;       
88 }
89
90 /* Print version information. */
91 void version()
92 {
93         cout << "Ragel Code Generator version " VERSION << " " PUBDATE << endl <<
94                         "Copyright (c) 2001-2006 by Adrian Thurston" << endl;
95 }
96
97 /* Scans a string looking for the file extension. If there is a file
98  * extension then pointer returned points to inside the string
99  * passed in. Otherwise returns null. */
100 char *findFileExtension( char *stemFile )
101 {
102         char *ppos = stemFile + strlen(stemFile) - 1;
103
104         /* Scan backwards from the end looking for the first dot.
105          * If we encounter a '/' before the first dot, then stop the scan. */
106         while ( 1 ) {
107                 /* If we found a dot or got to the beginning of the string then
108                  * we are done. */
109                 if ( ppos == stemFile || *ppos == '.' )
110                         break;
111
112                 /* If we hit a / then there is no extension. Done. */
113                 if ( *ppos == '/' ) {
114                         ppos = stemFile;
115                         break;
116                 }
117                 ppos--;
118         } 
119
120         /* If we got to the front of the string then bail we 
121          * did not find an extension  */
122         if ( ppos == stemFile )
123                 ppos = 0;
124
125         return ppos;
126 }
127
128 /* Make a file name from a stem. Removes the old filename suffix and
129  * replaces it with a new one. Returns a newed up string. */
130 char *fileNameFromStem( char *stemFile, char *suffix )
131 {
132         int len = strlen( stemFile );
133         assert( len > 0 );
134
135         /* Get the extension. */
136         char *ppos = findFileExtension( stemFile );
137
138         /* If an extension was found, then shorten what we think the len is. */
139         if ( ppos != 0 )
140                 len = ppos - stemFile;
141
142         /* Make the return string from the stem and the suffix. */
143         char *retVal = new char[ len + strlen( suffix ) + 1 ];
144         strncpy( retVal, stemFile, len );
145         strcpy( retVal + len, suffix );
146
147         return retVal;
148 }
149
150 /* Total error count. */
151 int gblErrorCount = 0;
152
153 /* Print the opening to a program error, then return the error stream. */
154 ostream &error()
155 {
156         gblErrorCount += 1;
157         cerr << PROGNAME ": ";
158         return cerr;
159 }
160
161 /* Print the opening to an error in the input, then return the error ostream. */
162 //ostream &error( const YYLTYPE &loc )
163 //{
164 //      gblErrorCount += 1;
165 //      cerr << gblFileName << ":" << loc.first_line << ":" << loc.first_column << ": ";
166 //      return cerr;
167 //}
168
169 /* Print the opening to an error in the input, then return the error ostream. */
170 //ostream &error( const InputLoc &loc )
171 //{
172 //      gblErrorCount += 1;
173 //      cerr << gblFileName << ":" << loc.line << ":" << loc.col << ": ";
174 //      return cerr;
175 //}
176
177 ostream &error( int first_line, int first_column )
178 {
179         gblErrorCount += 1;
180         cerr << gblFileName << ":" << ":" << first_line << ":" << first_column << ": ";
181         return cerr;
182 }
183
184 ostream &warning( )
185 {
186         cerr << gblFileName << ":" << ": warning: ";
187         return cerr;
188 }
189
190 ostream &warning( const InputLoc &loc )
191 {
192         cerr << gblFileName << loc.line << ":" << loc.col << ": warning: ";
193         return cerr;
194 }
195
196 std::ostream &warning( int first_line, int first_column )
197 {
198         cerr << gblFileName << ":" << first_line << ":" << 
199                         first_column << ": warning: ";
200         return cerr;
201 }
202
203 //ostream &xml_error( const YYLTYPE &loc )
204 //{
205 //      gblErrorCount += 1;
206 //      cerr << "<xml-input>:" << loc.first_line << ":" << loc.first_column << ": ";
207 //      return cerr;
208 //}
209
210 ostream &xml_error( const InputLoc &loc )
211 {
212         gblErrorCount += 1;
213         cerr << "<xml-input>:" << loc.line << ":" << loc.col << ": ";
214         return cerr;
215 }
216
217 /* Counts newlines before sending sync. */
218 int output_filter::sync( )
219 {
220         line += 1;
221         return std::filebuf::sync();
222 }
223
224 /* Counts newlines before sending data out to file. */
225 std::streamsize output_filter::xsputn( const char *s, std::streamsize n )
226 {
227         for ( int i = 0; i < n; i++ ) {
228                 if ( s[i] == '\n' )
229                         line += 1;
230         }
231         return std::filebuf::xsputn( s, n );
232 }
233
234 void escapeLineDirectivePath( std::ostream &out, char *path )
235 {
236         for ( char *pc = path; *pc != 0; pc++ ) {
237                 if ( *pc == '\\' )
238                         out << "\\\\";
239                 else
240                         out << *pc;
241         }
242 }
243
244 /* Invoked by the parser, after the source file 
245  * name is taken from XML file. */
246 void openOutput( char *inputFile )
247 {
248         /* If the output format is code and no output file name is given, then
249          * make a default. */
250         if ( outputFormat == OutCode && outputFileName == 0 ) {
251                 char *ext = findFileExtension( inputFile );
252                 if ( ext != 0 && strcmp( ext, ".rh" ) == 0 )
253                         outputFileName = fileNameFromStem( inputFile, ".h" );
254                 else {
255                         char *defExtension = 0;
256                         switch ( hostLangType ) {
257                                 case CCode: defExtension = ".c"; break;
258                                 case DCode: defExtension = ".d"; break;
259                                 case JavaCode: defExtension = ".java"; break;
260                         }
261                         outputFileName = fileNameFromStem( inputFile, defExtension );
262                 }
263         }
264
265         /* Make sure we are not writing to the same file as the input file. */
266         if ( outputFileName != 0 && strcmp( inputFile, outputFileName  ) == 0 ) {
267                 error() << "output file \"" << outputFileName  << 
268                                 "\" is the same as the input file" << endl;
269         }
270
271         if ( outputFileName != 0 ) {
272                 /* Create the filter on the output and open it. */
273                 outFilter = new output_filter;
274                 outFilter->open( outputFileName, ios::out|ios::trunc );
275                 if ( !outFilter->is_open() ) {
276                         error() << "error opening " << outputFileName << " for writing" << endl;
277                         exit(1);
278                 }
279
280                 /* Open the output stream, attaching it to the filter. */
281                 outStream = new ostream( outFilter );
282         }
283         else {
284                 /* Writing out ot std out. */
285                 outStream = &cout;
286         }
287 }
288
289 /* Main, process args and call yyparse to start scanning input. */
290 int main(int argc, char **argv)
291 {
292         ParamCheck pc("o:VpT:F:G:vHh?-:P:", argc, argv);
293         char *xmlInputFileName = 0;
294
295         while ( pc.check() ) {
296                 switch ( pc.state ) {
297                 case ParamCheck::match:
298                         switch ( pc.parameter ) {
299                         /* Output. */
300                         case 'o':
301                                 if ( *pc.parameterArg == 0 )
302                                         error() << "a zero length output file name was given" << endl;
303                                 else if ( outputFileName != 0 )
304                                         error() << "more than one output file name was given" << endl;
305                                 else {
306                                         /* Ok, remember the output file name. */
307                                         outputFileName = pc.parameterArg;
308                                 }
309                                 break;
310
311                         /* Output formats. */
312                         case 'V':
313                                 outputFormat = OutGraphvizDot;
314                                 break;
315
316                         case 'p':
317                                 printPrintables = true;
318                                 break;
319
320                         /* Code style. */
321                         case 'T':
322                                 if ( pc.parameterArg[0] == '0' )
323                                         codeStyle = GenTables;
324                                 else if ( pc.parameterArg[0] == '1' )
325                                         codeStyle = GenFTables;
326                                 else {
327                                         error() << "-T" << pc.parameterArg[0] << 
328                                                         " is an invalid argument" << endl;
329                                         exit(1);
330                                 }
331                                 break;
332                         case 'F':
333                                 if ( pc.parameterArg[0] == '0' )
334                                         codeStyle = GenFlat;
335                                 else if ( pc.parameterArg[0] == '1' )
336                                         codeStyle = GenFFlat;
337                                 else {
338                                         error() << "-F" << pc.parameterArg[0] << 
339                                                         " is an invalid argument" << endl;
340                                         exit(1);
341                                 }
342                                 break;
343                         case 'G':
344                                 if ( pc.parameterArg[0] == '0' )
345                                         codeStyle = GenGoto;
346                                 else if ( pc.parameterArg[0] == '1' )
347                                         codeStyle = GenFGoto;
348                                 else if ( pc.parameterArg[0] == '2' )
349                                         codeStyle = GenIpGoto;
350                                 else {
351                                         error() << "-G" << pc.parameterArg[0] << 
352                                                         " is an invalid argument" << endl;
353                                         exit(1);
354                                 }
355                                 break;
356                         case 'P':
357                                 codeStyle = GenSplit;
358                                 numSplitPartitions = atoi( pc.parameterArg );
359                                 break;
360
361                         /* Version and help. */
362                         case 'v':
363                                 version();
364                                 exit(0);
365                         case 'H': case 'h': case '?':
366                                 usage();
367                                 exit(0);
368                         case '-':
369                                 if ( strcasecmp(pc.parameterArg, "help") == 0 ) {
370                                         usage();
371                                         exit(0);
372                                 }
373                                 else if ( strcasecmp(pc.parameterArg, "version") == 0 ) {
374                                         version();
375                                         exit(0);
376                                 }
377                                 else {
378                                         error() << "--" << pc.parameterArg << 
379                                                         " is an invalid argument" << endl;
380                                         break;
381                                 }
382                         }
383                         break;
384
385                 case ParamCheck::invalid:
386                         error() << "-" << pc.parameter << " is an invalid argument" << endl;
387                         break;
388
389                 case ParamCheck::noparam:
390                         if ( *pc.curArg == 0 )
391                                 error() << "a zero length input file name was given" << endl;
392                         else if ( xmlInputFileName != 0 )
393                                 error() << "more than one input file name was given" << endl;
394                         else {
395                                 /* OK, Remember the filename. */
396                                 xmlInputFileName = pc.curArg;
397                         }
398                         break;
399                 }
400         }
401
402         /* Bail on above errors. */
403         if ( gblErrorCount > 0 )
404                 exit(1);
405
406         /* Open the input file for reading. */
407         if ( xmlInputFileName != 0 ) {
408                 /* Open the input file for reading. */
409                 ifstream *inFile = new ifstream( xmlInputFileName );
410                 inStream = inFile;
411                 if ( ! inFile->is_open() )
412                         error() << "could not open " << xmlInputFileName << " for reading" << endl;
413         }
414         else {
415                 xmlInputFileName = "<stdin>";
416                 inStream = &cin;
417         }
418
419         /* Bail on above errors. */
420         if ( gblErrorCount > 0 )
421                 exit(1);
422
423         /* Parse the input! */
424         xml_parse( *inStream, xmlInputFileName );
425
426         /* If writing to a file, delete the ostream, causing it to flush.
427          * Standard out is flushed automatically. */
428         if ( outputFileName != 0 ) {
429                 delete outStream;
430                 delete outFilter;
431         }
432
433         /* Finished, final check for errors.. */
434         if ( gblErrorCount > 0 ) {
435                 /* If we opened an output file, remove it. */
436                 if ( outputFileName != 0 )
437                         unlink( outputFileName );
438                 exit(1);
439         }
440         return 0;
441 }