Introduce custom adjacency-list based graph

This commit is contained in:
Alex Coyte
2016-08-24 16:12:51 +10:00
committed by Matthew Barr
parent 05683655cb
commit e1e9010cac
92 changed files with 3730 additions and 1812 deletions

View File

@@ -61,6 +61,8 @@
#include <boost/icl/interval_set.hpp>
using namespace std;
using boost::depth_first_search;
using boost::depth_first_visit;
namespace ue2 {
@@ -99,7 +101,7 @@ struct ReachFilter {
const Graph *g = nullptr;
};
typedef boost::filtered_graph<NFAGraph, ReachFilter<NFAGraph> > RepeatGraph;
typedef boost::filtered_graph<NGHolder, ReachFilter<NGHolder>> RepeatGraph;
struct ReachSubgraph {
vector<NFAVertex> vertices;
@@ -126,9 +128,11 @@ void findInitDepths(const NGHolder &g,
}
}
template<class Graph>
static
void buildTopoOrder(const Graph &g, vector<NFAVertex> &topoOrder) {
vector<NFAVertex> buildTopoOrder(const RepeatGraph &g) {
/* Note: RepeatGraph is a filtered version of NGHolder and still has
* NFAVertex as its vertex descriptor */
typedef ue2::unordered_set<NFAEdge> EdgeSet;
EdgeSet deadEdges;
@@ -140,10 +144,13 @@ void buildTopoOrder(const Graph &g, vector<NFAVertex> &topoOrder) {
color_map(make_assoc_property_map(colours)));
auto acyclic_g = make_filtered_graph(g, make_bad_edge_filter(&deadEdges));
vector<NFAVertex> topoOrder;
topological_sort(acyclic_g, back_inserter(topoOrder),
color_map(make_assoc_property_map(colours)));
reverse(topoOrder.begin(), topoOrder.end());
return topoOrder;
}
static
@@ -171,7 +178,7 @@ bool roguePredecessor(const NGHolder &g, NFAVertex v,
continue;
}
if (!contains(pred, u)) {
DEBUG_PRINTF("%u is a rogue pred\n", g[u].index);
DEBUG_PRINTF("%zu is a rogue pred\n", g[u].index);
return true;
}
@@ -197,7 +204,7 @@ bool rogueSuccessor(const NGHolder &g, NFAVertex v,
}
if (!contains(succ, w)) {
DEBUG_PRINTF("%u is a rogue succ\n", g[w].index);
DEBUG_PRINTF("%zu is a rogue succ\n", g[w].index);
return true;
}
@@ -223,8 +230,8 @@ bool hasDifferentTops(const NGHolder &g, const vector<NFAVertex> &verts) {
if (u != g.start && u != g.startDs) {
continue; // Only edges from starts have valid top properties.
}
DEBUG_PRINTF("edge (%u,%u) with %zu tops\n", g[u].index, g[v].index,
g[e].tops.size());
DEBUG_PRINTF("edge (%zu,%zu) with %zu tops\n", g[u].index,
g[v].index, g[e].tops.size());
if (!tops) {
tops = &g[e].tops;
} else if (g[e].tops != *tops) {
@@ -243,14 +250,14 @@ bool vertexIsBad(const NGHolder &g, NFAVertex v,
const ue2::unordered_set<NFAVertex> &pred,
const ue2::unordered_set<NFAVertex> &succ,
const flat_set<ReportID> &reports) {
DEBUG_PRINTF("check vertex %u\n", g[v].index);
DEBUG_PRINTF("check vertex %zu\n", g[v].index);
// We must drop any vertex that is the target of a back-edge within
// our subgraph. The tail set contains all vertices that are after v in a
// topo ordering.
for (auto u : inv_adjacent_vertices_range(v, g)) {
if (contains(tail, u)) {
DEBUG_PRINTF("back-edge (%u,%u) in subgraph found\n",
DEBUG_PRINTF("back-edge (%zu,%zu) in subgraph found\n",
g[u].index, g[v].index);
return true;
}
@@ -260,18 +267,18 @@ bool vertexIsBad(const NGHolder &g, NFAVertex v,
// edges from *all* the vertices in pred and no other external entries.
// Similarly for exits.
if (roguePredecessor(g, v, involved, pred)) {
DEBUG_PRINTF("preds for %u not well-formed\n", g[v].index);
DEBUG_PRINTF("preds for %zu not well-formed\n", g[v].index);
return true;
}
if (rogueSuccessor(g, v, involved, succ)) {
DEBUG_PRINTF("succs for %u not well-formed\n", g[v].index);
DEBUG_PRINTF("succs for %zu not well-formed\n", g[v].index);
return true;
}
// All reporting vertices should have the same reports.
if (is_match_vertex(v, g) && reports != g[v].reports) {
DEBUG_PRINTF("report mismatch to %u\n", g[v].index);
DEBUG_PRINTF("report mismatch to %zu\n", g[v].index);
return true;
}
@@ -291,8 +298,7 @@ void splitSubgraph(const NGHolder &g, const deque<NFAVertex> &verts,
NFAUndirectedGraph ug;
ue2::unordered_map<NFAVertex, NFAUndirectedVertex> old2new;
ue2::unordered_map<u32, NFAVertex> newIdx2old;
createUnGraph(verts_g.g, true, true, ug, old2new, newIdx2old);
createUnGraph(verts_g, true, true, ug, old2new);
ue2::unordered_map<NFAUndirectedVertex, u32> repeatMap;
@@ -517,7 +523,7 @@ bool processSubgraph(const NGHolder &g, ReachSubgraph &rsi,
if (u == first) {
continue; // no self-loops
}
DEBUG_PRINTF("pred vertex %u\n", g[u].index);
DEBUG_PRINTF("pred vertex %zu\n", g[u].index);
dist[u].insert(0);
}
@@ -619,7 +625,7 @@ void buildTugTrigger(NGHolder &g, NFAVertex cyclic, NFAVertex v,
vector<NFAVertex> &tugs) {
if (allPredsInSubgraph(v, g, involved)) {
// We can transform this vertex into a tug trigger in-place.
DEBUG_PRINTF("all preds in subgraph, vertex %u becomes tug\n",
DEBUG_PRINTF("all preds in subgraph, vertex %zu becomes tug\n",
g[v].index);
add_edge(cyclic, v, g);
tugs.push_back(v);
@@ -631,7 +637,7 @@ void buildTugTrigger(NGHolder &g, NFAVertex cyclic, NFAVertex v,
NFAVertex t = clone_vertex(g, v);
depths[t] = depths[v];
DEBUG_PRINTF("there are other paths, cloned tug %u from vertex %u\n",
DEBUG_PRINTF("there are other paths, cloned tug %zu from vertex %zu\n",
g[t].index, g[v].index);
tugs.push_back(t);
@@ -648,7 +654,7 @@ NFAVertex createCyclic(NGHolder &g, ReachSubgraph &rsi) {
NFAVertex cyclic = clone_vertex(g, last);
add_edge(cyclic, cyclic, g);
DEBUG_PRINTF("created cyclic vertex %u\n", g[cyclic].index);
DEBUG_PRINTF("created cyclic vertex %zu\n", g[cyclic].index);
return cyclic;
}
@@ -659,7 +665,7 @@ NFAVertex createPos(NGHolder &g, ReachSubgraph &rsi) {
g[pos].char_reach = g[first].char_reach;
DEBUG_PRINTF("created pos vertex %u\n", g[pos].index);
DEBUG_PRINTF("created pos vertex %zu\n", g[pos].index);
return pos;
}
@@ -705,7 +711,7 @@ void unpeelNearEnd(NGHolder &g, ReachSubgraph &rsi,
NFAVertex d = clone_vertex(g, last);
depths[d] = depths[last];
DEBUG_PRINTF("created vertex %u\n", g[d].index);
DEBUG_PRINTF("created vertex %zu\n", g[d].index);
for (auto v : *succs) {
add_edge(d, v, g);
@@ -946,7 +952,7 @@ bool peelSubgraph(const NGHolder &g, const Grey &grey, ReachSubgraph &rsi,
zap = it;
break;
} else {
DEBUG_PRINTF("%u is involved in another repeat\n", g[*it].index);
DEBUG_PRINTF("%zu is involved in another repeat\n", g[*it].index);
}
}
DEBUG_PRINTF("peeling %zu vertices from front\n",
@@ -963,7 +969,7 @@ bool peelSubgraph(const NGHolder &g, const Grey &grey, ReachSubgraph &rsi,
zap = it.base(); // Note: erases everything after it.
break;
} else {
DEBUG_PRINTF("%u is involved in another repeat\n", g[*it].index);
DEBUG_PRINTF("%zu is involved in another repeat\n", g[*it].index);
}
}
DEBUG_PRINTF("peeling %zu vertices from back\n",
@@ -974,7 +980,7 @@ bool peelSubgraph(const NGHolder &g, const Grey &grey, ReachSubgraph &rsi,
// no-no.
for (auto v : rsi.vertices) {
if (contains(created, v)) {
DEBUG_PRINTF("vertex %u is in another repeat\n", g[v].index);
DEBUG_PRINTF("vertex %zu is in another repeat\n", g[v].index);
return false;
}
}
@@ -997,7 +1003,7 @@ void peelStartDotStar(const NGHolder &g,
NFAVertex first = rsi.vertices.front();
if (depths.at(first).fromStartDotStar.min == depth(1)) {
DEBUG_PRINTF("peeling start front vertex %u\n", g[first].index);
DEBUG_PRINTF("peeling start front vertex %zu\n", g[first].index);
rsi.vertices.erase(rsi.vertices.begin());
reprocessSubgraph(g, grey, rsi);
}
@@ -1006,8 +1012,8 @@ void peelStartDotStar(const NGHolder &g,
static
void buildReachSubgraphs(const NGHolder &g, vector<ReachSubgraph> &rs,
const u32 minNumVertices) {
const ReachFilter<NFAGraph> fil(&g.g);
const RepeatGraph rg(g.g, fil);
const ReachFilter<NGHolder> fil(&g);
const RepeatGraph rg(g, fil);
if (!isCompBigEnough(rg, minNumVertices)) {
DEBUG_PRINTF("component not big enough, bailing\n");
@@ -1015,19 +1021,17 @@ void buildReachSubgraphs(const NGHolder &g, vector<ReachSubgraph> &rs,
}
NFAUndirectedGraph ug;
ue2::unordered_map<NFAVertex, NFAUndirectedVertex> old2new;
ue2::unordered_map<u32, NFAVertex> newIdx2old;
createUnGraph(rg, true, true, ug, old2new, newIdx2old);
unordered_map<RepeatGraph::vertex_descriptor, NFAUndirectedVertex> old2new;
createUnGraph(rg, true, true, ug, old2new);
ue2::unordered_map<NFAUndirectedVertex, u32> repeatMap;
unordered_map<NFAUndirectedVertex, u32> repeatMap;
unsigned int num;
num = connected_components(ug, make_assoc_property_map(repeatMap));
DEBUG_PRINTF("found %u connected repeat components\n", num);
// Now, we build a set of topo-ordered ReachSubgraphs.
vector<NFAVertex> topoOrder;
buildTopoOrder(rg, topoOrder);
vector<NFAVertex> topoOrder = buildTopoOrder(rg);
rs.resize(num);
@@ -1078,7 +1082,7 @@ bool entered_at_fixed_offset(NFAVertex v, const NGHolder &g,
if (is_triggered(g) && !contains(reached_by_fixed_tops, v)) {
/* can't do this for infix/suffixes unless we know trigger literals
* can only occur at one offset */
DEBUG_PRINTF("bad top(s) for %u\n", g[v].index);
DEBUG_PRINTF("bad top(s) for %zu\n", g[v].index);
return false;
}
@@ -1098,8 +1102,8 @@ bool entered_at_fixed_offset(NFAVertex v, const NGHolder &g,
for (auto u : inv_adjacent_vertices_range(v, g)) {
const depth &u_max_depth = depths.at(u).fromStart.max;
DEBUG_PRINTF("pred %u max depth %s from start\n",
g[u].index, u_max_depth.str().c_str());
DEBUG_PRINTF("pred %zu max depth %s from start\n", g[u].index,
u_max_depth.str().c_str());
if (u_max_depth != first - depth(1)) {
return false;
}
@@ -1122,7 +1126,7 @@ NFAVertex buildTriggerStates(NGHolder &g, const vector<CharReach> &trigger,
u = v;
}
DEBUG_PRINTF("trigger len=%zu has sink %u\n", trigger.size(), g[u].index);
DEBUG_PRINTF("trigger len=%zu has sink %zu\n", trigger.size(), g[u].index);
return u;
}
@@ -1252,7 +1256,7 @@ void buildRepeatGraph(NGHolder &rg,
if (is_triggered(rg)) {
// Add vertices for all our triggers
addTriggers(rg, triggers);
rg.renumberVertices();
renumber_vertices(rg);
// We don't know anything about how often this graph is triggered, so we
// make the start vertex cyclic for the purposes of this analysis ONLY.
@@ -1274,30 +1278,26 @@ void buildInputGraph(NGHolder &lhs,
ue2::unordered_map<NFAVertex, NFAVertex> &lhs_map,
const NGHolder &g, const NFAVertex first,
const map<u32, vector<vector<CharReach>>> &triggers) {
DEBUG_PRINTF("building lhs with first=%u\n", g[first].index);
DEBUG_PRINTF("building lhs with first=%zu\n", g[first].index);
cloneHolder(lhs, g, &lhs_map);
assert(g.kind == lhs.kind);
addTriggers(lhs, triggers);
lhs.renumberVertices();
renumber_vertices(lhs);
// Replace each back-edge (u,v) with an edge (startDs,v), which will
// generate entries at at least the rate of the loop created by that
// back-edge.
set<NFAEdge> dead;
BackEdges<set<NFAEdge> > backEdgeVisitor(dead);
depth_first_search(
lhs.g, visitor(backEdgeVisitor)
.root_vertex(lhs.start)
.vertex_index_map(get(&NFAGraphVertexProps::index, lhs.g)));
depth_first_search(lhs, visitor(backEdgeVisitor).root_vertex(lhs.start));
for (const auto &e : dead) {
const NFAVertex u = source(e, lhs), v = target(e, lhs);
if (u == v) {
continue; // Self-loops are OK.
}
DEBUG_PRINTF("replacing back-edge (%u,%u) with edge (startDs,%u)\n",
lhs[u].index, lhs[v].index,
lhs[v].index);
DEBUG_PRINTF("replacing back-edge (%zu,%zu) with edge (startDs,%zu)\n",
lhs[u].index, lhs[v].index, lhs[v].index);
add_edge_if_not_present(lhs.startDs, v, lhs);
remove_edge(e, lhs);
@@ -1384,13 +1384,13 @@ bool hasSoleEntry(const NGHolder &g, const ReachSubgraph &rsi,
for (const auto &v : rsi.vertices) {
assert(!is_special(v, g)); // no specials in repeats
assert(contains(rg_map, v));
DEBUG_PRINTF("rg vertex %u in repeat\n", rg[rg_map.at(v)].index);
DEBUG_PRINTF("rg vertex %zu in repeat\n", rg[rg_map.at(v)].index);
region_map.emplace(rg_map.at(v), repeat_region);
}
for (const auto &v : vertices_range(rg)) {
if (!contains(region_map, v)) {
DEBUG_PRINTF("rg vertex %u in lhs (trigger)\n", rg[v].index);
DEBUG_PRINTF("rg vertex %zu in lhs (trigger)\n", rg[v].index);
region_map.emplace(v, lhs_region);
}
}
@@ -1432,7 +1432,7 @@ struct StrawWalker {
if (next == v) { // Ignore self loop.
++ai;
if (ai == ae) {
return NFAGraph::null_vertex();
return NGHolder::null_vertex();
}
next = *ai;
}
@@ -1447,7 +1447,7 @@ struct StrawWalker {
succs.erase(v);
for (tie(ai, ae) = adjacent_vertices(v, g); ai != ae; ++ai) {
next = *ai;
DEBUG_PRINTF("checking %u\n", g[next].index);
DEBUG_PRINTF("checking %zu\n", g[next].index);
if (next == v) {
continue;
}
@@ -1468,32 +1468,31 @@ struct StrawWalker {
return next;
}
DEBUG_PRINTF("bailing\n");
return NFAGraph::null_vertex();
return NGHolder::null_vertex();
}
return next;
}
NFAVertex walk(NFAVertex v, vector<NFAVertex> &straw) const {
DEBUG_PRINTF("walk from %u\n", g[v].index);
DEBUG_PRINTF("walk from %zu\n", g[v].index);
ue2::unordered_set<NFAVertex> visited;
straw.clear();
while (!is_special(v, g)) {
DEBUG_PRINTF("checking %u\n", g[v].index);
DEBUG_PRINTF("checking %zu\n", g[v].index);
NFAVertex next = step(v);
if (next == NFAGraph::null_vertex()) {
if (next == NGHolder::null_vertex()) {
break;
}
if (!visited.insert(next).second) {
DEBUG_PRINTF("already visited %u, bailing\n",
g[next].index);
DEBUG_PRINTF("already visited %zu, bailing\n", g[next].index);
break; /* don't want to get stuck in any complicated loops */
}
const CharReach &reach_v = g[v].char_reach;
const CharReach &reach_next = g[next].char_reach;
if (!reach_v.isSubsetOf(reach_next)) {
DEBUG_PRINTF("%u's reach is not a superset of %u's\n",
DEBUG_PRINTF("%zu's reach is not a superset of %zu's\n",
g[next].index, g[v].index);
break;
}
@@ -1501,7 +1500,7 @@ struct StrawWalker {
// If this is cyclic with the right reach, we're done. Note that
// startDs fulfils this requirement.
if (hasSelfLoop(next, g) && !isBoundedRepeatCyclic(next)) {
DEBUG_PRINTF("found cyclic %u\n", g[next].index);
DEBUG_PRINTF("found cyclic %zu\n", g[next].index);
return next;
}
@@ -1510,7 +1509,7 @@ struct StrawWalker {
}
straw.clear();
return NFAGraph::null_vertex();
return NGHolder::null_vertex();
}
private:
@@ -1525,8 +1524,8 @@ static
NFAVertex walkStrawToCyclicRev(const NGHolder &g, NFAVertex v,
const vector<BoundedRepeatData> &all_repeats,
vector<NFAVertex> &straw) {
typedef boost::reverse_graph<NFAGraph, const NFAGraph&> RevGraph;
const RevGraph revg(g.g);
typedef boost::reverse_graph<NGHolder, const NGHolder &> RevGraph;
const RevGraph revg(g);
auto cyclic = StrawWalker<RevGraph>(g, revg, all_repeats).walk(v, straw);
reverse(begin(straw), end(straw)); // path comes from cyclic
@@ -1537,7 +1536,7 @@ static
NFAVertex walkStrawToCyclicFwd(const NGHolder &g, NFAVertex v,
const vector<BoundedRepeatData> &all_repeats,
vector<NFAVertex> &straw) {
return StrawWalker<NFAGraph>(g, g.g, all_repeats).walk(v, straw);
return StrawWalker<NGHolder>(g, g, all_repeats).walk(v, straw);
}
/** True if entries to this subgraph must pass through a cyclic state with
@@ -1553,7 +1552,7 @@ bool hasCyclicSupersetEntryPath(const NGHolder &g, const ReachSubgraph &rsi,
// until we encounter our cyclic, all of which must have superset reach.
vector<NFAVertex> straw;
return walkStrawToCyclicRev(g, rsi.vertices.front(), all_repeats, straw) !=
NFAGraph::null_vertex();
NGHolder::null_vertex();
}
static
@@ -1561,7 +1560,7 @@ bool hasCyclicSupersetExitPath(const NGHolder &g, const ReachSubgraph &rsi,
const vector<BoundedRepeatData> &all_repeats) {
vector<NFAVertex> straw;
return walkStrawToCyclicFwd(g, rsi.vertices.back(), all_repeats, straw) !=
NFAGraph::null_vertex();
NGHolder::null_vertex();
}
static
@@ -1844,7 +1843,7 @@ void buildFeeder(NGHolder &g, const BoundedRepeatData &rd,
add_edge(u, feeder, g);
}
DEBUG_PRINTF("added feeder %u\n", g[feeder].index);
DEBUG_PRINTF("added feeder %zu\n", g[feeder].index);
} else {
// No neg trigger means feeder is empty, and unnecessary.
assert(g[rd.pos_trigger].char_reach.all());
@@ -1892,13 +1891,13 @@ bool improveLeadingRepeat(NGHolder &g, BoundedRepeatData &rd,
// This transformation is only safe if the straw path from startDs that
// we've discovered can *only* lead to this repeat, since we're going to
// remove the self-loop on startDs.
if (hasGreaterOutDegree(2, g.startDs, g)) {
if (proper_out_degree(g.startDs, g) > 1) {
DEBUG_PRINTF("startDs has other successors\n");
return false;
}
for (const auto &v : straw) {
if (proper_out_degree(v, g) != 1) {
DEBUG_PRINTF("branch between startDs and repeat, from vertex %u\n",
DEBUG_PRINTF("branch between startDs and repeat, from vertex %zu\n",
g[v].index);
return false;
}
@@ -2068,8 +2067,8 @@ public:
const depth &our_depth_in)
: top_depths(top_depths_in), our_depth(our_depth_in) {}
void discover_vertex(NFAVertex v, UNUSED const NFAGraph &g) {
DEBUG_PRINTF("discovered %u (depth %s)\n", g[v].index,
void discover_vertex(NFAVertex v, UNUSED const NGHolder &g) {
DEBUG_PRINTF("discovered %zu (depth %s)\n", g[v].index,
our_depth.str().c_str());
auto it = top_depths.find(v);
@@ -2120,22 +2119,21 @@ void populateFixedTopInfo(const map<u32, u32> &fixed_depth_tops,
}
}
DEBUG_PRINTF("scanning from %u depth=%s\n", g[v].index,
DEBUG_PRINTF("scanning from %zu depth=%s\n", g[v].index,
td.str().c_str());
/* for each vertex reachable from v update its map to reflect that it is
* reachable from a top of depth td. */
depth_first_visit(
g.g, v, pfti_visitor(top_depths, td),
make_iterator_property_map(colours.begin(),
get(&NFAGraphVertexProps::index, g.g)));
depth_first_visit(g, v, pfti_visitor(top_depths, td),
make_iterator_property_map(colours.begin(),
get(vertex_index, g)));
}
for (const auto &v_depth : top_depths) {
const NFAVertex v = v_depth.first;
const depth &d = v_depth.second;
if (d.is_finite()) {
DEBUG_PRINTF("%u reached by fixed tops at depth %s\n",
DEBUG_PRINTF("%zu reached by fixed tops at depth %s\n",
g[v].index, d.str().c_str());
reached_by_fixed_tops->insert(v);
}
@@ -2152,19 +2150,16 @@ bool hasOverlappingRepeats(UNUSED const NGHolder &g,
for (const auto &br : repeats) {
if (contains(involved, br.cyclic)) {
DEBUG_PRINTF("already seen cyclic %u\n",
g[br.cyclic].index);
DEBUG_PRINTF("already seen cyclic %zu\n", g[br.cyclic].index);
return true;
}
if (contains(involved, br.pos_trigger)) {
DEBUG_PRINTF("already seen pos %u\n",
g[br.pos_trigger].index);
DEBUG_PRINTF("already seen pos %zu\n", g[br.pos_trigger].index);
return true;
}
for (auto v : br.tug_triggers) {
if (contains(involved, v)) {
DEBUG_PRINTF("already seen tug %u\n",
g[v].index);
DEBUG_PRINTF("already seen tug %zu\n", g[v].index);
return true;
}
}
@@ -2310,7 +2305,7 @@ void analyseRepeats(NGHolder &g, const ReportManager *rm,
// Go to town on the remaining acceptable subgraphs.
ue2::unordered_set<NFAVertex> created;
for (auto &rsi : rs) {
DEBUG_PRINTF("subgraph (beginning vertex %u) is a {%s,%s} repeat\n",
DEBUG_PRINTF("subgraph (beginning vertex %zu) is a {%s,%s} repeat\n",
g[rsi.vertices.front()].index,
rsi.repeatMin.str().c_str(), rsi.repeatMax.str().c_str());
@@ -2343,7 +2338,7 @@ void analyseRepeats(NGHolder &g, const ReportManager *rm,
// Some of our analyses require correctly numbered vertices, so we
// renumber after changes.
g.renumberVertices();
renumber_vertices(g);
}
bool modified_start_ds = false;
@@ -2384,8 +2379,8 @@ void analyseRepeats(NGHolder &g, const ReportManager *rm,
// We have modified the graph, so we need to ensure that our edges
// and vertices are correctly numbered.
g.renumberVertices();
g.renumberEdges();
renumber_vertices(g);
renumber_edges(g);
// Remove stray report IDs.
clearReports(g);
}
@@ -2424,14 +2419,14 @@ bool isPureRepeat(const NGHolder &g, PureRepeat &repeat) {
// Must be start anchored.
assert(edge(g.startDs, g.startDs, g).second);
if (hasGreaterOutDegree(1, g.startDs, g)) {
if (out_degree(g.startDs, g) > 1) {
DEBUG_PRINTF("Unanchored\n");
return false;
}
// Must not be EOD-anchored.
assert(edge(g.accept, g.acceptEod, g).second);
if (hasGreaterInDegree(1, g.acceptEod, g)) {
if (in_degree(g.acceptEod, g) > 1) {
DEBUG_PRINTF("EOD anchored\n");
return false;
}