// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Graph optimizations
//
// Code available from: http://www.veripool.org/verilator
//
//*************************************************************************
//
// Copyright 2005-2019 by Wilson Snyder.  This program is free software; you can
// redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
//
// Verilator is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
//*************************************************************************

#include "config_build.h"
#include "verilatedos.h"

#include "V3Global.h"
#include "V3GraphDfa.h"
#include "V3GraphAlg.h"

#include <cstdarg>
#include <map>
#include <set>
#include <stack>

//######################################################################
//######################################################################
// Algorithms - find starting node

DfaVertex* DfaGraph::findStart() {
    DfaVertex* startp = NULL;
    for (V3GraphVertex* vertexp = this->verticesBeginp(); vertexp; vertexp=vertexp->verticesNextp()) {
	if (DfaVertex* vvertexp = dynamic_cast<DfaVertex*>(vertexp)) {
	    if (vvertexp->start()) {
                if (startp) vertexp->v3fatalSrc("Multiple start points in NFA graph");
		startp = vvertexp;
	    }
	} else {
            vertexp->v3fatalSrc("Non DfaVertex in DfaGraph");
	}
    }
    if (!startp) v3fatalSrc("No start point in NFA graph");
    return startp;
}

//######################################################################
//######################################################################
// Algorithms - convert NFA to a DFA
// Uses the Subset Construction Algorithm

class GraphNfaToDfa : GraphAlg<> {
    // We have two types of nodes in one graph, NFA and DFA nodes.
    // Edges from NFA to NFA come from the user, and indicate input or epsilon transitions
    // Edges from DFA to NFA indicate the NFA from which that DFA was formed.
    // Edges from DFA to DFA indicate a completed input transition
private:
    // TYPES
    typedef std::deque<DfaVertex*> DfaStates;
    typedef std::multimap<vluint64_t,DfaVertex*> HashMap;

    // MEMBERS
    uint32_t		m_step;		// Processing step, so we can avoid clearUser all the time
    HashMap		m_hashMap;	// Dfa Vertex for each set of NFA vertexes

#ifdef VL_CPPCHECK
    static int debug() { return 9; }
#else
    static int debug() { return 0; }
#endif

    // METHODS
    DfaGraph* graphp() { return static_cast<DfaGraph*>(m_graphp); }
    bool nfaState(V3GraphVertex* vertexp) { return vertexp->color()==0; }
    //bool dfaState(V3GraphVertex* vertexp) { return vertexp->color()==1; }

    void nextStep() { m_step++; }

    bool unseenNfaThisStep(V3GraphVertex* vertexp) {
	// A nfa node not already seen this processing step
	return (nfaState(vertexp) && !(vertexp->user()==m_step));
    }

    DfaVertex* newDfaVertex(DfaVertex* nfaTemplatep=NULL) {
        DfaVertex* vertexp = new DfaVertex(graphp());
	vertexp->color(1);  // Mark as dfa
	if (nfaTemplatep && nfaTemplatep->start()) vertexp->start(true);
	if (nfaTemplatep && nfaTemplatep->accepting()) vertexp->accepting(true);
	UINFO(9, "        New "<<vertexp<<endl);
	return vertexp;
    }

    // Hashing
    static uint32_t hashVertex(V3GraphVertex* vertexp) {
	union { void* up; struct {uint32_t upper; uint32_t lower;} l;} u;
	u.l.upper=0; u.l.lower=0; u.up=vertexp;
	return u.l.upper ^ u.l.lower;
    }

    uint32_t hashDfaOrigins(DfaVertex* dfaStatep) {
	// Find the NFA states this dfa came from,
	// Record a checksum, so we can search for it later by the list of nfa nodes.
	// The order of the nodes is not deterministic; the hash thus must not depend on order of edges
	uint32_t hash = 0;
	// Foreach NFA state (this DFA state was formed from)
	if (debug()) nextStep();
	for (V3GraphEdge* dfaEdgep = dfaStatep->outBeginp(); dfaEdgep; dfaEdgep=dfaEdgep->outNextp()) {
	    if (nfaState(dfaEdgep->top())) {
		DfaVertex* nfaStatep = static_cast<DfaVertex*>(dfaEdgep->top());
		hash ^= hashVertex(nfaStatep);
		if (debug()) {
                    if (nfaStatep->user()==m_step) {
                        nfaStatep->v3fatalSrc("DFA state points to duplicate NFA state.");
                    }
		    nfaStatep->user(m_step);
		}
	    }
	}
	return hash;
    }

