C# patch Daniel Tang.
[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/stat.h>
32 #include <fcntl.h>
33 #include <errno.h>
34
35 #ifndef WIN32
36 #include <sys/wait.h>
37 #else
38 #include <windows.h>
39 #include <psapi.h>
40 #endif
41
42 /* Parsing. */
43 #include "ragel.h"
44 #include "rlscan.h"
45
46 /* Parameters and output. */
47 #include "pcheck.h"
48 #include "vector.h"
49 #include "version.h"
50 #include "common.h"
51
52 using std::istream;
53 using std::ostream;
54 using std::ifstream;
55 using std::ofstream;
56 using std::cin;
57 using std::cout;
58 using std::cerr;
59 using std::endl;
60 using std::ios;
61 using std::streamsize;
62
63 /* Controls minimization. */
64 MinimizeLevel minimizeLevel = MinimizePartition2;
65 MinimizeOpt minimizeOpt = MinimizeMostOps;
66
67 /* Graphviz dot file generation. */
68 char *machineSpec = 0, *machineName = 0;
69 bool machineSpecFound = false;
70 bool wantDupsRemoved = true;
71
72 bool printStatistics = false;
73 bool frontendOnly = false;
74 bool generateDot = false;
75
76 typedef Vector<const char *> ArgsVector;
77 ArgsVector frontendArgs;
78 ArgsVector backendArgs;
79
80 /* Print a summary of the options. */
81 void usage()
82 {
83         cout <<
84 "usage: ragel [options] file\n"
85 "general:\n"
86 "   -h, -H, -?, --help   Print this usage and exit\n"
87 "   -v, --version        Print version information and exit\n"
88 "   -o <file>            Write output to <file>\n"
89 "   -s                   Print some statistics on stderr\n"
90 "   -d                   Do not remove duplicates from action lists\n"
91 "fsm minimization:\n"
92 "   -n                   Do not perform minimization\n"
93 "   -m                   Minimize at the end of the compilation\n"
94 "   -l                   Minimize after most operations (default)\n"
95 "   -e                   Minimize after every operation\n"
96 "visualization:\n"
97 "   -x                   Run the frontend only: emit XML intermediate format\n"
98 "   -V                   Generate a dot file for Graphviz\n"
99 "   -p                   Display printable characters on labels\n"
100 "   -S <spec>            FSM specification to output (for rlgen-dot)\n"
101 "   -M <machine>         Machine definition/instantiation to output (for rlgen-dot)\n"
102 "host language:\n"
103 "   -C                   The host language is C, C++, Obj-C or Obj-C++ (default)\n"
104 "   -D                   The host language is D\n"
105 "   -J                   The host language is Java\n"
106 "   -R                   The host language is Ruby\n"
107 "   -A                   The host language is C#\n"
108 "line direcives: (C/D/C# only)\n"
109 "   -L                   Inhibit writing of #line directives\n"
110 "code style: (C/Ruby/C# only)\n"
111 "   -T0                  Table driven FSM (default)\n"
112 "   -T1                  Faster table driven FSM\n"
113 "   -F0                  Flat table driven FSM\n"
114 "   -F1                  Faster flat table-driven FSM\n"
115 "code style: (C/C# only)\n"
116 "   -G0                  Goto-driven FSM\n"
117 "   -G1                  Faster goto-driven FSM\n"
118 "code style: (C only)\n"
119 "   -G2                  Really fast goto-driven FSM\n"
120 "   -P<N>                N-Way Split really fast goto-driven FSM\n"
121         ;       
122 }
123
124 /* Print version information. */
125 void version()
126 {
127         cout << "Ragel State Machine Compiler version " VERSION << " " PUBDATE << endl <<
128                         "Copyright (c) 2001-2007 by Adrian Thurston" << endl;
129 }
130
131 /* Total error count. */
132 int gblErrorCount = 0;
133
134 /* Print the opening to a warning in the input, then return the error ostream. */
135 ostream &warning( const InputLoc &loc )
136 {
137         assert( loc.fileName != 0 );
138         cerr << loc.fileName << ":" << loc.line << ":" << 
139                         loc.col << ": warning: ";
140         return cerr;
141 }
142
143 /* Print the opening to a program error, then return the error stream. */
144 ostream &error()
145 {
146         gblErrorCount += 1;
147         cerr << PROGNAME ": ";
148         return cerr;
149 }
150
151 ostream &error( const InputLoc &loc )
152 {
153         gblErrorCount += 1;
154         assert( loc.fileName != 0 );
155         cerr << loc.fileName << ":" << loc.line << ": ";
156         return cerr;
157 }
158
159 void escapeLineDirectivePath( std::ostream &out, char *path )
160 {
161         for ( char *pc = path; *pc != 0; pc++ ) {
162                 if ( *pc == '\\' )
163                         out << "\\\\";
164                 else
165                         out << *pc;
166         }
167 }
168
169 void processArgs( int argc, char **argv, char *&inputFileName, char *&outputFileName )
170 {
171         ParamCheck pc("xo:dnmleabjkS:M:CDJRAvHh?-:sT:F:G:P:LpV", argc, argv);
172
173         while ( pc.check() ) {
174                 switch ( pc.state ) {
175                 case ParamCheck::match:
176                         switch ( pc.parameter ) {
177                         case 'V':
178                                 generateDot = true;
179                                 break;
180
181                         case 'x':
182                                 frontendOnly = true;
183                                 break;
184
185                         /* Output. */
186                         case 'o':
187                                 if ( *pc.parameterArg == 0 )
188                                         error() << "a zero length output file name was given" << endl;
189                                 else if ( outputFileName != 0 )
190                                         error() << "more than one output file name was given" << endl;
191                                 else {
192                                         /* Ok, remember the output file name. */
193                                         outputFileName = pc.parameterArg;
194                                 }
195                                 break;
196
197                         /* Minimization, mostly hidden options. */
198                         case 'd':
199                                 wantDupsRemoved = false;
200                                 frontendArgs.append( "-d" );
201                                 break;
202
203                         /* Minimization, mostly hidden options. */
204                         case 'n':
205                                 minimizeOpt = MinimizeNone;
206                                 frontendArgs.append( "-n" );
207                                 break;
208                         case 'm':
209                                 minimizeOpt = MinimizeEnd;
210                                 frontendArgs.append( "-m" );
211                                 break;
212                         case 'l':
213                                 minimizeOpt = MinimizeMostOps;
214                                 frontendArgs.append( "-l" );
215                                 break;
216                         case 'e':
217                                 minimizeOpt = MinimizeEveryOp;
218                                 frontendArgs.append( "-e" );
219                                 break;
220                         case 'a':
221                                 minimizeLevel = MinimizeApprox;
222                                 frontendArgs.append( "-a" );
223                                 break;
224                         case 'b':
225                                 minimizeLevel = MinimizeStable;
226                                 frontendArgs.append( "-b" );
227                                 break;
228                         case 'j':
229                                 minimizeLevel = MinimizePartition1;
230                                 frontendArgs.append( "-j" );
231                                 break;
232                         case 'k':
233                                 minimizeLevel = MinimizePartition2;
234                                 frontendArgs.append( "-k" );
235                                 break;
236
237                         /* Machine spec. */
238                         case 'S':
239                                 if ( *pc.parameterArg == 0 )
240                                         error() << "please specify an argument to -S" << endl;
241                                 else if ( machineSpec != 0 )
242                                         error() << "more than one -S argument was given" << endl;
243                                 else {
244                                         /* Ok, remember the path to the machine to generate. */
245                                         machineSpec = pc.parameterArg;
246                                         frontendArgs.append( "-S" );
247                                         frontendArgs.append( pc.parameterArg );
248                                 }
249                                 break;
250
251                         /* Machine path. */
252                         case 'M':
253                                 if ( *pc.parameterArg == 0 )
254                                         error() << "please specify an argument to -M" << endl;
255                                 else if ( machineName != 0 )
256                                         error() << "more than one -M argument was given" << endl;
257                                 else {
258                                         /* Ok, remember the machine name to generate. */
259                                         machineName = pc.parameterArg;
260                                         frontendArgs.append( "-M" );
261                                         frontendArgs.append( pc.parameterArg );
262                                 }
263                                 break;
264
265                         /* Host language types. */
266                         case 'C':
267                                 hostLang = &hostLangC;
268                                 frontendArgs.append( "-C" );
269                                 break;
270                         case 'D':
271                                 hostLang = &hostLangD;
272                                 frontendArgs.append( "-D" );
273                                 break;
274                         case 'J':
275                                 hostLang = &hostLangJava;
276                                 frontendArgs.append( "-J" );
277                                 break;
278                         case 'R':
279                                 hostLang = &hostLangRuby;
280                                 frontendArgs.append( "-R" );
281                                 break;
282                         case 'A':
283                                 hostLang = &hostLangCSharp;
284                                 frontendArgs.append( "-A" );
285                                 break;
286
287                         /* Version and help. */
288                         case 'v':
289                                 version();
290                                 exit(0);
291                         case 'H': case 'h': case '?':
292                                 usage();
293                                 exit(0);
294                         case 's':
295                                 printStatistics = true;
296                                 frontendArgs.append( "-s" );
297                                 break;
298                         case '-':
299                                 if ( strcasecmp(pc.parameterArg, "help") == 0 ) {
300                                         usage();
301                                         exit(0);
302                                 }
303                                 else if ( strcasecmp(pc.parameterArg, "version") == 0 ) {
304                                         version();
305                                         exit(0);
306                                 }
307                                 else {
308                                         error() << "--" << pc.parameterArg << 
309                                                         " is an invalid argument" << endl;
310                                 }
311
312                         /* Passthrough args. */
313                         case 'T': 
314                                 backendArgs.append( "-T" );
315                                 backendArgs.append( pc.parameterArg );
316                                 break;
317                         case 'F': 
318                                 backendArgs.append( "-F" );
319                                 backendArgs.append( pc.parameterArg );
320                                 break;
321                         case 'G': 
322                                 backendArgs.append( "-G" );
323                                 backendArgs.append( pc.parameterArg );
324                                 break;
325                         case 'P':
326                                 backendArgs.append( "-P" );
327                                 backendArgs.append( pc.parameterArg );
328                                 break;
329                         case 'p':
330                                 backendArgs.append( "-p" );
331                                 break;
332                         case 'L':
333                                 backendArgs.append( "-L" );
334                                 break;
335                         }
336                         break;
337
338                 case ParamCheck::invalid:
339                         error() << "-" << pc.parameter << " is an invalid argument" << endl;
340                         break;
341
342                 case ParamCheck::noparam:
343                         /* It is interpreted as an input file. */
344                         if ( *pc.curArg == 0 )
345                                 error() << "a zero length input file name was given" << endl;
346                         else if ( inputFileName != 0 )
347                                 error() << "more than one input file name was given" << endl;
348                         else {
349                                 /* OK, Remember the filename. */
350                                 inputFileName = pc.curArg;
351                         }
352                         break;
353                 }
354         }
355 }
356
357 int frontend( char *inputFileName, char *outputFileName )
358 {
359         /* Open the input file for reading. */
360         assert( inputFileName != 0 );
361         ifstream *inFile = new ifstream( inputFileName );
362         istream *inStream = inFile;
363         if ( ! inFile->is_open() )
364                 error() << "could not open " << inputFileName << " for reading" << endp;
365
366         /* Used for just a few things. */
367         std::ostringstream hostData;
368
369         if ( machineSpec == 0 && machineName == 0 )
370                 hostData << "<host line=\"1\" col=\"1\">";
371
372         Scanner scanner( inputFileName, *inStream, hostData, 0, 0, 0, false );
373         scanner.do_scan();
374
375         /* Finished, final check for errors.. */
376         if ( gblErrorCount > 0 )
377                 return 1;
378         
379         /* Now send EOF to all parsers. */
380         terminateAllParsers();
381
382         /* Finished, final check for errors.. */
383         if ( gblErrorCount > 0 )
384                 return 1;
385
386         if ( machineSpec == 0 && machineName == 0 )
387                 hostData << "</host>\n";
388
389         if ( gblErrorCount > 0 )
390                 return 1;
391         
392         ostream *outputFile = 0;
393         if ( outputFileName != 0 )
394                 outputFile = new ofstream( outputFileName );
395         else
396                 outputFile = &cout;
397
398         /* Write the machines, then the surrounding code. */
399         writeMachines( *outputFile, hostData.str(), inputFileName );
400
401         /* Close the intermediate file. */
402         if ( outputFileName != 0 )
403                 delete outputFile;
404
405         return gblErrorCount > 0;
406 }
407
408 char *makeIntermedTemplate( char *baseFileName )
409 {
410         char *result = 0;
411         const char *templ = "ragel-XXXXXX.xml";
412         char *lastSlash = strrchr( baseFileName, '/' );
413         if ( lastSlash == 0 ) {
414                 result = new char[strlen(templ)+1];
415                 strcpy( result, templ );
416         }
417         else {
418                 int baseLen = lastSlash - baseFileName + 1;
419                 result = new char[baseLen + strlen(templ) + 1];
420                 memcpy( result, baseFileName, baseLen );
421                 strcpy( result+baseLen, templ );
422         }
423         return result;
424 };
425
426 char *openIntermed( char *inputFileName, char *outputFileName )
427 {
428         srand(time(0));
429         char *result = 0;
430
431         /* Which filename do we use as the base? */
432         char *baseFileName = outputFileName != 0 ? outputFileName : inputFileName;
433
434         /* The template for the intermediate file name. */
435         char *intermedFileName = makeIntermedTemplate( baseFileName );
436
437         /* Randomize the name and try to open. */
438         char fnChars[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
439         char *firstX = strrchr( intermedFileName, 'X' ) - 5;
440         for ( int tries = 0; tries < 20; tries++ ) {
441                 /* Choose a random name. */
442                 for ( int x = 0; x < 6; x++ )
443                         firstX[x] = fnChars[rand() % 52];
444
445                 /* Try to open the file. */
446                 int fd = ::open( intermedFileName, O_WRONLY|O_EXCL|O_CREAT, S_IRUSR|S_IWUSR );
447
448                 if ( fd > 0 ) {
449                         /* Success. Close the file immediately and return the name for use
450                          * by the child processes. */
451                         ::close( fd );
452                         result = intermedFileName;
453                         break;
454                 }
455
456                 if ( errno == EACCES ) {
457                         error() << "failed to open temp file " << intermedFileName << 
458                                         ", access denied" << endp;
459                 }
460         }
461
462         if ( result == 0 )
463                 error() << "abnormal error: cannot find unique name for temp file" << endp;
464
465         return result;
466 }
467
468
469 void cleanExit( char *intermed, int status )
470 {
471         unlink( intermed );
472         exit( status );
473 }
474
475 #ifndef WIN32
476
477 /* If any forward slash is found in argv0 then it is assumed that the path is
478  * explicit and the path to the backend executable should be derived from
479  * that. Whe check that location and also go up one then inside a directory of
480  * the same name in case we are executing from the source tree. If no forward
481  * slash is found it is assumed the file is being run from the installed
482  * location. The PREFIX supplied during configuration is used. */
483 char **makePathChecksUnix( const char *argv0, const char *progName )
484 {
485         char **result = new char*[3];
486         const char *lastSlash = strrchr( argv0, '/' );
487         int numChecks = 0;
488
489         if ( lastSlash != 0 ) {
490                 char *path = strdup( argv0 );
491                 int givenPathLen = (lastSlash - argv0) + 1;
492                 path[givenPathLen] = 0;
493
494                 int progNameLen = strlen(progName);
495                 int length = givenPathLen + progNameLen + 1;
496                 char *check = new char[length];
497                 sprintf( check, "%s%s", path, progName );
498                 result[numChecks++] = check;
499
500                 length = givenPathLen + 3 + progNameLen + 1 + progNameLen + 1;
501                 check = new char[length];
502                 sprintf( check, "%s../%s/%s", path, progName, progName );
503                 result[numChecks++] = check;
504         }
505         else {
506                 int prefixLen = strlen(PREFIX);
507                 int progNameLen = strlen(progName);
508                 int length = prefixLen + 5 + progNameLen + 1;
509                 char *check = new char[length];
510
511                 sprintf( check, PREFIX "/bin/%s", progName );
512                 result[numChecks++] = check;
513         }
514
515         result[numChecks] = 0;
516         return result;
517 }
518
519
520 void forkAndExec( const char *progName, char **pathChecks, 
521                 ArgsVector &args, char *intermed )
522 {
523         pid_t pid = fork();
524         if ( pid < 0 ) {
525                 /* Error, no child created. */
526                 error() << "failed to fork for " << progName << endl;
527                 cleanExit( intermed, 1 );
528         }
529         else if ( pid == 0 ) {
530                 /* child */
531                 while ( *pathChecks != 0 ) {
532                         /* Execv does not modify argv, it just uses the const form that is
533                          * compatible with the most code. Ours not included. */
534                         execv( *pathChecks, (char *const*) args.data );
535                         pathChecks += 1;
536                 }
537                 error() << "failed to exec " << progName << endl;
538                 cleanExit( intermed, 1 );
539         }
540
541         /* Parent process, wait for the child. */
542         int status;
543         wait( &status );
544
545         /* What happened with the child. */
546         if ( ! WIFEXITED( status ) ) {
547                 error() << progName << " did not exit normally" << endl;
548                 cleanExit( intermed, 1 );
549         }
550         
551         if ( WEXITSTATUS(status) != 0 )
552                 cleanExit( intermed, WEXITSTATUS(status) );
553 }
554
555 #else
556
557 /* GetModuleFileNameEx is used to find out where the the current process's
558  * binary is. That location is searched first. If that fails then we go up one
559  * directory and look for the executable inside a directory of the same name
560  * in case we are executing from the source tree.
561  * */
562 char **makePathChecksWin( const char *progName )
563 {
564         int len = 1024;
565         char *imageFileName = new char[len];
566         HANDLE h = GetCurrentProcess();
567         len = GetModuleFileNameEx( h, NULL, imageFileName, len );
568         imageFileName[len] = 0;
569
570         char **result = new char*[3];
571         const char *lastSlash = strrchr( imageFileName, '\\' );
572         int numChecks = 0;
573
574         assert( lastSlash != 0 );
575         char *path = strdup( imageFileName );
576         int givenPathLen = (lastSlash - imageFileName) + 1;
577         path[givenPathLen] = 0;
578
579         int progNameLen = strlen(progName);
580         int length = givenPathLen + progNameLen + 1;
581         char *check = new char[length];
582         sprintf( check, "%s%s", path, progName );
583         result[numChecks++] = check;
584
585         length = givenPathLen + 3 + progNameLen + 1 + progNameLen + 1;
586         check = new char[length];
587         sprintf( check, "%s..\\%s\\%s", path, progName, progName );
588         result[numChecks++] = check;
589
590         result[numChecks] = 0;
591         return result;
592 }
593
594 void spawn( const char *progName, char **pathChecks, 
595                 ArgsVector &args, char *intermed )
596 {
597         int result = 0;
598         while ( *pathChecks != 0 ) {
599                 //cerr << "trying to execute " << *pathChecks << endl;
600                 result = _spawnv( _P_WAIT, *pathChecks, args.data );
601                 if ( result >= 0 || errno != ENOENT )
602                         break;
603                 pathChecks += 1;
604         }
605
606         if ( result < 0 ) {
607                 error() << "failed to spawn " << progName << endl;
608                 cleanExit( intermed, 1 );
609         }
610
611         if ( result > 0 )
612                 cleanExit( intermed, 1 );
613 }
614
615 #endif
616
617 void execFrontend( const char *argv0, char *inputFileName, char *intermed )
618 {
619         /* The frontend program name. */
620         const char *progName = "ragel";
621
622         frontendArgs.insert( 0, progName );
623         frontendArgs.insert( 1, "-x" );
624         frontendArgs.append( "-o" );
625         frontendArgs.append( intermed );
626         frontendArgs.append( inputFileName );
627         frontendArgs.append( 0 );
628
629 #ifndef WIN32
630         char **pathChecks = makePathChecksUnix( argv0, progName );
631         forkAndExec( progName, pathChecks, frontendArgs, intermed );
632 #else
633         char **pathChecks = makePathChecksWin( progName );
634         spawn( progName, pathChecks, frontendArgs, intermed );
635 #endif
636 }
637
638 void execBackend( const char *argv0, char *intermed, char *outputFileName )
639 {
640         /* Locate the backend program */
641         const char *progName = 0;
642         if ( generateDot )
643                 progName = "rlgen-dot";
644         else {
645                 switch ( hostLang->lang ) {
646                         case HostLang::C:
647                         case HostLang::D:
648                                 progName = "rlgen-cd";
649                                 break;
650                         case HostLang::Java:
651                                 progName = "rlgen-java";
652                                 break;
653                         case HostLang::Ruby:
654                                 progName = "rlgen-ruby";
655                                 break;
656                         case HostLang::CSharp:
657                                 progName = "rlgen-csharp";
658                 }
659         }
660
661         backendArgs.insert( 0, progName );
662         if ( outputFileName != 0 ) {
663                 backendArgs.append( "-o" );
664                 backendArgs.append( outputFileName );
665         }
666         backendArgs.append( intermed );
667         backendArgs.append( 0 );
668
669 #ifndef WIN32
670         char **pathChecks = makePathChecksUnix( argv0, progName );
671         forkAndExec( progName, pathChecks, backendArgs, intermed );
672 #else
673         char **pathChecks = makePathChecksWin( progName );
674         spawn( progName, pathChecks, backendArgs, intermed );
675 #endif
676 }
677
678 /* Main, process args and call yyparse to start scanning input. */
679 int main(int argc, char **argv)
680 {
681         char *inputFileName = 0;
682         char *outputFileName = 0;
683
684         processArgs( argc, argv, inputFileName, outputFileName );
685
686         /* If -M or -S are given and we're not generating a dot file then invoke
687          * the frontend. These options are not useful with code generators. */
688         if ( machineName != 0 || machineSpec != 0 ) {
689                 if ( !generateDot )
690                         frontendOnly = true;
691         }
692
693         /* Require an input file. If we use standard in then we won't have a file
694          * name on which to base the output. */
695         if ( inputFileName == 0 )
696                 error() << "no input file given" << endl;
697
698         /* Bail on argument processing errors. */
699         if ( gblErrorCount > 0 )
700                 exit(1);
701
702         /* Make sure we are not writing to the same file as the input file. */
703         if ( inputFileName != 0 && outputFileName != 0 && 
704                         strcmp( inputFileName, outputFileName  ) == 0 )
705         {
706                 error() << "output file \"" << outputFileName  << 
707                                 "\" is the same as the input file" << endp;
708         }
709
710         if ( frontendOnly )
711                 return frontend( inputFileName, outputFileName );
712
713         char *intermed = openIntermed( inputFileName, outputFileName );
714
715         /* From here on in the cleanExit function should be used to exit. */
716
717         /* Run the frontend, then the backend processes. */
718         execFrontend( argv[0], inputFileName, intermed );
719         execBackend( argv[0], intermed, outputFileName );
720
721         /* Clean up the intermediate. */
722         cleanExit( intermed, 0 );
723
724         return 0;
725 }