c952dca4bd2283b0b6f177057e153897ce16da2d
[external/ragel.git] / rlgen-ruby / 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 "rlgen-ruby.h"
30 #include "xmlparse.h"
31 #include "pcheck.h"
32 #include "vector.h"
33 #include "version.h"
34 #include "common.h"
35 #include "rubycodegen.h"
36
37 using std::istream;
38 using std::ifstream;
39 using std::ostream;
40 using std::ios;
41 using std::cin;
42 using std::cout;
43 using std::cerr;
44 using std::endl;
45
46 /* Io globals. */
47 istream *inStream = 0;
48 ostream *outStream = 0;
49 output_filter *outFilter = 0;
50 char *outputFileName = 0;
51
52 /* Graphviz dot file generation. */
53 bool graphvizDone = false;
54
55 int numSplitPartitions = 0;
56 bool printPrintables = false;
57
58 /* Print a summary of the options. */
59 void usage()
60 {
61         cout <<
62 "usage: " PROGNAME " [options] file\n"
63 "general:\n"
64 "   -h, -H, -?, --help    Print this usage and exit\n"
65 "   -v, --version         Print version information and exit\n"
66 "   -o <file>             Write output to <file>\n"
67         ;       
68 }
69
70 /* Print version information. */
71 void version()
72 {
73         cout << "Ragel Code Generator for Ruby" << endl <<
74                         "Version " VERSION << ", " PUBDATE << endl <<
75                         "Copyright (c) 2001-2007 by Adrian Thurston" << endl;
76 }
77
78 /* Scans a string looking for the file extension. If there is a file
79  * extension then pointer returned points to inside the string
80  * passed in. Otherwise returns null. */
81 char *findFileExtension( char *stemFile )
82 {
83         char *ppos = stemFile + strlen(stemFile) - 1;
84
85         /* Scan backwards from the end looking for the first dot.
86          * If we encounter a '/' before the first dot, then stop the scan. */
87         while ( 1 ) {
88                 /* If we found a dot or got to the beginning of the string then
89                  * we are done. */
90                 if ( ppos == stemFile || *ppos == '.' )
91                         break;
92
93                 /* If we hit a / then there is no extension. Done. */
94                 if ( *ppos == '/' ) {
95                         ppos = stemFile;
96                         break;
97                 }
98                 ppos--;
99         } 
100
101         /* If we got to the front of the string then bail we 
102          * did not find an extension  */
103         if ( ppos == stemFile )
104                 ppos = 0;
105
106         return ppos;
107 }
108
109 /* Make a file name from a stem. Removes the old filename suffix and
110  * replaces it with a new one. Returns a newed up string. */
111 char *fileNameFromStem( char *stemFile, char *suffix )
112 {
113         int len = strlen( stemFile );
114         assert( len > 0 );
115
116         /* Get the extension. */
117         char *ppos = findFileExtension( stemFile );
118
119         /* If an extension was found, then shorten what we think the len is. */
120         if ( ppos != 0 )
121                 len = ppos - stemFile;
122
123         /* Make the return string from the stem and the suffix. */
124         char *retVal = new char[ len + strlen( suffix ) + 1 ];
125         strncpy( retVal, stemFile, len );
126         strcpy( retVal + len, suffix );
127
128         return retVal;
129 }
130
131 /* Total error count. */
132 int gblErrorCount = 0;
133
134 ostream &error()
135 {
136         gblErrorCount += 1;
137         cerr << PROGNAME ": ";
138         return cerr;
139 }
140
141 /* Counts newlines before sending sync. */
142 int output_filter::sync( )
143 {
144         line += 1;
145         return std::filebuf::sync();
146 }
147
148 /* Counts newlines before sending data out to file. */
149 std::streamsize output_filter::xsputn( const char *s, std::streamsize n )
150 {
151         for ( int i = 0; i < n; i++ ) {
152                 if ( s[i] == '\n' )
153                         line += 1;
154         }
155         return std::filebuf::xsputn( s, n );
156 }
157
158 void escapeLineDirectivePath( std::ostream &out, char *path )
159 {
160         for ( char *pc = path; *pc != 0; pc++ ) {
161                 if ( *pc == '\\' )
162                         out << "\\\\";
163                 else
164                         out << *pc;
165         }
166 }
167
168 /*
169  * Callbacks invoked by the XML data parser.
170  */
171
172 /* Invoked by the parser when the root element is opened. */
173 ostream *openOutput( char *inputFile, char *language )
174 {
175         if ( strcmp( language, "Ruby" ) == 0 ) {
176 //              hostLangType = JavaCode;
177 //              hostLang = &hostLangJava;
178         }
179         else {
180                 error() << "this code genreator is for Java only" << endl;
181         }
182
183         /* If the output format is code and no output file name is given, then
184          * make a default. */
185         if ( outputFileName == 0 ) {
186                 char *ext = findFileExtension( inputFile );
187                 if ( ext != 0 && strcmp( ext, ".rh" ) == 0 )
188                         outputFileName = fileNameFromStem( inputFile, ".h" );
189                 else {
190                         char *defExtension = 0;
191                         switch ( hostLangType ) {
192                                 case CCode: defExtension = ".c"; break;
193                                 case DCode: defExtension = ".d"; break;
194                                 case JavaCode: defExtension = ".java"; break;
195                         }
196                         outputFileName = fileNameFromStem( inputFile, defExtension );
197                 }
198         }
199
200         /* Make sure we are not writing to the same file as the input file. */
201         if ( outputFileName != 0 && strcmp( inputFile, outputFileName  ) == 0 ) {
202                 error() << "output file \"" << outputFileName  << 
203                                 "\" is the same as the input file" << endl;
204         }
205
206         if ( outputFileName != 0 ) {
207                 /* Create the filter on the output and open it. */
208                 outFilter = new output_filter( outputFileName );
209                 outFilter->open( outputFileName, ios::out|ios::trunc );
210                 if ( !outFilter->is_open() ) {
211                         error() << "error opening " << outputFileName << " for writing" << endl;
212                         exit(1);
213                 }
214
215                 /* Open the output stream, attaching it to the filter. */
216                 outStream = new ostream( outFilter );
217         }
218         else {
219                 /* Writing out ot std out. */
220                 outStream = &cout;
221         }
222         return outStream;
223 }
224
225 /* Invoked by the parser when a ragel definition is opened. */
226 CodeGenData *makeCodeGen( char *sourceFileName, char *fsmName, 
227                 ostream &out, bool wantComplete )
228 {
229         CodeGenData *codeGen = new RubyCodeGen(out);
230
231         codeGen->sourceFileName = sourceFileName;
232         codeGen->fsmName = fsmName;
233         codeGen->wantComplete = wantComplete;
234
235         return codeGen;
236 }
237
238 /* Main, process args and call yyparse to start scanning input. */
239 int main(int argc, char **argv)
240 {
241         ParamCheck pc("o:VpT:F:G:vHh?-:P:", argc, argv);
242         char *xmlInputFileName = 0;
243
244         while ( pc.check() ) {
245                 switch ( pc.state ) {
246                 case ParamCheck::match:
247                         switch ( pc.parameter ) {
248                         /* Output. */
249                         case 'o':
250                                 if ( *pc.parameterArg == 0 )
251                                         error() << "a zero length output file name was given" << endl;
252                                 else if ( outputFileName != 0 )
253                                         error() << "more than one output file name was given" << endl;
254                                 else {
255                                         /* Ok, remember the output file name. */
256                                         outputFileName = pc.parameterArg;
257                                 }
258                                 break;
259
260                         /* Version and help. */
261                         case 'v':
262                                 version();
263                                 exit(0);
264                         case 'H': case 'h': case '?':
265                                 usage();
266                                 exit(0);
267                         case '-':
268                                 if ( strcasecmp(pc.parameterArg, "help") == 0 ) {
269                                         usage();
270                                         exit(0);
271                                 }
272                                 else if ( strcasecmp(pc.parameterArg, "version") == 0 ) {
273                                         version();
274                                         exit(0);
275                                 }
276                                 else {
277                                         error() << "--" << pc.parameterArg << 
278                                                         " is an invalid argument" << endl;
279                                         break;
280                                 }
281                         }
282                         break;
283
284                 case ParamCheck::invalid:
285                         error() << "-" << pc.parameter << " is an invalid argument" << endl;
286                         break;
287
288                 case ParamCheck::noparam:
289                         if ( *pc.curArg == 0 )
290                                 error() << "a zero length input file name was given" << endl;
291                         else if ( xmlInputFileName != 0 )
292                                 error() << "more than one input file name was given" << endl;
293                         else {
294                                 /* OK, Remember the filename. */
295                                 xmlInputFileName = pc.curArg;
296                         }
297                         break;
298                 }
299         }
300
301         /* Bail on above errors. */
302         if ( gblErrorCount > 0 )
303                 exit(1);
304
305         /* Open the input file for reading. */
306         if ( xmlInputFileName != 0 ) {
307                 /* Open the input file for reading. */
308                 ifstream *inFile = new ifstream( xmlInputFileName );
309                 inStream = inFile;
310                 if ( ! inFile->is_open() )
311                         error() << "could not open " << xmlInputFileName << " for reading" << endl;
312         }
313         else {
314                 xmlInputFileName = "<stdin>";
315                 inStream = &cin;
316         }
317
318         /* Bail on above errors. */
319         if ( gblErrorCount > 0 )
320                 exit(1);
321
322         bool wantComplete = true;
323         bool outputActive = true;
324
325         /* Parse the input! */
326         xml_parse( *inStream, xmlInputFileName, outputActive, wantComplete );
327
328         /* If writing to a file, delete the ostream, causing it to flush.
329          * Standard out is flushed automatically. */
330         if ( outputFileName != 0 ) {
331                 delete outStream;
332                 delete outFilter;
333         }
334
335         /* Finished, final check for errors.. */
336         if ( gblErrorCount > 0 ) {
337                 /* If we opened an output file, remove it. */
338                 if ( outputFileName != 0 )
339                         unlink( outputFileName );
340                 exit(1);
341         }
342         return 0;
343 }