    uint32_t hashDfaOrigins(const DfaStates& nfasWithInput) {
	// Find the NFA states this dfa came from,
	uint32_t hash = 0;
	for (DfaStates::const_iterator nfaIt=nfasWithInput.begin(); nfaIt!=nfasWithInput.end(); ++nfaIt) {
	    DfaVertex* nfaStatep = *nfaIt;
	    hash ^= hashVertex(nfaStatep);
	}
	return hash;
    }

    bool compareDfaOrigins(const DfaStates& nfasWithInput, DfaVertex* dfa2p) {
	// Return true if the NFA nodes both DFAs came from are the same list
	// Assume there are no duplicates in either input list or NFAs under dfa2
	nextStep();
	// Mark all input vertexes
	int num1s = 0;
	for (DfaStates::const_iterator nfaIt=nfasWithInput.begin(); nfaIt!=nfasWithInput.end(); ++nfaIt) {
	    DfaVertex* nfaStatep = *nfaIt;
	    nfaStatep->user(m_step);
	    num1s++;
	}
	if (!num1s) v3fatalSrc("DFA node construction that contains no NFA states");

	// Check comparison; must all be marked
	// (Check all in dfa2p were in dfa1p)
	int num2s = 0;
	for (V3GraphEdge* dfaEdgep = dfa2p->outBeginp(); dfaEdgep; dfaEdgep=dfaEdgep->outNextp()) {
	    if (nfaState(dfaEdgep->top())) {
		if (dfaEdgep->top()->user() != m_step) return false;
		num2s++;
	    }
	}
	// If we saw all of the nodes, then they have the same number of hits
	// (Else something in dfa1p that wasn't in dfa2p)
        return (num1s == num2s);
    }

    void insertDfaOrigins(DfaVertex* dfaStatep) {
	// Record the NFA states this dfa came from
	uint32_t hash = hashDfaOrigins(dfaStatep);
	m_hashMap.insert(make_pair(hash,dfaStatep));
    }

    DfaVertex* findDfaOrigins(const DfaStates& nfasWithInput) {
	// Find another DFA state which comes from the identical set of NFA states
	// The order of the nodes is not deterministic; the hash thus must not depend on order of edges
	uint32_t hash = hashDfaOrigins(nfasWithInput);

        std::pair<HashMap::iterator,HashMap::iterator> eqrange = m_hashMap.equal_range(hash);
	for (HashMap::iterator it = eqrange.first; it != eqrange.second; ++it) {
	    DfaVertex* testp = it->second;
	    if (compareDfaOrigins(nfasWithInput, testp)) {
		UINFO(9,"              DFA match for set: "<<testp<<endl);
		return testp;	// Identical
	    }
	}
	return NULL;  // No match
    }

