Don't allow the left machine of <: to escape through the right machine via the
[external/ragel.git] / ragel / main.cpp
index ae70c8b..072a3a6 100644 (file)
 #include <fstream>
 #include <unistd.h>
 #include <sstream>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifndef WIN32
+#include <sys/wait.h>
+#else
+#include <windows.h>
+#include <psapi.h>
+#endif
 
 /* Parsing. */
 #include "ragel.h"
@@ -45,6 +57,8 @@ using std::cin;
 using std::cout;
 using std::cerr;
 using std::endl;
+using std::ios;
+using std::streamsize;
 
 /* Controls minimization. */
 MinimizeLevel minimizeLevel = MinimizePartition2;
@@ -55,6 +69,12 @@ char *machineSpec = 0, *machineName = 0;
 bool machineSpecFound = false;
 
 bool printStatistics = false;
+bool frontendOnly = false;
+bool generateDot = false;
+
+typedef Vector<const char *> ArgsVector;
+ArgsVector frontendArgs;
+ArgsVector backendArgs;
 
 /* Print a summary of the options. */
 void usage()
@@ -71,14 +91,29 @@ void usage()
 "   -m                   Minimize at the end of the compilation\n"
 "   -l                   Minimize after most operations (default)\n"
 "   -e                   Minimize after every operation\n"
-"machine selection:\n"
-"   -S <spec>            FSM specification to output for -V\n"
-"   -M <machine>         Machine definition/instantiation to output for -V\n"
+"visualization:\n"
+"   -x                   Run the frontend only: emit XML intermediate format\n"
+"   -V                   Generate a dot file for Graphviz\n"
+"   -p                   Display printable characters on labels\n"
+"   -S <spec>            FSM specification to output (for rlgen-dot)\n"
+"   -M <machine>         Machine definition/instantiation to output (for rlgen-dot)\n"
 "host language:\n"
 "   -C                   The host language is C, C++, Obj-C or Obj-C++ (default)\n"
 "   -D                   The host language is D\n"
 "   -J                   The host language is Java\n"
 "   -R                   The host language is Ruby\n"
+"line direcives: (C/D only)\n"
+"   -L                   Inhibit writing of #line directives\n"
+"code style: (C/Ruby only)\n"
+"   -T0                  Table driven FSM (default)\n"
+"   -T1                  Faster table driven FSM\n"
+"   -F0                  Flat table driven FSM\n"
+"   -F1                  Faster flat table-driven FSM\n"
+"code style: (C only)\n"
+"   -G0                  Goto-driven FSM\n"
+"   -G1                  Faster goto-driven FSM\n"
+"   -G2                  Really fast goto-driven FSM\n"
+"   -P<N>                N-Way Split really fast goto-driven FSM\n"
        ;       
 }
 
@@ -127,17 +162,22 @@ void escapeLineDirectivePath( std::ostream &out, char *path )
        }
 }
 
