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