    void findNfasWithInput(DfaVertex* dfaStatep, DfaInput input,
			   DfaStates& nfasWithInput) {
	// Return all NFA states, with the given input transition from
	// the nfa states a given dfa state was constructed from.
	nextStep();
	nfasWithInput.clear();	// NFAs with given input

	// Foreach NFA state (this DFA state was formed from)
	for (V3GraphEdge* dfaEdgep = dfaStatep->outBeginp(); dfaEdgep; dfaEdgep=dfaEdgep->outNextp()) {
	    if (nfaState(dfaEdgep->top())) {
		DfaVertex* nfaStatep = static_cast<DfaVertex*>(dfaEdgep->top());
		// Foreach input transition (on this nfaStatep)
		for (V3GraphEdge* nfaEdgep = nfaStatep->outBeginp(); nfaEdgep; nfaEdgep=nfaEdgep->outNextp()) {
		    DfaEdge* cNfaEdgep = static_cast<DfaEdge*>(nfaEdgep);
		    if (cNfaEdgep->input().toNodep() == input.toNodep()) {
			DfaVertex* nextStatep = static_cast<DfaVertex*>(cNfaEdgep->top());
			if (unseenNfaThisStep(nextStatep)) {  // Not processed?
			    nfasWithInput.push_back(nextStatep);
			    nextStatep->user(m_step);
			    UINFO(9,"      Reachable "<<nextStatep<<endl);
			}
		    }
		}
	    }
	}

	// Expand the nfasWithInput list to include epsilon states reachable by those on nfasWithInput
	for (DfaStates::iterator nfaIt=nfasWithInput.begin(); nfaIt!=nfasWithInput.end(); ++nfaIt) {
	    DfaVertex* nfaStatep = *nfaIt;
	    // Foreach epsilon-reachable (on this nfaStatep)
	    for (V3GraphEdge* nfaEdgep = nfaStatep->outBeginp(); nfaEdgep; nfaEdgep=nfaEdgep->outNextp()) {
		DfaEdge* cNfaEdgep = static_cast<DfaEdge*>(nfaEdgep);
		if (cNfaEdgep->epsilon()) {
		    DfaVertex* nextStatep = static_cast<DfaVertex*>(cNfaEdgep->top());
		    if (unseenNfaThisStep(nextStatep)) {  // Not processed?
			nfasWithInput.push_back(nextStatep);
			nextStatep->user(m_step);
			UINFO(9,"      Epsilon Reachable "<<nextStatep<<endl);
		    }
		}
	    }
	}
    }

