changeset 375:0bd21b746a04

- Added code to main() for recognizing options to the importgraph command. - Added HelpImportGraph message. - Added enum IGraphOption. - Added class Vertex inheriting from Module. Changed struct Edge to a class. - Modified a lot of code in cmd.ImportGraph; too many to list here. - Made some minor changes to dil.Parser.
author aziz
date Sat, 08 Sep 2007 16:42:02 +0000
parents 6d22f0b6a674
children 5ebe80ce84f2
files trunk/src/cmd/ImportGraph.d trunk/src/dil/Messages.d trunk/src/dil/Parser.d trunk/src/lang_de.d trunk/src/lang_en.d trunk/src/lang_fi.d trunk/src/lang_tr.d trunk/src/main.d
diffstat 8 files changed, 289 insertions(+), 71 deletions(-) [+]
line wrap: on
line diff
--- a/trunk/src/cmd/ImportGraph.d	Fri Sep 07 11:36:01 2007 +0000
+++ b/trunk/src/cmd/ImportGraph.d	Sat Sep 08 16:42:02 2007 +0000
@@ -10,10 +10,24 @@
 import dil.File;
 import dil.Module;
 import dil.Settings;
-import std.stdio : writefln;
+import std.stdio : writefln, writef;
 import std.path : getDirName, dirSep = sep;
 import std.file : exists;
 import std.string : replace;
+import std.regexp;
+
+enum IGraphOption
+{
+  None,
+  IncludeUnlocatableModules = 1,
+  HighlightCyclicEdges      = 1<<1,
+  HighlightCyclicVertices   = 1<<2,
+  PrintDot                  = 1<<3,
+  PrintPaths                = 1<<4,
+  PrintList                 = 1<<5,
+  GroupByPackageNames       = 1<<6,
+  GroupByFullPackageName    = 1<<7,
+}
 
 string findModulePath(string moduleFQN, string[] importPaths)
 {
@@ -27,121 +41,249 @@
   return null;
 }
 
-struct Edge
+class Edge
 {
-  Module outgoing;
-  Module incoming;
-  static Edge opCall(Module o, Module i)
+  Vertex outgoing;
+  Vertex incoming;
+  bool isCyclic;
+  this(Vertex o, Vertex i)
   {
-    Edge e;
-    e.outgoing = o;
-    e.incoming = i;
-    return e;
+    this.outgoing = o;
+    this.incoming = i;
   }
 }
 