-/* Main, process args and call yyparse to start scanning input. */
-int main(int argc, char **argv)
+void processArgs( int argc, char **argv, char *&inputFileName, char *&outputFileName )
 {
-       ParamCheck pc("o:nmleabjkS:M:CDJRvHh?-:s", argc, argv);
-       char *inputFileName = 0;
-       char *outputFileName = 0;
+       ParamCheck pc("xo:nmleabjkS:M:CDJRvHh?-:sT:F:G:P:LpV", argc, argv);
 
        while ( pc.check() ) {
                switch ( pc.state ) {
                case ParamCheck::match:
                        switch ( pc.parameter ) {
+                       case 'V':
+                               generateDot = true;
+                               break;
+
+                       case 'x':
+                               frontendOnly = true;
+                               break;
+
                        /* Output. */
                        case 'o':
                                if ( *pc.parameterArg == 0 )
@@ -153,27 +193,35 @@ int main(int argc, char **argv)
                        /* Minimization, mostly hidden options. */
                        case 'n':
                                minimizeOpt = MinimizeNone;
+                               frontendArgs.append( "-n" );
                                break;
                        case 'm':
                                minimizeOpt = MinimizeEnd;
+                               frontendArgs.append( "-m" );
                                break;
                        case 'l':
                                minimizeOpt = MinimizeMostOps;
+                               frontendArgs.append( "-l" );
                                break;
                        case 'e':
                                minimizeOpt = MinimizeEveryOp;
+                               frontendArgs.append( "-e" );
                                break;
                        case 'a':
                                minimizeLevel = MinimizeApprox;
+                               frontendArgs.append( "-a" );
                                break;
                        case 'b':
                                minimizeLevel = MinimizeStable;
+                               frontendArgs.append( "-b" );
                                break;
                        case 'j':
                                minimizeLevel = MinimizePartition1;
+                               frontendArgs.append( "-j" );
                                break;
                        case 'k':
                                minimizeLevel = MinimizePartition2;
+                               frontendArgs.append( "-k" );
                                break;
 
                        /* Machine spec. */
@@ -185,6 +233,8 @@ int main(int argc, char **argv)
                                else {
                                        /* Ok, remember the path to the machine to generate. */
                                        machineSpec = pc.parameterArg;
+                                       frontendArgs.append( "-S" );
+                                       frontendArgs.append( pc.parameterArg );
                                }
                                break;
 
@@ -197,21 +247,27 @@ int main(int argc, char **argv)
                                else {
                                        /* Ok, remember the machine name to generate. */
                                        machineName = pc.parameterArg;
+                                       frontendArgs.append( "-M" );
+                                       frontendArgs.append( pc.parameterArg );
                                }
                                break;
 
                        /* Host language types. */
                        case 'C':
                                hostLang = &hostLangC;
+                               frontendArgs.append( "-C" );
                                break;
                        case 'D':
                                hostLang = &hostLangD;
+                               frontendArgs.append( "-D" );
                                break;
                        case 'J':
                                hostLang = &hostLangJava;
+                               frontendArgs.append( "-J" );
                                break;
                        case 'R':
                                hostLang = &hostLangRuby;
+                               frontendArgs.append( "-R" );
                                break;
 
                        /* Version and help. */
@@ -223,6 +279,7 @@ int main(int argc, char **argv)
                                exit(0);
                        case 's':
                                printStatistics = true;
+                               frontendArgs.append( "-s" );
                                break;
                        case '-':
                                if ( strcasecmp(pc.parameterArg, "help") == 0 ) {
@@ -237,6 +294,30 @@ int main(int argc, char **argv)
                                        error() << "--" << pc.parameterArg << 
                                                        " is an invalid argument" << endl;
                                }
+
+                       /* Passthrough args. */
+                       case 'T': 
+                               backendArgs.append( "-T" );
+                               backendArgs.append( pc.parameterArg );
+                               break;
+                       case 'F': 
+                               backendArgs.append( "-F" );
+                               backendArgs.append( pc.parameterArg );
+                               break;
+                       case 'G': 
+                               backendArgs.append( "-G" );
+                               backendArgs.append( pc.parameterArg );
+                               break;
+                       case 'P':
+                               backendArgs.append( "-P" );
+                               backendArgs.append( pc.parameterArg );
+                               break;
+                       case 'p':
+                               backendArgs.append( "-p" );
+                               break;
+                       case 'L':
+                               backendArgs.append( "-L" );
+                               break;
                        }
                        break;
 
@@ -257,44 +338,24 @@ int main(int argc, char **argv)
                        break;
                }
        }
+}
 