    void main() {
	UINFO(5,"Dfa to Nfa conversion...\n");
	// Vertex::color() begin: 1 indicates vertex on DFA graph, 0=NFA graph
	m_graphp->clearColors();
	// Vertex::m_user begin: # indicates processed this m_step number
	m_graphp->userClearVertices();

	if (debug()>=6) m_graphp->dumpDotFilePrefixed("dfa_nfa");

	// Find NFA start
	DfaVertex* nfaStartp = graphp()->findStart();

	// Create new DFA State (start state) from the NFA states
	DfaVertex* dfaStartp = newDfaVertex(nfaStartp);

	DfaStates dfaUnprocps;		// Unprocessed DFA nodes
	dfaUnprocps.push_back(dfaStartp);

	UINFO(5,"Starting state conversion...\n");
	// Form DFA starting state from epsilon closure of NFA start
	nextStep();
	DfaStates workps; workps.push_back(nfaStartp);

	while (!workps.empty()) {  // While work
	    DfaVertex* nfaStatep = workps.back(); workps.pop_back();
	    //UINFO(9," Processing "<<nfaStatep<<endl);
	    nfaStatep->user(m_step);	// Mark as processed
	    // Add a edge so we can find NFAs from a given DFA.
	    // The NFA will never see this edge, because we only look at TO edges.
	    new DfaEdge(graphp(), dfaStartp, nfaStatep, DfaEdge::NA());
	    // Find epsilon closure of this nfa node, and destinations to work list
	    for (V3GraphEdge* nfaEdgep = nfaStatep->outBeginp(); nfaEdgep; nfaEdgep=nfaEdgep->outNextp()) {
		DfaEdge* cNfaEdgep = static_cast<DfaEdge*>(nfaEdgep);
		DfaVertex* ecNfaStatep = static_cast<DfaVertex*>(nfaEdgep->top());
		//UINFO(9,"   Consider "<<nfaEdgep->top()<<" EP "<<cNfaEdgep->epsilon()<<endl);
		if (cNfaEdgep->epsilon()
		    && unseenNfaThisStep(ecNfaStatep)) {  // Not processed?
		    workps.push_back(ecNfaStatep);
		}
	    }
	}
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("dfa_start");
	insertDfaOrigins(dfaStartp);

	int i=0;
	UINFO(5,"Main state conversion...\n");
	while (!dfaUnprocps.empty()) {
	    DfaVertex* dfaStatep = dfaUnprocps.back(); dfaUnprocps.pop_back();
	    UINFO(9,"  On dfaState "<<dfaStatep<<endl);

	    // From this dfaState, what corresponding nfaStates have what inputs?
            std::set<int> inputs;
	    // Foreach NFA state (this DFA state was formed from)
	    for (V3GraphEdge* dfaEdgep = dfaStatep->outBeginp(); dfaEdgep; dfaEdgep=dfaEdgep->outNextp()) {
		if (nfaState(dfaEdgep->top())) {
		    DfaVertex* nfaStatep = static_cast<DfaVertex*>(dfaEdgep->top());
		    // Foreach input on this nfaStatep
		    for (V3GraphEdge* nfaEdgep = nfaStatep->outBeginp(); nfaEdgep; nfaEdgep=nfaEdgep->outNextp()) {
			DfaEdge* cNfaEdgep = static_cast<DfaEdge*>(nfaEdgep);
			if (!cNfaEdgep->epsilon()) {
			    if (inputs.find(cNfaEdgep->input().toInt()) == inputs.end()) {
				inputs.insert(cNfaEdgep->input().toInt());
				UINFO(9,"    Input to "<<dfaStatep<<" is "<<(cNfaEdgep->input().toInt())<<" via "<<nfaStatep<<endl);
			    }
			}
		    }
		}
	    }

	    // Foreach input state (NFA inputs of this DFA state)
            for (std::set<int>::const_iterator inIt=inputs.begin(); inIt!=inputs.end(); ++inIt) {
		DfaInput input = *inIt;
		UINFO(9,"    ==="<<++i<<"=======================\n");
                UINFO(9,"    On input "<<cvtToHex(input.toNodep())<<endl);

		// Find all states reachable for given input
		DfaStates nfasWithInput;
		findNfasWithInput(dfaStatep, input, nfasWithInput/*ref*/);

		// nfasWithInput now maps to the DFA we want a transition to.
		// Does a DFA already exist with this, and only this subset of NFA's?
		DfaVertex* toDfaStatep = findDfaOrigins(nfasWithInput);
		if (!toDfaStatep) {
		    // Doesn't exist, make new dfa state corresponding to this one,
		    toDfaStatep = newDfaVertex();
		    dfaUnprocps.push_back(toDfaStatep);  // Add to process list
		    // Track what nfa's point to it.
		    for (DfaStates::const_iterator nfaIt=nfasWithInput.begin(); nfaIt!=nfasWithInput.end(); ++nfaIt) {
			UINFO(9,"          NewContainsNfa "<<*nfaIt<<endl);
                        new DfaEdge(graphp(), toDfaStatep, *nfaIt, DfaEdge::NA());
			if ((*nfaIt)->accepting()) toDfaStatep->accepting(true);
		    }
		    insertDfaOrigins(toDfaStatep);
		}
		// Add input transition
                new DfaEdge(graphp(), dfaStatep, toDfaStatep, input);

		if (debug()>=6) m_graphp->dumpDotFilePrefixed("step");
	    }
	}

	// Remove old NFA states
	UINFO(5,"Removing NFA states...\n");
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("dfa_withnfa");
	for (V3GraphVertex* nextp,*vertexp = m_graphp->verticesBeginp(); vertexp; vertexp=nextp) {
	    nextp = vertexp->verticesNextp();
	    if (nfaState(vertexp)) {
		vertexp->unlinkDelete(m_graphp); VL_DANGLING(vertexp);
	    }
	}

	UINFO(5,"Done.\n");
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("dfa_done");
    }

public:
    GraphNfaToDfa(V3Graph* graphp, V3EdgeFuncP edgeFuncp)
        : GraphAlg<>(graphp, edgeFuncp) {
	m_step = 0;
	main();
    }
    ~GraphNfaToDfa() {}
};

void DfaGraph::nfaToDfa() {
    GraphNfaToDfa(this, &V3GraphEdge::followAlwaysTrue);
}

//######################################################################
//######################################################################
// Algorithms - optimize a DFA structure
//
// Scan the DFA, cleaning up trailing states.

class DfaGraphReduce : GraphAlg<> {
private:
    // METHODS
    static int debug() { return 0; }
    DfaGraph* graphp() { return static_cast<DfaGraph*>(m_graphp); }

