Auto merge of #508 - impowski:graphviz_dot, r=fitzgen

Graphviz implementation

This will solve #484 . Right now it's really basic and I will change some of things in future commits like docs and other things.

r? @fitzgen
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cbaaf3c..8c34710 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,6 +15,7 @@
   - [Overview](#overview)
   - [Running All Tests](#running-all-tests)
   - [Authoring New Tests](#authoring-new-tests)
+- [Generating Graphviz Dot File](#generating-graphviz-dot-file)
 - [Automatic code formatting](#automatic-code-formatting)
 - [Debug Logging](#debug-logging)
 - [Using `creduce` to Minimize Test Cases](#using-creduce-to-minimize-test-cases)
@@ -112,6 +113,27 @@
 $ cargo test -p tests_expectations
 ```
 
+## Generating Graphviz Dot Files
+
+We have a special thing which will help you debug your codegen context if something
+will go wrong. It will generate a [`graphviz`](http://graphviz.org/pdf/dotguide.pdf)
+dot file and then you can create a PNG from it with `graphviz` tool in your OS.
+
+Here is an example how it could be done:
+
+```
+$ cargo run -- example.hpp --emit-ir-graphviz output.dot
+```
+
+It will generate your graphviz dot file and then you will need tog
+create a PNG from it with `graphviz`.
+
+Something like this:
+
+```
+$ dot -Tpng output.dot -o output.png
+```
+
 ## Automatic code formatting
 
 We use [`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) to enforce a
diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs
index 92e3487..ad6736b 100644
--- a/src/codegen/mod.rs
+++ b/src/codegen/mod.rs
@@ -2499,6 +2499,13 @@
             }
         }
 
+        if let Some(path) = context.options().emit_ir_graphviz.as_ref() {
+            match context.emit_ir_graphviz(path.clone()) {
+                Ok(()) => info!("Your dot file was generated successfully into: {}", path),
+                Err(e) => error!("{}", e),
+            }
+        }
+
         context.resolve_item(context.root_module())
             .codegen(context, &mut result, &whitelisted_items, &());
 
diff --git a/src/ir/context.rs b/src/ir/context.rs
index d2fb2be..7383c09 100644
--- a/src/ir/context.rs
+++ b/src/ir/context.rs
@@ -5,7 +5,7 @@
 use super::item::{Item, ItemCanonicalPath, ItemSet};
 use super::item_kind::ItemKind;
 use super::module::{Module, ModuleKind};
-use super::traversal::{self, Edge, ItemTraversal};
+use super::traversal::{self, Edge, ItemTraversal, Trace};
 use super::ty::{FloatKind, TemplateDeclaration, Type, TypeKind};
 use BindgenOptions;
 use cexpr;
@@ -18,6 +18,8 @@
 use std::collections::{HashMap, hash_map};
 use std::collections::btree_map::{self, BTreeMap};
 use std::fmt;
+use std::fs::File;
+use std::io::{self, Write};
 use std::iter::IntoIterator;
 use syntax::ast::Ident;
 use syntax::codemap::{DUMMY_SP, Span};
@@ -1109,6 +1111,33 @@
         &self.options
     }
 
+    /// Output graphviz dot file.
+    pub fn emit_ir_graphviz(&self, path: String) -> io::Result<()> {
+        let file = try!(File::create(path));
+        let mut dot_file = io::BufWriter::new(file);
+        writeln!(&mut dot_file, "digraph {{")?;
+
+        let mut err: Option<io::Result<_>> = None;
+
+        for (id, item) in self.items() {
+            writeln!(&mut dot_file, "{} {};", id.0, item.dot_attributes(self))?;
+
+            item.trace(self, &mut |sub_id: ItemId, _edge_kind| {
+                match writeln!(&mut dot_file, "{} -> {};", id.0, sub_id.as_usize()) {
+                    Ok(_) => {},
+                    Err(e) => err = Some(Err(e)),
+                }
+            }, &());
+
+            if err.is_some() {
+                return err.unwrap();
+            }
+        }
+
+        writeln!(&mut dot_file, "}}")?;
+        Ok(())
+    }
+
     /// Tokenizes a namespace cursor in order to get the name and kind of the
     /// namespace,
     fn tokenize_namespace(&self,
diff --git a/src/ir/item.rs b/src/ir/item.rs
index 5b78551..8f16a96 100644
--- a/src/ir/item.rs
+++ b/src/ir/item.rs
@@ -372,6 +372,20 @@
         self.id
     }
 
+    /// Get this `Item`'s dot attributes.
+    pub fn dot_attributes(&self, ctx: &BindgenContext) -> String {
+        format!("[fontname=\"courier\", label=< \
+                 <table border=\"0\"> \
+                 <tr><td>ItemId({})</td></tr> \
+                 <tr><td>name</td><td>{}</td></tr> \
+                 <tr><td>kind</td><td>{}</td></tr> \
+                 </table> \
+                 >]",
+                self.id.as_usize(),
+                self.name(ctx).get(),
+                self.kind.kind_name())
+    }
+
     /// Get this `Item`'s parent's identifier.
     ///
     /// For the root module, the parent's ID is its own ID.
diff --git a/src/ir/item_kind.rs b/src/ir/item_kind.rs
index d9e4690..3ff0673 100644
--- a/src/ir/item_kind.rs
+++ b/src/ir/item_kind.rs
@@ -32,6 +32,16 @@
         }
     }
 
+    /// Transform our `ItemKind` into a string.
+    pub fn kind_name(&self) -> &'static str {
+        match *self {
+            ItemKind::Module(..) => "Module",
+            ItemKind::Type(..) => "Type",
+            ItemKind::Function(..) => "Function",
+            ItemKind::Var(..) => "Var"
+        }        
+    }
+
     /// Is this a module?
     pub fn is_module(&self) -> bool {
         self.as_module().is_some()
diff --git a/src/lib.rs b/src/lib.rs
index 7bf9806..42363eb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -175,6 +175,13 @@
         self
     }
 