-       /* Bail on above errors. */
-       if ( gblErrorCount > 0 )
-               exit(1);
-
-       /* Make sure we are not writing to the same file as the input file. */
-       if ( inputFileName != 0 && outputFileName != 0 && 
-                       strcmp( inputFileName, outputFileName  ) == 0 )
-       {
-               error() << "output file \"" << outputFileName  << 
-                               "\" is the same as the input file" << endl;
-       }
-
+int frontend( char *inputFileName, char *outputFileName )
+{
        /* Open the input file for reading. */
-       istream *inStream;
-       if ( inputFileName != 0 ) {
-               /* Open the input file for reading. */
-               ifstream *inFile = new ifstream( inputFileName );
-               inStream = inFile;
-               if ( ! inFile->is_open() )
-                       error() << "could not open " << inputFileName << " for reading" << endl;
-       }
-       else {
-               inputFileName = "<stdin>";
-               inStream = &cin;
-       }
+       assert( inputFileName != 0 );
+       ifstream *inFile = new ifstream( inputFileName );
+       istream *inStream = inFile;
+       if ( ! inFile->is_open() )
+               error() << "could not open " << inputFileName << " for reading" << endp;
 
-
-       /* Bail on above errors. */
-       if ( gblErrorCount > 0 )
-               exit(1);
-
-       std::ostringstream outputBuffer;
+       /* Used for just a few things. */
+       std::ostringstream hostData;
 
        if ( machineSpec == 0 && machineName == 0 )
-               outputBuffer << "<host line=\"1\" col=\"1\">";
+               hostData << "<host line=\"1\" col=\"1\">";
 
-       Scanner scanner( inputFileName, *inStream, outputBuffer, 0, 0, 0, false );
+       Scanner scanner( inputFileName, *inStream, hostData, 0, 0, 0, false );
        scanner.do_scan();
 
        /* Finished, final check for errors.. */
@@ -309,7 +370,7 @@ int main(int argc, char **argv)
                return 1;
 
        if ( machineSpec == 0 && machineName == 0 )
-               outputBuffer << "</host>\n";
+               hostData << "</host>\n";
 
        if ( gblErrorCount > 0 )
                return 1;
@@ -321,10 +382,328 @@ int main(int argc, char **argv)
                outputFile = &cout;
 
        /* Write the machines, then the surrounding code. */
-       writeMachines( *outputFile, outputBuffer.str(), inputFileName );
+       writeMachines( *outputFile, hostData.str(), inputFileName );
 
+       /* Close the intermediate file. */
        if ( outputFileName != 0 )
                delete outputFile;
 
+       return gblErrorCount > 0;
+}
+
+char *makeIntermedTemplate( char *baseFileName )
+{
+       char *result = 0;
+       const char *templ = "ragel-XXXXXX.xml";
+       char *lastSlash = strrchr( baseFileName, '/' );
+       if ( lastSlash == 0 ) {
+               result = new char[strlen(templ)+1];
+               strcpy( result, templ );
+       }
+       else {
+               int baseLen = lastSlash - baseFileName + 1;
+               result = new char[baseLen + strlen(templ) + 1];
+               memcpy( result, baseFileName, baseLen );
+               strcpy( result+baseLen, templ );
+       }
+       return result;
+};
+
+char *openIntermed( char *inputFileName, char *outputFileName )
+{
+       srand(time(0));
+       char *result = 0;
+
+       /* Which filename do we use as the base? */
+       char *baseFileName = outputFileName != 0 ? outputFileName : inputFileName;
+
+       /* The template for the intermediate file name. */
+       char *intermedFileName = makeIntermedTemplate( baseFileName );
+
+       /* Randomize the name and try to open. */
+       char fnChars[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+       char *firstX = strrchr( intermedFileName, 'X' ) - 5;
+       for ( int tries = 0; tries < 20; tries++ ) {
+               /* Choose a random name. */
+               for ( int x = 0; x < 6; x++ )
+                       firstX[x] = fnChars[rand() % 52];
+
+               /* Try to open the file. */
+               int fd = ::open( intermedFileName, O_WRONLY|O_EXCL|O_CREAT, S_IRUSR|S_IWUSR );
+
+               if ( fd > 0 ) {
+                       /* Success. Close the file immediately and return the name for use
+                        * by the child processes. */
+                       ::close( fd );
+                       result = intermedFileName;
+                       break;
+               }
+
+               if ( errno == EACCES ) {
+                       error() << "failed to open temp file " << intermedFileName << 
+                                       ", access denied" << endp;
+               }
+       }
+
+       if ( result == 0 )
+               error() << "abnormal error: cannot find unique name for temp file" << endp;
+
+       return result;
+}
+
+
+void cleanExit( char *intermed, int status )
+{
+       unlink( intermed );
+       exit( status );
+}
+
+#ifndef WIN32
+
+/* If any forward slash is found in argv0 then it is assumed that the path is
+ * explicit and the path to the backend executable should be derived from
+ * that. Whe check that location and also go up one then inside a directory of
+ * the same name in case we are executing from the source tree. If no forward
+ * slash is found it is assumed the file is being run from the installed
+ * location. The PREFIX supplied during configuration is used. */
+char **makePathChecksUnix( const char *argv0, const char *progName )
+{
+       char **result = new char*[3];
+       const char *lastSlash = strrchr( argv0, '/' );
+       int numChecks = 0;
+
+       if ( lastSlash != 0 ) {
+               char *path = strdup( argv0 );
+               int givenPathLen = (lastSlash - argv0) + 1;
+               path[givenPathLen] = 0;
+
+               int progNameLen = strlen(progName);
+               int length = givenPathLen + progNameLen + 1;
+               char *check = new char[length];
+               sprintf( check, "%s%s", path, progName );
+               result[numChecks++] = check;
+
+               length = givenPathLen + 3 + progNameLen + 1 + progNameLen + 1;
+               check = new char[length];
+               sprintf( check, "%s../%s/%s", path, progName, progName );
+               result[numChecks++] = check;
+       }
+       else {
+               int prefixLen = strlen(PREFIX);
+               int progNameLen = strlen(progName);
+               int length = prefixLen + 5 + progNameLen + 1;
+               char *check = new char[length];
+
+               sprintf( check, PREFIX "/bin/%s", progName );
+               result[numChecks++] = check;
+       }
+
+       result[numChecks] = 0;
+       return result;
+}
+
+
+void forkAndExec( const char *progName, char **pathChecks, 
+               ArgsVector &args, char *intermed )
+{
+       pid_t pid = fork();
+       if ( pid < 0 ) {
+               /* Error, no child created. */
+               error() << "failed to fork for " << progName << endl;
+               cleanExit( intermed, 1 );
+       }
+       else if ( pid == 0 ) {
+               /* child */
+               while ( *pathChecks != 0 ) {
+                       /* Execv does not modify argv, it just uses the const form that is
+                        * compatible with the most code. Ours not included. */
+                       execv( *pathChecks, (char *const*) args.data );
+                       pathChecks += 1;
+               }
+               error() << "failed to exec " << progName << endl;
+               cleanExit( intermed, 1 );
+       }
+
+       /* Parent process, wait for the child. */
+       int status;
+       wait( &status );
+
+       /* What happened with the child. */
+       if ( ! WIFEXITED( status ) ) {
+               error() << progName << " did not exit normally" << endl;
+               cleanExit( intermed, 1 );
+       }
+       
+       if ( WEXITSTATUS(status) != 0 )
+               cleanExit( intermed, WEXITSTATUS(status) );
+}
+
+#else
+
+/* GetModuleFileNameEx is used to find out where the the current process's
+ * binary is. That location is searched first. If that fails then we go up one
+ * directory and look for the executable inside a directory of the same name
+ * in case we are executing from the source tree.
+ * */
+char **makePathChecksWin( const char *progName )
+{
+       int len = 1024;
+       char *imageFileName = new char[len];
+       HANDLE h = GetCurrentProcess();
+       len = GetModuleFileNameEx( h, NULL, imageFileName, len );
+       imageFileName[len] = 0;
+
+       char **result = new char*[3];
+       const char *lastSlash = strrchr( imageFileName, '\\' );
+       int numChecks = 0;
+
+       assert( lastSlash != 0 );
+       char *path = strdup( imageFileName );
+       int givenPathLen = (lastSlash - imageFileName) + 1;
+       path[givenPathLen] = 0;
+
+       int progNameLen = strlen(progName);
+       int length = givenPathLen + progNameLen + 1;
+       char *check = new char[length];
+       sprintf( check, "%s%s", path, progName );
+       result[numChecks++] = check;
+
+       length = givenPathLen + 3 + progNameLen + 1 + progNameLen + 1;
+       check = new char[length];
+       sprintf( check, "%s..\\%s\\%s", path, progName, progName );
+       result[numChecks++] = check;
+
+       result[numChecks] = 0;
+       return result;
+}
+
+void spawn( char *progName, char **pathChecks, 
+               ArgsVector &args, char *intermed )
+{
+       int result = 0;
+       while ( *pathChecks != 0 ) {
+               cerr << "trying to execute " << *pathChecks << endl;
+               result = _spawnv( _P_WAIT, *pathChecks, args.data );
+               if ( result >= 0 || errno != ENOENT )
+                       break;
+               pathChecks += 1;
+       }
+
+       if ( result < 0 ) {
+               error() << "failed to spawn " << progName << endl;
+               cleanExit( intermed, 1 );
+       }
+
+       if ( result > 0 )
+               cleanExit( intermed, 1 );
+}
+
+#endif
+
+void execFrontend( const char *argv0, char *inputFileName, char *intermed )
+{
+       /* The frontend program name. */
+       const char *progName = "ragel";
+
+       frontendArgs.insert( 0, progName );
+       frontendArgs.insert( 1, "-x" );
+       frontendArgs.append( "-o" );
+       frontendArgs.append( intermed );
+       frontendArgs.append( inputFileName );
+       frontendArgs.append( 0 );
+
+#ifndef WIN32
+       char **pathChecks = makePathChecksUnix( argv0, progName );
+       forkAndExec( progName, pathChecks, frontendArgs, intermed );
+#else
+       char **pathChecks = makePathChecksWin( progName );
+       spawn( progName, pathChecks, frontendArgs, intermed );
+#endif
+}
+
+void execBackend( const char *argv0, char *intermed, char *outputFileName )
+{
+       /* Locate the backend program */
+       const char *progName = 0;
+       if ( generateDot )
+               progName = "rlgen-dot";
+       else {
+               switch ( hostLang->lang ) {
+                       case HostLang::C:
+                       case HostLang::D:
+                               progName = "rlgen-cd";
+                               break;
+                       case HostLang::Java:
+                               progName = "rlgen-java";
+                               break;
+                       case HostLang::Ruby:
+                               progName = "rlgen-ruby";
+                               break;
+               }
+       }
+
+       backendArgs.insert( 0, progName );
+       if ( outputFileName != 0 ) {
+               backendArgs.append( "-o" );
+               backendArgs.append( outputFileName );
+       }
+       backendArgs.append( intermed );
+       backendArgs.append( 0 );
+
+#ifndef WIN32
+       char **pathChecks = makePathChecksUnix( argv0, progName );
+       forkAndExec( progName, pathChecks, backendArgs, intermed );
+#else
+       char **pathChecks = makePathChecksWin( progName );
+       spawn( progName, pathChecks, backendArgs, intermed );
+#endif
+}
+
+/* Main, process args and call yyparse to start scanning input. */
+int main(int argc, char **argv)
+{
+       char *inputFileName = 0;
+       char *outputFileName = 0;
+
+       processArgs( argc, argv, inputFileName, outputFileName );
+
+       /* If -M or -S are given and we're not generating a dot file then invoke
+        * the frontend. These options are not useful with code generators. */
+       if ( machineName != 0 || machineSpec != 0 ) {
+               if ( !generateDot )
+                       frontendOnly = true;
+       }
+
+       /* Require an input file. If we use standard in then we won't have a file
+        * name on which to base the output. */
+       if ( inputFileName == 0 )
+               error() << "no input file given" << endl;
+
+       /* Bail on argument processing errors. */
+       if ( gblErrorCount > 0 )
+               exit(1);
+
+       /* Make sure we are not writing to the same file as the input file. */
+       if ( inputFileName != 0 && outputFileName != 0 && 
+                       strcmp( inputFileName, outputFileName  ) == 0 )
+       {
+               error() << "output file \"" << outputFileName  << 
+                               "\" is the same as the input file" << endp;
+       }
+
+       if ( frontendOnly )
+               return frontend( inputFileName, outputFileName );
+
+       char *intermed = openIntermed( inputFileName, outputFileName );
+
+       /* From here on in the cleanExit function should be used to exit. */
+
+       /* Run the frontend, then the backend processes. */
+       execFrontend( argv[0], inputFileName, intermed );
+       execBackend( argv[0], intermed, outputFileName );
+
+       /* Clean up the intermediate. */
+       cleanExit( intermed, 0 );
+
        return 0;
 }