    bool isDead(DfaVertex* vertexp) {
	// A state is dead if not accepting, and goes nowhere
	if (vertexp->accepting() || vertexp->start()) return false;
	for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep=edgep->outNextp()) {
	    if (edgep->top() != vertexp) return false;
	}
	return true;
    }

    void optimize_accepting_out() {
	// Delete outbound edges from accepting states
	// (As once we've accepted, we no longer care about anything else.)
	for (V3GraphVertex* vertexp = m_graphp->verticesBeginp(); vertexp; vertexp=vertexp->verticesNextp()) {
	    if (DfaVertex* vvertexp = dynamic_cast<DfaVertex*>(vertexp)) {
		if (vvertexp->accepting()) {
		    for (V3GraphEdge* nextp,*edgep = vertexp->outBeginp(); edgep; edgep=nextp) {
			nextp = edgep->outNextp();
			edgep->unlinkDelete(); VL_DANGLING(edgep);
		    }
		}
	    }
	}
    }

    void optimize_orphans() {
	// Remove states that don't come from start
	// Presumably the previous optimization orphaned them.

	// Vertex::m_user begin: 1 indicates on the work list, 2 processed
	// (Otherwise we might have nodes on the list twice, and reference after deleting them.)
	m_graphp->userClearVertices();

	DfaVertex* startp = graphp()->findStart();
        std::stack<V3GraphVertex*> workps;  workps.push(startp);

	// Mark all nodes connected to start
	while (!workps.empty()) {
	    V3GraphVertex* vertexp = workps.top(); workps.pop();
	    vertexp->user(2);  // Processed
	    // Add nodes from here to the work list
	    for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep=edgep->outNextp()) {
		V3GraphVertex* tovertexp = edgep->top();
		if (!tovertexp->user()) {
		    workps.push(tovertexp);
		    tovertexp->user(1);
		}
	    }
	}

	// Delete all nodes not connected
	for (V3GraphVertex* nextp,*vertexp = m_graphp->verticesBeginp(); vertexp; vertexp=nextp) {
	    nextp = vertexp->verticesNextp();
	    if (!vertexp->user()) {
		vertexp->unlinkDelete(m_graphp); VL_DANGLING(vertexp);
	    }
	}
    }

    void optimize_no_outbound() {
	// Non-accepting states with no outbound transitions may be
	// deleted.  Then, any arcs feeding those states, and perhaps those
	// states...

	// Vertex::m_user begin: 1 indicates on the work list
	// (Otherwise we might have nodes on the list twice, and reference after deleting them.)
	m_graphp->userClearVertices();

	// Find all dead vertexes
        std::stack<DfaVertex*> workps;
	for (V3GraphVertex* vertexp = m_graphp->verticesBeginp(); vertexp; vertexp=vertexp->verticesNextp()) {
	    if (DfaVertex* vvertexp = dynamic_cast<DfaVertex*>(vertexp)) {
		workps.push(vvertexp);
		vertexp->user(1);
	    } else {
		// If ever remove this, need dyn cast below
                vertexp->v3fatalSrc("Non DfaVertex in dfa graph");
	    }
	}

	// While deadness... Delete and find new dead nodes.
	while (!workps.empty()) {
	    DfaVertex* vertexp = workps.top(); workps.pop();
	    vertexp->user(0);
	    if (isDead(vertexp)) {
		// Add nodes that go here to the work list
		for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep=edgep->inNextp()) {
		    DfaVertex* fromvertexp = static_cast<DfaVertex*>(edgep->fromp());
		    if (fromvertexp != vertexp
			&& !fromvertexp->user()) {
			workps.push(fromvertexp);
			fromvertexp->user(1);
		    }
		}
		// Transitions to this state removed by the unlink function
		vertexp->unlinkDelete(m_graphp); VL_DANGLING(vertexp);
	    }
	}
    }
public:
    DfaGraphReduce(V3Graph* graphp, V3EdgeFuncP edgeFuncp)
        : GraphAlg<>(graphp, edgeFuncp) {
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("opt_in");
	optimize_accepting_out();
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("opt_acc");
	optimize_orphans();
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("opt_orph");
	optimize_no_outbound();
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("opt_noout");
    }
    ~DfaGraphReduce() {}
};