-void execute(string fileName, string[] importPaths)
+class Vertex : Module
 {
+  uint id;
+  Vertex[] incoming;
+  bool isCyclic; /// Whether this vertex is in a cyclic relationship with other vertices.
+
+  this(string filePath)
+  {
+    super(filePath, true);
+  }
+
+  Vertex[] outgoing()
+  {
+    return cast(Vertex[])super.modules;
+  }
+}
+
+void execute(string filePath, string[] importPaths, string[] strRegexps, uint levels, IGraphOption options)
+{
+  // Init regular expressions.
+  RegExp[] regexps;
+  foreach (strRegexp; strRegexps)
+    regexps ~= new RegExp(strRegexp);
+
   // Add directory of file and global directories to import paths.
-  auto fileDir = getDirName(fileName);
+  auto fileDir = getDirName(filePath);
   if (fileDir.length)
     importPaths ~= fileDir;
   importPaths ~= GlobalSettings.importPaths;
 
-  Module[string] loadedModules;
-  Module[] loadedModulesList; // Ordered list of loaded modules.
+  Vertex[string] loadedModules;
+  Vertex[] loadedModulesList; // Ordered list of loaded modules.
   Edge edges[];
 
-  Module loadModule(string moduleFQNPath)
+  Vertex loadModule(string moduleFQNPath)
   {
     auto mod_ = moduleFQNPath in loadedModules;
     if (mod_ !is null)
-      return *mod_;
-// writefln(moduleFQN);
+      return *mod_; // Return already loaded module.
+
+    // Ignore module names matching regular expressions.
+    foreach (rx; regexps)
+      if (rx.test(replace(moduleFQNPath, dirSep, ".")))
+        return null;
+
     auto modulePath = findModulePath(moduleFQNPath, importPaths);
-    Module mod;
+    Vertex mod;
     if (modulePath is null)
     {
-// writefln("Warning: Module %s.d couldn't be found.", moduleFQNPath);
-      mod = new Module(null, true);
-      mod.setFQN(replace(moduleFQNPath, dirSep, "."));
-      loadedModules[moduleFQNPath] = mod;
-      loadedModulesList ~= mod;
+      if (options & IGraphOption.IncludeUnlocatableModules)
+      {
+        mod = new Vertex(null);
+        mod.setFQN(replace(moduleFQNPath, dirSep, "."));
+        loadedModules[moduleFQNPath] = mod;
+        loadedModulesList ~= mod;
+        mod.id = loadedModulesList.length -1;
+      }
     }
     else
     {
-      mod = new Module(modulePath, true);
+// writefln(modulePath);
+      mod = new Vertex(modulePath);
       mod.parse();
 
       loadedModules[moduleFQNPath] = mod;
       loadedModulesList ~= mod;
+      mod.id = loadedModulesList.length -1;
 
       auto moduleFQNs = mod.getImports();
 
       foreach (moduleFQN_; moduleFQNs)
       {
         auto loaded_mod = loadModule(moduleFQN_);
-        edges ~= Edge(mod, loaded_mod);
-        mod.modules ~= loaded_mod;
+        if (loaded_mod !is null)
+        {
+          edges ~= new Edge(mod, loaded_mod);
+          mod.modules ~= loaded_mod;
+          loaded_mod.incoming ~= mod;
+        }
       }
-      return mod;
     }
     return mod;
   }
 
-  auto mod = new Module(fileName, true);
+  auto mod = new Vertex(filePath);
   mod.parse();
 
   auto moduleFQNs = mod.getImports();
 
   loadedModules[mod.getFQNPath()] = mod;
   loadedModulesList ~= mod;
+  mod.id = 0; // loadedModulesList.length -1
 
   foreach (moduleFQN_; moduleFQNs)
   {
     auto loaded_mod = loadModule(moduleFQN_);
-    edges ~= Edge(mod, loaded_mod);
-    mod.modules ~= loaded_mod;
+    if (loaded_mod !is null)
+    {
+      edges ~= new Edge(mod, loaded_mod);
+      mod.modules ~= loaded_mod;
+      loaded_mod.incoming ~= mod;
+    }
   }
+  // Finished loading modules.
+
+
+  if (options & IGraphOption.PrintPaths)
+    printPaths(loadedModulesList, levels+1, "");
+  else if (options & IGraphOption.PrintList)
+    printList(loadedModulesList, levels+1, "");
+  else if (options & IGraphOption.PrintDot)
+    printDot(loadedModulesList, edges, options);
+}
+
+void printPaths(Vertex[] vertices, uint level, char[] indent)
+{
+  if (!level)
+    return;
+  foreach (vertex; vertices)
+  {
+    writefln(indent, vertex.filePath);
+    if (vertex.outgoing.length)
+      printPaths(vertex.outgoing, level-1, indent~"  ");
+  }
+}
 
