Backend main functions are now factored out.
[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 #ifdef _WIN32
36 #include <windows.h>
37 #include <psapi.h>
38 #include <time.h>
39 #include <io.h>
40 #include <process.h>
41
42 #if _MSC_VER
43 #define S_IRUSR _S_IREAD
44 #define S_IWUSR _S_IWRITE
45 #endif
46 #endif
47
48 /* Parsing. */
49 #include "ragel.h"
50 #include "rlscan.h"
51
52 /* Parameters and output. */
53 #include "pcheck.h"
54 #include "vector.h"
55 #include "version.h"
56 #include "common.h"
57
58 using std::istream;
59 using std::ostream;
60 using std::ifstream;
61 using std::ofstream;
62 using std::cin;
63 using std::cout;
64 using std::cerr;
65 using std::endl;
66 using std::ios;
67 using std::streamsize;
68
69 /* Controls minimization. */
70 MinimizeLevel minimizeLevel = MinimizePartition2;
71 MinimizeOpt minimizeOpt = MinimizeMostOps;
72
73 /* Graphviz dot file generation. */
74 const char *machineSpec = 0, *machineName = 0;
75 bool machineSpecFound = false;
76 bool wantDupsRemoved = true;
77
78 bool printStatistics = false;
79 bool frontendOnly = false;
80 bool generateDot = false;
81
82 /* Target language and output style. */
83 CodeStyleEnum codeStyle = GenTables;
84
85 int numSplitPartitions = 0;
86 bool noLineDirectives = false;
87
88 bool displayPrintables = false;
89 bool graphvizDone = false;
90
91 /* Target ruby impl */
92 RubyImplEnum rubyImpl = MRI;
93
94 ArgsVector includePaths;
95
96 istream *inStream = 0;
97 ostream *outStream = 0;
98 output_filter *outFilter = 0;
99 const char *outputFileName = 0;
100
101 /* Print a summary of the options. */
102 void usage()
103 {
104         cout <<
105 "usage: ragel [options] file\n"
106 "general:\n"
107 "   -h, -H, -?, --help   Print this usage and exit\n"
108 "   -v, --version        Print version information and exit\n"
109 "   -o <file>            Write output to <file>\n"
110 "   -s                   Print some statistics on stderr\n"
111 "   -d                   Do not remove duplicates from action lists\n"
112 "   -I <dir>             Add <dir> to the list of directories to search\n"
113 "                        for included an imported files\n"
114 "error reporting format:\n"
115 "   --error-format=gnu   file:line:column: message (default)\n"
116 "   --error-format=msvc  file(line,column): message\n"
117 "fsm minimization:\n"
118 "   -n                   Do not perform minimization\n"
119 "   -m                   Minimize at the end of the compilation\n"
120 "   -l                   Minimize after most operations (default)\n"
121 "   -e                   Minimize after every operation\n"
122 "visualization:\n"
123 "   -x                   Run the frontend only: emit XML intermediate format\n"
124 "   -V                   Generate a dot file for Graphviz\n"
125 "   -p                   Display printable characters on labels\n"
126 "   -S <spec>            FSM specification to output (for rlgen-dot)\n"
127 "   -M <machine>         Machine definition/instantiation to output (for rlgen-dot)\n"
128 "host language:\n"
129 "   -C                   The host language is C, C++, Obj-C or Obj-C++ (default)\n"
130 "   -D                   The host language is D\n"
131 "   -J                   The host language is Java\n"
132 "   -R                   The host language is Ruby\n"
133 "   -A                   The host language is C#\n"
134 "line direcives: (C/D/C# only)\n"
135 "   -L                   Inhibit writing of #line directives\n"
136 "code style: (C/Ruby/C# only)\n"
137 "   -T0                  Table driven FSM (default)\n"
138 "   -T1                  Faster table driven FSM\n"
139 "   -F0                  Flat table driven FSM\n"
140 "   -F1                  Faster flat table-driven FSM\n"
141 "code style: (C/C# only)\n"
142 "   -G0                  Goto-driven FSM\n"
143 "   -G1                  Faster goto-driven FSM\n"
144 "code style: (C only)\n"
145 "   -G2                  Really fast goto-driven FSM\n"
146 "   -P<N>                N-Way Split really fast goto-driven FSM\n"
147         ;       
148
149         exit(0);
150 }
151
152 /* Print version information and exit. */
153 void version()
154 {
155         cout << "Ragel State Machine Compiler version " VERSION << " " PUBDATE << endl <<
156                         "Copyright (c) 2001-2007 by Adrian Thurston" << endl;
157         exit(0);
158 }
159
160 /* Error reporting format. */
161 ErrorFormat errorFormat = ErrorFormatGNU;
162
163 InputLoc makeInputLoc( const char *fileName, int line, int col)
164 {
165         InputLoc loc = { fileName, line, col };
166         return loc;
167 }
168
169 ostream &operator<<( ostream &out, const InputLoc &loc )
170 {
171         assert( loc.fileName != 0 );
172         switch ( errorFormat ) {
173         case ErrorFormatMSVC:
174                 out << loc.fileName << "(" << loc.line;
175                 if ( loc.col )
176                         out << "," << loc.col;
177                 out << ")";
178                 break;
179
180         default:
181                 out << loc.fileName << ":" << loc.line;
182                 if ( loc.col )
183                         out << ":" << loc.col;
184                 break;
185         }
186         return out;
187 }
188
189 /* Total error count. */
190 int gblErrorCount = 0;
191
192 /* Print the opening to a warning in the input, then return the error ostream. */
193 ostream &warning( const InputLoc &loc )
194 {
195         cerr << loc << ": warning: ";
196         return cerr;
197 }
198
199 /* Print the opening to a program error, then return the error stream. */
200 ostream &error()
201 {
202         gblErrorCount += 1;
203         cerr << PROGNAME ": ";
204         return cerr;
205 }
206
207 ostream &error( const InputLoc &loc )
208 {
209         gblErrorCount += 1;
210         cerr << loc << ": ";
211         return cerr;
212 }
213
214 void escapeLineDirectivePath( std::ostream &out, char *path )
215 {
216         for ( char *pc = path; *pc != 0; pc++ ) {
217                 if ( *pc == '\\' )
218                         out << "\\\\";
219                 else
220                         out << *pc;
221         }
222 }
223
224 void processArgs( int argc, const char **argv, const char *&inputFileName )
225 {
226         ParamCheck pc("xo:dnmleabjkS:M:I:CDJRAvHh?-:sT:F:G:P:LpV", argc, argv);
227
228         /* FIXME: Need to check code styles VS langauge. */
229
230         while ( pc.check() ) {
231                 switch ( pc.state ) {
232                 case ParamCheck::match:
233                         switch ( pc.parameter ) {
234                         case 'V':
235                                 generateDot = true;
236                                 break;
237
238                         case 'x':
239                                 frontendOnly = true;
240                                 break;
241
242                         /* Output. */
243                         case 'o':
244                                 if ( *pc.paramArg == 0 )
245                                         error() << "a zero length output file name was given" << endl;
246                                 else if ( outputFileName != 0 )
247                                         error() << "more than one output file name was given" << endl;
248                                 else {
249                                         /* Ok, remember the output file name. */
250                                         outputFileName = pc.paramArg;
251                                 }
252                                 break;
253
254                         /* Flag for turning off duplicate action removal. */
255                         case 'd':
256                                 wantDupsRemoved = false;
257                                 break;
258
259                         /* Minimization, mostly hidden options. */
260                         case 'n':
261                                 minimizeOpt = MinimizeNone;
262                                 break;
263                         case 'm':
264                                 minimizeOpt = MinimizeEnd;
265                                 break;
266                         case 'l':
267                                 minimizeOpt = MinimizeMostOps;
268                                 break;
269                         case 'e':
270                                 minimizeOpt = MinimizeEveryOp;
271                                 break;
272                         case 'a':
273                                 minimizeLevel = MinimizeApprox;
274                                 break;
275                         case 'b':
276                                 minimizeLevel = MinimizeStable;
277                                 break;
278                         case 'j':
279                                 minimizeLevel = MinimizePartition1;
280                                 break;
281                         case 'k':
282                                 minimizeLevel = MinimizePartition2;
283                                 break;
284
285                         /* Machine spec. */
286                         case 'S':
287                                 if ( *pc.paramArg == 0 )
288                                         error() << "please specify an argument to -S" << endl;
289                                 else if ( machineSpec != 0 )
290                                         error() << "more than one -S argument was given" << endl;
291                                 else {
292                                         /* Ok, remember the path to the machine to generate. */
293                                         machineSpec = pc.paramArg;
294                                 }
295                                 break;
296
297                         /* Machine path. */
298                         case 'M':
299                                 if ( *pc.paramArg == 0 )
300                                         error() << "please specify an argument to -M" << endl;
301                                 else if ( machineName != 0 )
302                                         error() << "more than one -M argument was given" << endl;
303                                 else {
304                                         /* Ok, remember the machine name to generate. */
305                                         machineName = pc.paramArg;
306                                 }
307                                 break;
308
309                         case 'I':
310                                 if ( *pc.paramArg == 0 )
311                                         error() << "please specify an argument to -I" << endl;
312                                 else {
313                                         includePaths.append( pc.paramArg );
314                                 }
315                                 break;
316
317                         /* Host language types. */
318                         case 'C':
319                                 hostLang = &hostLangC;
320                                 break;
321                         case 'D':
322                                 hostLang = &hostLangD;
323                                 break;
324                         case 'J':
325                                 hostLang = &hostLangJava;
326                                 break;
327                         case 'R':
328                                 hostLang = &hostLangRuby;
329                                 break;
330                         case 'A':
331                                 hostLang = &hostLangCSharp;
332                                 break;
333
334                         /* Version and help. */
335                         case 'v':
336                                 version();
337                                 break;
338                         case 'H': case 'h': case '?':
339                                 usage();
340                                 break;
341                         case 's':
342                                 printStatistics = true;
343                                 break;
344                         case '-': {
345                                 char *eq = strchr( pc.paramArg, '=' );
346
347                                 if ( eq != 0 )
348                                         *eq++ = 0;
349
350                                 if ( strcmp( pc.paramArg, "help" ) == 0 )
351                                         usage();
352                                 else if ( strcmp( pc.paramArg, "version" ) == 0 )
353                                         version();
354                                 else if ( strcmp( pc.paramArg, "error-format" ) == 0 ) {
355                                         if ( eq == 0 )
356                                                 error() << "expecting '=value' for error-format" << endl;
357                                         else if ( strcmp( eq, "gnu" ) == 0 )
358                                                 errorFormat = ErrorFormatGNU;
359                                         else if ( strcmp( eq, "msvc" ) == 0 )
360                                                 errorFormat = ErrorFormatMSVC;
361                                         else
362                                                 error() << "invalid value for error-format" << endl;
363                                 }
364                                 else if ( strcmp( pc.paramArg, "rbx" ) == 0 )
365                                         rubyImpl = Rubinius;
366                                 else {
367                                         error() << "--" << pc.paramArg << 
368                                                         " is an invalid argument" << endl;
369                                 }
370                                 break;
371                         }
372
373                         /* Passthrough args. */
374                         case 'T': 
375                                 if ( pc.paramArg[0] == '0' )
376                                         codeStyle = GenTables;
377                                 else if ( pc.paramArg[0] == '1' )
378                                         codeStyle = GenFTables;
379                                 else {
380                                         error() << "-T" << pc.paramArg[0] << 
381                                                         " is an invalid argument" << endl;
382                                         exit(1);
383                                 }
384                                 break;
385                         case 'F': 
386                                 if ( pc.paramArg[0] == '0' )
387                                         codeStyle = GenFlat;
388                                 else if ( pc.paramArg[0] == '1' )
389                                         codeStyle = GenFFlat;
390                                 else {
391                                         error() << "-F" << pc.paramArg[0] << 
392                                                         " is an invalid argument" << endl;
393                                         exit(1);
394                                 }
395                                 break;
396                         case 'G': 
397                                 if ( pc.paramArg[0] == '0' )
398                                         codeStyle = GenGoto;
399                                 else if ( pc.paramArg[0] == '1' )
400                                         codeStyle = GenFGoto;
401                                 else if ( pc.paramArg[0] == '2' )
402                                         codeStyle = GenIpGoto;
403                                 else {
404                                         error() << "-G" << pc.paramArg[0] << 
405                                                         " is an invalid argument" << endl;
406                                         exit(1);
407                                 }
408                                 break;
409                         case 'P':
410                                 codeStyle = GenSplit;
411                                 numSplitPartitions = atoi( pc.paramArg );
412                                 break;
413
414                         case 'p':
415                                 displayPrintables = true;
416                                 break;
417
418                         case 'L':
419                                 noLineDirectives = true;
420                                 break;
421                         }
422                         break;
423
424                 case ParamCheck::invalid:
425                         error() << "-" << pc.parameter << " is an invalid argument" << endl;
426                         break;
427
428                 case ParamCheck::noparam:
429                         /* It is interpreted as an input file. */
430                         if ( *pc.curArg == 0 )
431                                 error() << "a zero length input file name was given" << endl;
432                         else if ( inputFileName != 0 )
433                                 error() << "more than one input file name was given" << endl;
434                         else {
435                                 /* OK, Remember the filename. */
436                                 inputFileName = pc.curArg;
437                         }
438                         break;
439                 }
440         }
441 }
442
443 int frontend( const char *inputFileName, const char *intermed )
444 {
445         /* Open the input file for reading. */
446         assert( inputFileName != 0 );
447         ifstream *inFile = new ifstream( inputFileName );
448         istream *inStream = inFile;
449         if ( ! inFile->is_open() )
450                 error() << "could not open " << inputFileName << " for reading" << endp;
451
452         /* Used for just a few things. */
453         std::ostringstream hostData;
454
455         if ( machineSpec == 0 && machineName == 0 )
456                 hostData << "<host line=\"1\" col=\"1\">";
457
458         Scanner scanner( inputFileName, *inStream, hostData, 0, 0, 0, false );
459         scanner.do_scan();
460
461         /* Finished, final check for errors.. */
462         if ( gblErrorCount > 0 )
463                 return 1;
464         
465         /* Now send EOF to all parsers. */
466         terminateAllParsers();
467
468         /* Finished, final check for errors.. */
469         if ( gblErrorCount > 0 )
470                 return 1;
471
472         if ( machineSpec == 0 && machineName == 0 )
473                 hostData << "</host>\n";
474
475         if ( gblErrorCount > 0 )
476                 return 1;
477         
478         ostream *outputFile = new ofstream( intermed );
479
480         /* Write the machines, then the surrounding code. */
481         writeMachines( *outputFile, hostData.str(), inputFileName );
482
483         /* Close the intermediate file. */
484         delete outputFile;
485
486         return gblErrorCount > 0;
487 }
488
489 char *makeIntermedTemplate( const char *baseFileName )
490 {
491         char *result = 0;
492         const char *templ = "ragel-XXXXXX.xml";
493         char *lastSlash = strrchr( baseFileName, '/' );
494         if ( lastSlash == 0 ) {
495                 result = new char[strlen(templ)+1];
496                 strcpy( result, templ );
497         }
498         else {
499                 int baseLen = lastSlash - baseFileName + 1;
500                 result = new char[baseLen + strlen(templ) + 1];
501                 memcpy( result, baseFileName, baseLen );
502                 strcpy( result+baseLen, templ );
503         }
504         return result;
505 };
506
507 const char *openIntermed( const char *inputFileName, const char *outputFileName )
508 {
509         srand(time(0));
510         const char *result = 0;
511
512         /* Which filename do we use as the base? */
513         const char *baseFileName = outputFileName != 0 ? outputFileName : inputFileName;
514
515         /* The template for the intermediate file name. */
516         const char *intermedFileName = makeIntermedTemplate( baseFileName );
517
518         /* Randomize the name and try to open. */
519         char fnChars[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
520         char *firstX = strrchr( intermedFileName, 'X' ) - 5;
521         for ( int tries = 0; tries < 20; tries++ ) {
522                 /* Choose a random name. */
523                 for ( int x = 0; x < 6; x++ )
524                         firstX[x] = fnChars[rand() % 52];
525
526                 /* Try to open the file. */
527                 int fd = ::open( intermedFileName, O_WRONLY|O_EXCL|O_CREAT, S_IRUSR|S_IWUSR );
528
529                 if ( fd > 0 ) {
530                         /* Success. Close the file immediately and return the name for use
531                          * by the child processes. */
532                         ::close( fd );
533                         result = intermedFileName;
534                         break;
535                 }
536
537                 if ( errno == EACCES ) {
538                         error() << "failed to open temp file " << intermedFileName << 
539                                         ", access denied" << endp;
540                 }
541         }
542
543         if ( result == 0 )
544                 error() << "abnormal error: cannot find unique name for temp file" << endp;
545
546         return result;
547 }
548
549
550 void cleanExit( const char *intermed, int status )
551 {
552         unlink( intermed );
553         exit( status );
554 }
555
556 void backend( const char *intermed )
557 {
558         const char *xmlInputFileName = intermed;
559
560         bool wantComplete = true;
561         bool outputActive = true;
562
563         /* Open the input file for reading. */
564         ifstream *inFile = new ifstream( xmlInputFileName );
565         inStream = inFile;
566         if ( ! inFile->is_open() )
567                 error() << "could not open " << xmlInputFileName << " for reading" << endl;
568
569         /* Bail on above error. */
570         if ( gblErrorCount > 0 )
571                 exit(1);
572
573         /* Locate the backend program */
574         if ( generateDot ) {
575                 wantComplete = false;
576                 outputActive = false;
577         }
578
579         xml_parse( *inStream, xmlInputFileName, outputActive, wantComplete );
580
581         /* If writing to a file, delete the ostream, causing it to flush.
582          * Standard out is flushed automatically. */
583         if ( outputFileName != 0 ) {
584                 delete outStream;
585                 delete outFilter;
586         }
587
588         /* Finished, final check for errors.. */
589         if ( gblErrorCount > 0 ) {
590                 /* If we opened an output file, remove it. */
591                 if ( outputFileName != 0 )
592                         unlink( outputFileName );
593                 exit(1);
594         }
595 }
596
597 /* Main, process args and call yyparse to start scanning input. */
598 int main( int argc, const char **argv )
599 {
600         const char *inputFileName = 0;
601         processArgs( argc, argv, inputFileName );
602
603         /* If -M or -S are given and we're not generating a dot file then invoke
604          * the frontend. These options are not useful with code generators. */
605         if ( machineName != 0 || machineSpec != 0 ) {
606                 if ( !generateDot )
607                         frontendOnly = true;
608         }
609
610         /* Require an input file. If we use standard in then we won't have a file
611          * name on which to base the output. */
612         if ( inputFileName == 0 )
613                 error() << "no input file given" << endl;
614
615         /* Bail on argument processing errors. */
616         if ( gblErrorCount > 0 )
617                 exit(1);
618
619         /* Make sure we are not writing to the same file as the input file. */
620         if ( inputFileName != 0 && outputFileName != 0 && 
621                         strcmp( inputFileName, outputFileName  ) == 0 )
622         {
623                 error() << "output file \"" << outputFileName  << 
624                                 "\" is the same as the input file" << endp;
625         }
626
627         const char *intermed = openIntermed( inputFileName, outputFileName );
628         frontend( inputFileName, intermed );
629         backend( intermed );
630
631         /* Clean up the intermediate. */
632         cleanExit( intermed, 0 );
633
634         return 0;
635 }