diff --git a/gap/dot.gd b/gap/dot.gd
index 0296444..a684bdb 100644
--- a/gap/dot.gd
+++ b/gap/dot.gd
@@ -52,17 +52,18 @@
#! https://graphviz.readthedocs.io/en/stable/examples.html or
#! https://graphviz.org/Gallery/directed/fsm.html.
#!
-#! @BeginLogSession
+#! @BeginExampleSession
#! gap> LoadPackage("GraphvizForGAP");;
#! gap> f := GraphvizDigraph("finite_state_machine");
#!
-#! gap> GraphvizSetAttr(f, "rankdir=LR");
+#! gap> GraphvizSetAttr(f, "rankdir", "LR");
#!
-#! gap> GraphvizSetAttr(f, "size=\"8,5\"");
+#! gap> GraphvizSetAttr(f, "size", "8,5");
#!
#! gap> terminals := GraphvizAddContext(f, "terminals");
#!
-#! gap> GraphvizSetAttr(terminals, "node [shape=doublecircle]");
+#! gap> GraphvizSetAttrs(terminals, rec(node := rec(shape :=
+#! > "doublecircle")));
#!
#! gap> GraphvizAddNode(terminals, "LR_0");
#!
@@ -74,7 +75,7 @@
#!
#! gap> nodes := GraphvizAddContext(f, "nodes");
#!
-#! gap> GraphvizSetAttr(nodes, "node [shape=circle]");
+#! gap> GraphvizSetAttrs(nodes, rec(node := rec(shape := "circle")));
#!
#! gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_0", "LR_2"), "label", "\"SS(B)\"");
#!
@@ -107,19 +108,18 @@
#! gap> Print(AsString(f), "\n");
#! //dot
#! digraph finite_state_machine {
-#! rankdir=LR size="8,5"
-#! // terminals context
-#! {
-#! node [shape=doublecircle]
+#! graph [rankdir="LR", size="8,5"]
+#! {
+#! // context "terminals"
+#! node [shape="doublecircle"]
#! LR_0
#! LR_3
#! LR_4
#! LR_8
-#! rankdir=LR size="8,5"
-#! }
-#! // nodes context
-#! {
-#! node [shape=circle]
+#! }
+#! {
+#! // context "nodes"
+#! node [shape="circle"]
#! LR_2
#! LR_0 -> LR_2 [label="SS(B)"]
#! LR_1
@@ -139,12 +139,10 @@
#! LR_7 -> LR_5 [label="S(a)"]
#! LR_8 -> LR_6 [label="S(b)"]
#! LR_8 -> LR_5 [label="S(a)"]
-#! rankdir=LR size="8,5"
-#! }
-#!
+#! }
#! }
-#! gap> Splash(f);
-#! @EndLogSession
+#! gap> # Splash(f);
+#! @EndExampleSession
#!
#! Provided that you have &Graphviz; installed on your computer, the last line
#! of the example Splash(f) would produce the following picture:
@@ -370,15 +368,24 @@ DeclareOperation("GraphvizNodes", [IsGraphvizGraphDigraphOrContext]);
#! @EndExampleSession
DeclareOperation("GraphvizNode", [IsGraphvizGraphDigraphOrContext, IsObject]);
-#! @Arguments graph
+#! @Arguments gv
#! @Returns the number of nodes in a &GAPGraphviz; object.
#!
#! @Description This operations returns the number of nodes in the
-#! &GAPGraphviz; graph, digraph, or context graph.
+#! &GAPGraphviz; graph, digraph, or context gv.
#! @BeginExampleSession
#! @EndExampleSession
DeclareOperation("GraphvizNumberOfNodes", [IsGraphvizGraphDigraphOrContext]);
+#! @Arguments gv
+#! @Returns the number of nodes in a &GAPGraphviz; object.
+#!
+#! @Description This operations returns the number of nodes in the
+#! &GAPGraphviz; graph, digraph, or context gv.
+#! @BeginExampleSession
+#! @EndExampleSession
+DeclareOperation("GraphvizNumberOfEdges", [IsGraphvizGraphDigraphOrContext]);
+
#! @Arguments graph
#! @Returns a print record of &GAPGraphviz; graphs
#! @Description gets the subgraphs of a provided graphviz graph.
diff --git a/gap/dot.gi b/gap/dot.gi
index d5305bd..5ec07ad 100644
--- a/gap/dot.gi
+++ b/gap/dot.gi
@@ -8,23 +8,23 @@
#############################################################################
##
+# TODO
+# 2. ensure arg names are consistent (using gv, name, e, n, key, val, head,
+# tail)
+# 3. Use GraphvizStringify which for a string does nothing (no copy); for an
+# object calls String; for a IsGraphvizObject returns its name;
+
#############################################################################
# Constructors
#############################################################################
-InstallMethod(GraphvizGraph, "for a string", [IsString],
-name -> GV_GraphOrDigraph(name, IsGraphvizGraph));
-
InstallMethod(GraphvizGraph, "for an object", [IsObject],
-obj -> GraphvizGraph(String(obj)));
+obj -> GV_GraphOrDigraph(String(obj), IsGraphvizGraph));
InstallMethod(GraphvizGraph, "for no args", [], {} -> GraphvizGraph(""));
-InstallMethod(GraphvizDigraph, "for a string", [IsString],
-name -> GV_GraphOrDigraph(name, IsGraphvizDigraph));
-
InstallMethod(GraphvizDigraph, "for an object", [IsObject],
-obj -> GraphvizDigraph(String(obj)));
+obj -> GV_GraphOrDigraph(String(obj), IsGraphvizDigraph));
InstallMethod(GraphvizDigraph, "for no args", [], {} -> GraphvizDigraph(""));
@@ -40,22 +40,16 @@ e -> StringFormatted("", GraphvizName(e)));
InstallMethod(PrintString, "for a graphviz (di)graph or context",
[IsGraphvizGraphDigraphOrContext],
-function(g)
+function(gv)
local result, edges, nodes, kind;
result := "";
- edges := 0;
- nodes := 0;
-
- GV_GraphSearchChildren(g, function(s)
- nodes := nodes + Length(RecNames(GraphvizNodes(s)));
- edges := edges + Length(GraphvizEdges(s));
- return false;
- end);
+ edges := GraphvizNumberOfEdges(gv);
+ nodes := GraphvizNumberOfNodes(gv);
- if IsGraphvizContext(g) then
+ if IsGraphvizContext(gv) then
kind := "context";
- elif IsGraphvizDigraph(g) then
+ elif IsGraphvizDigraph(gv) then
kind := "digraph";
else
kind := "graph";
@@ -63,8 +57,8 @@ function(g)
Append(result, StringFormatted(" "" then
- Append(result, StringFormatted("\"{}\" ", GraphvizName(g)));
+ if GraphvizName(gv) <> "" then
+ Append(result, StringFormatted("\"{}\" ", GraphvizName(gv)));
fi;
Append(result, StringFormatted("with {} ", Pluralize(nodes, "node")));
@@ -79,32 +73,55 @@ end);
#############################################################################
InstallMethod(GraphvizName, "for a graphviz object", [IsGraphvizObject],
-x -> x!.Name);
+gv -> gv!.Name);
InstallMethod(GraphvizAttrs, "for a graphviz object", [IsGraphvizObject],
-x -> x!.Attrs);
+gv -> gv!.Attrs);
InstallMethod(GraphvizNodes, "for a graphviz (di)graph or context",
-[IsGraphvizGraphDigraphOrContext], x -> x!.Nodes);
+[IsGraphvizGraphDigraphOrContext], gv -> gv!.Nodes);
InstallMethod(GraphvizNode, "for a graphviz (di)graph or context and object",
[IsGraphvizGraphDigraphOrContext, IsObject],
-function(gv, val)
- local graph;
- val := String(val);
- graph := GV_FindGraphWithNode(gv, val);
- if graph = fail then
+function(gv, obj)
+ local name, subgraph;
+
+ name := String(obj);
+ subgraph := GV_FindGraphWithNode(gv, name);
+ if subgraph = fail then
return fail;
fi;
- return GraphvizNodes(graph).(val);
+ return GraphvizNodes(subgraph).(name);
end);
-# FIXME the below only counts nodes in the root graph, not in its children
InstallMethod(GraphvizNumberOfNodes, "for a graphviz (di)graph or context",
-[IsGraphvizGraphDigraphOrContext], x -> Length(RecNames(GraphvizNodes(x))));
+[IsGraphvizGraphDigraphOrContext],
+function(gv)
+ local nodes;
+
+ nodes := 0;
+ GV_GraphSearchChildren(gv, function(subgraph)
+ nodes := nodes + Length(RecNames(GraphvizNodes(subgraph)));
+ return false;
+ end);
+ return nodes;
+end);
+
+InstallMethod(GraphvizNumberOfEdges, "for a graphviz (di)graph or context",
+[IsGraphvizGraphDigraphOrContext],
+function(gv)
+ local edges;
+
+ edges := 0;
+ GV_GraphSearchChildren(gv, function(subgraph)
+ edges := edges + Length(GraphvizEdges(subgraph));
+ return false;
+ end);
+ return edges;
+end);
InstallMethod(GraphvizEdges, "for a graphviz (di)graph or context",
-[IsGraphvizGraphDigraphOrContext], x -> x!.Edges);
+[IsGraphvizGraphDigraphOrContext], gv -> gv!.Edges);
InstallMethod(GraphvizEdges,
"for a graphviz (di)graph or context, object, and object",
@@ -132,52 +149,41 @@ function(gv, head, tail)
end);
InstallMethod(GraphvizSubgraphs, "for a graphviz (di)graph or context",
-[IsGraphvizGraphDigraphOrContext], x -> x!.Subgraphs);
+[IsGraphvizGraphDigraphOrContext], gv -> gv!.Subgraphs);
InstallMethod(GraphvizContexts, "for a graphviz (di)graph or context",
-[IsGraphvizGraphDigraphOrContext], x -> x!.Contexts);
+[IsGraphvizGraphDigraphOrContext], gv -> gv!.Contexts);
InstallMethod(GraphvizTail, "for a graphviz edge", [IsGraphvizEdge],
-x -> x!.Tail);
+e -> e!.Tail);
InstallMethod(GraphvizHead, "for a graphviz edge", [IsGraphvizEdge],
-x -> x!.Head);
-
-# Operators for nodes
+e -> e!.Head);
-InstallMethod(\=, "for graphviz nodes",
-[IsGraphvizNode, IsGraphvizNode], IsIdenticalObj);
+# Operators for graphviz objects
-InstallMethod(\=, "for graphviz edges",
-[IsGraphvizEdge, IsGraphvizEdge], IsIdenticalObj);
+InstallMethod(\=, "for graphviz objects",
+[IsGraphvizObject, IsGraphvizObject], IsIdenticalObj);
# Accessor for graphs and digraphs
InstallMethod(GraphvizFindSubgraphRecursive,
-"for a graphviz (di)graph or context and a string",
-[IsGraphvizGraphDigraphOrContext, IsString],
-{g, s} -> GV_GraphTreeSearch(g, v -> GraphvizName(v) = s and
- not IsGraphvizContext(v)));
-
-InstallMethod(GraphvizFindSubgraphRecursive,
-"for a graphviz (di)graph or context and a string",
+"for a graphviz (di)graph or context and an object",
[IsGraphvizGraphDigraphOrContext, IsObject],
-{g, o} -> GraphvizFindSubgraphRecursive(g, String(o)));
+{gv, obj} -> GV_GraphTreeSearch(gv, subgraph -> GraphvizName(subgraph) = String(obj)
+ and not IsGraphvizContext(subgraph)));
#############################################################################
# GraphvizSetName
#############################################################################
-InstallMethod(GraphvizSetName, "for a graphviz (di)graph or context and string",
-[IsGraphvizGraphDigraphOrContext, IsString],
-function(x, name)
- x!.Name := name;
- return x;
-end);
-
-InstallMethod(GraphvizSetName, "for a graphviz (di)graph or context and string",
+InstallMethod(GraphvizSetName,
+"for a graphviz (di)graph or context and object",
[IsGraphvizGraphDigraphOrContext, IsObject],
-{g, o} -> GraphvizSetName(g, String(o)));
+function(gv, name)
+ gv!.Name := String(name);
+ return gv;
+end);
#############################################################################
# GraphvizSetAttr(s)
@@ -283,16 +289,16 @@ end);
InstallMethod(GraphvizAddNode, "for a graphviz (di)graph or context and object",
[IsGraphvizGraphDigraphOrContext, IsObject],
-function(gv, val)
- local node;
- val := String(val);
+function(gv, obj)
+ local node, name;
+ name := String(obj);
- if Length(val) = 0 then
- ErrorNoReturn("the argument (the name of the node being added) cannot be empty");
+ if Length(name) = 0 then
+ ErrorNoReturn("the value returned by `String(obj)` must not be empty");
fi;
# Reuse existing node if available
- node := GraphvizNode(gv, val);
+ node := GraphvizNode(gv, name);
if node <> fail then
if not IsIdenticalObj(GV_GetParent(node), gv) then
ErrorFormatted("The 2nd argument \"{}\" is the name of a node in a ",
@@ -301,24 +307,24 @@ function(gv, val)
"(a graphviz (di)graph / context) named \"{}\" (",
"adding the node \"{}\" to the 1st argument would ",
"silently change the enclosing context of \"{}\")",
- val,
+ name,
GraphvizName(GV_GetParent(node)),
GraphvizName(gv),
- val,
+ name,
GraphvizName(gv),
- val);
+ name);
fi;
return node;
fi;
- node := GV_Node(gv, val);
+ node := GV_Node(gv, name);
GV_AddNode(gv, node);
return node;
end);
InstallMethod(GraphvizAddNode, "for a graphviz (di)graph or context and string",
[IsGraphvizGraphDigraphOrContext, IsGraphvizNode],
-function(gv, name) # gaplint: disable=unused-func-args
+function(gv, n) # gaplint: disable=unused-func-args
ErrorNoReturn("it is not currently possible to add Graphviz node ",
"objects directly to Graphviz graphs or digraphs, use ",
"the node's name instead");
@@ -331,10 +337,10 @@ end);
InstallMethod(GraphvizAddEdge,
"for a graphviz (di)graph or context and two graphviz nodes",
[IsGraphvizGraphDigraphOrContext, IsGraphvizNode, IsGraphvizNode],
-function(x, head, tail)
+function(gv, head, tail)
local edge, root_x, root_head, root_tail;
- root_x := GV_GetRoot(x);
+ root_x := GV_GetRoot(gv);
root_head := GV_GetRoot(head);
root_tail := GV_GetRoot(tail);
if not IsIdenticalObj(root_x, root_head) then
@@ -343,7 +349,7 @@ function(x, head, tail)
"(adding the edge would silently change the enclosing ",
"context of \"{}\")",
GraphvizName(head),
- GraphvizName(x),
+ GraphvizName(gv),
GraphvizName(head));
fi;
if not IsIdenticalObj(root_x, root_tail) then
@@ -352,45 +358,21 @@ function(x, head, tail)
"(adding the edge would silently change the enclosing ",
"context of \"{}\")",
GraphvizName(tail),
- GraphvizName(x),
+ GraphvizName(gv),
GraphvizName(tail));
fi;
- edge := GV_Edge(x, head, tail);
- GV_AddEdge(x, edge);
+ edge := GV_Edge(gv, head, tail);
+ GV_AddEdge(gv, edge);
return edge;
end);
-InstallMethod(GraphvizAddEdge,
-"for a graphviz (di)graph or context and two strings",
-[IsGraphvizGraphDigraphOrContext, IsString, IsString],
-function(x, head, tail)
- local head_node, tail_node;
-
- head_node := GraphvizNode(x, head);
- if head_node = fail then
- Info(InfoGraphviz, 3, "the head of the edge \"", head,
- "\" is not an existing node, adding node \"", head, "\"");
- head_node := GV_Node(x, head);
- GV_AddNode(x, head_node);
- fi;
-
- tail_node := GraphvizNode(x, tail);
- if tail_node = fail then
- Info(InfoGraphviz, 3, "the tail of the edge \"", tail,
- "\" is not an existing node, adding node \"", tail, "\"");
- tail_node := GV_Node(x, tail);
- GV_AddNode(x, tail_node);
- fi;
-
- return GraphvizAddEdge(x, head_node, tail_node);
-end);
-
InstallMethod(GraphvizAddEdge,
"for a graphviz (di)graph or context and two objects",
[IsGraphvizGraphDigraphOrContext, IsObject, IsObject],
function(gv, head, tail)
- local head_name, tail_name;
+ local head_name, tail_name, head_node, tail_node;
+
if IsGraphvizNode(head) then
head_name := GraphvizName(head);
else
@@ -401,7 +383,24 @@ function(gv, head, tail)
else
tail_name := String(tail);
fi;
- return GraphvizAddEdge(gv, head_name, tail_name);
+
+ head_node := GraphvizNode(gv, head_name);
+ if head_node = fail then
+ Info(InfoGraphviz, 3, "the head of the edge \"", head_name,
+ "\" is not an existing node, adding node \"", head_name, "\"");
+ head_node := GV_Node(gv, head_name);
+ GV_AddNode(gv, head_node);
+ fi;
+
+ tail_node := GraphvizNode(gv, tail_name);
+ if tail_node = fail then
+ Info(InfoGraphviz, 3, "the tail of the edge \"", tail_name,
+ "\" is not an existing node, adding node \"", tail_name, "\"");
+ tail_node := GV_Node(gv, tail_name);
+ GV_AddNode(gv, tail_node);
+ fi;
+
+ return GraphvizAddEdge(gv, head_node, tail_node);
end);
#############################################################################
@@ -409,10 +408,12 @@ end);
#############################################################################
InstallMethod(GraphvizAddSubgraph,
-"for a graphviz (di)graph or context and string",
-[IsGraphvizGraphDigraphOrContext, IsString],
-function(gv, name)
- local subgraphs, subgraph;
+"for a graphviz (di)graph or context and object",
+[IsGraphvizGraphDigraphOrContext, IsObject],
+function(gv, obj)
+ local subgraphs, subgraph, name;
+
+ name := String(obj);
subgraphs := GraphvizSubgraphs(gv);
if IsBound(subgraphs.(name)) then
@@ -426,58 +427,47 @@ function(gv, name)
return subgraph;
end);
-InstallMethod(GraphvizAddSubgraph,
-"for a graphviz (di)graph or context and an object",
-[IsGraphvizGraphDigraphOrContext, IsObject],
-{g, o} -> GraphvizAddSubgraph(g, String(o)));
-
InstallMethod(GraphvizAddSubgraph, "for a graphviz (di)graph or context",
[IsGraphvizGraphDigraphOrContext],
-graph -> GraphvizAddSubgraph(graph,
- StringFormatted("no_name_{}",
- GV_GetCounter(graph))));
+gv -> GraphvizAddSubgraph(gv, StringFormatted("no_name_{}", GV_GetCounter(gv))));
InstallMethod(GraphvizAddContext,
-"for a graphviz (di)graph or context and a string",
-[IsGraphvizGraphDigraphOrContext, IsString],
-function(graph, name)
- local contexts, ctx;
+"for a graphviz (di)graph or context and object",
+[IsGraphvizGraphDigraphOrContext, IsObject],
+function(gv, obj)
+ local contexts, ctx, name;
- contexts := GraphvizContexts(graph);
+ name := String(obj);
+ contexts := GraphvizContexts(gv);
if IsBound(contexts.(name)) then
ErrorFormatted("the 1st argument (a graphviz (di)graph/context) ",
"already has a context with name \"{}\"",
name);
fi;
- ctx := GV_Context(graph, name);
+ ctx := GV_Context(gv, name);
contexts.(name) := ctx;
return ctx;
end);
-InstallMethod(GraphvizAddContext,
-"for a graphviz (di)graph or context",
+InstallMethod(GraphvizAddContext, "for a graphviz (di)graph or context",
[IsGraphvizGraphDigraphOrContext],
-g -> GraphvizAddContext(g, StringFormatted("no_name_{}", GV_GetCounter(g))));
-
-InstallMethod(GraphvizAddContext,
-"for a graphviz (di)graph or context and an object",
-[IsGraphvizGraphDigraphOrContext, IsObject],
-{g, o} -> GraphvizAddContext(g, String(o)));
+gv -> GraphvizAddContext(gv, StringFormatted("no_name_{}", GV_GetCounter(gv))));
# Removal
InstallMethod(GraphvizRemoveNode,
"for a graphviz (di)graph or context and node",
[IsGraphvizGraphDigraphOrContext, IsGraphvizNode],
-{g, node} -> GraphvizRemoveNode(g, GraphvizName(node)));
+{gv, n} -> GraphvizRemoveNode(gv, GraphvizName(n)));
InstallMethod(GraphvizRemoveNode,
"for a graphviz (di)graph or context and a string",
-[IsGraphvizGraphDigraphOrContext, IsString],
-function(g, name)
- local nodes;
- nodes := GraphvizNodes(g);
+[IsGraphvizGraphDigraphOrContext, IsObject],
+function(gv, obj)
+ local nodes, name;
+ name := String(obj);
+ nodes := GraphvizNodes(gv);
if IsBound(nodes.(name)) then
Unbind(nodes.(name));
else
@@ -488,7 +478,7 @@ function(g, name)
fi;
# remove incident edges
- GraphvizFilterEdges(g,
+ GraphvizFilterEdges(gv,
function(e)
local head, tail;
head := GraphvizHead(e);
@@ -496,42 +486,41 @@ function(g, name)
return name <> GraphvizName(tail) and name <> GraphvizName(head);
end);
- return g;
+ return gv;
end);
-InstallMethod(GraphvizRemoveNode,
-"for a graphviz (di)graph or context and a string",
-[IsGraphvizGraphDigraphOrContext, IsObject],
-{g, o} -> GraphvizRemoveNode(g, String(o)));
-
InstallMethod(GraphvizFilterEdges,
"for a graphviz (di)graph or context and edge filter",
[IsGraphvizGraphDigraphOrContext, IsFunction],
-function(g, filter)
+function(gv, func)
local edge, idx, edges;
+ # TODO check func has correct number of args
- edges := GraphvizEdges(g);
+ edges := GraphvizEdges(gv);
idx := Length(edges);
while idx > 0 do
edge := edges[idx];
- if not filter(edge) then
+ if not func(edge) then
Remove(edges, idx);
fi;
idx := idx - 1;
od;
- return g;
+ return gv;
end);
InstallMethod(GraphvizRemoveEdges,
-"for a graphviz (di)graph or context, string, and string",
-[IsGraphvizGraphDigraphOrContext, IsString, IsString],
-function(g, hn, tn)
- local lh, lt, len;
+"for a graphviz (di)graph or context, object, and object",
+[IsGraphvizGraphDigraphOrContext, IsObject, IsObject],
+function(gv, head_obj, tail_obj)
+ local lh, lt, len, hn, tn;
+
+ hn := String(head_obj);
+ tn := String(tail_obj);
# if no such nodes exist -> error out
- lh := GraphvizNode(g, hn) = fail;
- lt := GraphvizNode(g, tn) = fail;
+ lh := GraphvizNode(gv, hn) = fail;
+ lt := GraphvizNode(gv, tn) = fail;
if lh and lt then
ErrorFormatted("no nodes with names \"{}\" or \"{}\"", hn, tn);
elif lh then
@@ -540,32 +529,27 @@ function(g, hn, tn)
ErrorFormatted("no node with name \"{}\"", tn);
fi;
- len := Length(GraphvizEdges(g));
- GraphvizFilterEdges(g,
+ len := Length(GraphvizEdges(gv));
+ GraphvizFilterEdges(gv,
function(e)
local head, tail, tmp;
head := GraphvizHead(e);
tail := GraphvizTail(e);
- if IsGraphvizDigraph(g) then
+ if IsGraphvizDigraph(gv) then
return tn <> GraphvizName(tail) or hn <> GraphvizName(head);
else
tmp := tn <> GraphvizName(tail) or hn <> GraphvizName(head);
return tmp and (hn <> GraphvizName(tail) or tn <> GraphvizName(head));
fi;
end);
- if len - Length(GraphvizEdges(g)) = 0 then
+ if len - Length(GraphvizEdges(gv)) = 0 then
ErrorFormatted("no edges exist from \"{}\" to \"{}\"",
tn, hn);
fi;
- return g;
+ return gv;
end);
-InstallMethod(GraphvizRemoveEdges,
-"for a graphviz (di)graph or context, object, and object",
-[IsGraphvizGraphDigraphOrContext, IsObject, IsObject],
-{gv, o1, o2} -> GraphvizRemoveEdges(gv, String(o1), String(o2)));
-
#############################################################################
# Stringify
#############################################################################
@@ -575,7 +559,7 @@ InstallMethod(GraphvizRemoveEdges,
# immutable, and hence cannot be changed after it is first set.
InstallMethod(AsString, "for a graphviz (di)graph",
-[IsGraphvizGraphDigraphOrContext], graph -> GV_StringifyGraph(graph, false));
+[IsGraphvizGraphDigraphOrContext], gv -> GV_StringifyGraph(gv, false));
# Can't do the following because it conflicts with the PrintString above, we
# leave this here as a reminder.
diff --git a/gap/gv.gd b/gap/gv.gd
index f10049e..b21c07e 100644
--- a/gap/gv.gd
+++ b/gap/gv.gd
@@ -39,4 +39,4 @@ DeclareOperation("GV_AddEdge",
[IsGraphvizGraphDigraphOrContext, IsGraphvizEdge]);
DeclareGlobalFunction("GV_IsValidColor");
-DeclareGlobalFunction("GV_ErrorIfNotNodeColoring");
+DeclareOperation("GV_ErrorIfNotNodeColoring", [IsGraphvizObject, IsList]);
diff --git a/gap/gv.gi b/gap/gv.gi
index bf123de..14b6204 100644
--- a/gap/gv.gi
+++ b/gap/gv.gi
@@ -8,8 +8,6 @@
#############################################################################
##
-# TODO remove non-public functions from gv.gd
-
###############################################################################
# Family + type
###############################################################################
@@ -40,46 +38,43 @@ BindGlobal("GV_GraphDigraphOrContextType",
# Graph child counter functions
BindGlobal("GV_IncCounter",
-function(x)
- x!.Counter := x!.Counter + 1;
+function(gv)
+ Assert(0, IsGraphvizObject(gv));
+ gv!.Counter := gv!.Counter + 1;
end);
InstallMethod(GV_GetCounter, "for a graphviz graph",
-[IsGraphvizGraphDigraphOrContext],
-x -> x!.Counter);
+[IsGraphvizGraphDigraphOrContext], gv -> gv!.Counter);
InstallMethod(GV_Node, "for a string",
[IsGraphvizGraphDigraphOrContext, IsString],
-function(graph, name)
- local out;
+function(gv, name)
+ local result;
Assert(0, not IsEmpty(name));
- out := Objectify(GV_NodeType,
- rec(
- Name := name,
- Attrs := rec(),
- Parent := graph,
- Idx := GV_GetCounter(graph)));
- GV_IncCounter(graph);
- return out;
+ result := Objectify(GV_NodeType,
+ rec(Name := name,
+ Attrs := rec(),
+ Parent := gv,
+ Idx := GV_GetCounter(gv)));
+ GV_IncCounter(gv);
+ return result;
end);
InstallMethod(GV_Edge, "for two graphviz nodes",
[IsGraphvizGraphDigraphOrContext, IsGraphvizNode, IsGraphvizNode],
-function(graph, head, tail)
- local out;
-
- out := Objectify(GV_EdgeType,
- rec(
- Name := StringFormatted("({}, {})",
- GraphvizName(head),
- GraphvizName(tail)),
- Head := head,
- Tail := tail,
- Attrs := rec(),
- Parent := graph,
- Idx := GV_GetCounter(graph)));
- GV_IncCounter(graph);
- return out;
+function(gv, head, tail)
+ local result;
+ result := Objectify(GV_EdgeType,
+ rec(Name := StringFormatted("({}, {})",
+ GraphvizName(head),
+ GraphvizName(tail)),
+ Head := head,
+ Tail := tail,
+ Attrs := rec(),
+ Parent := gv,
+ Idx := GV_GetCounter(gv)));
+ GV_IncCounter(gv);
+ return result;
end);
# Graph constructors
@@ -90,97 +85,100 @@ function(name, filt)
local result;
Assert(0, filt = IsGraphvizGraph or filt = IsGraphvizDigraph);
- result := Objectify(
- GV_GraphDigraphOrContextType,
- rec(
- # Note that the following aren't attributes because we require them to be
- # mutable.
- Name := name,
- Subgraphs := rec(),
- Contexts := rec(),
- Nodes := rec(),
- Edges := [],
- Attrs := rec(graph := rec(),
- edge := rec(),
- node := rec()),
- Parent := fail,
- Idx := 1,
- Counter := 1));
+ # Note that the components of the record below aren't attributes because we
+ # require them to be mutable.
+ result := Objectify(GV_GraphDigraphOrContextType,
+ rec(Name := name,
+ Subgraphs := rec(),
+ Contexts := rec(),
+ Nodes := rec(),
+ Edges := [],
+ Attrs := rec(graph := rec(),
+ edge := rec(),
+ node := rec()),
+ Parent := fail,
+ Idx := 1,
+ Counter := 1));
SetFilterObj(result, filt);
SetGV_Filter(result, filt);
return result;
end);
-InstallMethod(GV_Subgraph,
-"for a graphviz graph and a string",
+InstallMethod(GV_Subgraph, "for a graphviz graph and a string",
[IsGraphvizGraphDigraphOrContext, IsString],
-function(parent, name)
- local out;
+function(gv, name)
+ local result;
- out := GV_GraphOrDigraph(name, GV_Filter(parent));
- out!.Parent := parent;
- out!.Idx := GV_GetCounter(parent);
+ result := GV_GraphOrDigraph(name, GV_Filter(gv));
+ result!.Parent := gv;
+ result!.Idx := GV_GetCounter(gv);
- GV_IncCounter(parent);
- return out;
+ GV_IncCounter(gv);
+ return result;
end);
-InstallMethod(GV_Context,
-"for a string and a string",
+InstallMethod(GV_Context, "for a graphviz (di)graph or context and a string",
[IsGraphvizGraphDigraphOrContext, IsString],
-function(parent, name)
- local out;
- out := GV_Subgraph(parent, name);
- SetFilterObj(out, IsGraphvizContext);
- return out;
+function(gv, name)
+ local result;
+ result := GV_Subgraph(gv, name);
+ SetFilterObj(result, IsGraphvizContext);
+ return result;
end);
# Nodes
-InstallMethod(GV_GetParent,
-"for a graphviz object",
-[IsGraphvizObject], graph -> graph!.Parent);
+InstallMethod(GV_GetParent, "for a graphviz object",
+[IsGraphvizObject], gv -> gv!.Parent);
+# TODO: GV_GraphTreeSearch apparently searches both up and down in the tree,
+# while GV_GraphSearchChildren only searches down. These could be amalgamated
+# by just initialising to_visit with all the ancestors of the gv, then calling
+# GV_GraphSearchChildren
InstallMethod(GV_GraphTreeSearch,
-"for a graphviz graph and a predicate",
+"for a graphviz (di)graph or context and a predicate",
[IsGraphvizGraphDigraphOrContext, IsFunction],
-function(graph, pred)
- local seen, to_visit, g, key, subgraph, context, parent;
- seen := [graph];
- to_visit := [graph];
+function(gv, pred)
+ local seen, to_visit, subgraph, subsubgraph, context, parent, key;
+
+ # TODO this shouldn't be necessary, the subgraphs should be a tree, so we
+ # shouldn't be able to see a subgraph occurring in more than one object.
+ seen := [gv];
+ to_visit := [gv];
while Length(to_visit) > 0 do
- g := Remove(to_visit, Length(to_visit));
+ subgraph := Remove(to_visit, Length(to_visit));
- # Check this graph
- if pred(g) then
- return g;
+ if pred(subgraph) then
+ return subgraph;
fi;
# add subgraphs to list of to visit if not visited
- for key in RecNames(GraphvizSubgraphs(g)) do
- subgraph := GraphvizSubgraphs(g).(key);
- if not ForAny(seen, s -> IsIdenticalObj(s, subgraph)) then
- Add(seen, subgraph);
- Add(to_visit, subgraph);
+ for key in RecNames(GraphvizSubgraphs(subgraph)) do
+ subsubgraph := GraphvizSubgraphs(subgraph).(key);
+ if not subsubgraph in seen then
+ Add(seen, subsubgraph);
+ Add(to_visit, subsubgraph);
fi;
od;
# add contexts to list of to visit if not visited
- for key in RecNames(GraphvizContexts(g)) do
- context := GraphvizContexts(g).(key);
- if not ForAny(seen, s -> IsIdenticalObj(s, context)) then
+ for key in RecNames(GraphvizContexts(subgraph)) do
+ context := GraphvizContexts(subgraph).(key);
+ if not context in seen then
Add(seen, context);
Add(to_visit, context);
fi;
od;
# add parent if not visited
- parent := GV_GetParent(g);
+ parent := GV_GetParent(subgraph);
+
if not IsGraphvizGraphDigraphOrContext(parent) then
+ # TODO Not sure this can ever occur
continue;
- fi;
- if not ForAny(seen, s -> IsIdenticalObj(s, parent)) then
+ elif not parent in seen then
+ # TODO Not sure this can ever occur
Add(seen, parent);
Add(to_visit, parent);
fi;
@@ -193,10 +191,12 @@ end);
InstallMethod(GV_GraphSearchChildren,
"for a graphviz graph and a predicate",
[IsGraphvizGraphDigraphOrContext, IsFunction],
-function(graph, pred)
+function(gv, func)
local _, curr, queue, count, nexts, key;
- queue := [graph];
+ # TODO check func has right number of args
+
+ queue := [gv];
while Length(queue) > 0 do
count := Length(queue);
@@ -205,7 +205,7 @@ function(graph, pred)
curr := Remove(queue, 1);
# Check this graph (visit)
- if pred(curr) then
+ if func(curr) then
return curr;
fi;
@@ -227,22 +227,27 @@ end);
InstallMethod(GV_FindGraphWithNode,
"for a graphviz graph and a node",
[IsGraphvizGraphDigraphOrContext, IsString],
-{g, n} -> GV_GraphTreeSearch(g, v -> IsBound(GraphvizNodes(v).(n))));
+{gv, name} -> GV_GraphTreeSearch(gv, subgraph -> IsBound(GraphvizNodes(subgraph).(name))));
InstallMethod(GV_GetRoot,
"for a graphviz graph",
[IsGraphvizObject],
-function(graph)
- while GV_GetParent(graph) <> fail do
- graph := GV_GetParent(graph);
+function(gv)
+ while GV_GetParent(gv) <> fail do
+ gv := GV_GetParent(gv);
od;
- return graph;
+ return gv;
end);
-BindGlobal("GV_Indent", lvl -> ListWithIdenticalEntries(2 * lvl, ' '));
+BindGlobal("GV_Indent",
+function(lvl)
+ Assert(0, IsInt(lvl));
+ return ListWithIdenticalEntries(2 * lvl, ' ');
+end);
BindGlobal("GV_Depth", function(gv)
local depth;
+ Assert(0, IsGraphvizObject(gv));
depth := 0;
while GV_GetParent(gv) <> fail do
gv := GV_GetParent(gv);
@@ -254,53 +259,59 @@ end);
InstallMethod(GV_AddNode,
"for a graphviz graph and node",
[IsGraphvizGraphDigraphOrContext, IsGraphvizNode],
-function(x, node)
+function(gv, node)
local name, nodes;
name := GraphvizName(node);
- nodes := GraphvizNodes(x);
+ nodes := GraphvizNodes(gv);
nodes.(name) := node;
- return x;
+ return gv;
end);
InstallMethod(GV_AddEdge,
"for a graphviz graph and edge",
[IsGraphvizGraphDigraphOrContext, IsGraphvizEdge],
-function(x, edge)
- Add(x!.Edges, edge);
- return x;
+function(gv, edge)
+ Add(gv!.Edges, edge);
+ return gv;
end);
###############################################################################
# Stringifying
###############################################################################
-# @ Return DOT graph head line.
BindGlobal("GV_StringifyGraphHead",
-graph -> StringFormatted("graph {} {{\n", GraphvizName(graph)));
+function(gv)
+ Assert(0, IsGraphvizGraph(gv));
+ return StringFormatted("graph {} {{\n", GraphvizName(gv));
+end);
-# @ Return DOT digraph head line.
BindGlobal("GV_StringifyDigraphHead",
-graph -> StringFormatted("digraph {} {{\n", GraphvizName(graph)));
+function(gv)
+ Assert(0, IsGraphvizDigraph(gv));
+ return StringFormatted("digraph {} {{\n", GraphvizName(gv));
+end);
-# @ Return DOT subgraph head line.
BindGlobal("GV_StringifySubgraphHead",
-gv -> StringFormatted("{}subgraph {} {{\n", GV_Indent(GV_Depth(gv)), GraphvizName(gv)));
+function(gv)
+ Assert(0, IsGraphvizDigraph(gv) or IsGraphvizGraph(gv));
+ return StringFormatted("{}subgraph {} {{\n",
+ GV_Indent(GV_Depth(gv)),
+ GraphvizName(gv));
+end);
-# @ Return DOT subgraph head line.
BindGlobal("GV_StringifyContextHead",
function(gv)
- local indent;
- indent := GV_Indent(GV_Depth(gv));
+ Assert(0, IsGraphvizContext(gv));
return StringFormatted("{1}{{\n{1} // context \"{2}\" \n",
- indent,
+ GV_Indent(GV_Depth(gv)),
GraphvizName(gv));
end);
BindGlobal("GV_StringifyNodeName",
function(node)
local name, old;
-
Assert(0, IsGraphvizNode(node));
+
name := GraphvizName(node);
if (ForAny("- .+", x -> x in name)
or (IsDigitChar(First(name)) and IsAlphaChar(Last(name))))
@@ -345,10 +356,10 @@ function(attrs)
return result;
end);
-# @ Return DOT node statement line.
BindGlobal("GV_StringifyNode",
function(node)
local name, attrs;
+ Assert(0, IsGraphvizNode(node));
name := GV_StringifyNodeName(node);
attrs := GraphvizAttrs(node);
return StringFormatted("{}{}{}\n",
@@ -357,7 +368,6 @@ function(node)
GV_StringifyAttrList(attrs));
end);
-# @ Return DOT graph edge statement line.
BindGlobal("GV_StringifyEdge",
function(edge, edge_str)
local head, tail, attrs;
@@ -376,17 +386,22 @@ function(edge, edge_str)
GV_StringifyAttrList(attrs));
end);
-BindGlobal("GV_GetIdx", x -> x!.Idx);
+BindGlobal("GV_GetIdx",
+function(gv)
+ Assert(0, IsGraphvizObject(gv));
+ return gv!.Idx;
+end);
BindGlobal("GV_ConstructHistory",
-function(graph)
+function(gv)
local ctxs, nodes, edges, subs,
ctxs_hist, node_hist, edge_hist, subs_hist, hist;
+ Assert(0, IsGraphvizGraphDigraphOrContext(gv));
- nodes := GraphvizNodes(graph);
- edges := GraphvizEdges(graph);
- subs := GraphvizSubgraphs(graph);
- ctxs := GraphvizContexts(graph);
+ nodes := GraphvizNodes(gv);
+ edges := GraphvizEdges(gv);
+ subs := GraphvizSubgraphs(gv);
+ ctxs := GraphvizContexts(gv);
node_hist := List(RecNames(nodes), n -> [GV_GetIdx(nodes.(n)), nodes.(n)]);
subs_hist := List(RecNames(subs), s -> [GV_GetIdx(subs.(s)), subs.(s)]);
@@ -403,48 +418,48 @@ end);
InstallMethod(GV_StringifyGraph,
"for a graphviz graph and a string",
[IsGraphvizGraphDigraphOrContext, IsBool],
-function(graph, is_subgraph)
+function(gv, is_subgraph)
local result, record, type, obj;
result := "";
# get the correct head to use
if is_subgraph then
- if IsGraphvizContext(graph) then
- Append(result, GV_StringifyContextHead(graph));
+ if IsGraphvizContext(gv) then
+ Append(result, GV_StringifyContextHead(gv));
else
- Append(result, GV_StringifySubgraphHead(graph));
+ Append(result, GV_StringifySubgraphHead(gv));
fi;
- elif IsGraphvizDigraph(graph) then
+ elif IsGraphvizDigraph(gv) then
Append(result, "//dot\n");
- Append(result, GV_StringifyDigraphHead(graph));
- elif IsGraphvizGraph(graph) then
+ Append(result, GV_StringifyDigraphHead(gv));
+ elif IsGraphvizGraph(gv) then
Append(result, "//dot\n");
- Append(result, GV_StringifyGraphHead(graph));
+ Append(result, GV_StringifyGraphHead(gv));
else
- ErrorFormatted("Unknown graph category, ",
- "expected a context, digraph or graph.");
+ ErrorFormatted("Unknown gv category, ",
+ "expected a context, digraph or gv.");
fi;
for type in ["graph", "node", "edge"] do
- record := GraphvizAttrs(graph).(type);
+ record := GraphvizAttrs(gv).(type);
if Length(RecNames(record)) > 0 then
Append(result,
StringFormatted("{}{}{}\n",
- GV_Indent(GV_Depth(graph) + 1),
+ GV_Indent(GV_Depth(gv) + 1),
type,
GV_StringifyAttrList(record)));
fi;
od;
# Add child graphviz objects
- for obj in GV_ConstructHistory(graph) do
+ for obj in GV_ConstructHistory(gv) do
if IsGraphvizGraphDigraphOrContext(obj) then
Append(result, GV_StringifyGraph(obj, true));
elif IsGraphvizNode(obj) then
Append(result, GV_StringifyNode(obj));
elif IsGraphvizEdge(obj) then
- if (IsGraphvizDigraph(GV_GetRoot(graph))) then
+ if (IsGraphvizDigraph(GV_GetRoot(gv))) then
Append(result, GV_StringifyEdge(obj, "->"));
else
Append(result, GV_StringifyEdge(obj, "--"));
@@ -452,8 +467,8 @@ function(graph, is_subgraph)
fi;
od;
- Append(result, StringFormatted("{}}}", GV_Indent(GV_Depth(graph))));
- if GV_Depth(graph) > 0 then
+ Append(result, StringFormatted("{}}}", GV_Indent(GV_Depth(gv))));
+ if GV_Depth(gv) > 0 then
Append(result, "\n");
fi;
return result;
@@ -579,6 +594,8 @@ BindGlobal("GV_IsValidRGBColor",
function(str)
local valid, i;
+ Assert(0, IsString(str));
+
valid := "0123456789ABCDEFabcdef";
if Length(str) <> 7 or str[1] <> '#' then
@@ -596,7 +613,9 @@ end);
InstallGlobalFunction(GV_IsValidColor,
c -> IsString(c) and (GV_IsValidRGBColor(c) or c in GV_ValidColorNames));
-InstallGlobalFunction(GV_ErrorIfNotNodeColoring,
+InstallMethod(GV_ErrorIfNotNodeColoring,
+"for a graphviz (di)graph or context and list",
+[IsGraphvizGraphDigraphOrContext, IsList],
function(gv, colors)
local N;
N := GraphvizNumberOfNodes(gv);
diff --git a/tst/graphvizforgap01.tst b/tst/graphvizforgap01.tst
index d77be25..b6fdcd1 100644
--- a/tst/graphvizforgap01.tst
+++ b/tst/graphvizforgap01.tst
@@ -1,4 +1,4 @@
-# GraphvizForGAP, chapter 2
+# GraphvizForGAP, chapter 1
#
# DO NOT EDIT THIS FILE - EDIT EXAMPLES IN THE SOURCE INSTEAD!
#
@@ -10,157 +10,96 @@
#
gap> START_TEST("graphvizforgap01.tst");
-# doc/_Chapter_Full_Reference.xml:78-89
-gap> gv := GraphvizGraph("GraphyMcGraphFace");
-
-gap> GraphvizName(gv);
-"GraphyMcGraphFace"
-gap> GraphvizGraph(666);
-
-gap> gv := GraphvizGraph();
-
-gap> GraphvizName(gv);
-""
-
-# doc/_Chapter_Full_Reference.xml:131-142
-gap> gv := GraphvizDigraph("GraphyMcGraphFace");
-
-gap> GraphvizName(gv);
-"GraphyMcGraphFace"
-gap> GraphvizDigraph(666);
-
-gap> gv := GraphvizDigraph();
-
-gap> GraphvizName(gv);
-""
-
-# doc/_Chapter_Full_Reference.xml:167-178
-gap> dot := GraphvizDigraph("The Round Table");;
-gap> GraphvizName(dot);
-"The Round Table"
-gap> n := GraphvizSetAttr(GraphvizAddNode(dot, "A"), "label", "King Arthur");
-
-gap> GraphvizName(n);
-"A"
-gap> e := GraphvizAddEdge(dot, "A", "B");;
-gap> GraphvizName(e);
-"(A, B)"
-
-# doc/_Chapter_Full_Reference.xml:191-192
-
-# doc/_Chapter_Full_Reference.xml:220-231
-gap> gv := GraphvizDigraph();
-
-gap> context := GraphvizAddContext(gv);
-
-gap> GraphvizAddNode(context, "a");
-
-gap> GraphvizNodes(gv); # There are no nodes in gv directly, only in context
-rec( )
-gap> GraphvizNodes(context);
-rec( a := )
-
-# doc/_Chapter_Full_Reference.xml:252-261
-gap> gv := GraphvizDigraph();
-
-gap> context := GraphvizAddContext(gv);
-
-gap> GraphvizAddNode(context, "a");
-
-gap> GraphvizNode(gv, "a");
-
-
-# doc/_Chapter_Full_Reference.xml:275-276
-
-# doc/_Chapter_Full_Reference.xml:289-290
-
-# doc/_Chapter_Full_Reference.xml:304-305
-
-# doc/_Chapter_Full_Reference.xml:321-322
-
-# doc/_Chapter_Full_Reference.xml:339-346
-gap> gv := GraphvizGraph();;
-gap> GraphvizAddEdge(gv, "a", "b");;
-gap> GraphvizEdges(gv);
-[ ]
-gap> GraphvizEdges(gv, "a", "b");
-[ ]
-
-# doc/_Chapter_Full_Reference.xml:360-366
-gap> gv := GraphvizGraph();;
-gap> e := GraphvizAddEdge(gv, "a", "b");
-
-gap> GraphvizHead(e);
-
-
-# doc/_Chapter_Full_Reference.xml:379-385
-gap> gv := GraphvizGraph();;
-gap> e := GraphvizAddEdge(gv, "a", "b");
-
-gap> GraphvizTail(e);
-
-
-# doc/_Chapter_Full_Reference.xml:407-412
-gap> gv := GraphvizGraph();
-
-gap> GraphvizSetName(gv, "newname");
-
-
-# doc/_Chapter_Full_Reference.xml:428-435
-gap> gv := GraphvizGraph();
-
-gap> GraphvizAddNode(gv, "a");
-
-gap> gv;
-
-
-# doc/_Chapter_Full_Reference.xml:457-464
-gap> gv := GraphvizGraph();
-
-gap> GraphvizAddEdge(gv, "a", "b");
-
-gap> gv;
-
-
-# doc/_Chapter_Full_Reference.xml:482-483
-
-# doc/_Chapter_Full_Reference.xml:510-511
-
-# doc/_Chapter_Full_Reference.xml:530-537
-gap> gv := GraphvizGraph();;
-gap> GraphvizAddNode(gv, "a");;
-gap> gv;
-
-gap> GraphvizRemoveNode(gv, "a");
-
-
-# doc/_Chapter_Full_Reference.xml:550-551
-
-# doc/_Chapter_Full_Reference.xml:566-573
-gap> gv := GraphvizGraph();;
-gap> GraphvizAddEdge(gv, "a", "b");;
-gap> gv;
-
-gap> GraphvizRemoveEdges(gv, "a", "b");
-
-
-# doc/_Chapter_Full_Reference.xml:596-597
-
-# doc/_Chapter_Full_Reference.xml:615-616
-
-# doc/_Chapter_Full_Reference.xml:631-632
-
-# doc/_Chapter_Full_Reference.xml:648-649
-
-# doc/_Chapter_Full_Reference.xml:668-669
-
-# doc/_Chapter_Full_Reference.xml:683-684
-
-# doc/_Chapter_Full_Reference.xml:799-800
-
-# doc/_Chapter_Full_Reference.xml:817-818
-
-# doc/_Chapter_Full_Reference.xml:833-834
+# doc/_Chapter_.xml:58-148
+gap> LoadPackage("GraphvizForGAP");;
+gap> f := GraphvizDigraph("finite_state_machine");
+
+gap> GraphvizSetAttr(f, "rankdir", "LR");
+
+gap> GraphvizSetAttr(f, "size", "8,5");
+
+gap> terminals := GraphvizAddContext(f, "terminals");
+
+gap> GraphvizSetAttrs(terminals, rec(node := rec(shape :=
+> "doublecircle")));
+
+gap> GraphvizAddNode(terminals, "LR_0");
+
+gap> GraphvizAddNode(terminals, "LR_3");
+
+gap> GraphvizAddNode(terminals, "LR_4");
+
+gap> GraphvizAddNode(terminals, "LR_8");
+
+gap> nodes := GraphvizAddContext(f, "nodes");
+
+gap> GraphvizSetAttrs(nodes, rec(node := rec(shape := "circle")));
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_0", "LR_2"), "label", "\"SS(B)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_0", "LR_1"), "label", "\"SS(S)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_1", "LR_3"), "label", "\"S($end)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_2", "LR_6"), "label", "\"SS(b)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_2", "LR_5"), "label", "\"SS(a)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_2", "LR_4"), "label", "\"S(A)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_5", "LR_7"), "label", "\"S(b)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_5", "LR_5"), "label", "\"S(a)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_6", "LR_6"), "label", "\"S(b)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_6", "LR_5"), "label", "\"S(a)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_7", "LR_8"), "label", "\"S(b)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_7", "LR_5"), "label", "\"S(a)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_8", "LR_6"), "label", "\"S(b)\"");
+
+gap> GraphvizSetAttr(GraphvizAddEdge(nodes, "LR_8", "LR_5"), "label", "\"S(a)\"");
+
+gap> Print(AsString(f), "\n");
+//dot
+digraph finite_state_machine {
+ graph [rankdir="LR", size="8,5"]
+ {
+ // context "terminals"
+ node [shape="doublecircle"]
+ LR_0
+ LR_3
+ LR_4
+ LR_8
+ }
+ {
+ // context "nodes"
+ node [shape="circle"]
+ LR_2
+ LR_0 -> LR_2 [label="SS(B)"]
+ LR_1
+ LR_0 -> LR_1 [label="SS(S)"]
+ LR_1 -> LR_3 [label="S($end)"]
+ LR_6
+ LR_2 -> LR_6 [label="SS(b)"]
+ LR_5
+ LR_2 -> LR_5 [label="SS(a)"]
+ LR_2 -> LR_4 [label="S(A)"]
+ LR_7
+ LR_5 -> LR_7 [label="S(b)"]
+ LR_5 -> LR_5 [label="S(a)"]
+ LR_6 -> LR_6 [label="S(b)"]
+ LR_6 -> LR_5 [label="S(a)"]
+ LR_7 -> LR_8 [label="S(b)"]
+ LR_7 -> LR_5 [label="S(a)"]
+ LR_8 -> LR_6 [label="S(b)"]
+ LR_8 -> LR_5 [label="S(a)"]
+ }
+}
+gap> # Splash(f);
#
gap> STOP_TEST("graphvizforgap01.tst", 1);
diff --git a/tst/graphvizforgap02.tst b/tst/graphvizforgap02.tst
new file mode 100644
index 0000000..74271a8
--- /dev/null
+++ b/tst/graphvizforgap02.tst
@@ -0,0 +1,168 @@
+# GraphvizForGAP, chapter 2
+#
+# DO NOT EDIT THIS FILE - EDIT EXAMPLES IN THE SOURCE INSTEAD!
+#
+# This file has been generated by AutoDoc. It contains examples extracted from
+# the package documentation. Each example is preceded by a comment which gives
+# the name of a GAPDoc XML file and a line range from which the example were
+# taken. Note that the XML file in turn may have been generated by AutoDoc
+# from some other input.
+#
+gap> START_TEST("graphvizforgap02.tst");
+
+# doc/_Chapter_Full_Reference.xml:79-90
+gap> gv := GraphvizGraph("GraphyMcGraphFace");
+
+gap> GraphvizName(gv);
+"GraphyMcGraphFace"
+gap> GraphvizGraph(666);
+
+gap> gv := GraphvizGraph();
+
+gap> GraphvizName(gv);
+""
+
+# doc/_Chapter_Full_Reference.xml:133-144
+gap> gv := GraphvizDigraph("GraphyMcGraphFace");
+
+gap> GraphvizName(gv);
+"GraphyMcGraphFace"
+gap> GraphvizDigraph(666);
+
+gap> gv := GraphvizDigraph();
+
+gap> GraphvizName(gv);
+""
+
+# doc/_Chapter_Full_Reference.xml:170-181
+gap> dot := GraphvizDigraph("The Round Table");;
+gap> GraphvizName(dot);
+"The Round Table"
+gap> n := GraphvizSetAttr(GraphvizAddNode(dot, "A"), "label", "King Arthur");
+
+gap> GraphvizName(n);
+"A"
+gap> e := GraphvizAddEdge(dot, "A", "B");;
+gap> GraphvizName(e);
+"(A, B)"
+
+# doc/_Chapter_Full_Reference.xml:194-195
+
+# doc/_Chapter_Full_Reference.xml:223-234
+gap> gv := GraphvizDigraph();
+
+gap> context := GraphvizAddContext(gv);
+
+gap> GraphvizAddNode(context, "a");
+
+gap> GraphvizNodes(gv); # There are no nodes in gv directly, only in context
+rec( )
+gap> GraphvizNodes(context);
+rec( a := )
+
+# doc/_Chapter_Full_Reference.xml:255-264
+gap> gv := GraphvizDigraph();
+
+gap> context := GraphvizAddContext(gv);
+
+gap> GraphvizAddNode(context, "a");
+
+gap> GraphvizNode(gv, "a");
+
+
+# doc/_Chapter_Full_Reference.xml:278-279
+
+# doc/_Chapter_Full_Reference.xml:293-294
+
+# doc/_Chapter_Full_Reference.xml:307-308
+
+# doc/_Chapter_Full_Reference.xml:322-323
+
+# doc/_Chapter_Full_Reference.xml:339-340
+
+# doc/_Chapter_Full_Reference.xml:357-364
+gap> gv := GraphvizGraph();;
+gap> GraphvizAddEdge(gv, "a", "b");;
+gap> GraphvizEdges(gv);
+[ ]
+gap> GraphvizEdges(gv, "a", "b");
+[ ]
+
+# doc/_Chapter_Full_Reference.xml:378-384
+gap> gv := GraphvizGraph();;
+gap> e := GraphvizAddEdge(gv, "a", "b");
+
+gap> GraphvizHead(e);
+
+
+# doc/_Chapter_Full_Reference.xml:397-403
+gap> gv := GraphvizGraph();;
+gap> e := GraphvizAddEdge(gv, "a", "b");
+
+gap> GraphvizTail(e);
+
+
+# doc/_Chapter_Full_Reference.xml:425-430
+gap> gv := GraphvizGraph();
+
+gap> GraphvizSetName(gv, "newname");
+
+
+# doc/_Chapter_Full_Reference.xml:446-453
+gap> gv := GraphvizGraph();
+
+gap> GraphvizAddNode(gv, "a");
+
+gap> gv;
+
+
+# doc/_Chapter_Full_Reference.xml:475-482
+gap> gv := GraphvizGraph();
+
+gap> GraphvizAddEdge(gv, "a", "b");
+
+gap> gv;
+
+
+# doc/_Chapter_Full_Reference.xml:500-501
+
+# doc/_Chapter_Full_Reference.xml:528-529
+
+# doc/_Chapter_Full_Reference.xml:548-555
+gap> gv := GraphvizGraph();;
+gap> GraphvizAddNode(gv, "a");;
+gap> gv;
+
+gap> GraphvizRemoveNode(gv, "a");
+
+
+# doc/_Chapter_Full_Reference.xml:568-569
+
+# doc/_Chapter_Full_Reference.xml:584-591
+gap> gv := GraphvizGraph();;
+gap> GraphvizAddEdge(gv, "a", "b");;
+gap> gv;
+
+gap> GraphvizRemoveEdges(gv, "a", "b");
+
+
+# doc/_Chapter_Full_Reference.xml:614-615
+
+# doc/_Chapter_Full_Reference.xml:633-634
+
+# doc/_Chapter_Full_Reference.xml:649-650
+
+# doc/_Chapter_Full_Reference.xml:666-667
+
+# doc/_Chapter_Full_Reference.xml:686-687
+
+# doc/_Chapter_Full_Reference.xml:701-702
+
+# doc/_Chapter_Full_Reference.xml:817-818
+
+# doc/_Chapter_Full_Reference.xml:835-836
+
+# doc/_Chapter_Full_Reference.xml:851-852
+
+#
+gap> STOP_TEST("graphvizforgap02.tst", 1);
diff --git a/tst/node.tst b/tst/node.tst
index 89e6235..443f0e9 100644
--- a/tst/node.tst
+++ b/tst/node.tst
@@ -28,7 +28,7 @@ gap> n := GraphvizAddNode(GraphvizGraph(), " ");
# Test making a node with empty name fails
gap> n := GraphvizAddNode(GraphvizGraph(), "");
-Error, the argument (the name of the node being added) cannot be empty
+Error, the value returned by `String(obj)` must not be empty
# Test whitespace in node names
gap> n := GraphvizAddNode(GraphvizGraph(), "a a ");