1 /// Author: Aziz Köksal
2 /// License: GPL3
3 /// $(Maturity average)
4 module main;
5 
6 import dil.parser.Parser;
7 import dil.lexer.Lexer,
8        dil.lexer.Funcs,
9        dil.lexer.Token,
10        dil.lexer.TokenSerializer;
11 import dil.ast.Declarations,
12        dil.ast.Expressions,
13        dil.ast.Node,
14        dil.ast.Visitor,
15        dil.ast.ASTPrinter,
16        dil.ast.ASTSerializer;
17 import dil.semantic.Module,
18        dil.semantic.Symbols,
19        dil.semantic.Pass1,
20        dil.semantic.Pass2,
21        dil.semantic.Passes;
22 import dil.code.Interpreter;
23 import dil.translator.PyTreeEmitter;
24 import dil.i18n.Messages;
25 import dil.String,
26        dil.Version,
27        dil.Diagnostics,
28        dil.SourceText,
29        dil.Compilation;
30 
31 import util.Path,
32        util.OptParser;
33 
34 import cmd.Command,
35        cmd.Compile,
36        cmd.Highlight,
37        cmd.Statistics,
38        cmd.ImportGraph,
39        cmd.DDoc;
40 
41 import Settings,
42        SettingsLoader,
43        common;
44 
45 import std.file,
46        std.conv,
47        std.datetime,
48        std.regex;
49 
50 version(TangoTrace) debug
51 import tango.core.tools.TraceExceptions;
52 
53 alias toInt = to!int;
54 
55 /// Entry function of DIL.
56 void main(cstring[] args)
57 {
58   debug if (args.length >= 2 && args[1] == "unittests")
59     return runUnittests();
60 
61   if (args.length == 0)
62     throw new Exception("main() received 0 arguments");
63 
64   // 1. Create a global compilation context.
65   auto globalCC = newCompilationContext();
66   auto diag = globalCC.diag;
67   // 2. Load the configuration file of DIL.
68   auto config = ConfigLoader(globalCC, diag, args[0]);
69   config.load();
70   diag.bundle = config.resourceBundle;
71   updateWithSettings(globalCC);
72 
73   if (diag.hasInfo())
74     return printErrors(diag);
75   if (args.length <= 1)
76     return printHelp("main", diag);
77 
78   // 3. Execute a command.
79   auto op = new OptParser(args[2..$]);
80   op.missingArgMessage = diag.bundle.msg(MID.MissingOptionArgument);
81   op.usageErrorMessage = diag.bundle.msg(MID.UsageError);
82   cstring command = args[1];
83 
84   switch (command)
85   {
86   case "c2",
87        "c", "compile":
88     if (!op.hasArgs)
89       return printHelp(command, diag);
90     bool useCommand2 = command == "c2";
91 
92     auto cmd = new CompileCommand();
93     auto cc = cmd.context = globalCC;
94     cmd.diag = diag;
95     cstring value;
96 
97     op.add({ return parseDebugOrVersion(op, cc); });
98     op.add("-I", value, { cc.importPaths ~= value; });
99     op.add("-J", value, { cc.includePaths ~= value; });
100     op.add("-m32", cmd.m32);
101     op.add("-m64", cmd.m64);
102     op.add("-of", value, { cmd.binOutput = value; });
103     op.add("-release", cc.releaseBuild);
104     op.add("-unittest", cc.unittestBuild, {
105       version(D2)
106         cc.addVersionId("unittest");
107     });
108     op.add("-d", cc.acceptDeprecated);
109     op.add("-ps", cmd.printSymbolTree);
110     op.add("-pm", cmd.printModuleTree);
111     op.add("-v", cmd.verbose);
112     if (!op.parseArgs())
113       return op.printUsageError();
114 
115     cmd.filePaths = op.argv; // Remaining arguments are file paths.
116 
117     Command cmd_ = cmd;
118     if (useCommand2)
119     { // Temporary code to test CompileCommand2.
120       auto cmd2 = new CompileCommand2();
121       cmd2.cc = cc;
122       cmd2.filePaths = cmd.filePaths;
123       cmd2.mm = cmd.moduleMan;
124       cmd2.verbose = cmd.verbose;
125       cmd_ = cmd2;
126     }
127 
128     // TODO:
129     //if (cmd.m32)
130     //{
131     //  cc.removeVersionId("D_InlineAsm_X86_64");
132     //  cc.removeVersionId("X86_64");
133     //  cc.addVersionId("D_InlineAsm_X86");
134     //  cc.addVersionId("X86");
135     //}
136     //if (cmd.m64)
137     //{
138     //  cc.addVersionId("D_InlineAsm_X86_64");
139     //  cc.addVersionId("X86_64");
140     //  cc.removeVersionId("D_InlineAsm_X86");
141     //  cc.removeVersionId("X86");
142     //}
143 
144     cmd_.run();
145 
146     diag.hasInfo() && printErrors(diag);
147     break;
148   case "print":
149     if (!op.hasArgs)
150       return printHelp(command, diag);
151     cstring path;
152     op.addDefault({ path = op.popArg(); });
153     op.parseArgs();
154     auto m = new Module(path, globalCC);
155     m.parse();
156     if (!m.hasErrors())
157     {
158       auto ap = new ASTPrinter(true, globalCC);
159       auto text = ap.print(m.root);
160       Printfln("{}", text);
161     }
162     diag.hasInfo() && printErrors(diag);
163     break;
164   case "pytree", "py":
165     if (op.argv.length < 2)
166       return printHelp(command, diag);
167 
168     class PyTreeCommand : Command
169     {
170       CompilationContext cc;
171       Path dest;
172       cstring format = "d_{0}.py";
173       cstring[] filePaths;
174 
175       override void run()
176       {
177         foreach (path; filePaths)
178         {
179           auto modul = new Module(path, cc);
180           lzy(log("parse: {}", path));
181           modul.parse();
182           if (modul.hasErrors())
183             continue;
184           auto py = new PyTreeEmitter(modul);
185           auto modFQN = modul.getFQN().replace('.', '_');
186           auto pckgName = modul.packageName.replace('.', '_');
187           auto modName = modul.moduleName;
188           auto fileName = Format(format, modFQN, pckgName, modName);
189           auto destPath = (dest/fileName)[];
190           lzy(log("emit:  {}", destPath));
191           destPath.write(py.emit());
192         }
193       }
194     }
195 
196     auto cmd = new PyTreeCommand();
197     cmd.dest = Path(op.popArg());
198     cmd.cc = globalCC;
199 
200     op.add("--fmt", cmd.format);
201     op.add("-v", cmd.verbose);
202     if (!op.parseArgs())
203       return op.printUsageError();
204     cmd.filePaths = op.argv; // Remaining arguments are file paths.
205 
206     cmd.run();
207 
208     diag.hasInfo() && printErrors(diag);
209     break;
210   case "ddoc", "d":
211     if (op.argv.length < 2)
212       return printHelp(command, diag);
213 
214     auto cmd = new DDocCommand();
215     cmd.destDirPath = op.popArg();
216     cmd.context = globalCC;
217     cmd.diag = diag;
218     cstring value;
219 
220     // Parse arguments.
221     op.add({ return parseDebugOrVersion(op, cmd.context); });
222     op.add("--xml", cmd.writeXML);
223     op.add("--raw", cmd.rawOutput);
224     op.add("-hl", cmd.writeHLFiles);
225     op.add("-i", cmd.includeUndocumented);
226     op.add("--inc-private", cmd.includePrivate);
227     op.add("-v", cmd.verbose);
228     op.add("--kandil", cmd.useKandil);
229     op.add("--report", cmd.writeReport);
230     op.add("-rx", value, { cmd.regexps ~= regex(value); });
231     op.add("-m", cmd.modsTxtPath);
232     if (!op.parseArgs())
233       return op.printUsageError();
234     foreach (arg; op.argv)
235       if (arg.length > 6 && String(arg[$-5..$]).ieql(".ddoc"))
236         cmd.macroPaths ~= arg;
237       else
238         cmd.filePaths ~= arg;
239 
240     cmd.run();
241     diag.hasInfo() && printErrors(diag);
242     break;
243   case "hl", "highlight":
244     if (!op.hasArgs)
245       return printHelp(command, diag);
246 
247     auto cmd = new HighlightCommand();
248     cmd.cc = globalCC;
249     cmd.diag = diag;
250 
251     bool dummy;
252     alias HO = HighlightCommand.Option;
253 
254     op.add("--syntax", dummy, { cmd.add(HO.Syntax); });
255     op.add("--xml", dummy, { cmd.add(HO.XML); });
256     op.add("--html", dummy, { cmd.add(HO.HTML); });
257     op.add("--lines", dummy, { cmd.add(HO.PrintLines); });
258     if (!op.parseArgs())
259       return op.printUsageError();
260     foreach (arg; op.argv)
261       if (!cmd.filePathSrc)
262         cmd.filePathSrc = arg;
263       else
264         cmd.filePathDest = arg;
265 
266     cmd.run();
267     diag.hasInfo() && printErrors(diag);
268     break;
269   case "importgraph", "igraph":
270     if (!op.hasArgs)
271       return printHelp(command, diag);
272 
273     auto cmd = new IGraphCommand();
274     cmd.context = globalCC;
275 
276     cstring value;
277     bool dummy;
278     alias IO = IGraphCommand.Option;
279 
280     op.add({ return parseDebugOrVersion(op, cmd.context); });
281     op.add("-I", value, { cmd.context.importPaths ~= value; });
282     op.add("-x", value, { cmd.regexps ~= value; });
283     op.add("-l", value, { cmd.levels = toInt(value); });
284     op.add("-si", value, { cmd.siStyle = value; });
285     op.add("-pi", value, { cmd.piStyle = value; });
286     op.add("--dot", dummy,   { cmd.add(IO.PrintDot); });
287     op.add("--paths", dummy, { cmd.add(IO.PrintPaths); });
288     op.add("--list", dummy,  { cmd.add(IO.PrintList); });
289     op.add("--hle", dummy,   { cmd.add(IO.HighlightCyclicEdges); });
290     op.add("--hlv", dummy,   { cmd.add(IO.HighlightCyclicVertices); });
291     op.add("--gbp", dummy,   { cmd.add(IO.GroupByPackageNames); });
292     op.add("--gbf", dummy,   { cmd.add(IO.GroupByFullPackageName); });
293     op.add("-i", dummy,      { cmd.add(IO.IncludeUnlocatableModules); });
294     op.add("-m", dummy,      { cmd.add(IO.MarkCyclicModules); });
295     op.addDefault({ cmd.filePath = op.popArg(); });
296     if (!op.parseArgs())
297       return op.printUsageError();
298 
299     cmd.run();
300     diag.hasInfo() && printErrors(diag);
301     break;
302   case "stats", "statistics":
303     if (!op.hasArgs)
304       return printHelp(command, diag);
305 
306     auto cmd = new StatsCommand();
307     cmd.cc = globalCC;
308 
309     op.add("--toktable", cmd.printTokensTable);
310     op.add("--asttable", cmd.printNodesTable);
311     if (!op.parseArgs())
312       return op.printUsageError();
313     cmd.filePaths = op.argv;
314 
315     cmd.run();
316     break;
317   case "tok", "tokenize":
318     if (!op.hasArgs)
319       return printHelp(command, diag);
320 
321     class TokenizeCommand : Command
322     {
323       CompilationContext cc;
324       cstring srcFilePath;
325       cstring separator = "\n";
326       bool ignoreWSToks, printWS, fromStdin;
327 
328       override void run()
329       {
330         auto sourceText = fromStdin ?
331           new SourceText("stdin", readStdin()) :
332           new SourceText(srcFilePath, true);
333 
334         auto lx = new Lexer(sourceText, cc.tables.lxtables, cc.diag);
335         lx.scanAll();
336 
337         if (cc.diag.hasInfo())
338           return;
339 
340         foreach (token; lx.tokenList[0..$-1])
341         {
342           if (token.kind == TOK.Newline || ignoreWSToks && token.isWhitespace)
343             continue;
344           if (printWS && token.ws)
345             Stdout(token.wsChars);
346           Stdout(token.text)(separator);
347         }
348       }
349     }
350 
351     auto cmd = new TokenizeCommand();
352     cmd.cc = globalCC;
353 
354     op.add("-s", cmd.separator);
355     op.add("-", cmd.fromStdin);
356     op.add("-i", cmd.ignoreWSToks);
357     op.add("-ws", cmd.printWS);
358     op.addDefault({ cmd.srcFilePath = op.popArg(); });
359     if (!op.parseArgs())
360       return op.printUsageError();
361 
362     cmd.run();
363 
364     diag.hasInfo() && printErrors(diag);
365     break;
366   case "dlexed", "dlx":
367     if (!op.hasArgs)
368       return printHelp(command, diag);
369 
370     class SerializeCommand : Command
371     {
372       CompilationContext cc;
373       cstring srcFilePath, outFilePath;
374       bool fromStdin;
375 
376       override void run()
377       {
378         auto sourceText = fromStdin ?
379           new SourceText("stdin", readStdin()) :
380           new SourceText(srcFilePath, true);
381 
382         auto lx = new Lexer(sourceText, cc.tables.lxtables, cc.diag);
383         lx.scanAll();
384 
385         if (cc.diag.hasInfo())
386           return;
387 
388         auto data = TokenSerializer.serialize(lx.tokenList);
389         if (outFilePath.length)
390           outFilePath.write(data);
391         else
392           Stdout(cast(cstring)data);
393       }
394     }
395 
396     auto cmd = new SerializeCommand();
397     cmd.cc = globalCC;
398 
399     op.add("-", cmd.fromStdin);
400     op.add("-o", cmd.outFilePath);
401     op.addDefault({ cmd.srcFilePath = op.popArg(); });
402     if (!op.parseArgs())
403       return op.printUsageError();
404 
405     cmd.run();
406 
407     diag.hasInfo() && printErrors(diag);
408     break;
409   case "serialize", "sz":
410     if (!op.hasArgs)
411       return printHelp(command, diag);
412 
413     class SerializeASTCommand : Command
414     {
415       CompilationContext cc;
416       cstring srcFilePath, outFilePath;
417       bool fromStdin;
418 
419       override void run()
420       {
421         auto sourceText = fromStdin ?
422           new SourceText("stdin", readStdin()) :
423           new SourceText(srcFilePath, true);
424 
425         auto mod = new Module(sourceText, cc);
426         mod.parse();
427 
428         if (cc.diag.hasInfo())
429           return;
430 
431         auto astWriter = new ASTSerializer();
432         const data = astWriter.serialize(mod);
433 
434         if (outFilePath.length)
435           outFilePath.write(data);
436         else
437           Stdout(cast(cstring)data);
438       }
439     }
440 
441     auto cmd = new SerializeASTCommand();
442     cmd.cc = globalCC;
443 
444     op.add("-", cmd.fromStdin);
445     op.add("-o", cmd.outFilePath);
446     op.addDefault({ cmd.srcFilePath = op.popArg(); });
447     if (!op.parseArgs())
448       return op.printUsageError();
449 
450     cmd.run();
451 
452     diag.hasInfo() && printErrors(diag);
453     break;
454   case "profile":
455     if (!op.hasArgs)
456       break;
457     const(String)[] filePaths = (args[2] == "dstress") ?
458       String(cast(cstring)"dstress_files".read()).split('\0') :
459       toStrings(args[2..$]);
460 
461     auto tables = globalCC.tables.lxtables;
462 
463     static void printSecs(TickDuration swatch) {
464       Printfln("Scanned in {}ms.", swatch.msecs);
465     }
466     auto mt = measureTime!(printSecs);
467 
468     foreach (filePath; filePaths)
469       new Lexer(new SourceText(filePath[], true), tables).scanAll();
470     break;
471   case "settings", "set":
472     alias GS = GlobalSettings;
473     cstring
474       versionIds = String(";").join(GS.versionIds)[],
475       importPaths = String(";").join(GS.importPaths)[],
476       ddocPaths = String(";").join(GS.ddocFilePaths)[];
477 
478     cstring[string] settings = [
479       "DIL":config.executablePath,
480       "DILCONF":config.dilconfPath,
481       "DATADIR":GS.dataDir, "VERSION_IDS":versionIds,
482       "KANDILDIR":GS.kandilDir,
483       "IMPORT_PATHS":importPaths, "DDOC_FILES":ddocPaths,
484       "LANG_FILE":GS.langFile, "XML_MAP":GS.xmlMapFile,
485       "HTML_MAP":GS.htmlMapFile, "LEXER_ERROR":GS.lexerErrorFormat,
486       "PARSER_ERROR":GS.parserErrorFormat,
487       "SEMANTIC_ERROR":GS.semanticErrorFormat,
488       "TAB_WIDTH":Format("{}", GS.tabWidth)
489     ];
490 
491     cstring[] retrieve_settings;
492     if (args.length > 2)
493       retrieve_settings = args[2..$];
494 
495     if (retrieve_settings.length) // Print select settings.
496       foreach (name; retrieve_settings)
497       {
498         if (auto psetting = (name = String(name).toupper()[]) in settings)
499           Printfln("{}={}", name, *psetting);
500       }
501     else // Print all settings.
502       foreach (name; settings.keys.sort)
503         Printfln("{}={}", name, settings[name]);
504     break;
505   case "?", "h", "help":
506     printHelp(op.hasArgs ? op.popArg() : "", diag);
507     break;
508   case "-v", "v", "--version", "version":
509     Stdout(VERSION).newline;
510     break;
511   default:
512     printHelp("main", diag);
513   }
514 }
515 
516 /// Reads the standard input and returns its contents.
517 char[] readStdin()
518 {
519   import std.stdio;
520   char[] text;
521   foreach (buffer; stdin.byChunk(4096))
522     text ~= buffer;
523   return text;
524 }
525 
526 /// Available commands.
527 enum string COMMANDS =
528   "  help (?)
529   compile (c)
530   ddoc (d)
531   dlexed (dlx)
532   highlight (hl)
533   importgraph (igraph)
534   pytree (py)
535   serialize (sz)
536   settings (set)
537   statistics (stats)
538   tokenize (tok)
539   version (v)";
540 
541 /// Creates the global compilation context.
542 CompilationContext newCompilationContext()
543 {
544   /// Generates code: Adds a version id to a list if defined.
545   /// E.g.: version(X86) ids ~= "X86";
546   char[] defineVersionIds(string[] ids...)
547   {
548     char[] code;
549     foreach (id; ids)
550       code ~= "version(" ~ id ~ ") ids ~= \"" ~ id ~ "\";";
551     return code;
552   }
553 
554   auto cc = new CompilationContext;
555   string[] ids = ["DIL", "all"];
556   mixin(defineVersionIds(
557     "Windows", "Win32", "Win64", "linux", "OSX", "FreeBSD", "OpenBSD", "BSD",
558     "Solaris", "Posix", "AIX", "SkyOS", "SysV3", "SysV4", "Hurd", "Android",
559     "Cygwin", "MinGW", "X86", "X86_64", "ARM", "PPC", "PPC64", "IA64", "MIPS",
560     "MIPS64", "SPARC", "SPARC64", "S390", "S390X", "HPPA", "HPPA64", "SH",
561     "SH64", "Alpha", "LittleEndian", "BigEndian", "D_InlineAsm_X86",
562     "D_InlineAsm_X86_64", "D_LP64", "D_PIC", "D_SIMD"
563   ));
564 version(D2)
565   ids ~= "D_Version2";
566   foreach (id; ids)
567     cc.addVersionId(id);
568   return cc;
569 }
570 
571 /// Updates cc with variables from GlobalSettings.
572 void updateWithSettings(CompilationContext cc)
573 {
574   cc.importPaths = GlobalSettings.importPaths;
575   foreach (versionId; GlobalSettings.versionIds)
576     if (cc.tables.idents.isValidUnreservedIdentifier(versionId))
577       cc.addVersionId(versionId);
578 }
579 
580 /// Parses a debug or version command line option.
581 bool parseDebugOrVersion(ref OptParser op, CompilationContext context)
582 {
583   cstring val;
584   if (op.argv[0] == "-debug")
585     context.debugLevel = 1;
586   else if (op.parse("-debug", val))
587   {
588     if (val.length && isdigit(val[0]))
589       context.debugLevel = toInt(val);
590     else if (context.tables.idents.isValidUnreservedIdentifier(val))
591       context.addDebugId(val);
592   }
593   else if (op.parse("-version", val))
594   {
595     if (val.length && isdigit(val[0]))
596       context.versionLevel = toInt(val);
597     else if (context.tables.idents.isValidUnreservedIdentifier(val))
598       context.addVersionId(val);
599   }
600   else
601     return false;
602   return true;
603 }
604 
605 /// Prints the errors collected in diag.
606 void printErrors(Diagnostics diag)
607 {
608   foreach (info; diag.info)
609   {
610     cstring errorFormat;
611     auto tid = typeid(info);
612     if (tid is typeid(LexerError))
613       errorFormat = GlobalSettings.lexerErrorFormat;
614     else if (tid is typeid(ParserError))
615       errorFormat = GlobalSettings.parserErrorFormat;
616     else if (tid is typeid(SemanticError))
617       errorFormat = GlobalSettings.semanticErrorFormat;
618     else if (tid is typeid(Warning))
619       errorFormat = "{0}: Warning: {3}";
620     else if (tid is typeid(GeneralError))
621       errorFormat = "Error: {3}";
622     else
623       continue;
624     auto err = cast(Problem)info;
625     Stderr.formatln(errorFormat, err.filePath, err.loc, err.col, err.getMsg);
626   }
627 }
628 
629 /// Prints the help message of a command.
630 /// If the command wasn't found, the main help message is printed.
631 void printHelp(cstring command, Diagnostics diag)
632 {
633   cstring msg;
634   MID mid;
635   switch (command)
636   {
637   case "c", "compile":          mid = MID.HelpCompile;     goto Lcommon;
638   case "py", "pytree":          mid = MID.HelpPytree;      goto Lcommon;
639   case "ddoc", "d":             mid = MID.HelpDdoc;        goto Lcommon;
640   case "hl", "highlight":       mid = MID.HelpHighlight;   goto Lcommon;
641   case "importgraph", "igraph": mid = MID.HelpImportGraph; goto Lcommon;
642   case "tok", "tokenize":       mid = MID.HelpTokenize;    goto Lcommon;
643   case "dlexed", "dlx":         mid = MID.HelpDlexed;      goto Lcommon;
644   case "serialize", "sz":       mid = MID.HelpSerialize;   goto Lcommon;
645   case "stats", "statistics":   mid = MID.HelpStatistics;  goto Lcommon;
646   case "settings", "set":       mid = MID.HelpSettings;    goto Lcommon;
647   case "?", "h", "help":        mid = MID.HelpHelp;        goto Lcommon;
648   Lcommon:
649     msg = diag.msg(mid);
650     break;
651   case "main", "":
652     const COMPILED_WITH = __VENDOR__;
653     const COMPILED_VERSION = (V => V[0] ~ "." ~ V[1..4])(itoa(__VERSION__));
654     const COMPILED_DATE = __TIMESTAMP__;
655     msg = diag.formatMsg(MID.HelpMain, VERSION, COMMANDS, COMPILED_WITH,
656       COMPILED_VERSION, COMPILED_DATE);
657     break;
658   default:
659     msg = diag.formatMsg(MID.UnknownCommand, command);
660   }
661   Stdout(msg).newline;
662 }
663 
664 void runUnittests()
665 {
666   import cmd.ImportGraph,
667          dil.ast.Visitor,
668          dil.doc.Macro,
669          dil.doc.Parser,
670          dil.lexer.Lexer,
671          dil.Float,
672          dil.Complex,
673          dil.Converter,
674          dil.FileBOM,
675          dil.HtmlEntities,
676          dil.String,
677          util.uni;
678 
679   auto testFuncs = [
680     &testGraph, &testFloat, &testComplex, &testConverter,
681     &testTellBOM, &testEntity2Unicode, &testString,
682     &testVisitor, &testMacroConvert, &testDocParser,
683     &testLexer, &testLexerPeek, &testIsUniAlpha,
684     &testSet,
685   ];
686 
687   foreach (testFunc; testFuncs)
688     testFunc();
689 }