+    /// Set the output graphviz file.
+    pub fn emit_ir_graphviz<T: Into<String>>(mut self, path: T) -> Builder {
+        let path = path.into();
+        self.options.emit_ir_graphviz = Some(path);
+        self
+    }
+
     /// Whether the generated bindings should contain documentation comments or
     /// not.
     ///
@@ -491,6 +498,9 @@
     /// True if we should dump our internal IR for debugging purposes.
     pub emit_ir: bool,
 
+    /// Output graphviz dot file.
+    pub emit_ir_graphviz: Option<String>,
+
     /// True if we should emulate C++ namespaces with Rust modules in the
     /// generated bindings.
     pub enable_cxx_namespaces: bool,
@@ -595,6 +605,7 @@
             links: vec![],
             emit_ast: false,
             emit_ir: false,
+            emit_ir_graphviz: None,
             derive_debug: true,
             derive_default: false,
             enable_cxx_namespaces: false,
diff --git a/src/options.rs b/src/options.rs
index 49bad84..e54ee01 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -84,6 +84,11 @@
             Arg::with_name("emit-ir")
                 .long("emit-ir")
                 .help("Output our internal IR for debugging purposes."),
+            Arg::with_name("emit-ir-graphviz")
+                .long("emit-ir-graphviz")
+                .help("Dump graphviz dot file.")
+                .value_name("path")
+                .takes_value(true),
             Arg::with_name("enable-cxx-namespaces")
                 .long("enable-cxx-namespaces")
                 .help("Enable support for C++ namespaces."),
@@ -270,6 +275,10 @@
         builder = builder.emit_ir();
     }
 
+    if let Some(path) = matches.value_of("emit-ir-graphviz") {
+        builder = builder.emit_ir_graphviz(path);
+    }
+
     if matches.is_present("enable-cxx-namespaces") {
         builder = builder.enable_cxx_namespaces();
     }