Check the status of the backend.
[external/ragel.git] / ragel / main.cpp
1 /*
2  *  Copyright 2001-2007 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 #include <sstream>
29 #include <unistd.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <errno.h>
35
36 /* Parsing. */
37 #include "ragel.h"
38 #include "rlscan.h"
39
40 /* Parameters and output. */
41 #include "pcheck.h"
42 #include "vector.h"
43 #include "version.h"
44 #include "common.h"
45
46 using std::istream;
47 using std::ostream;
48 using std::ifstream;
49 using std::ofstream;
50 using std::cin;
51 using std::cout;
52 using std::cerr;
53 using std::endl;
54 using std::ios;
55 using std::streamsize;
56
57 /* Controls minimization. */
58 MinimizeLevel minimizeLevel = MinimizePartition2;
59 MinimizeOpt minimizeOpt = MinimizeMostOps;
60
61 /* Graphviz dot file generation. */
62 char *machineSpec = 0, *machineName = 0;
63 bool machineSpecFound = false;
64
65 bool printStatistics = false;
66
67 typedef Vector<char*> ArgsVector;
68 ArgsVector backendArgs;
69
70 /* Print a summary of the options. */
71 void usage()
72 {
73         cout <<
74 "usage: ragel [options] file\n"
75 "general:\n"
76 "   -h, -H, -?, --help   Print this usage and exit\n"
77 "   -v, --version        Print version information and exit\n"
78 "   -o <file>            Write output to <file>\n"
79 "   -s                   Print some statistics on stderr\n"
80 "fsm minimization:\n"
81 "   -n                   Do not perform minimization\n"
82 "   -m                   Minimize at the end of the compilation\n"
83 "   -l                   Minimize after most operations (default)\n"
84 "   -e                   Minimize after every operation\n"
85 "machine selection:\n"
86 "   -S <spec>            FSM specification to output (for rlgen-dot)\n"
87 "   -M <machine>         Machine definition/instantiation to output (for rlgen-dot)\n"
88 "host language:\n"
89 "   -C                   The host language is C, C++, Obj-C or Obj-C++ (default)\n"
90 "   -D                   The host language is D\n"
91 "   -J                   The host language is Java\n"
92 "   -R                   The host language is Ruby\n"
93         ;       
94 }
95
96 /* Print version information. */
97 void version()
98 {
99         cout << "Ragel State Machine Compiler version " VERSION << " " PUBDATE << endl <<
100                         "Copyright (c) 2001-2007 by Adrian Thurston" << endl;
101 }
102
103 /* Total error count. */
104 int gblErrorCount = 0;
105
106 /* Print the opening to a warning in the input, then return the error ostream. */
107 ostream &warning( const InputLoc &loc )
108 {
109         assert( loc.fileName != 0 );
110         cerr << loc.fileName << ":" << loc.line << ":" << 
111                         loc.col << ": warning: ";
112         return cerr;
113 }
114
115 /* Print the opening to a program error, then return the error stream. */
116 ostream &error()
117 {
118         gblErrorCount += 1;
119         cerr << PROGNAME ": ";
120         return cerr;
121 }
122
123 ostream &error( const InputLoc &loc )
124 {
125         gblErrorCount += 1;
126         assert( loc.fileName != 0 );
127         cerr << loc.fileName << ":" << loc.line << ": ";
128         return cerr;
129 }
130
131 void escapeLineDirectivePath( std::ostream &out, char *path )
132 {
133         for ( char *pc = path; *pc != 0; pc++ ) {
134                 if ( *pc == '\\' )
135                         out << "\\\\";
136                 else
137                         out << *pc;
138         }
139 }
140
141 /* If any forward slash is found in argv0 then it is assumed that the path is
142  * explicit and the path to the backend executable should be derived from
143  * that. If no forward slash is found it is assumed the file is being run from
144  * the installed location. The PREFIX supplied during configuration is used.
145  * */
146 char **makePathChecks( const char *argv0, const char *progName )
147 {
148         char **result = new char*[3];
149         const char *lastSlash = strrchr( argv0, '/' );
150         int numChecks = 0;
151
152         if ( lastSlash != 0 ) {
153                 char *path = strdup( argv0 );
154                 int givenPathLen = (lastSlash - argv0) + 1;
155                 path[givenPathLen] = 0;
156
157                 int progNameLen = strlen(progName);
158                 int length = givenPathLen + progNameLen + 1;
159                 char *check = new char[length];
160                 sprintf( check, "%s%s", path, progName );
161                 result[numChecks++] = check;
162
163                 length = givenPathLen + 3 + progNameLen + 1 + progNameLen + 1;
164                 check = new char[length];
165                 sprintf( check, "%s../%s/%s", path, progName, progName );
166                 result[numChecks++] = check;
167         }
168         else {
169                 int prefixLen = strlen(PREFIX);
170                 int progNameLen = strlen(progName);
171                 int length = prefixLen + 5 + progNameLen + 1;
172                 char *check = new char[length];
173
174                 sprintf( check, PREFIX "/bin/%s", progName );
175                 result[numChecks++] = check;
176         }
177
178         result[numChecks] = 0;
179         return result;
180 }
181
182
183 int execBackend( const char *argv0, costream *intermed )
184 {
185         /* Locate the backend program */
186         const char *progName = 0;
187         switch ( hostLang->lang ) {
188                 case HostLang::C:
189                 case HostLang::D:
190                         progName = "rlgen-cd";
191                         break;
192                 case HostLang::Java:
193                         progName = "rlgen-java";
194                         break;
195                 case HostLang::Ruby:
196                         progName = "rlgen-ruby";
197                         break;
198         }
199
200         char **pathChecks = makePathChecks( argv0, progName );
201
202         backendArgs.insert( 0, "rlgen-ruby" );
203         backendArgs.append( intermed->b->fileName );
204         backendArgs.append( 0 );
205
206         pid_t pid = fork();
207         if ( pid < 0 ) {
208                 /* Error, no child created. */
209                 error() << "failed to fork backend" << endp;
210         }
211         else if ( pid == 0 ) {
212                 /* child */
213                 while ( *pathChecks != 0 ) {
214                         execv( *pathChecks, backendArgs.data );
215                         pathChecks += 1;
216                 }
217                 error() << "failed to exec backend" << endp;
218         }
219
220         /* Parent process, wait for the child. */
221         int status;
222         wait( &status );
223
224         /* Clean up the intermediate. */
225         unlink( intermed->b->fileName );
226
227         /* What happened with the child. */
228         if ( ! WIFEXITED( status ) )
229                 error() << "backend did not exit normally" << endp;
230         
231         if ( WEXITSTATUS(status) != 0 )
232                 exit( WEXITSTATUS(status) );
233
234         return status;
235 }
236
237 char *makeIntermedTemplate( char *baseFileName )
238 {
239         char *result;
240         char *lastSlash = strrchr( baseFileName, '/' );
241         if ( lastSlash == 0 ) {
242                 result = new char[13];
243                 strcpy( result, "ragel-XXXXXX.xml" );
244         }
245         else {
246                 int baseLen = lastSlash - baseFileName + 1;
247                 result = new char[baseLen + 13];
248                 memcpy( result, baseFileName, baseLen );
249                 strcpy( result+baseLen, "ragel-XXXXXX.xml" );
250         }
251         return result;
252 };
253
254 char fnChars[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
255
256 costream *openIntermed( char *inputFileName, char *outputFileName )
257 {
258         srandom(time(0));
259         costream *result = 0;
260
261         /* Which filename do we use as the base? */
262         char *baseFileName = outputFileName != 0 ? outputFileName : inputFileName;
263
264         /* The template for the intermediate file name. */
265         char *intermedFileName = makeIntermedTemplate( baseFileName );
266
267         /* Randomize the name and try to open. */
268         char *firstX = strrchr( intermedFileName, 'X' ) - 5;
269         for ( int tries = 0; tries < 20; tries++ ) {
270                 /* Choose a random name. */
271                 for ( int x = 0; x < 6; x++ )
272                         firstX[x] = fnChars[random() % 52];
273
274                 /* Try to open the file. */
275                 int fd = ::open( intermedFileName, O_WRONLY|O_EXCL|O_CREAT, S_IRUSR|S_IWUSR );
276
277                 if ( fd > 0 ) {
278                         /* Success. */
279                         FILE *file = fdopen( fd, "wt" );
280                         if ( file == 0 )
281                                 error() << "fdopen(...) on intermediate file failed" << endp;
282
283                         cfilebuf *b = new cfilebuf( intermedFileName, file );
284                         result = new costream( b );
285                         break;
286                 }
287
288                 if ( errno == EACCES ) {
289                         error() << "failed to open temp file " << intermedFileName << 
290                                         ", access denied" << endp;
291                 }
292         }
293
294         if ( result == 0 )
295                 error() << "abnormal error: cannot find unique name for temp file" << endp;
296
297         return result;
298 }
299
300 /* Main, process args and call yyparse to start scanning input. */
301 int main(int argc, char **argv)
302 {
303         ParamCheck pc("o:nmleabjkS:M:CDJRvHh?-:sT:F:G:P:Lp", argc, argv);
304         char *inputFileName = 0;
305         char *outputFileName = 0;
306
307         while ( pc.check() ) {
308                 switch ( pc.state ) {
309                 case ParamCheck::match:
310                         switch ( pc.parameter ) {
311                         /* Output. */
312                         case 'o':
313                                 if ( *pc.parameterArg == 0 )
314                                         error() << "a zero length output file name was given" << endl;
315                                 else if ( outputFileName != 0 )
316                                         error() << "more than one output file name was given" << endl;
317                                 else {
318                                         /* Ok, remember the output file name. */
319                                         outputFileName = pc.parameterArg;
320                                         backendArgs.append( "-o" );
321                                         backendArgs.append( pc.parameterArg );
322                                 }
323                                 break;
324
325                         /* Minimization, mostly hidden options. */
326                         case 'n':
327                                 minimizeOpt = MinimizeNone;
328                                 break;
329                         case 'm':
330                                 minimizeOpt = MinimizeEnd;
331                                 break;
332                         case 'l':
333                                 minimizeOpt = MinimizeMostOps;
334                                 break;
335                         case 'e':
336                                 minimizeOpt = MinimizeEveryOp;
337                                 break;
338                         case 'a':
339                                 minimizeLevel = MinimizeApprox;
340                                 break;
341                         case 'b':
342                                 minimizeLevel = MinimizeStable;
343                                 break;
344                         case 'j':
345                                 minimizeLevel = MinimizePartition1;
346                                 break;
347                         case 'k':
348                                 minimizeLevel = MinimizePartition2;
349                                 break;
350
351                         /* Machine spec. */
352                         case 'S':
353                                 if ( *pc.parameterArg == 0 )
354                                         error() << "please specify an argument to -S" << endl;
355                                 else if ( machineSpec != 0 )
356                                         error() << "more than one -S argument was given" << endl;
357                                 else {
358                                         /* Ok, remember the path to the machine to generate. */
359                                         machineSpec = pc.parameterArg;
360                                 }
361                                 break;
362
363                         /* Machine path. */
364                         case 'M':
365                                 if ( *pc.parameterArg == 0 )
366                                         error() << "please specify an argument to -M" << endl;
367                                 else if ( machineName != 0 )
368                                         error() << "more than one -M argument was given" << endl;
369                                 else {
370                                         /* Ok, remember the machine name to generate. */
371                                         machineName = pc.parameterArg;
372                                 }
373                                 break;
374
375                         /* Host language types. */
376                         case 'C':
377                                 hostLang = &hostLangC;
378                                 break;
379                         case 'D':
380                                 hostLang = &hostLangD;
381                                 break;
382                         case 'J':
383                                 hostLang = &hostLangJava;
384                                 break;
385                         case 'R':
386                                 hostLang = &hostLangRuby;
387                                 break;
388
389                         /* Version and help. */
390                         case 'v':
391                                 version();
392                                 exit(0);
393                         case 'H': case 'h': case '?':
394                                 usage();
395                                 exit(0);
396                         case 's':
397                                 printStatistics = true;
398                                 break;
399                         case '-':
400                                 if ( strcasecmp(pc.parameterArg, "help") == 0 ) {
401                                         usage();
402                                         exit(0);
403                                 }
404                                 else if ( strcasecmp(pc.parameterArg, "version") == 0 ) {
405                                         version();
406                                         exit(0);
407                                 }
408                                 else {
409                                         error() << "--" << pc.parameterArg << 
410                                                         " is an invalid argument" << endl;
411                                 }
412
413                         /* Passthrough args. */
414                         case 'T': 
415                                 backendArgs.append( "-T" );
416                                 backendArgs.append( pc.parameterArg );
417                                 break;
418                         case 'F': 
419                                 backendArgs.append( "-F" );
420                                 backendArgs.append( pc.parameterArg );
421                                 break;
422                         case 'G': 
423                                 backendArgs.append( "-G" );
424                                 backendArgs.append( pc.parameterArg );
425                                 break;
426                         case 'P':
427                                 backendArgs.append( "-P" );
428                                 backendArgs.append( pc.parameterArg );
429                                 break;
430                         case 'p':
431                                 backendArgs.append( "-p" );
432                                 break;
433                         case 'L':
434                                 backendArgs.append( "-L" );
435                                 break;
436                         }
437                         break;
438
439                 case ParamCheck::invalid:
440                         error() << "-" << pc.parameter << " is an invalid argument" << endl;
441                         break;
442
443                 case ParamCheck::noparam:
444                         /* It is interpreted as an input file. */
445                         if ( *pc.curArg == 0 )
446                                 error() << "a zero length input file name was given" << endl;
447                         else if ( inputFileName != 0 )
448                                 error() << "more than one input file name was given" << endl;
449                         else {
450                                 /* OK, Remember the filename. */
451                                 inputFileName = pc.curArg;
452                         }
453                         break;
454                 }
455         }
456
457         /* Bail on above errors. */
458         if ( gblErrorCount > 0 )
459                 exit(1);
460
461         /* Make sure we are not writing to the same file as the input file. */
462         if ( inputFileName != 0 && outputFileName != 0 && 
463                         strcmp( inputFileName, outputFileName  ) == 0 )
464         {
465                 error() << "output file \"" << outputFileName  << 
466                                 "\" is the same as the input file" << endp;
467         }
468
469         /* Open the input file for reading. */
470         istream *inStream;
471         if ( inputFileName != 0 ) {
472                 /* Open the input file for reading. */
473                 ifstream *inFile = new ifstream( inputFileName );
474                 inStream = inFile;
475                 if ( ! inFile->is_open() )
476                         error() << "could not open " << inputFileName << " for reading" << endp;
477         }
478         else {
479                 inputFileName = "<stdin>";
480                 inStream = &cin;
481         }
482
483         /* Used for just a few things. */
484         std::ostringstream hostData;
485
486         if ( machineSpec == 0 && machineName == 0 )
487                 hostData << "<host line=\"1\" col=\"1\">";
488
489         Scanner scanner( inputFileName, *inStream, hostData, 0, 0, 0, false );
490         scanner.do_scan();
491
492         /* Finished, final check for errors.. */
493         if ( gblErrorCount > 0 )
494                 return 1;
495         
496         /* Now send EOF to all parsers. */
497         terminateAllParsers();
498
499         /* Finished, final check for errors.. */
500         if ( gblErrorCount > 0 )
501                 return 1;
502
503         if ( machineSpec == 0 && machineName == 0 )
504                 hostData << "</host>\n";
505
506         if ( gblErrorCount > 0 )
507                 return 1;
508         
509         costream *intermed = openIntermed( inputFileName, outputFileName );
510
511         /* Write the machines, then the surrounding code. */
512         writeMachines( *intermed, hostData.str(), inputFileName );
513
514         /* Close the intermediate file. */
515         intermed->fclose();
516
517         /* Run the backend process. */
518         execBackend( argv[0], intermed );
519
520         return 0;
521 }