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