- Implement the %args% substitution
[oota-llvm.git] / tools / llvmc / Configuration.cpp
1 //===- Configuration.cpp - Configuration Data Mgmt --------------*- C++ -*-===//
2 // 
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file was developed by Reid Spencer and is distributed under the 
6 // University of Illinois Open Source License. See LICENSE.TXT for details.
7 // 
8 //===----------------------------------------------------------------------===//
9 //
10 // This file implements the parsing of configuration files for the LLVM Compiler
11 // Driver (llvmc).
12 //
13 //===------------------------------------------------------------------------===
14
15 #include "Configuration.h"
16 #include "ConfigLexer.h"
17 #include "CompilerDriver.h"
18 #include "Config/config.h"
19 #include "Support/CommandLine.h"
20 #include "Support/StringExtras.h"
21 #include <iostream>
22 #include <fstream>
23
24 using namespace llvm;
25
26 namespace sys {
27   // From CompilerDriver.cpp (for now)
28   extern bool FileIsReadable(const std::string& fname);
29 }
30
31 namespace llvm {
32   ConfigLexerInfo ConfigLexerState;
33   InputProvider* ConfigLexerInput = 0;
34
35   InputProvider::~InputProvider() {}
36   void InputProvider::error(const std::string& msg) {
37     std::cerr << name << ":" << ConfigLexerState.lineNum << ": Error: " << 
38       msg << "\n";
39     errCount++;
40   }
41
42   void InputProvider::checkErrors() {
43     if (errCount > 0) {
44       std::cerr << name << " had " << errCount << " errors. Terminating.\n";
45       exit(errCount);
46     }
47   }
48
49 }
50
51 namespace {
52
53   class FileInputProvider : public InputProvider {
54     public:
55       FileInputProvider(const std::string & fname)
56         : InputProvider(fname) 
57         , F(fname.c_str()) {
58         ConfigLexerInput = this;
59       }
60       virtual ~FileInputProvider() { F.close(); ConfigLexerInput = 0; }
61       virtual unsigned read(char *buffer, unsigned max_size) {
62         if (F.good()) {
63           F.read(buffer,max_size);
64           if ( F.gcount() ) return F.gcount() - 1;
65         }
66         return 0;
67       }
68
69       bool okay() { return F.good(); }
70     private:
71       std::ifstream F;
72   };
73
74   cl::opt<bool> DumpTokens("dump-tokens", cl::Optional, cl::Hidden, cl::init(false),
75     cl::desc("Dump lexical tokens (debug use only)."));
76
77   struct Parser
78   {
79     Parser() {
80       token = EOFTOK;
81       provider = 0;
82       confDat = 0;
83       ConfigLexerState.lineNum = 1;
84       ConfigLexerState.in_value = false;
85       ConfigLexerState.StringVal.clear();
86       ConfigLexerState.IntegerVal = 0;
87     };
88
89     ConfigLexerTokens token;
90     InputProvider* provider;
91     CompilerDriver::ConfigData* confDat;
92
93     int next() { 
94       token = Configlex();
95       if (DumpTokens) 
96         std::cerr << token << "\n";
97       return token;
98     }
99
100     bool next_is_real() { 
101       next();
102       return (token != EOLTOK) && (token != ERRORTOK) && (token != 0);
103     }
104
105     void eatLineRemnant() {
106       while (next_is_real()) ;
107     }
108
109     void error(const std::string& msg, bool skip = true) {
110       provider->error(msg);
111       if (skip)
112         eatLineRemnant();
113     }
114
115     std::string parseName() {
116       std::string result;
117       if (next() == EQUALS) {
118         while (next_is_real()) {
119           switch (token ) {
120             case STRING :
121             case OPTION : 
122               result += ConfigLexerState.StringVal + " ";
123               break;
124             default:
125               error("Invalid name");
126               break;
127           }
128         }
129         if (result.empty())
130           error("Name exepected");
131         else
132           result.erase(result.size()-1,1);
133       } else
134         error("= expected");
135       return result;
136     }
137
138     bool parseBoolean() {
139       bool result = true;
140       if (next() == EQUALS) {
141         if (next() == FALSETOK) {
142           result = false;
143         } else if (token != TRUETOK) {
144           error("Expecting boolean value");
145           return false;
146         }
147         if (next() != EOLTOK && token != 0) {
148           error("Extraneous tokens after boolean");
149         }
150       }
151       else
152         error("Expecting '='");
153       return result;
154     }
155
156     bool parseSubstitution(CompilerDriver::StringVector& optList) {
157       switch (token) {
158         case ARGS_SUBST:        optList.push_back("%args%"); break;
159         case IN_SUBST:          optList.push_back("%in%"); break;
160         case OUT_SUBST:         optList.push_back("%out%"); break;
161         case TIME_SUBST:        optList.push_back("%time%"); break;
162         case STATS_SUBST:       optList.push_back("%stats%"); break;
163         case OPT_SUBST:         optList.push_back("%opt%"); break;
164         case TARGET_SUBST:      optList.push_back("%target%"); break;
165         default:
166           return false;
167       }
168       return true;
169     }
170
171     void parseOptionList(CompilerDriver::StringVector& optList ) {
172       if (next() == EQUALS) {
173         while (next_is_real()) {
174           if (token == STRING || token == OPTION)
175             optList.push_back(ConfigLexerState.StringVal);
176           else if (!parseSubstitution(optList)) {
177             error("Expecting a program argument or substitution", false);
178             break;
179           }
180         }
181       } else
182         error("Expecting '='");
183     }
184
185     void parseVersion() {
186       if (next() == EQUALS) {
187         while (next_is_real()) {
188           if (token == STRING || token == OPTION)
189             confDat->version = ConfigLexerState.StringVal;
190           else
191             error("Expecting a version string");
192         }
193       } else
194         error("Expecting '='");
195     }
196
197     void parseLang() {
198       switch (next() ) {
199         case NAME: 
200           confDat->langName = parseName(); 
201           break;
202         case OPT1: 
203           parseOptionList(confDat->opts[CompilerDriver::OPT_FAST_COMPILE]); 
204           break;
205         case OPT2: 
206           parseOptionList(confDat->opts[CompilerDriver::OPT_SIMPLE]); 
207           break;
208         case OPT3: 
209           parseOptionList(confDat->opts[CompilerDriver::OPT_AGGRESSIVE]); 
210           break;
211         case OPT4: 
212           parseOptionList(confDat->opts[CompilerDriver::OPT_LINK_TIME]); 
213           break;
214         case OPT5: 
215           parseOptionList(
216             confDat->opts[CompilerDriver::OPT_AGGRESSIVE_LINK_TIME]);
217           break;
218         default:   
219           error("Expecting 'name' or 'optN' after 'lang.'"); 
220           break;
221       }
222     }
223
224     void parseCommand(CompilerDriver::Action& action) {
225       if (next() == EQUALS) {
226         if (next() == EOLTOK) {
227           // no value (valid)
228           action.program.clear();
229           action.args.clear();
230         } else {
231           if (token == STRING || token == OPTION) {
232             action.program = ConfigLexerState.StringVal;
233           } else {
234             error("Expecting a program name");
235           }
236           while (next_is_real()) {
237             if (token == STRING || token == OPTION) {
238               action.args.push_back(ConfigLexerState.StringVal);
239             } else if (!parseSubstitution(action.args)) {
240               error("Expecting a program argument or substitution", false);
241               break;
242             }
243           }
244         }
245       }
246     }
247
248     void parsePreprocessor() {
249       switch (next()) {
250         case COMMAND:
251           parseCommand(confDat->PreProcessor);
252           break;
253         case REQUIRED:
254           if (parseBoolean())
255             confDat->PreProcessor.set(CompilerDriver::REQUIRED_FLAG);
256           else
257             confDat->PreProcessor.clear(CompilerDriver::REQUIRED_FLAG);
258           break;
259         default:
260           error("Expecting 'command' or 'required' but found '" +
261               ConfigLexerState.StringVal);
262           break;
263       }
264     }
265
266     bool parseOutputFlag() {
267       if (next() == EQUALS) {
268         if (next() == ASSEMBLY) {
269           return true;
270         } else if (token == BYTECODE) {
271           return false;
272         } else {
273           error("Expecting output type value");
274           return false;
275         }
276         if (next() != EOLTOK && token != 0) {
277           error("Extraneous tokens after output value");
278         }
279       }
280       else
281         error("Expecting '='");
282       return false;
283     }
284
285     void parseTranslator() {
286       switch (next()) {
287         case COMMAND: 
288           parseCommand(confDat->Translator);
289           break;
290         case REQUIRED:
291           if (parseBoolean())
292             confDat->Translator.set(CompilerDriver::REQUIRED_FLAG);
293           else
294             confDat->Translator.clear(CompilerDriver::REQUIRED_FLAG);
295           break;
296         case PREPROCESSES:
297           if (parseBoolean())
298             confDat->Translator.set(CompilerDriver::PREPROCESSES_FLAG);
299           else 
300             confDat->Translator.clear(CompilerDriver::PREPROCESSES_FLAG);
301           break;
302         case OUTPUT:
303           if (parseOutputFlag())
304             confDat->Translator.set(CompilerDriver::OUTPUT_IS_ASM_FLAG);
305           else
306             confDat->Translator.clear(CompilerDriver::OUTPUT_IS_ASM_FLAG);
307           break;
308
309         default:
310           error("Expecting 'command', 'required', 'preprocesses', or "
311                 "'output' but found '" + ConfigLexerState.StringVal +
312                 "' instead");
313           break;
314       }
315     }
316
317     void parseOptimizer() {
318       switch (next()) {
319         case COMMAND:
320           parseCommand(confDat->Optimizer);
321           break;
322         case PREPROCESSES:
323           if (parseBoolean())
324             confDat->Optimizer.set(CompilerDriver::PREPROCESSES_FLAG);
325           else
326             confDat->Optimizer.clear(CompilerDriver::PREPROCESSES_FLAG);
327           break;
328         case TRANSLATES:
329           if (parseBoolean())
330             confDat->Optimizer.set(CompilerDriver::TRANSLATES_FLAG);
331           else
332             confDat->Optimizer.clear(CompilerDriver::TRANSLATES_FLAG);
333           break;
334         case REQUIRED:
335           if (parseBoolean())
336             confDat->Optimizer.set(CompilerDriver::REQUIRED_FLAG);
337           else
338             confDat->Optimizer.clear(CompilerDriver::REQUIRED_FLAG);
339           break;
340         case OUTPUT:
341           if (parseOutputFlag())
342             confDat->Translator.set(CompilerDriver::OUTPUT_IS_ASM_FLAG);
343           else
344             confDat->Translator.clear(CompilerDriver::OUTPUT_IS_ASM_FLAG);
345           break;
346         default:
347           error(std::string("Expecting 'command', 'preprocesses', ") +
348               "'translates' or 'output' but found '" + 
349               ConfigLexerState.StringVal + "' instead");
350           break;
351       }
352     }
353
354     void parseAssembler() {
355       switch(next()) {
356         case COMMAND:
357           parseCommand(confDat->Assembler);
358           break;
359         default:
360           error("Expecting 'command'");
361           break;
362       }
363     }
364
365     void parseLinker() {
366       switch(next()) {
367         case LIBS:
368           break; //FIXME
369         case LIBPATHS:
370           break; //FIXME
371         default:
372           error("Expecting 'libs' or 'libpaths'");
373           break;
374       }
375     }
376
377     void parseAssignment() {
378       switch (token) {
379         case VERSION:       parseVersion(); break;
380         case LANG:          parseLang(); break;
381         case PREPROCESSOR:  parsePreprocessor(); break;
382         case TRANSLATOR:    parseTranslator(); break;
383         case OPTIMIZER:     parseOptimizer(); break;
384         case ASSEMBLER:     parseAssembler(); break;
385         case LINKER:        parseLinker(); break;
386         case EOLTOK:        break; // just ignore
387         case ERRORTOK:
388         default:          
389           error("Invalid top level configuration item");
390           break;
391       }
392     }
393
394     void parseFile() {
395       while ( next() != EOFTOK ) {
396         if (token == ERRORTOK)
397           error("Invalid token");
398         else if (token != EOLTOK)
399           parseAssignment();
400       }
401       provider->checkErrors();
402     }
403   };
404
405   void
406   ParseConfigData(InputProvider& provider, CompilerDriver::ConfigData& confDat) {
407     Parser p;
408     p.token = EOFTOK;
409     p.provider = &provider;
410     p.confDat = &confDat;
411     p.parseFile();
412   }
413 }
414
415 CompilerDriver::ConfigData*
416 LLVMC_ConfigDataProvider::ReadConfigData(const std::string& ftype) {
417   CompilerDriver::ConfigData* result = 0;
418   std::string dir_name;
419   if (configDir.empty()) {
420     // Try the environment variable
421     const char* conf = getenv("LLVM_CONFIG_DIR");
422     if (conf) {
423       dir_name = conf;
424       dir_name += "/";
425       if (!::sys::FileIsReadable(dir_name + ftype))
426         throw "Configuration file for '" + ftype + "' is not available.";
427     } else {
428       // Try the user's home directory
429       const char* home = getenv("HOME");
430       if (home) {
431         dir_name = home;
432         dir_name += "/.llvm/etc/";
433         if (!::sys::FileIsReadable(dir_name + ftype)) {
434           // Okay, try the LLVM installation directory
435           dir_name = LLVM_ETCDIR;
436           dir_name += "/";
437           if (!::sys::FileIsReadable(dir_name + ftype)) {
438             // Okay, try the "standard" place
439             dir_name = "/etc/llvm/";
440             if (!::sys::FileIsReadable(dir_name + ftype)) {
441               throw "Configuration file for '" + ftype + "' is not available.";
442             }
443           }
444         }
445       }
446     }
447   } else {
448     dir_name = configDir + "/";
449     if (!::sys::FileIsReadable(dir_name + ftype)) {
450       throw "Configuration file for '" + ftype + "' is not available.";
451     }
452   }
453   FileInputProvider fip( dir_name + ftype );
454   if (!fip.okay()) {
455     throw "Configuration file for '" + ftype + "' is not available.";
456   }
457   result = new CompilerDriver::ConfigData();
458   ParseConfigData(fip,*result);
459   return result;
460 }
461
462 LLVMC_ConfigDataProvider::LLVMC_ConfigDataProvider() 
463   : Configurations() 
464   , configDir() 
465 {
466   Configurations.clear();
467 }
468
469 LLVMC_ConfigDataProvider::~LLVMC_ConfigDataProvider()
470 {
471   ConfigDataMap::iterator cIt = Configurations.begin();
472   while (cIt != Configurations.end()) {
473     CompilerDriver::ConfigData* cd = cIt->second;
474     ++cIt;
475     delete cd;
476   }
477   Configurations.clear();
478 }
479
480 CompilerDriver::ConfigData* 
481 LLVMC_ConfigDataProvider::ProvideConfigData(const std::string& filetype) {
482   CompilerDriver::ConfigData* result = 0;
483   if (!Configurations.empty()) {
484     ConfigDataMap::iterator cIt = Configurations.find(filetype);
485     if ( cIt != Configurations.end() ) {
486       // We found one in the case, return it.
487       result = cIt->second;
488     }
489   }
490   if (result == 0) {
491     // The configuration data doesn't exist, we have to go read it.
492     result = ReadConfigData(filetype);
493     // If we got one, cache it
494     if ( result != 0 )
495       Configurations.insert(std::make_pair(filetype,result));
496   }
497   return result; // Might return 0
498 }
499
500 // vim: sw=2 smartindent smarttab tw=80 autoindent expandtab