2 * Copyright 2001-2007 Adrian Thurston <thurston@cs.queensu.ca>
5 /* This file is part of Ragel.
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.
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.
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
30 #include <sys/types.h>
46 /* Parameters and output. */
61 using std::streamsize;
63 /* Controls minimization. */
64 MinimizeLevel minimizeLevel = MinimizePartition2;
65 MinimizeOpt minimizeOpt = MinimizeMostOps;
67 /* Graphviz dot file generation. */
68 char *machineSpec = 0, *machineName = 0;
69 bool machineSpecFound = false;
71 bool printStatistics = false;
72 bool frontendOnly = false;
74 typedef Vector<char*> ArgsVector;
75 ArgsVector frontendArgs;
76 ArgsVector backendArgs;
78 /* Print a summary of the options. */
82 "usage: ragel [options] file\n"
84 " -h, -H, -?, --help Print this usage and exit\n"
85 " -v, --version Print version information and exit\n"
86 " -o <file> Write output to <file>\n"
87 " -s Print some statistics on stderr\n"
89 " -n Do not perform minimization\n"
90 " -m Minimize at the end of the compilation\n"
91 " -l Minimize after most operations (default)\n"
92 " -e Minimize after every operation\n"
93 "machine selection:\n"
94 " -S <spec> FSM specification to output (for rlgen-dot)\n"
95 " -M <machine> Machine definition/instantiation to output (for rlgen-dot)\n"
97 " -C The host language is C, C++, Obj-C or Obj-C++ (default)\n"
98 " -D The host language is D\n"
99 " -J The host language is Java\n"
100 " -R The host language is Ruby\n"
104 /* Print version information. */
107 cout << "Ragel State Machine Compiler version " VERSION << " " PUBDATE << endl <<
108 "Copyright (c) 2001-2007 by Adrian Thurston" << endl;
111 /* Total error count. */
112 int gblErrorCount = 0;
114 /* Print the opening to a warning in the input, then return the error ostream. */
115 ostream &warning( const InputLoc &loc )
117 assert( loc.fileName != 0 );
118 cerr << loc.fileName << ":" << loc.line << ":" <<
119 loc.col << ": warning: ";
123 /* Print the opening to a program error, then return the error stream. */
127 cerr << PROGNAME ": ";
131 ostream &error( const InputLoc &loc )
134 assert( loc.fileName != 0 );
135 cerr << loc.fileName << ":" << loc.line << ": ";
139 void escapeLineDirectivePath( std::ostream &out, char *path )
141 for ( char *pc = path; *pc != 0; pc++ ) {
149 void processArgs( int argc, char **argv, char *&inputFileName, char *&outputFileName )
151 ParamCheck pc("fo:nmleabjkS:M:CDJRvHh?-:sT:F:G:P:Lp", argc, argv);
153 while ( pc.check() ) {
154 switch ( pc.state ) {
155 case ParamCheck::match:
156 switch ( pc.parameter ) {
163 if ( *pc.parameterArg == 0 )
164 error() << "a zero length output file name was given" << endl;
165 else if ( outputFileName != 0 )
166 error() << "more than one output file name was given" << endl;
168 /* Ok, remember the output file name. */
169 outputFileName = pc.parameterArg;
173 /* Minimization, mostly hidden options. */
175 minimizeOpt = MinimizeNone;
176 frontendArgs.append( "-n" );
179 minimizeOpt = MinimizeEnd;
180 frontendArgs.append( "-m" );
183 minimizeOpt = MinimizeMostOps;
184 frontendArgs.append( "-l" );
187 minimizeOpt = MinimizeEveryOp;
188 frontendArgs.append( "-e" );
191 minimizeLevel = MinimizeApprox;
192 frontendArgs.append( "-a" );
195 minimizeLevel = MinimizeStable;
196 frontendArgs.append( "-b" );
199 minimizeLevel = MinimizePartition1;
200 frontendArgs.append( "-j" );
203 minimizeLevel = MinimizePartition2;
204 frontendArgs.append( "-k" );
209 if ( *pc.parameterArg == 0 )
210 error() << "please specify an argument to -S" << endl;
211 else if ( machineSpec != 0 )
212 error() << "more than one -S argument was given" << endl;
214 /* Ok, remember the path to the machine to generate. */
215 machineSpec = pc.parameterArg;
216 frontendArgs.append( "-S" );
217 frontendArgs.append( pc.parameterArg );
223 if ( *pc.parameterArg == 0 )
224 error() << "please specify an argument to -M" << endl;
225 else if ( machineName != 0 )
226 error() << "more than one -M argument was given" << endl;
228 /* Ok, remember the machine name to generate. */
229 machineName = pc.parameterArg;
230 frontendArgs.append( "-M" );
231 frontendArgs.append( pc.parameterArg );
235 /* Host language types. */
237 hostLang = &hostLangC;
238 frontendArgs.append( "-C" );
241 hostLang = &hostLangD;
242 frontendArgs.append( "-D" );
245 hostLang = &hostLangJava;
246 frontendArgs.append( "-J" );
249 hostLang = &hostLangRuby;
250 frontendArgs.append( "-R" );
253 /* Version and help. */
257 case 'H': case 'h': case '?':
261 printStatistics = true;
262 frontendArgs.append( "-s" );
265 if ( strcasecmp(pc.parameterArg, "help") == 0 ) {
269 else if ( strcasecmp(pc.parameterArg, "version") == 0 ) {
274 error() << "--" << pc.parameterArg <<
275 " is an invalid argument" << endl;
278 /* Passthrough args. */
280 backendArgs.append( "-T" );
281 backendArgs.append( pc.parameterArg );
284 backendArgs.append( "-F" );
285 backendArgs.append( pc.parameterArg );
288 backendArgs.append( "-G" );
289 backendArgs.append( pc.parameterArg );
292 backendArgs.append( "-P" );
293 backendArgs.append( pc.parameterArg );
296 backendArgs.append( "-p" );
299 backendArgs.append( "-L" );
304 case ParamCheck::invalid:
305 error() << "-" << pc.parameter << " is an invalid argument" << endl;
308 case ParamCheck::noparam:
309 /* It is interpreted as an input file. */
310 if ( *pc.curArg == 0 )
311 error() << "a zero length input file name was given" << endl;
312 else if ( inputFileName != 0 )
313 error() << "more than one input file name was given" << endl;
315 /* OK, Remember the filename. */
316 inputFileName = pc.curArg;
323 int frontend( char *inputFileName, char *outputFileName )
325 /* Open the input file for reading. */
327 if ( inputFileName != 0 ) {
328 /* Open the input file for reading. */
329 ifstream *inFile = new ifstream( inputFileName );
331 if ( ! inFile->is_open() )
332 error() << "could not open " << inputFileName << " for reading" << endp;
335 inputFileName = "<stdin>";
339 /* Used for just a few things. */
340 std::ostringstream hostData;
342 if ( machineSpec == 0 && machineName == 0 )
343 hostData << "<host line=\"1\" col=\"1\">";
345 Scanner scanner( inputFileName, *inStream, hostData, 0, 0, 0, false );
348 /* Finished, final check for errors.. */
349 if ( gblErrorCount > 0 )
352 /* Now send EOF to all parsers. */
353 terminateAllParsers();
355 /* Finished, final check for errors.. */
356 if ( gblErrorCount > 0 )
359 if ( machineSpec == 0 && machineName == 0 )
360 hostData << "</host>\n";
362 if ( gblErrorCount > 0 )
365 ostream *outputFile = 0;
366 if ( outputFileName != 0 )
367 outputFile = new ofstream( outputFileName );
371 /* Write the machines, then the surrounding code. */
372 writeMachines( *outputFile, hostData.str(), inputFileName );
374 /* Close the intermediate file. */
375 if ( outputFileName != 0 )
378 return gblErrorCount > 0;
381 char *makeIntermedTemplate( char *baseFileName )
383 char *result = 0, *templ = "ragel-XXXXXX.xml";
384 char *lastSlash = strrchr( baseFileName, '/' );
385 if ( lastSlash == 0 ) {
386 result = new char[strlen(templ)+1];
387 strcpy( result, templ );
390 int baseLen = lastSlash - baseFileName + 1;
391 result = new char[baseLen + strlen(templ) + 1];
392 memcpy( result, baseFileName, baseLen );
393 strcpy( result+baseLen, templ );
398 char *openIntermed( char *inputFileName, char *outputFileName )
403 /* Which filename do we use as the base? */
404 char *baseFileName = outputFileName != 0 ? outputFileName : inputFileName;
406 /* The template for the intermediate file name. */
407 char *intermedFileName = makeIntermedTemplate( baseFileName );
409 /* Randomize the name and try to open. */
410 char fnChars[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
411 char *firstX = strrchr( intermedFileName, 'X' ) - 5;
412 for ( int tries = 0; tries < 20; tries++ ) {
413 /* Choose a random name. */
414 for ( int x = 0; x < 6; x++ )
415 firstX[x] = fnChars[rand() % 52];
417 /* Try to open the file. */
418 int fd = ::open( intermedFileName, O_WRONLY|O_EXCL|O_CREAT, S_IRUSR|S_IWUSR );
421 /* Success. Close the file immediately and return the name for use
422 * by the child processes. */
424 result = intermedFileName;
428 if ( errno == EACCES ) {
429 error() << "failed to open temp file " << intermedFileName <<
430 ", access denied" << endp;
435 error() << "abnormal error: cannot find unique name for temp file" << endp;
441 void cleanExit( char *intermed, int status )
449 /* If any forward slash is found in argv0 then it is assumed that the path is
450 * explicit and the path to the backend executable should be derived from
451 * that. If no forward slash is found it is assumed the file is being run from
452 * the installed location. The PREFIX supplied during configuration is used.
454 char **makePathChecksUnix( const char *argv0, const char *progName )
456 char **result = new char*[3];
457 const char *lastSlash = strrchr( argv0, '/' );
460 if ( lastSlash != 0 ) {
461 char *path = strdup( argv0 );
462 int givenPathLen = (lastSlash - argv0) + 1;
463 path[givenPathLen] = 0;
465 int progNameLen = strlen(progName);
466 int length = givenPathLen + progNameLen + 1;
467 char *check = new char[length];
468 sprintf( check, "%s%s", path, progName );
469 result[numChecks++] = check;
471 length = givenPathLen + 3 + progNameLen + 1 + progNameLen + 1;
472 check = new char[length];
473 sprintf( check, "%s../%s/%s", path, progName, progName );
474 result[numChecks++] = check;
477 int prefixLen = strlen(PREFIX);
478 int progNameLen = strlen(progName);
479 int length = prefixLen + 5 + progNameLen + 1;
480 char *check = new char[length];
482 sprintf( check, PREFIX "/bin/%s", progName );
483 result[numChecks++] = check;
486 result[numChecks] = 0;
491 int forkAndExec( char *progName, char **pathChecks,
492 ArgsVector &args, char *intermed )
496 /* Error, no child created. */
497 error() << "failed to fork for " << progName << endl;
498 cleanExit( intermed, 1 );
500 else if ( pid == 0 ) {
502 while ( *pathChecks != 0 ) {
503 execv( *pathChecks, args.data );
506 error() << "failed to exec " << progName << endl;
507 cleanExit( intermed, 1 );
510 /* Parent process, wait for the child. */
514 /* What happened with the child. */
515 if ( ! WIFEXITED( status ) ) {
516 error() << progName << " did not exit normally" << endl;
517 cleanExit( intermed, 1 );
520 if ( WEXITSTATUS(status) != 0 )
521 cleanExit( intermed, WEXITSTATUS(status) );
528 /* If any forward slash is found in argv0 then it is assumed that the path is
529 * explicit and the path to the backend executable should be derived from
530 * that. If no forward slash is found it is assumed the file is being run from
531 * the installed location. The PREFIX supplied during configuration is used.
533 char **makePathChecksWin( const char *progName )
536 char *imageFileName = new char[len];
537 HANDLE h = GetCurrentProcess();
538 len = GetModuleFileNameEx( h, NULL, imageFileName, len );
539 imageFileName[len] = 0;
541 char **result = new char*[3];
542 const char *lastSlash = strrchr( imageFileName, '\\' );
545 assert( lastSlash != 0 );
546 char *path = strdup( imageFileName );
547 int givenPathLen = (lastSlash - imageFileName) + 1;
548 path[givenPathLen] = 0;
550 int progNameLen = strlen(progName);
551 int length = givenPathLen + progNameLen + 1;
552 char *check = new char[length];
553 sprintf( check, "%s%s", path, progName );
554 result[numChecks++] = check;
556 length = givenPathLen + 3 + progNameLen + 1 + progNameLen + 1;
557 check = new char[length];
558 sprintf( check, "%s..\\%s\\%s", path, progName, progName );
559 result[numChecks++] = check;
561 result[numChecks] = 0;
567 void execFrontend( const char *argv0, char *inputFileName, char *intermed )
569 /* The frontend program name. */
570 char *progName = "ragel";
572 frontendArgs.insert( 0, progName );
573 frontendArgs.insert( 1, "-f" );
574 frontendArgs.append( "-o" );
575 frontendArgs.append( intermed );
576 frontendArgs.append( inputFileName );
577 frontendArgs.append( 0 );
580 char **pathChecks = makePathChecksUnix( argv0, progName );
581 forkAndExec( progName, pathChecks, frontendArgs, intermed );
583 char **pathChecks = makePathChecksWin( progName );
584 while ( *pathChecks != 0 )
585 cerr << *pathChecks++ << endl;
589 void execBackend( const char *argv0, char *intermed, char *outputFileName )
591 /* Locate the backend program */
593 switch ( hostLang->lang ) {
596 progName = "rlgen-cd";
599 progName = "rlgen-java";
602 progName = "rlgen-ruby";
606 backendArgs.insert( 0, progName );
607 if ( outputFileName != 0 ) {
608 backendArgs.append( "-o" );
609 backendArgs.append( outputFileName );
611 backendArgs.append( intermed );
612 backendArgs.append( 0 );
615 char **pathChecks = makePathChecksUnix( argv0, progName );
616 forkAndExec( progName, pathChecks, backendArgs, intermed );
618 char **pathChecks = makePathChecksWin( progName );
619 while ( *pathChecks != 0 )
620 cerr << *pathChecks++ << endl;
624 /* Main, process args and call yyparse to start scanning input. */
625 int main(int argc, char **argv)
627 char *inputFileName = 0;
628 char *outputFileName = 0;
630 processArgs( argc, argv, inputFileName, outputFileName );
632 /* Bail on above errors. */
633 if ( gblErrorCount > 0 )
636 /* Make sure we are not writing to the same file as the input file. */
637 if ( inputFileName != 0 && outputFileName != 0 &&
638 strcmp( inputFileName, outputFileName ) == 0 )
640 error() << "output file \"" << outputFileName <<
641 "\" is the same as the input file" << endp;
645 return frontend( inputFileName, outputFileName );
647 char *intermed = openIntermed( inputFileName, outputFileName );
649 /* Run the frontend, then the backend processes. */
650 execFrontend( argv[0], inputFileName, intermed );
651 execBackend( argv[0], intermed, outputFileName );
653 /* Clean up the intermediate. */
654 cleanExit( intermed, 0 );