void DfaGraph::dfaReduce() {
    DfaGraphReduce(this, &V3GraphEdge::followAlwaysTrue);
}

//######################################################################
//######################################################################
// Algorithms - complement a DFA
//
// The traditional algorithm is to make a rejecting state, add edges to
// reject from all missing values, then swap accept and reject.  Rather
// than swap at the end, it's faster if we swap up front, then do the edge
// changes.
//
// 1. Since we didn't log rejecting states, make a temp state (this will be
// the old accept, and new reject).
//
// 2. All vertexes except start/accept get edges to NEW accept for any
// non-existing case.  Weedely we don't have a nice way of representing
// this so we just create a edge for each case and mark it "complemented."
//
// 3. Delete temp vertex (old accept/new reject) and related edges.
// The user's old accept is now the new accept.  This is imporant as
// we want the virtual type of it to be intact.

class DfaGraphComplement : GraphAlg<> {
private:
    // MEMBERS
    DfaVertex* m_tempNewerReject;

    // METHODS
    static int debug() { return 9; }
    DfaGraph* graphp() { return static_cast<DfaGraph*>(m_graphp); }

    void add_complement_edges() {
	// Find accepting vertex
	DfaVertex* acceptp = NULL;
	for (V3GraphVertex* vertexp = m_graphp->verticesBeginp(); vertexp; vertexp=vertexp->verticesNextp()) {
	    if (DfaVertex* vvertexp = dynamic_cast<DfaVertex*>(vertexp)) {
		if (vvertexp->accepting()) {
		    acceptp = vvertexp;
		    break;
		}
	    }
	}
	if (!acceptp) v3fatalSrc("No accepting vertex in DFA");

	// Remap edges
	for (V3GraphVertex* vertexp = m_graphp->verticesBeginp(); vertexp; vertexp=vertexp->verticesNextp()) {
	    if (DfaVertex* vvertexp = dynamic_cast<DfaVertex*>(vertexp)) {
		//UINFO(9, "   on vertex "<<vvertexp->name()<<endl);
		if (!vvertexp->accepting() && vvertexp != m_tempNewerReject) {
		    for (V3GraphEdge* nextp, *edgep = vertexp->outBeginp(); edgep; edgep=nextp) {
			nextp = edgep->outNextp();
			if (!edgep->user()) { // Not processed
			    // Old edges to accept now go to new reject
			    DfaEdge* vedgep = static_cast<DfaEdge*>(edgep);
			    DfaVertex* tovertexp = static_cast<DfaVertex*>(edgep->top());
			    if (tovertexp->accepting()) {
				new DfaEdge(graphp(), vvertexp, m_tempNewerReject, vedgep);
				edgep->unlinkDelete(); VL_DANGLING(edgep);
			    }

			    // NOT of all values goes to accept
			    // We make a edge for each value to OR, IE
			    // edge(complemented,a) edge(complemented,b) means !(a | b)
			    if (!tovertexp->accepting()) {  // Note we must include edges moved above to reject
                                DfaEdge* newp = new DfaEdge(graphp(), vvertexp, acceptp, vedgep);
				newp->complement(!newp->complement());
				newp->user(1);
			    }
			}
		    }
		}
	    }
	}
    }
public:
    DfaGraphComplement(V3Graph* dfagraphp, V3EdgeFuncP edgeFuncp)
        : GraphAlg<>(dfagraphp, edgeFuncp) {
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("comp_in");

	// Vertex::m_user begin: 1 indicates new edge, no more processing
	m_graphp->userClearEdges();

	m_tempNewerReject = new DfaVertex(graphp());
	add_complement_edges();
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("comp_preswap");

	m_tempNewerReject->unlinkDelete(graphp()); m_tempNewerReject=NULL;
	if (debug()>=6) m_graphp->dumpDotFilePrefixed("comp_out");
    }
    ~DfaGraphComplement() {}
};

void DfaGraph::dfaComplement() {
    DfaGraphComplement(this, &V3GraphEdge::followAlwaysTrue);
}
