WhiteSpaceAgnostic Boo Printer
[boo.git] / src / booc / App.cs
blob45c8d3339ee07f0b62224e1e8361e5bbfab5a919
1 #region license
2 // Copyright (c) 2004, Rodrigo B. de Oliveira (rbo@acm.org)
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without modification,
6 // are permitted provided that the following conditions are met:
7 //
8 // * Redistributions of source code must retain the above copyright notice,
9 // this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright notice,
11 // this list of conditions and the following disclaimer in the documentation
12 // and/or other materials provided with the distribution.
13 // * Neither the name of Rodrigo B. de Oliveira nor the names of its
14 // contributors may be used to endorse or promote products derived from this
15 // software without specific prior written permission.
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #endregion
29 using System;
30 using System.Text;
31 using System.Collections;
32 using System.Diagnostics;
33 using System.IO;
34 using System.Globalization;
35 using System.Threading;
36 using Boo.Lang.Compiler.Ast.Visitors;
37 using Assembly = System.Reflection.Assembly;
38 using Boo.Lang.Compiler;
39 using Boo.Lang.Compiler.IO;
40 using Boo.Lang.Compiler.Pipelines;
41 using Boo.Lang.Compiler.Resources;
42 using Boo.Lang;
44 namespace BooC
46 /// <summary>
47 ///
48 /// </summary>
49 class App
51 ArrayList _responseFileList = new ArrayList();
52 CompilerParameters _options = null;
54 ArrayList _references = new ArrayList();
55 ArrayList _packages = new ArrayList();
56 bool _noConfig = false;
57 string _pipelineName = null;
58 bool _debugSteps = false;
59 bool _whiteSpaceAgnostic = false;
61 /// <summary>
62 /// The main entry point for the application.
63 /// </summary>
64 [STAThread]
65 static int Main(string[] args)
67 if (((IList)args).Contains("-utf8"))
69 using (StreamWriter writer = new StreamWriter(Console.OpenStandardError(), Encoding.UTF8))
71 // leave the byte order mark in its own line and out
72 writer.WriteLine();
74 Console.SetError(writer);
75 return new App().Run(args);
78 else
80 return new App().Run(args);
84 public int Run(string[] args)
86 int resultCode = -1;
88 AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(AssemblyResolve);
90 CheckBooCompiler();
92 try
94 DateTime start = DateTime.Now;
96 _options = new CompilerParameters(false); //false means no stdlib loading yet
97 _options.GenerateInMemory = false;
99 BooCompiler compiler = new BooCompiler(_options);
101 ParseOptions(args);
103 if (0 == _options.Input.Count)
105 throw new ApplicationException(Boo.Lang.ResourceManager.GetString("BooC.NoInputSpecified"));
108 //move standard libpaths below any new ones:
109 _options.LibPaths.Add(_options.LibPaths[0]);
110 _options.LibPaths.Add(_options.LibPaths[1]);
111 _options.LibPaths.RemoveAt(0);
112 _options.LibPaths.RemoveAt(0);
114 if (_options.StdLib)
116 _options.LoadDefaultReferences();
118 else if (!_noConfig)
120 _references.Insert(0, "mscorlib");
123 LoadReferences();
124 ConfigurePipeline();
126 if (_options.TraceSwitch.TraceInfo)
128 compiler.Parameters.Pipeline.BeforeStep += new CompilerStepEventHandler(OnBeforeStep);
129 compiler.Parameters.Pipeline.AfterStep += new CompilerStepEventHandler(OnAfterStep);
132 TimeSpan setupTime = DateTime.Now - start;
134 start = DateTime.Now;
135 CompilerContext context = compiler.Run();
136 TimeSpan processingTime = DateTime.Now - start;
138 if (context.Warnings.Count > 0)
140 Console.Error.WriteLine(context.Warnings);
141 Console.Error.WriteLine(Boo.Lang.ResourceManager.Format("BooC.Warnings", context.Warnings.Count));
144 if (context.Errors.Count > 0)
146 foreach (CompilerError error in context.Errors)
148 Console.Error.WriteLine(error.ToString(_options.TraceSwitch.TraceInfo));
150 Console.Error.WriteLine(Boo.Lang.ResourceManager.Format("BooC.Errors", context.Errors.Count));
152 else
154 resultCode = 0;
157 if (_options.TraceSwitch.TraceWarning)
159 Console.Error.WriteLine(Boo.Lang.ResourceManager.Format("BooC.ProcessingTime", _options.Input.Count, processingTime.TotalMilliseconds, setupTime.TotalMilliseconds));
162 catch (Exception x)
164 object message = _options.TraceSwitch.TraceWarning ? (object)x : (object)x.Message;
165 Console.Error.WriteLine(Boo.Lang.ResourceManager.Format("BooC.FatalError", message));
167 return resultCode;
170 void LoadReferences()
172 foreach (string r in _references)
174 _options.References.Add(_options.LoadAssembly(r, true));
176 foreach (string p in _packages)
178 _options.LoadReferencesFromPackage(p);
182 void CheckBooCompiler()
184 string path = Path.Combine(Path.GetDirectoryName(
185 Assembly.GetExecutingAssembly().Location),
186 "Boo.Lang.Compiler.dll");
187 if (File.Exists(path))
189 foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
191 if (a.FullName.StartsWith("Boo.Lang.Compiler"))
193 if (string.Compare(a.Location, path, true) != 0)
195 //can't use ResourceManager, boo.lang.dll may be out of date
196 string msg=string.Format("WARNING: booc is not using the Boo.Lang.Compiler.dll next to booc.exe. Using '{0}' instead of '{1}'. You may need to remove boo dlls from the GAC using gacutil or Mscorcfg.",
197 a.Location, path);
198 //has to be all 1 line for things like msbuild that parse booc output.
199 Console.Error.WriteLine(msg);
201 break;
207 string Consume(TextReader reader)
209 StringWriter writer = new StringWriter();
210 string line = reader.ReadLine();
211 while (null != line)
213 writer.WriteLine(line);
214 line = reader.ReadLine();
216 return writer.ToString();
219 void DoLogo()
221 Console.Write("Boo Compiler version "+Builtins.BooVersion.ToString());
222 Console.WriteLine(" (CLR v"+Environment.Version.ToString()+")");
225 void Help ()
227 Console.WriteLine(
228 "Usage is: booc [options] file1 ...\n" +
229 "Options:\n" +
230 " -c:CULTURE Sets the UI culture to be CULTURE\n" +
231 " -debug[+|-] Generate debugging information (default: +)\n" +
232 " -delaysign Delay assembly signing\n" +
233 " -ducky Turns on duck typing by default\n" +
234 " -checked[+|-] Turns on or off checked operations (default: +)\n" +
235 " -embedres:FILE[,ID] Embeds FILE with the optional ID\n"+
236 " -lib:DIRS Adds the comma-separated DIRS to the assembly search path\n" +
237 " -noconfig Do not load the standard configuration\n" +
238 " -nostdlib Do not reference any of the default libraries\n" +
239 " -nologo Do not display the compiler logo\n" +
240 " -p:PIPELINE Sets the pipeline to PIPELINE\n" +
241 " -o:FILE Set the output file name to FILE\n" +
242 " -keyfile:FILE The strongname key file used to strongname the assembly\n" +
243 " -keycontainer:NAME The key pair container used to strongname the assembly\n" +
244 " -reference:ASS References the specified assembly (-r:ASS)\n" +
245 " -srcdir:DIR Adds DIR as a directory where sources can be found\n" +
246 " -target:TYPE Set the target type (exe, library or winexe)\n" +
247 " -resource:FILE[,ID] Embed FILE as a resource\n" +
248 " -utf8 Source file is in utf8 format\n" +
249 " -v, -vv, -vvv Set verbosity level from warnings to very detailed\n" +
250 " -wsa Enable white-space-agnostic builds\n"
255 void ParseOptions(string[] args)
257 bool noLogo = false;
259 ArrayList arglist = new ArrayList(args);
260 ExpandResponseFiles(ref arglist);
261 AddDefaultResponseFile(ref arglist);
262 foreach (string arg in arglist)
264 if ("-" == arg)
266 _options.Input.Add(new StringInput("<stdin>", Consume(Console.In)));
267 continue;
269 if (!IsFlag(arg))
271 _options.Input.Add(new FileInput(StripQuotes(arg)));
272 continue;
274 if ("-utf8" == arg) continue;
276 switch (arg[1])
278 case 'h':
280 if (arg == "-help" || arg == "-h")
282 Help();
284 break;
287 case 'w':
289 if (arg == "-wsa")
291 _options.WhiteSpaceAgnostic = _whiteSpaceAgnostic = true;
293 else
295 InvalidOption(arg);
297 break;
300 case 'v':
302 _options.TraceSwitch.Level = TraceLevel.Warning;
303 Trace.Listeners.Add(new TextWriterTraceListener(Console.Error));
304 if (arg.Length > 2)
306 switch (arg.Substring(1))
308 case "vv":
310 _options.TraceSwitch.Level = TraceLevel.Info;
311 MonitorAppDomain();
312 break;
315 case "vvv":
317 _options.TraceSwitch.Level = TraceLevel.Verbose;
318 break;
322 else
324 _options.TraceSwitch.Level = TraceLevel.Warning;
326 break;
329 case 'r':
331 if (arg.IndexOf(":") > 2 && arg.Substring(1, 9) != "reference")
333 switch (arg.Substring(1, 8))
335 case "resource":
337 int start = arg.IndexOf(":") + 1;
338 AddResource(StripQuotes(arg.Substring(start)));
339 break;
342 default:
344 InvalidOption(arg);
345 break;
349 else
351 string assemblyName = StripQuotes(arg.Substring(arg.IndexOf(":")+1));
352 _references.Add(assemblyName);
354 break;
357 case 'l':
359 switch (arg.Substring(1, 3))
361 case "lib":
363 string paths = arg.Substring(arg.IndexOf(":")+1);
364 if (paths == "")
366 Console.Error.WriteLine(Boo.Lang.ResourceManager.Format("BooC.BadLibPath", arg));
367 break;
370 foreach(string dir in paths.Split(new Char[] {','}))
372 if (Directory.Exists(dir))
374 _options.LibPaths.Add(dir);
376 else
378 Console.Error.WriteLine(Boo.Lang.ResourceManager.Format("BooC.BadLibPath", dir));
381 break;
384 default:
386 InvalidOption(arg);
387 break;
390 break;
393 case 'n':
395 if (arg == "-nologo")
397 noLogo = true;
399 else if (arg == "-noconfig")
401 _noConfig = true;
403 else if (arg == "-nostdlib")
405 _options.StdLib = false;
407 else
409 InvalidOption(arg);
411 break;
414 case 'o':
416 _options.OutputAssembly = StripQuotes(arg.Substring(arg.IndexOf(":")+1));
417 break;
420 case 't':
422 string targetType = arg.Substring(arg.IndexOf(":")+1);
423 switch (targetType)
425 case "library":
427 _options.OutputType = CompilerOutputType.Library;
428 break;
431 case "exe":
433 _options.OutputType = CompilerOutputType.ConsoleApplication;
434 break;
437 case "winexe":
439 _options.OutputType = CompilerOutputType.WindowsApplication;
440 break;
443 default:
445 InvalidOption(arg);
446 break;
449 break;
452 case 'p':
454 if (arg.StartsWith("-pkg:"))
456 string packages = StripQuotes(arg.Substring(arg.IndexOf(":")+1));
457 _packages.Add(packages);
459 else {
460 _pipelineName = StripQuotes(arg.Substring(3));
462 break;
465 case 'c':
467 switch (arg.Substring(1))
469 case "checked":
470 case "checked+":
472 _options.Checked = true;
473 break;
476 case "checked-":
478 _options.Checked = false;
479 break;
482 default:
484 string culture = arg.Substring(3);
485 Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(culture);
486 break;
489 break;
492 case 's':
494 switch (arg.Substring(1, 6))
496 case "srcdir":
498 string path = StripQuotes(arg.Substring(8));
499 AddFilesForPath(path, _options);
500 break;
503 default:
505 InvalidOption(arg);
506 break;
509 break;
512 case 'k':
514 if (arg.Substring(1, 7) == "keyfile")
516 _options.KeyFile = StripQuotes(arg.Substring(9));
518 else if (arg.Substring(1, 12) == "keycontainer")
520 _options.KeyContainer = StripQuotes(arg.Substring(14));
522 else
524 InvalidOption(arg);
526 break;
529 case 'd':
531 switch (arg.Substring(1))
533 case "debug":
534 case "debug+":
536 _options.Debug = true;
537 break;
540 case "debug-":
542 _options.Debug = false;
543 break;
546 case "ducky":
548 _options.Ducky = true;
549 break;
552 case "debug-steps":
554 _debugSteps = true;
555 break;
558 case "delaysign":
560 _options.DelaySign = true;
561 break;
564 default:
566 InvalidOption(arg);
567 break;
570 break;
573 case 'e':
575 switch (arg.Substring(1,8))
577 case "embedres":
579 if (!IsMono)
581 throw new ApplicationException("-embedres is only supported on mono. Try -resource.");
583 int start = arg.IndexOf(":") + 1;
584 EmbedResource(StripQuotes(arg.Substring(start)));
585 break;
588 default:
590 InvalidOption(arg);
591 break;
594 break;
597 default:
599 InvalidOption(arg);
600 break;
605 if (!noLogo)
607 DoLogo();
611 private bool IsMono
613 get { return Type.GetType("System.MonoType", false) != null; }
616 private void EmbedResource(string resourceFile)
619 int comma = resourceFile.LastIndexOf(',');
620 if (comma >= 0)
622 string resourceName = resourceFile.Substring(comma+1);
623 resourceFile = resourceFile.Substring(0, comma);
624 _options.Resources.Add(new NamedEmbeddedFileResource(resourceFile, resourceName));
626 else
628 _options.Resources.Add(new EmbeddedFileResource(resourceFile));
632 private void AddResource(string resourceFile)
634 int comma = resourceFile.LastIndexOf(',');
635 if (comma >= 0)
637 string resourceName = resourceFile.Substring(comma+1);
638 resourceFile = resourceFile.Substring(0, comma);
639 _options.Resources.Add(new NamedFileResource(resourceFile, resourceName));
641 else
643 _options.Resources.Add(new FileResource(resourceFile));
647 private void ConfigurePipeline()
649 if (null != _pipelineName)
651 _options.Pipeline = CompilerPipeline.GetPipeline(_pipelineName);
653 else
655 _options.Pipeline = new CompileToFile();
657 if (_whiteSpaceAgnostic)
659 _options.Pipeline[0] = new Boo.Lang.Parser.WSABooParsingStep();
661 if (_debugSteps)
663 _options.Pipeline.AfterStep += new CompilerStepEventHandler(new StepDebugger().AfterStep);
667 private string StripQuotes(string s)
669 if (s.Length > 1 && s.StartsWith("\"") && s.EndsWith("\""))
671 return s.Substring(1,s.Length-2);
673 return s;
676 private class StepDebugger
678 string _last;
680 public void AfterStep(object sender, CompilerStepEventArgs args)
682 Console.WriteLine("********* {0} *********", args.Step);
684 StringWriter writer = new StringWriter();
685 args.Context.CompileUnit.Accept(new BooPrinterVisitor(writer, BooPrinterVisitor.PrintOptions.PrintLocals));
686 string code = writer.ToString();
687 if (code != _last)
689 Console.WriteLine(code);
691 else
693 Console.WriteLine("no changes");
695 _last = code;
699 ArrayList LoadResponseFile(string file)
701 file = Path.GetFullPath(file);
702 if (_responseFileList.Contains(file))
704 throw new ApplicationException(
705 Boo.Lang.ResourceManager.Format("BCE0500", file));
707 _responseFileList.Add(file);
708 if (!File.Exists(file))
710 throw new ApplicationException(Boo.Lang.ResourceManager.Format("BCE0501", file));
712 ArrayList arglist = new ArrayList();
715 using (StreamReader sr = new StreamReader(file))
717 string line;
718 while ((line = sr.ReadLine()) != null)
720 line = line.Trim();
721 if (line.Length > 0 && line[0] != '#')
723 if (line.StartsWith("@") && line.Length > 2)
725 arglist.AddRange(LoadResponseFile(line.Substring(1)));
727 else
729 arglist.Add(line);
735 catch (ApplicationException)
737 throw;
739 catch (Exception x)
741 throw new ApplicationException(
742 Boo.Lang.ResourceManager.Format("BCE0502", file),
745 return arglist;
748 void ExpandResponseFiles(ref ArrayList arglist)
750 ArrayList result = new ArrayList();
751 foreach (string arg in arglist)
753 if (arg.StartsWith("@") && arg.Length > 2)
755 result.AddRange(LoadResponseFile(arg.Substring(1)));
757 else
759 result.Add(arg);
762 arglist = result;
765 void AddDefaultResponseFile(ref ArrayList arglist)
767 ArrayList result = new ArrayList();
768 foreach (string arg in arglist)
770 if (arg == "-noconfig")
772 _noConfig = true;
774 else
776 result.Add(arg);
779 if (!_noConfig)
781 string file = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "booc.rsp");
782 if (File.Exists(file))
784 result.InsertRange(0, LoadResponseFile(file));
787 arglist = result;
790 void OnBeforeStep(object sender, CompilerStepEventArgs args)
792 args.Context.TraceEnter("Entering {0}", args.Step);
795 void OnAfterStep(object sender, CompilerStepEventArgs args)
797 args.Context.TraceLeave("Leaving {0}", args.Step);
800 void InvalidOption(string arg)
802 Console.Error.WriteLine(Boo.Lang.ResourceManager.Format("BooC.InvalidOption", arg));
805 bool IsFlag(string arg)
807 return arg[0] == '-';
810 void AddFilesForPath(string path, CompilerParameters _options)
812 foreach (string fname in Directory.GetFiles(path, "*.boo"))
814 if (!fname.EndsWith(".boo")) continue;
815 _options.Input.Add(new FileInput(Path.GetFullPath(fname)));
818 foreach (string dirName in Directory.GetDirectories(path))
820 AddFilesForPath(dirName, _options);
824 void MonitorAppDomain()
826 if (_options.TraceSwitch.TraceInfo)
828 AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(OnAssemblyLoad);
829 foreach(Assembly a in AppDomain.CurrentDomain.GetAssemblies())
831 Trace.WriteLine("ASSEMBLY AT STARTUP: "+GetAssemblyLocation(a));
836 static void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
838 Trace.WriteLine("ASSEMBLY LOADED: " + GetAssemblyLocation(args.LoadedAssembly));
841 static string GetAssemblyLocation(Assembly a)
843 string loc;
846 loc = a.Location;
848 catch (Exception)
850 loc = "<dynamic>"+a.FullName;
852 return loc;
855 static Assembly AssemblyResolve(object sender, ResolveEventArgs args)
857 string simpleName = args.Name.Split(',')[0];
858 string fileName = Path.Combine(Environment.CurrentDirectory,
859 simpleName + ".dll");
860 if (File.Exists(fileName))
862 return Assembly.LoadFile(fileName);
864 return null;