about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2014-05-05 05:36:37 -0700
committerbors <bors@rust-lang.org>2014-05-05 05:36:37 -0700
commitecc18f34446cdecd42004be62805f84799733e72 (patch)
tree9812edf2ce8e24e2b772a45e0dc582c2487236d1
parent8895f252417495b423796807ba180eb59da5a1c1 (diff)
parent67307d4e0864fa9a7de42e1d8bd55f67f251520b (diff)
downloadrust-ecc18f34446cdecd42004be62805f84799733e72.tar.gz
rust-ecc18f34446cdecd42004be62805f84799733e72.zip
auto merge of #13749 : pnkfelix/rust/add-libgraphviz-crate, r=alexcrichton
Add a `graphviz` crate for making .dot files to layout and render graphs.

(This is a precursor to other work to render control-flow graphs from within rustc itself; but this crate should be independently usable, since it abstracts over the client's graph-representation and labeling method.)
-rw-r--r--mk/crates.mk3
-rw-r--r--src/libgraphviz/lib.rs755
-rw-r--r--src/libgraphviz/maybe_owned_vec.rs132
3 files changed, 889 insertions, 1 deletions
diff --git a/mk/crates.mk b/mk/crates.mk
index fc0afa6df62..bc8567d3c95 100644
--- a/mk/crates.mk
+++ b/mk/crates.mk
@@ -51,7 +51,7 @@
 
 TARGET_CRATES := libc std green rustuv native flate arena glob term semver \
                  uuid serialize sync getopts collections num test time rand \
-		 workcache url log regex
+		 workcache url log regex graphviz
 HOST_CRATES := syntax rustc rustdoc fourcc hexfloat regex_macros
 CRATES := $(TARGET_CRATES) $(HOST_CRATES)
 TOOLS := compiletest rustdoc rustc
@@ -67,6 +67,7 @@ DEPS_rustdoc := rustc native:sundown serialize sync getopts collections \
                 test time
 DEPS_flate := std native:miniz
 DEPS_arena := std collections
+DEPS_graphviz := std
 DEPS_glob := std
 DEPS_serialize := std collections log
 DEPS_term := std collections
diff --git a/src/libgraphviz/lib.rs b/src/libgraphviz/lib.rs
new file mode 100644
index 00000000000..afe29ea2e07
--- /dev/null
+++ b/src/libgraphviz/lib.rs
@@ -0,0 +1,755 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+/*! Generate files suitable for use with [Graphviz](http://www.graphviz.org/)
+
+The `render` function generates output (e.g. an `output.dot` file) for
+use with [Graphviz](http://www.graphviz.org/) by walking a labelled
+graph. (Graphviz can then automatically lay out the nodes and edges
+of the graph, and also optionally render the graph as an image or
+other [output formats](
+http://www.graphviz.org/content/output-formats), such as SVG.)
+
+Rather than impose some particular graph data structure on clients,
+this library exposes two traits that clients can implement on their
+own structs before handing them over to the rendering function.
+
+Note: This library does not yet provide access to the full
+expressiveness of the [DOT language](
+http://www.graphviz.org/doc/info/lang.html). For example, there are
+many [attributes](http://www.graphviz.org/content/attrs) related to
+providing layout hints (e.g. left-to-right versus top-down, which
+algorithm to use, etc). The current intention of this library is to
+emit a human-readable .dot file with very regular structure suitable
+for easy post-processing.
+
+# Examples
+
+The first example uses a very simple graph representation: a list of
+pairs of ints, representing the edges (the node set is implicit).
+Each node label is derived directly from the int representing the node,
+while the edge labels are all empty strings.
+
+This example also illustrates how to use `MaybeOwnedVector` to return
+an owned vector or a borrowed slice as appropriate: we construct the
+node vector from scratch, but borrow the edge list (rather than
+constructing a copy of all the edges from scratch).
+
+The output from this example renders five nodes, with the first four
+forming a diamond-shaped acyclic graph and then pointing to the fifth
+which is cyclic.
+
+```rust
+use dot = graphviz;
+use graphviz::maybe_owned_vec::IntoMaybeOwnedVector;
+
+type Nd = int;
+type Ed = (int,int);
+struct Edges(Vec<Ed>);
+
+pub fn main() {
+    use std::io::File;
+    let edges = Edges(vec!((0,1), (0,2), (1,3), (2,3), (3,4), (4,4)));
+    let mut f = File::create(&Path::new("example1.dot"));
+    dot::render(&edges, &mut f).unwrap()
+}
+
+impl<'a> dot::Labeller<'a, Nd, Ed> for Edges {
+    fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example1") }
+
+    fn node_id(&'a self, n: &Nd) -> dot::Id<'a> {
+        dot::Id::new(format!("N{}", *n))
+    }
+}
+
+impl<'a> dot::GraphWalk<'a, Nd, Ed> for Edges {
+    fn nodes(&self) -> dot::Nodes<'a,Nd> {
+        // (assumes that |N| \approxeq |E|)
+        let &Edges(ref v) = self;
+        let mut nodes = Vec::with_capacity(v.len());
+        for &(s,t) in v.iter() {
+            nodes.push(s); nodes.push(t);
+        }
+        nodes.sort();
+        nodes.dedup();
+        nodes.into_maybe_owned()
+    }
+
+    fn edges(&'a self) -> dot::Edges<'a,Ed> {
+        let &Edges(ref edges) = self;
+        edges.as_slice().into_maybe_owned()
+    }
+
+    fn source(&self, e: &Ed) -> Nd { let &(s,_) = e; s }
+
+    fn target(&self, e: &Ed) -> Nd { let &(_,t) = e; t }
+}
+```
+
+Output from first example (in `example1.dot`):
+
+```ignore
+digraph example1 {
+    N0[label="N0"];
+    N1[label="N1"];
+    N2[label="N2"];
+    N3[label="N3"];
+    N4[label="N4"];
+    N0 -> N1[label=""];
+    N0 -> N2[label=""];
+    N1 -> N3[label=""];
+    N2 -> N3[label=""];
+    N3 -> N4[label=""];
+    N4 -> N4[label=""];
+}
+```
+
+The second example illustrates using `node_label` and `edge_label` to
+add labels to the nodes and edges in the rendered graph. The graph
+here carries both `nodes` (the label text to use for rendering a
+particular node), and `edges` (again a list of `(source,target)`
+indices).
+
+This example also illustrates how to use a type (in this case the edge
+type) that shares substructure with the graph: the edge type here is a
+direct reference to the `(source,target)` pair stored in the graph's
+internal vector (rather than passing around a copy of the pair
+itself). Note that this implies that `fn edges(&'a self)` must
+construct a fresh `Vec<&'a (uint,uint)>` from the `Vec<(uint,uint)>`
+edges stored in `self`.
+
+Since both the set of nodes and the set of edges are always
+constructed from scratch via iterators, we use the `collect()` method
+from the `Iterator` trait to collect the nodes and edges into freshly
+constructed growable `Vec` values (rather use the `into_maybe_owned`
+from the `IntoMaybeOwnedVector` trait as was used in the first example
+above).
+
+The output from this example renders four nodes that make up the
+Hasse-diagram for the subsets of the set `{x, y}`. Each edge is
+labelled with the &sube; character (specified using the HTML character
+entity `&sube`).
+
+```rust
+use dot = graphviz;
+use std::str;
+use std::io::File;
+
+type Nd = uint;
+type Ed<'a> = &'a (uint, uint);
+struct Graph { nodes: Vec<&'static str>, edges: Vec<(uint,uint)> }
+
+pub fn main() {
+    let nodes = vec!("{x,y}","{x}","{y}","{}");
+    let edges = vec!((0,1), (0,2), (1,3), (2,3));
+    let graph = Graph { nodes: nodes, edges: edges };
+
+    let mut f = File::create(&Path::new("example2.dot"));
+    dot::render(&graph, &mut f).unwrap()
+}
+
+impl<'a> dot::Labeller<'a, Nd, Ed<'a>> for Graph {
+    fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example2") }
+    fn node_id(&'a self, n: &Nd) -> dot::Id<'a> {
+        dot::Id::new(format!("N{}", n))
+    }
+    fn node_label<'a>(&'a self, n: &Nd) -> dot::LabelText<'a> {
+        dot::LabelStr(str::Slice(self.nodes.get(*n).as_slice()))
+    }
+    fn edge_label<'a>(&'a self, _: &Ed) -> dot::LabelText<'a> {
+        dot::LabelStr(str::Slice("&sube;"))
+    }
+}
+
+impl<'a> dot::GraphWalk<'a, Nd, Ed<'a>> for Graph {
+    fn nodes(&self) -> dot::Nodes<'a,Nd> { range(0,self.nodes.len()).collect() }
+    fn edges(&'a self) -> dot::Edges<'a,Ed<'a>> { self.edges.iter().collect() }
+    fn source(&self, e: &Ed) -> Nd { let & &(s,_) = e; s }
+    fn target(&self, e: &Ed) -> Nd { let & &(_,t) = e; t }
+}
+```
+
+The third example is similar to the second, except now each node and
+edge now carries a reference to the string label for each node as well
+as that node's index. (This is another illustration of how to share
+structure with the graph itself, and why one might want to do so.)
+
+The output from this example is the same as the second example: the
+Hasse-diagram for the subsets of the set `{x, y}`.
+
+```rust
+use dot = graphviz;
+use std::str;
+use std::io::File;
+
+type Nd<'a> = (uint, &'a str);
+type Ed<'a> = (Nd<'a>, Nd<'a>);
+struct Graph { nodes: Vec<&'static str>, edges: Vec<(uint,uint)> }
+
+pub fn main() {
+    let nodes = vec!("{x,y}","{x}","{y}","{}");
+    let edges = vec!((0,1), (0,2), (1,3), (2,3));
+    let graph = Graph { nodes: nodes, edges: edges };
+
+    let mut f = File::create(&Path::new("example3.dot"));
+    dot::render(&graph, &mut f).unwrap()
+}
+
+impl<'a> dot::Labeller<'a, Nd<'a>, Ed<'a>> for Graph {
+    fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example3") }
+    fn node_id(&'a self, n: &Nd<'a>) -> dot::Id<'a> {
+        dot::Id::new(format!("N{:u}", n.val0()))
+    }
+    fn node_label<'a>(&'a self, n: &Nd<'a>) -> dot::LabelText<'a> {
+        let &(i, _) = n;
+        dot::LabelStr(str::Slice(self.nodes.get(i).as_slice()))
+    }
+    fn edge_label<'a>(&'a self, _: &Ed<'a>) -> dot::LabelText<'a> {
+        dot::LabelStr(str::Slice("&sube;"))
+    }
+}
+
+impl<'a> dot::GraphWalk<'a, Nd<'a>, Ed<'a>> for Graph {
+    fn nodes(&'a self) -> dot::Nodes<'a,Nd<'a>> {
+        self.nodes.iter().map(|s|s.as_slice()).enumerate().collect()
+    }
+    fn edges(&'a self) -> dot::Edges<'a,Ed<'a>> {
+        self.edges.iter()
+            .map(|&(i,j)|((i, self.nodes.get(i).as_slice()),
+                          (j, self.nodes.get(j).as_slice())))
+            .collect()
+    }
+    fn source(&self, e: &Ed<'a>) -> Nd<'a> { let &(s,_) = e; s }
+    fn target(&self, e: &Ed<'a>) -> Nd<'a> { let &(_,t) = e; t }
+}
+```
+
+# References
+
+* [Graphviz](http://www.graphviz.org/)
+
+* [DOT language](http://www.graphviz.org/doc/info/lang.html)
+
+*/
+
+#![crate_id = "graphviz#0.11-pre"]
+#![crate_type = "rlib"]
+#![crate_type = "dylib"]
+#![license = "MIT/ASL2"]
+#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
+       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
+       html_root_url = "http://static.rust-lang.org/doc/master")]
+
+#![experimental]
+
+use std::io;
+use std::str;
+use self::maybe_owned_vec::MaybeOwnedVector;
+
+pub mod maybe_owned_vec;
+
+/// The text for a graphviz label on a node or edge.
+pub enum LabelText<'a> {
+    /// This kind of label preserves the text directly as is.
+    ///
+    /// Occurrences of backslashes (`\`) are escaped, and thus appear
+    /// as backslashes in the rendered label.
+    LabelStr(str::MaybeOwned<'a>),
+
+    /// This kind of label uses the graphviz label escString type:
+    /// http://www.graphviz.org/content/attrs#kescString
+    ///
+    /// Occurrences of backslashes (`\`) are not escaped; instead they
+    /// are interpreted as initiating an escString escape sequence.
+    ///
+    /// Escape sequences of particular interest: in addition to `\n`
+    /// to break a line (centering the line preceding the `\n`), there
+    /// are also the escape sequences `\l` which left-justifies the
+    /// preceding line and `\r` which right-justifies it.
+    EscStr(str::MaybeOwned<'a>),
+}
+
+// There is a tension in the design of the labelling API.
+//
+// For example, I considered making a `Labeller<T>` trait that
+// provides labels for `T`, and then making the graph type `G`
+// implement `Labeller<Node>` and `Labeller<Edge>`. However, this is
+// not possible without functional dependencies. (One could work
+// around that, but I did not explore that avenue heavily.)
+//
+// Another approach that I actually used for a while was to make a
+// `Label<Context>` trait that is implemented by the client-specific
+// Node and Edge types (as well as an implementation on Graph itself
+// for the overall name for the graph). The main disadvantage of this
+// second approach (compared to having the `G` type parameter
+// implement a Labelling service) that I have encountered is that it
+// makes it impossible to use types outside of the current crate
+// directly as Nodes/Edges; you need to wrap them in newtype'd
+// structs. See e.g. the `No` and `Ed` structs in the examples. (In
+// practice clients using a graph in some other crate would need to
+// provide some sort of adapter shim over the graph anyway to
+// interface with this library).
+//
+// Another approach would be to make a single `Labeller<N,E>` trait
+// that provides three methods (graph_label, node_label, edge_label),
+// and then make `G` implement `Labeller<N,E>`. At first this did not
+// appeal to me, since I had thought I would need separate methods on
+// each data variant for dot-internal identifiers versus user-visible
+// labels. However, the identifier/label distinction only arises for
+// nodes; graphs themselves only have identifiers, and edges only have
+// labels.
+//
+// So in the end I decided to use the third approach described above.
+
+/// `Id` is a Graphviz `ID`.
+pub struct Id<'a> {
+    name: str::MaybeOwned<'a>,
+}
+
+impl<'a> Id<'a> {
+    /// Creates an `Id` named `name`.
+    ///
+    /// The caller must ensure that the input conforms to an
+    /// identifier format: it must be a non-empty string made up of
+    /// alphanumeric or underscore characters, not beginning with a
+    /// digit (i.e. the regular expression `[a-zA-Z_][a-zA-Z_0-9]*`).
+    ///
+    /// (Note: this format is a strict subset of the `ID` format
+    /// defined by the DOT language.  This function may change in the
+    /// future to accept a broader subset, or the entirety, of DOT's
+    /// `ID` format.)
+    pub fn new<Name:str::IntoMaybeOwned<'a>>(name: Name) -> Id<'a> {
+        let name = name.into_maybe_owned();
+        {
+            let mut chars = name.as_slice().chars();
+            assert!(is_letter_or_underscore(chars.next().unwrap()));
+            assert!(chars.all(is_constituent));
+        }
+        return Id{ name: name };
+
+        fn is_letter_or_underscore(c: char) -> bool {
+            in_range('a', c, 'z') || in_range('A', c, 'Z') || c == '_'
+        }
+        fn is_constituent(c: char) -> bool {
+            is_letter_or_underscore(c) || in_range('0', c, '9')
+        }
+        fn in_range(low: char, c: char, high: char) -> bool {
+            low as uint <= c as uint && c as uint <= high as uint
+        }
+    }
+
+    pub fn as_slice(&'a self) -> &'a str {
+        self.name.as_slice()
+    }
+
+    pub fn name(self) -> str::MaybeOwned<'a> {
+        self.name
+    }
+}
+
+/// Each instance of a type that implements `Label<C>` maps to a
+/// unique identifier with respect to `C`, which is used to identify
+/// it in the generated .dot file. They can also provide more
+/// elaborate (and non-unique) label text that is used in the graphviz
+/// rendered output.
+
+/// The graph instance is responsible for providing the DOT compatible
+/// identifiers for the nodes and (optionally) rendered labels for the nodes and
+/// edges, as well as an identifier for the graph itself.
+pub trait Labeller<'a,N,E> {
+    /// Must return a DOT compatible identifier naming the graph.
+    fn graph_id(&'a self) -> Id<'a>;
+
+    /// Maps `n` to a unique identifier with respect to `self`. The
+    /// implementor is responsible for ensuring that the returned name
+    /// is a valid DOT identifier.
+    fn node_id(&'a self, n: &N) -> Id<'a>;
+
+    /// Maps `n` to a label that will be used in the rendered output.
+    /// The label need not be unique, and may be the empty string; the
+    /// default is just the output from `node_id`.
+    fn node_label(&'a self, n: &N) -> LabelText<'a> {
+        LabelStr(self.node_id(n).name)
+    }
+
+    /// Maps `e` to a label that will be used in the rendered output.
+    /// The label need not be unique, and may be the empty string; the
+    /// default is in fact the empty string.
+    fn edge_label(&'a self, e: &E) -> LabelText<'a> {
+        let _ignored = e;
+        LabelStr(str::Slice(""))
+    }
+}
+
+impl<'a> LabelText<'a> {
+    fn escape_char(c: char, f: |char|) {
+        match c {
+            // not escaping \\, since Graphviz escString needs to
+            // interpret backslashes; see EscStr above.
+            '\\' => f(c),
+            _ => c.escape_default(f)
+        }
+    }
+    fn escape_str(s: &str) -> StrBuf {
+        let mut out = StrBuf::with_capacity(s.len());
+        for c in s.chars() {
+            LabelText::escape_char(c, |c| out.push_char(c));
+        }
+        out
+    }
+
+    /// Renders text as string suitable for a label in a .dot file.
+    pub fn escape(&self) -> ~str {
+        match self {
+            &LabelStr(ref s) => s.as_slice().escape_default(),
+            &EscStr(ref s) => LabelText::escape_str(s.as_slice()).into_owned(),
+        }
+    }
+}
+
+pub type Nodes<'a,N> = MaybeOwnedVector<'a,N>;
+pub type Edges<'a,E> = MaybeOwnedVector<'a,E>;
+
+// (The type parameters in GraphWalk should be associated items,
+// when/if Rust supports such.)
+
+/// GraphWalk is an abstraction over a directed graph = (nodes,edges)
+/// made up of node handles `N` and edge handles `E`, where each `E`
+/// can be mapped to its source and target nodes.
+///
+/// The lifetime parameter `'a` is exposed in this trait (rather than
+/// introduced as a generic parameter on each method declaration) so
+/// that a client impl can choose `N` and `E` that have substructure
+/// that is bound by the self lifetime `'a`.
+///
+/// The `nodes` and `edges` method each return instantiations of
+/// `MaybeOwnedVector` to leave implementors the freedom to create
+/// entirely new vectors or to pass back slices into internally owned
+/// vectors.
+pub trait GraphWalk<'a, N, E> {
+    /// Returns all the nodes in this graph.
+    fn nodes(&'a self) -> Nodes<'a, N>;
+    /// Returns all of the edges in this graph.
+    fn edges(&'a self) -> Edges<'a, E>;
+    /// The source node for `edge`.
+    fn source(&'a self, edge: &E) -> N;
+    /// The target node for `edge`.
+    fn target(&'a self, edge: &E) -> N;
+}
+
+/// Renders directed graph `g` into the writer `w` in DOT syntax.
+/// (Main entry point for the library.)
+pub fn render<'a, N, E, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, W:Writer>(
+              g: &'a G,
+              w: &mut W) -> io::IoResult<()>
+{
+    fn writeln<W:Writer>(w: &mut W, arg: &[&str]) -> io::IoResult<()> {
+        for &s in arg.iter() { try!(w.write_str(s)); }
+        w.write_char('\n')
+    }
+
+    fn indent<W:Writer>(w: &mut W) -> io::IoResult<()> {
+        w.write_str("    ")
+    }
+
+    try!(writeln(w, ["digraph ", g.graph_id().as_slice(), " {"]));
+    for n in g.nodes().iter() {
+        try!(indent(w));
+        let id = g.node_id(n);
+        let escaped = g.node_label(n).escape();
+        try!(writeln(w, [id.as_slice(),
+                         "[label=\"", escaped.as_slice(), "\"];"]));
+    }
+
+    for e in g.edges().iter() {
+        let escaped_label = g.edge_label(e).escape();
+        try!(indent(w));
+        let source = g.source(e);
+        let target = g.target(e);
+        let source_id = g.node_id(&source);
+        let target_id = g.node_id(&target);
+        try!(writeln(w, [source_id.as_slice(), " -> ", target_id.as_slice(),
+                         "[label=\"", escaped_label.as_slice(), "\"];"]));
+    }
+
+    writeln(w, ["}"])
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{Id, LabelText, LabelStr, EscStr, Labeller};
+    use super::{Nodes, Edges, GraphWalk, render};
+    use std::io::{MemWriter, BufReader, IoResult};
+    use std::str;
+
+    /// each node is an index in a vector in the graph.
+    type Node = uint;
+    struct Edge {
+        from: uint, to: uint, label: &'static str
+    }
+
+    fn Edge(from: uint, to: uint, label: &'static str) -> Edge {
+        Edge { from: from, to: to, label: label }
+    }
+
+    struct LabelledGraph {
+        /// The name for this graph. Used for labelling generated `digraph`.
+        name: &'static str,
+
+        /// Each node is an index into `node_labels`; these labels are
+        /// used as the label text for each node. (The node *names*,
+        /// which are unique identifiers, are derived from their index
+        /// in this array.)
+        ///
+        /// If a node maps to None here, then just use its name as its
+        /// text.
+        node_labels: Vec<Option<&'static str>>,
+
+        /// Each edge relates a from-index to a to-index along with a
+        /// label; `edges` collects them.
+        edges: Vec<Edge>,
+    }
+
+    // A simple wrapper around LabelledGraph that forces the labels to
+    // be emitted as EscStr.
+    struct LabelledGraphWithEscStrs {
+        graph: LabelledGraph
+    }
+
+    enum NodeLabels<L> {
+        AllNodesLabelled(Vec<L>),
+        UnlabelledNodes(uint),
+        SomeNodesLabelled(Vec<Option<L>>),
+    }
+
+    type Trivial = NodeLabels<&'static str>;
+
+    impl NodeLabels<&'static str> {
+        fn to_opt_strs(self) -> Vec<Option<&'static str>> {
+            match self {
+                UnlabelledNodes(len)
+                    => Vec::from_elem(len, None).move_iter().collect(),
+                AllNodesLabelled(lbls)
+                    => lbls.move_iter().map(
+                        |l|Some(l)).collect(),
+                SomeNodesLabelled(lbls)
+                    => lbls.move_iter().collect(),
+            }
+        }
+    }
+
+    impl LabelledGraph {
+        fn new(name: &'static str,
+               node_labels: Trivial,
+               edges: Vec<Edge>) -> LabelledGraph {
+            LabelledGraph {
+                name: name,
+                node_labels: node_labels.to_opt_strs(),
+                edges: edges
+            }
+        }
+    }
+
+    impl LabelledGraphWithEscStrs {
+        fn new(name: &'static str,
+               node_labels: Trivial,
+               edges: Vec<Edge>) -> LabelledGraphWithEscStrs {
+            LabelledGraphWithEscStrs {
+                graph: LabelledGraph::new(name, node_labels, edges)
+            }
+        }
+    }
+
+    fn id_name<'a>(n: &Node) -> Id<'a> {
+        Id::new(format!("N{:u}", *n))
+    }
+
+    impl<'a> Labeller<'a, Node, &'a Edge> for LabelledGraph {
+        fn graph_id(&'a self) -> Id<'a> {
+            Id::new(self.name.as_slice())
+        }
+        fn node_id(&'a self, n: &Node) -> Id<'a> {
+            id_name(n)
+        }
+        fn node_label(&'a self, n: &Node) -> LabelText<'a> {
+            match self.node_labels.get(*n) {
+                &Some(ref l) => LabelStr(str::Slice(l.as_slice())),
+                &None        => LabelStr(id_name(n).name()),
+            }
+        }
+        fn edge_label(&'a self, e: & &'a Edge) -> LabelText<'a> {
+            LabelStr(str::Slice(e.label.as_slice()))
+        }
+    }
+
+    impl<'a> Labeller<'a, Node, &'a Edge> for LabelledGraphWithEscStrs {
+        fn graph_id(&'a self) -> Id<'a> { self.graph.graph_id() }
+        fn node_id(&'a self, n: &Node) -> Id<'a> { self.graph.node_id(n) }
+        fn node_label(&'a self, n: &Node) -> LabelText<'a> {
+            match self.graph.node_label(n) {
+                LabelStr(s) | EscStr(s) => EscStr(s),
+            }
+        }
+        fn edge_label(&'a self, e: & &'a Edge) -> LabelText<'a> {
+            match self.graph.edge_label(e) {
+                LabelStr(s) | EscStr(s) => EscStr(s),
+            }
+        }
+    }
+
+    impl<'a> GraphWalk<'a, Node, &'a Edge> for LabelledGraph {
+        fn nodes(&'a self) -> Nodes<'a,Node> {
+            range(0u, self.node_labels.len()).collect()
+        }
+        fn edges(&'a self) -> Edges<'a,&'a Edge> {
+            self.edges.iter().collect()
+        }
+        fn source(&'a self, edge: & &'a Edge) -> Node {
+            edge.from
+        }
+        fn target(&'a self, edge: & &'a Edge) -> Node {
+            edge.to
+        }
+    }
+
+    impl<'a> GraphWalk<'a, Node, &'a Edge> for LabelledGraphWithEscStrs {
+        fn nodes(&'a self) -> Nodes<'a,Node> {
+            self.graph.nodes()
+        }
+        fn edges(&'a self) -> Edges<'a,&'a Edge> {
+            self.graph.edges()
+        }
+        fn source(&'a self, edge: & &'a Edge) -> Node {
+            edge.from
+        }
+        fn target(&'a self, edge: & &'a Edge) -> Node {
+            edge.to
+        }
+    }
+
+    fn test_input(g: LabelledGraph) -> IoResult<~str> {
+        let mut writer = MemWriter::new();
+        render(&g, &mut writer).unwrap();
+        let mut r = BufReader::new(writer.get_ref());
+        r.read_to_str()
+    }
+
+    // All of the tests use raw-strings as the format for the expected outputs,
+    // so that you can cut-and-paste the content into a .dot file yourself to
+    // see what the graphviz visualizer would produce.
+
+    #[test]
+    fn empty_graph() {
+        let labels : Trivial = UnlabelledNodes(0);
+        let r = test_input(LabelledGraph::new("empty_graph", labels, vec!()));
+        assert_eq!(r.unwrap().as_slice(),
+r#"digraph empty_graph {
+}
+"#);
+    }
+
+    #[test]
+    fn single_node() {
+        let labels : Trivial = UnlabelledNodes(1);
+        let r = test_input(LabelledGraph::new("single_node", labels, vec!()));
+        assert_eq!(r.unwrap().as_slice(),
+r#"digraph single_node {
+    N0[label="N0"];
+}
+"#);
+    }
+
+    #[test]
+    fn single_edge() {
+        let labels : Trivial = UnlabelledNodes(2);
+        let result = test_input(LabelledGraph::new("single_edge", labels,
+                                                   vec!(Edge(0, 1, "E"))));
+        assert_eq!(result.unwrap().as_slice(),
+r#"digraph single_edge {
+    N0[label="N0"];
+    N1[label="N1"];
+    N0 -> N1[label="E"];
+}
+"#);
+    }
+
+    #[test]
+    fn single_cyclic_node() {
+        let labels : Trivial = UnlabelledNodes(1);
+        let r = test_input(LabelledGraph::new("single_cyclic_node", labels,
+                                              vec!(Edge(0, 0, "E"))));
+        assert_eq!(r.unwrap().as_slice(),
+r#"digraph single_cyclic_node {
+    N0[label="N0"];
+    N0 -> N0[label="E"];
+}
+"#);
+    }
+
+    #[test]
+    fn hasse_diagram() {
+        let labels = AllNodesLabelled(vec!("{x,y}", "{x}", "{y}", "{}"));
+        let r = test_input(LabelledGraph::new(
+            "hasse_diagram", labels,
+            vec!(Edge(0, 1, ""), Edge(0, 2, ""),
+                 Edge(1, 3, ""), Edge(2, 3, ""))));
+        assert_eq!(r.unwrap().as_slice(),
+r#"digraph hasse_diagram {
+    N0[label="{x,y}"];
+    N1[label="{x}"];
+    N2[label="{y}"];
+    N3[label="{}"];
+    N0 -> N1[label=""];
+    N0 -> N2[label=""];
+    N1 -> N3[label=""];
+    N2 -> N3[label=""];
+}
+"#);
+    }
+
+    #[test]
+    fn left_aligned_text() {
+        let labels = AllNodesLabelled(vec!(
+            "if test {\
+           \\l    branch1\
+           \\l} else {\
+           \\l    branch2\
+           \\l}\
+           \\lafterward\
+           \\l",
+            "branch1",
+            "branch2",
+            "afterward"));
+
+        let mut writer = MemWriter::new();
+
+        let g = LabelledGraphWithEscStrs::new(
+            "syntax_tree", labels,
+            vec!(Edge(0, 1, "then"), Edge(0, 2, "else"),
+                 Edge(1, 3, ";"),    Edge(2, 3, ";"   )));
+
+        render(&g, &mut writer).unwrap();
+        let mut r = BufReader::new(writer.get_ref());
+        let r = r.read_to_str();
+
+        assert_eq!(r.unwrap().as_slice(),
+r#"digraph syntax_tree {
+    N0[label="if test {\l    branch1\l} else {\l    branch2\l}\lafterward\l"];
+    N1[label="branch1"];
+    N2[label="branch2"];
+    N3[label="afterward"];
+    N0 -> N1[label="then"];
+    N0 -> N2[label="else"];
+    N1 -> N3[label=";"];
+    N2 -> N3[label=";"];
+}
+"#);
+    }
+}
diff --git a/src/libgraphviz/maybe_owned_vec.rs b/src/libgraphviz/maybe_owned_vec.rs
new file mode 100644
index 00000000000..649bad702ea
--- /dev/null
+++ b/src/libgraphviz/maybe_owned_vec.rs
@@ -0,0 +1,132 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::fmt;
+use std::iter::FromIterator;
+use std::slice;
+
+// Note 1: It is not clear whether the flexibility of providing both
+// the `Growable` and `FixedLen` variants is sufficiently useful.
+// Consider restricting to just a two variant enum.
+
+// Note 2: Once Dynamically Sized Types (DST) lands, it might be
+// reasonable to replace this with something like `enum MaybeOwned<'a,
+// Sized? U>{ Owned(~U), Borrowed(&'a U) }`; and then `U` could be
+// instantiated with `[T]` or `str`, etc.  Of course, that would imply
+// removing the `Growable` variant, which relates to note 1 above.
+// Alternatively, we might add `MaybeOwned` for the general case but
+// keep some form of `MaybeOwnedVector` to avoid unnecessary copying
+// of the contents of `Vec<T>`, since we anticipate that to be a
+// frequent way to dynamically construct a vector.
+
+/// MaybeOwnedVector<'a,T> abstracts over `Vec<T>`, `~[T]`, `&'a [T]`.
+///
+/// Some clients will have a pre-allocated vector ready to hand off in
+/// a slice; others will want to create the set on the fly and hand
+/// off ownership, via either `Growable` or `FixedLen` depending on
+/// which kind of vector they have constucted.  (The `FixedLen`
+/// variant is provided for interoperability with `std::slice` methods
+/// that return `~[T]`.)
+pub enum MaybeOwnedVector<'a,T> {
+    Growable(Vec<T>),
+    FixedLen(~[T]),
+    Borrowed(&'a [T]),
+}
+
+/// Trait for moving into a `MaybeOwnedVector`
+pub trait IntoMaybeOwnedVector<'a,T> {
+    /// Moves self into a `MaybeOwnedVector`
+    fn into_maybe_owned(self) -> MaybeOwnedVector<'a,T>;
+}
+
+impl<'a,T> IntoMaybeOwnedVector<'a,T> for Vec<T> {
+    #[inline]
+    fn into_maybe_owned(self) -> MaybeOwnedVector<'a,T> { Growable(self) }
+}
+
+impl<'a,T> IntoMaybeOwnedVector<'a,T> for ~[T] {
+    #[inline]
+    fn into_maybe_owned(self) -> MaybeOwnedVector<'a,T> { FixedLen(self) }
+}
+
+impl<'a,T> IntoMaybeOwnedVector<'a,T> for &'a [T] {
+    #[inline]
+    fn into_maybe_owned(self) -> MaybeOwnedVector<'a,T> { Borrowed(self) }
+}
+
+impl<'a,T> MaybeOwnedVector<'a,T> {
+    pub fn iter(&'a self) -> slice::Items<'a,T> {
+        match self {
+            &Growable(ref v) => v.iter(),
+            &FixedLen(ref v) => v.iter(),
+            &Borrowed(ref v) => v.iter(),
+        }
+    }
+}
+
+// The `Vector` trait is provided in the prelude and is implemented on
+// both `&'a [T]` and `Vec<T>`, so it makes sense to try to support it
+// seamlessly.  The other vector related traits from the prelude do
+// not appear to be implemented on both `&'a [T]` and `Vec<T>`.  (It
+// is possible that this is an oversight in some cases.)
+//
+// In any case, with `Vector` in place, the client can just use
+// `as_slice` if they prefer that over `match`.
+
+impl<'b,T> slice::Vector<T> for MaybeOwnedVector<'b,T> {
+    fn as_slice<'a>(&'a self) -> &'a [T] {
+        match self {
+            &Growable(ref v) => v.as_slice(),
+            &FixedLen(ref v) => v.as_slice(),
+            &Borrowed(ref v) => v.as_slice(),
+        }
+    }
+}
+
+impl<'a,T> FromIterator<T> for MaybeOwnedVector<'a,T> {
+    fn from_iter<I:Iterator<T>>(iterator: I) -> MaybeOwnedVector<T> {
+        // If we are building from scratch, might as well build the
+        // most flexible variant.
+        Growable(FromIterator::from_iter(iterator))
+    }
+}
+
+impl<'a,T:fmt::Show> fmt::Show for MaybeOwnedVector<'a,T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.as_slice().fmt(f)
+    }
+}
+
+impl<'a,T:Clone> CloneableVector<T> for MaybeOwnedVector<'a,T> {
+    /// Returns a copy of `self`.
+    fn to_owned(&self) -> ~[T] {
+        self.as_slice().to_owned()
+    }
+
+    /// Convert `self` into an owned slice, not making a copy if possible.
+    fn into_owned(self) -> ~[T] {
+        match self {
+            Growable(v) => v.as_slice().to_owned(),
+            FixedLen(v) => v,
+            Borrowed(v) => v.to_owned(),
+        }
+    }
+}
+
+impl<'a,T:Clone> MaybeOwnedVector<'a,T> {
+    /// Convert `self` into a growable `Vec`, not making a copy if possible.
+    pub fn into_vec(self) -> Vec<T> {
+        match self {
+            Growable(v) => v,
+            FixedLen(v) => Vec::from_slice(v.as_slice()),
+            Borrowed(v) => Vec::from_slice(v),
+        }
+    }
+}