-  writefln("digraph module_dependencies\n{");
-  foreach (edge; edges)
+void printList(Vertex[] vertices, uint level, char[] indent)
+{
+  if (!level)
+    return;
+  foreach (vertex; vertices)
   {
-    writefln(`  "%s" -> "%s"`, edge.outgoing.getFQN(), edge.incoming.getFQN());
+    writefln(indent, vertex.getFQN());
+    if (vertex.outgoing.length)
+      printList(vertex.outgoing, level-1, indent~"  ");
   }
+}
+
+void printDot(Vertex[] loadedModulesList, Edge[] edges, IGraphOption options)
+{
+  Vertex[][string] verticesByPckgName;
+  if (options & IGraphOption.GroupByFullPackageName)
+    foreach (module_; loadedModulesList)
+      verticesByPckgName[module_.packageName] ~= module_;
+
+  if (options & (IGraphOption.HighlightCyclicVertices |
+                 IGraphOption.HighlightCyclicEdges))
+    analyzeGraph(loadedModulesList, edges.dup);
+
+  writefln("Digraph ModuleDependencies\n{");
+
+  if (options & IGraphOption.HighlightCyclicVertices)
+    foreach (i, module_; loadedModulesList)
+      writefln(`  n%d [label="%s"%s];`, i, module_.getFQN(), (module_.isCyclic ? ",style=filled,fillcolor=tomato" : ""));
+  else
+    foreach (i, module_; loadedModulesList)
+      writefln(`  n%d [label="%s"];`, i, module_.getFQN());
+
+  foreach (edge; edges)
+    writefln(`  n%d -> n%d%s;`, edge.outgoing.id, edge.incoming.id, (edge.isCyclic ? "[color=red]" : ""));
+
+  if (options & IGraphOption.GroupByFullPackageName)
+    foreach (packageName, vertices; verticesByPckgName)
+    {
+      writef(`  subgraph "cluster_%s" {`\n`    label="%s";color=blue;`"\n    ", packageName, packageName);
+      foreach (module_; vertices)
+        writef(`n%d;`, module_.id);
+      writefln("\n  }");
+    }
+
   writefln("}");
 }
 
-Edge[] findCyclicEdges(Module[] modules, Edge[] edges)
+void analyzeGraph(Vertex[] vertices, Edge[] edges)
 {
-  foreach (module_; modules)
+  void recursive(Vertex[] modules)
   {
-    uint outgoing, incoming;
-    foreach (edge; edges)
+    foreach (idx, vertex; vertices)
     {
-      if (edge.outgoing == module_)
-        outgoing++;
-      if (edge.incoming == module_)
-        incoming++;
+      uint outgoing, incoming;
+      foreach (j, edge; edges)
+      {
+        if (edge.outgoing is vertex)
+          outgoing++;
+        if (edge.incoming is vertex)
+          incoming++;
+      }
+
+      if (outgoing == 0)
+      {
+        if (incoming != 0)
+        {
+          // Vertex is a sink.
+          alias outgoing i; // Reuse
+          alias incoming j; // Reuse
+          // Remove edges.
+          for (i=j=0; i < edges.length; i++)
+            if (edges[i].incoming !is vertex)
+              edges[j++] = edges[i];
+          edges.length = j;
+          vertices = vertices[0..idx] ~ vertices[idx+1..$];
+          return recursive(modules);
+        }
+        else
+          assert(0, "orphaned module: "~vertex.getFQN()~" (has no edges in graph)"); // orphaned vertex (module) in graph
+      }
+      else if (incoming == 0)
+      {
+        // Vertex is a source
+        alias outgoing i; // Reuse
+        alias incoming j; // Reuse
+        // Remove edges.
+        for (i=j=0; i < edges.length; i++)
+          if (edges[i].outgoing !is vertex)
+            edges[j++] = edges[i];
+        edges.length = j;
+        vertices = vertices[0..idx] ~ vertices[idx+1..$];
+        return recursive(modules);
+      }
+//       else
+//       {
+//         // source && sink
+//       }
     }
 
-    if (outgoing == 0)
-    {
-      if (incoming != 0)
-      {
-        // sink
-      }
-      else
-        assert(0); // orphaned vertex (module) in graph
-    }
-    else if (incoming == 0)
-    {
-      // source
-    }
-    else
-    {
-      // source && sink
-    }
+    // When reaching this point it means only cylic edges and vertices are left.
+    foreach (vertex; vertices)
+      vertex.isCyclic = true;
+    foreach (edge; edges)
+      if (edge)
+        edge.isCyclic = true;
   }
-  return null;
+  recursive(vertices);
 }
--- a/trunk/src/dil/Messages.d	Fri Sep 07 11:36:01 2007 +0000
+++ b/trunk/src/dil/Messages.d	Sat Sep 08 16:42:02 2007 +0000
@@ -66,6 +66,7 @@
   // Help messages:
   HelpMain,
   HelpGenerate,
+  HelpImportGraph,
 }
 
 string GetMsg(MID mid)
--- a/trunk/src/dil/Parser.d	Fri Sep 07 11:36:01 2007 +0000
+++ b/trunk/src/dil/Parser.d	Sat Sep 08 16:42:02 2007 +0000
@@ -2593,8 +2593,8 @@
 
   Expression parseAsmOrOrExpression()
   {
+    alias parseAsmAndAndExpression parseNext;
     auto begin = token;
-    alias parseAsmAndAndExpression parseNext;
     auto e = parseNext();
     while (token.type == T.OrLogical)
     {
@@ -2608,8 +2608,8 @@
 
   Expression parseAsmAndAndExpression()
   {
+    alias parseAsmOrExpression parseNext;
     auto begin = token;
-    alias parseAsmOrExpression parseNext;
     auto e = parseNext();
     while (token.type == T.AndLogical)
     {
@@ -2623,8 +2623,8 @@
 
   Expression parseAsmOrExpression()
   {
+    alias parseAsmXorExpression parseNext;
     auto begin = token;
-    alias parseAsmXorExpression parseNext;
     auto e = parseNext();
     while (token.type == T.OrBinary)
     {
--- a/trunk/src/lang_de.d	Fri Sep 07 11:36:01 2007 +0000
+++ b/trunk/src/lang_de.d	Sat Sep 08 16:42:02 2007 +0000
@@ -70,6 +70,7 @@
 erhalten.
 
 Kompiliert mit {3} v{4} am {5}.`,
+
   `Generiere ein XML- oder HTML-Dokument aus einer D-Quelltextdatei.
 Verwendung:
   dil gen datei.d [Optionen]
@@ -81,4 +82,6 @@
 
 Beispiel:
   dil gen Parser.d --html --syntax > Parser.html`,
+
+  ``,
 ];
\ No newline at end of file
--- a/trunk/src/lang_en.d	Fri Sep 07 11:36:01 2007 +0000
+++ b/trunk/src/lang_en.d	Sat Sep 08 16:42:02 2007 +0000
@@ -69,6 +69,7 @@
 Type 'dil help <subcommand>' for more help on a particular subcommand.
 
 Compiled with {3} v{4} on {5}.`,
+
   `Generate an XML or HTML document from a D source file.
 Usage:
   dil gen file.d [Options]
@@ -80,4 +81,33 @@
 
 Example:
   dil gen Parser.d --html --syntax > Parser.html`,
+
+  `Parse a module and extract information from the resulting module dependency graph.
+Usage:
+  dil igraph file.d Format [Options]
+
+  The directory of file.d is implicitly added to the list of import paths.
+
+Format:
+  --dot            : generate a dot document
+  -gbp             : Group modules by package names
+  -gbf             : Group modules by full package name
+  -hle             : highlight cyclic edges in the graph
+  -hlv             : highlight modules in cyclic relationship
+
+  --paths          : print a list of paths to the modules imported by file.d
+  -lN              : print N levels.
+
+  --list           : print a list of the module names imported by file.d
+  -lN              : print N levels.
+
+Options:
+  -Ipath           : add 'path' to the list of import paths where modules are
+                     looked for
+  -rREGEXP         : exclude modules whose names match the regular expression
+                     REGEXP
+  -i               : include unlocatable modules
+
+Example:
+  dil igraph src/main.d`,
 ];
\ No newline at end of file
--- a/trunk/src/lang_fi.d	Fri Sep 07 11:36:01 2007 +0000
+++ b/trunk/src/lang_fi.d	Sat Sep 08 16:42:02 2007 +0000
@@ -82,4 +82,6 @@
 
 Esimerkki:
   dil gen Parser.d --html --syntax > Parser.html`,
+
+  ``,
 ];
\ No newline at end of file
--- a/trunk/src/lang_tr.d	Fri Sep 07 11:36:01 2007 +0000
+++ b/trunk/src/lang_tr.d	Sat Sep 08 16:42:02 2007 +0000
@@ -69,6 +69,7 @@
 Belirli komut'a yardım edinmek için 'dil help <komut>' yazınız.
 
 Bu yazılım {3} v{4} ile {5} tarihinde derletilmiş.`,
+
   `Bir D kaynak kodundan XML veya HTML dosyası oluştur.
 Kullanım:
   dil gen dosya.d [Seçenekler]
@@ -80,4 +81,6 @@
 
 Örnek:
   dil gen Parser.d --html --syntax > Parser.html`,
+
+  ``,
 ];
\ No newline at end of file
--- a/trunk/src/main.d	Fri Sep 07 11:36:01 2007 +0000
+++ b/trunk/src/main.d	Sat Sep 08 16:42:02 2007 +0000
@@ -3,7 +3,6 @@
   License: GPL3
 +/
 module main;
-import std.stdio;
 import dil.Parser;
 import dil.Lexer;
 import dil.Token;
@@ -14,6 +13,7 @@
 import cmd.Generate;
 import cmd.Statistics;
 import cmd.ImportGraph;
+import std.stdio, std.conv;
 
 void main(char[][] args)
 {
@@ -47,19 +47,43 @@
     cmd.Generate.execute(fileName, options);
     break;
   case "importgraph", "igraph":
-    string fileName;
+    string filePath;
     string[] includePaths;
+    string[] regexps;
+    uint levels;
+    IGraphOption options;
     foreach (arg; args[2..$])
     {
-      if (arg.length >= 2 && arg[0..2] == "-I")
-      {
-        if (arg.length >= 3)
-          includePaths ~= args[2..$];
-      }
+      if (strbeg(arg, "-I"))
+        includePaths ~= arg[2..$];
+      else if(strbeg(arg, "-r"))
+        regexps ~= arg[2..$];
+      else if(strbeg(arg, "-l"))
+        levels = toUint(arg[2..$]);
       else
-        fileName = arg;
+        switch (arg)
+        {
+        case "--dot":
+          options |= IGraphOption.PrintDot; break;
+        case "--paths":
+          options |= IGraphOption.PrintPaths; break;
+        case "--list":
+          options |= IGraphOption.PrintList; break;
+        case "-i":
+          options |= IGraphOption.IncludeUnlocatableModules; break;
+        case "-hle":
+          options |= IGraphOption.HighlightCyclicEdges; break;
+        case "-hlv":
+          options |= IGraphOption.HighlightCyclicVertices; break;
+        case "-gbp":
+          options |= IGraphOption.GroupByPackageNames; break;
+        case "-gbf":
+          options |= IGraphOption.GroupByFullPackageName; break;
+        default:
+          filePath = arg;
+        }
     }
-    cmd.ImportGraph.execute(fileName, includePaths);
+    cmd.ImportGraph.execute(filePath, includePaths, regexps, levels, options);
     break;
   case "stats", "statistics":
     cmd.Statistics.execute(args[2]);
@@ -84,6 +108,16 @@
   "  importgraph (igraph)\n"
   "  statistics (stats)\n";
 
+bool strbeg(char[] str, char[] begin)
+{
+  if (str.length >= begin.length)
+  {
+    if (str[0 .. begin.length] == begin)
+      return true;
+  }
+  return false;
+}
+
 char[] helpMain()
 {
   return format(MID.HelpMain, VERSION, COMMANDS, COMPILED_WITH, COMPILED_VERSION, COMPILED_DATE);
@@ -97,6 +131,9 @@
   case "gen", "generate":
     msg = GetMsg(MID.HelpGenerate);
     break;
+  case "importgraph", "igraph":
+    msg = GetMsg(MID.HelpImportGraph);
+    break;
   default:
     msg = helpMain();
   }
@@ -114,7 +151,7 @@
   foreach(decl; decls)
   {
     assert(decl !is null);
-    writefln(indent, decl.classinfo.name, ": begin=%s end=%s", decl.begin ? decl.begin.srcText : "\33[31mnull\33[0m", decl.end ? decl.end.srcText : "\33[31mnull\33[0m");
+//     writefln(indent, decl.classinfo.name, ": begin=%s end=%s", decl.begin ? decl.begin.srcText : "\33[31mnull\33[0m", decl.end ? decl.end.srcText : "\33[31mnull\33[0m");
     print(decl.children, indent ~ "  ");
   }
 }