// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Ast node structures
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2020 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 "V3Ast.h"
#include "V3File.h"
#include "V3Global.h"
#include "V3Graph.h"
#include "V3PartitionGraph.h"  // Just for mtask dumping

#include <cstdarg>
#include <iomanip>
#include <vector>

//======================================================================
// Special methods

// We need these here, because the classes they point to aren't defined when we declare the class
const char* AstIfaceRefDType::broken() const {
    BROKEN_RTN(m_ifacep && !m_ifacep->brokeExists());
    BROKEN_RTN(m_cellp && !m_cellp->brokeExists());
    BROKEN_RTN(m_modportp && !m_modportp->brokeExists());
    return NULL;
}

AstIface* AstIfaceRefDType::ifaceViaCellp() const {
    return ((m_cellp && m_cellp->modp()) ? VN_CAST(m_cellp->modp(), Iface) : m_ifacep);
}

const char* AstNodeVarRef::broken() const {
    BROKEN_RTN(m_varScopep && !m_varScopep->brokeExists());
    BROKEN_RTN(m_varp && !m_varp->brokeExists());
    return NULL;
}

void AstNodeVarRef::cloneRelink() {
    if (m_varp && m_varp->clonep()) { m_varp = m_varp->clonep(); }
}

string AstNodeVarRef::hiernameProtect() const {
    return VIdProtect::protectWordsIf(hiername(), protect());
}

int AstNodeSel::bitConst() const {
    AstConst* constp = VN_CAST(bitp(), Const);
    return (constp ? constp->toSInt() : 0);
}

void AstNodeUOrStructDType::repairMemberCache() {
    clearCache();
    for (AstMemberDType* itemp = membersp(); itemp; itemp=VN_CAST(itemp->nextp(), MemberDType)) {
        if (m_members.find(itemp->name())!=m_members.end()) {
            itemp->v3error("Duplicate declaration of member name: "<<itemp->prettyNameQ()); }
        else m_members.insert(make_pair(itemp->name(), itemp));
    }
}

const char* AstNodeUOrStructDType::broken() const {
    vl_unordered_set<AstMemberDType*> exists;
    for (AstMemberDType* itemp = membersp(); itemp; itemp=VN_CAST(itemp->nextp(), MemberDType)) {
        exists.insert(itemp);
    }
    for (MemberNameMap::const_iterator it=m_members.begin(); it!=m_members.end(); ++it) {
        if (VL_UNCOVERABLE(exists.find(it->second) == exists.end())) {
            this->v3error("Internal: Structure member broken: "<<it->first);
            return "member broken";
        }
    }
    return NULL;
}

void AstNodeCond::numberOperate(V3Number& out, const V3Number& lhs,
                                const V3Number& rhs, const V3Number& ths) {
    if (lhs.isNeqZero()) out.opAssign(rhs); else out.opAssign(ths);
}

int AstBasicDType::widthAlignBytes() const {
    if (width()<=8) return 1;
    else if (width()<=16) return 2;
    else if (isQuad()) return 8;
    else return 4;
}

int AstBasicDType::widthTotalBytes() const {
    if (width()<=8) return 1;
    else if (width()<=16) return 2;
    else if (isQuad()) return 8;
    else return widthWords() * (VL_EDATASIZE / 8);
}

int AstNodeUOrStructDType::widthTotalBytes() const {
    if (width()<=8) return 1;
    else if (width()<=16) return 2;
    else if (isQuad()) return 8;
    else return widthWords() * (VL_EDATASIZE / 8);
}

int AstNodeUOrStructDType::widthAlignBytes() const {
    // Could do max across members but that would be slow,
    // instead intuit based on total structure size
    if (width()<=8) return 1;
    else if (width()<=16) return 2;
    else if (width()<=32) return 4;
    else return 8;
}

AstNodeBiop* AstEq::newTyped(FileLine* fl, AstNode* lhsp, AstNode* rhsp) {
    if (lhsp->isString() && rhsp->isString()) {
        return new AstEqN(fl, lhsp, rhsp);
    } else if (lhsp->isDouble() && rhsp->isDouble()) {
        return new AstEqD(fl, lhsp, rhsp);
    } else {
        return new AstEq(fl, lhsp, rhsp);
    }
}

AstNodeBiop* AstGte::newTyped(FileLine* fl, AstNode* lhsp, AstNode* rhsp) {
    if (lhsp->isString() && rhsp->isString()) {
        return new AstGteN(fl, lhsp, rhsp);
    } else if (lhsp->isDouble() && rhsp->isDouble()) {
        return new AstGteD(fl, lhsp, rhsp);
    } else if (lhsp->isSigned() && rhsp->isSigned()) {
        return new AstGteS(fl, lhsp, rhsp);
    } else {
        return new AstGte(fl, lhsp, rhsp);
    }
}

AstNodeBiop* AstLte::newTyped(FileLine* fl, AstNode* lhsp, AstNode* rhsp) {
    if (lhsp->isString() && rhsp->isString()) {
        return new AstLteN(fl, lhsp, rhsp);
    } else if (lhsp->isDouble() && rhsp->isDouble()) {
        return new AstLteD(fl, lhsp, rhsp);
    } else if (lhsp->isSigned() && rhsp->isSigned()) {
        return new AstLteS(fl, lhsp, rhsp);
    } else {
        return new AstLte(fl, lhsp, rhsp);
    }
}

AstNodeBiop* AstEqWild::newTyped(FileLine* fl, AstNode* lhsp, AstNode* rhsp) {
    if (lhsp->isString() && rhsp->isString()) {
        return new AstEqN(fl, lhsp, rhsp);
    } else if (lhsp->isDouble() && rhsp->isDouble()) {
        return new AstEqD(fl, lhsp, rhsp);
    } else {
        return new AstEqWild(fl, lhsp, rhsp);
    }
}

AstExecGraph::AstExecGraph(FileLine* fileline)
    : AstNode(AstType::atExecGraph, fileline) {
    m_depGraphp = new V3Graph;
}
AstExecGraph::~AstExecGraph() {
    VL_DO_DANGLING(delete m_depGraphp, m_depGraphp);
}

bool AstVar::isSigPublic() const {
    return (m_sigPublic || (v3Global.opt.allPublic() && !isTemp() && !isGenVar()));
}
bool AstVar::isScQuad() const {
    return (isSc() && isQuad() && !isScBv() && !isScBigUint());
}
bool AstVar::isScBv() const {
    return ((isSc() && width() >= v3Global.opt.pinsBv()) || m_attrScBv);
}
bool AstVar::isScUint() const {
    return ((isSc() && v3Global.opt.pinsScUint()
             && width() >= 2 && width() <= 64) && !isScBv());
}
bool AstVar::isScBigUint() const {
    return ((isSc() && v3Global.opt.pinsScBigUint()
             && width() >= 65 && width() <= 512) && !isScBv());
}

void AstVar::combineType(AstVarType type) {
    // These flags get combined with the existing settings of the flags.
    // We don't test varType for certain types, instead set flags since
    // when we combine wires cross-hierarchy we need a union of all characteristics.
    m_varType = type;
    // These flags get combined with the existing settings of the flags.
    if (type==AstVarType::TRIWIRE || type==AstVarType::TRI0 || type==AstVarType::TRI1) {
        m_tristate = true;
    }
    if (type==AstVarType::TRI0) {
        m_isPulldown = true;
    }
    if (type==AstVarType::TRI1) {
        m_isPullup = true;
    }
}

string AstVar::verilogKwd() const {
    if (isIO()) {
        return direction().verilogKwd();
    } else if (isTristate()) {
        return "tri";
    } else if (varType()==AstVarType::WIRE) {
        return "wire";
    } else if (varType()==AstVarType::WREAL) {
        return "wreal";
    } else if (varType()==AstVarType::IFACEREF) {
        return "ifaceref";
    } else {
        return dtypep()->name();
    }
}

class AstVar::VlArgTypeRecursed {
public:
    string m_oref;  // To output, reference part before name, "&"
    string m_osuffix;  // To output, suffixed after name, "[3]"
    string m_oprefix;  // To output, prefixed before name, "Foo_t"
    void clear() {
        m_oref.clear();
        m_osuffix.clear();
        m_oprefix.clear();
    }
    string refParen(const string& name) {
        return m_oref.empty() ? name : "("+m_oref+" "+name+")";
    }
};

string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& namespc) const {
    UASSERT_OBJ(!forReturn, this,
                "Internal data is never passed as return, but as first argument");
    string ostatic;
    if (isStatic() && namespc.empty()) ostatic = "static ";

    VlArgTypeRecursed info = vlArgTypeRecurse(forFunc, dtypep(), false);

    string oname;
    if (named) {
        oname += " ";
        if (!namespc.empty()) oname += namespc+"::";
        oname += VIdProtect::protectIf(name(), protect());
    }
    return ostatic + info.m_oprefix + info.refParen(oname) + info.m_osuffix;
}

AstVar::VlArgTypeRecursed AstVar::vlArgTypeRecurse(bool forFunc, const AstNodeDType* dtypep,
                                                   bool arrayed) const {
    dtypep = dtypep->skipRefp();
    if (const AstAssocArrayDType* adtypep = VN_CAST_CONST(dtypep, AssocArrayDType)) {
        VlArgTypeRecursed key = vlArgTypeRecurse(forFunc, adtypep->keyDTypep(), true);
        VlArgTypeRecursed sub = vlArgTypeRecurse(forFunc, adtypep->subDTypep(), true);
        string out = "VlAssocArray<";
        out += key.m_oprefix;
        if (!key.m_osuffix.empty() || !key.m_oref.empty()) {
            out += " " + key.m_osuffix + key.m_oref;
        }
        out += ", ";
        out += sub.m_oprefix;
        if (!sub.m_osuffix.empty() || !sub.m_oref.empty()) {
            out += " " + sub.m_osuffix + sub.m_oref;
        }
        out += "> ";
        VlArgTypeRecursed info;
        info.m_oprefix = out;
        return info;
    } else if (const AstQueueDType* adtypep = VN_CAST_CONST(dtypep, QueueDType)) {
        VlArgTypeRecursed sub = vlArgTypeRecurse(forFunc, adtypep->subDTypep(), true);
        VlArgTypeRecursed info;
        string out = "VlQueue<" + sub.m_oprefix;
        if (!sub.m_osuffix.empty() || !sub.m_oref.empty()) {
            out += " " + sub.m_osuffix + sub.m_oref;
        }
        // + 1 below as VlQueue uses 0 to mean unlimited, 1 to mean size() max is 1
        if (adtypep->boundp()) out += ", " + cvtToStr(adtypep->boundConst() + 1);
        out += "> ";
        info.m_oprefix = out;
        return info;
    } else if (const AstUnpackArrayDType* adtypep = VN_CAST_CONST(dtypep, UnpackArrayDType)) {
        VlArgTypeRecursed info = vlArgTypeRecurse(forFunc, adtypep->subDTypep(), arrayed);
        info.m_osuffix = "[" + cvtToStr(adtypep->declRange().elements()) + "]" + info.m_osuffix;
        return info;
    } else if (const AstBasicDType* bdtypep = dtypep->basicp()) {
        string otype;
        string oarray;
        bool strtype = bdtypep && bdtypep->keyword() == AstBasicDTypeKwd::STRING;
        string bitvec;
        if (bdtypep && !bdtypep->isOpaque() && !v3Global.opt.protectIds()) {
            // We don't print msb()/lsb() as multidim packed would require recursion,
            // and may confuse users as C++ data is stored always with bit 0 used
            bitvec += "/*"+cvtToStr(dtypep->width()-1)+":0*/";
        }
        if ((forFunc && isReadOnly())
            || bdtypep->keyword() == AstBasicDTypeKwd::CHARPTR
            || bdtypep->keyword() == AstBasicDTypeKwd::SCOPEPTR) otype += "const ";
        if (bdtypep && bdtypep->keyword() == AstBasicDTypeKwd::CHARPTR) {
            otype += "char*";
        } else if (bdtypep && bdtypep->keyword() == AstBasicDTypeKwd::SCOPEPTR) {
            otype += "VerilatedScope*";
        } else if (bdtypep && bdtypep->keyword() == AstBasicDTypeKwd::DOUBLE) {
            otype += "double";
        } else if (bdtypep && bdtypep->keyword() == AstBasicDTypeKwd::FLOAT) {
            otype += "float";
        } else if (strtype) {
            otype += "std::string";
        } else if (dtypep->widthMin() <= 8) {  // Handle unpacked arrays; not bdtypep->width
            otype += "CData"+bitvec;
        } else if (dtypep->widthMin() <= 16) {
            otype += "SData"+bitvec;
        } else if (dtypep->widthMin() <= VL_IDATASIZE) {
            otype += "IData"+bitvec;
        } else if (dtypep->isQuad()) {
            otype += "QData"+bitvec;
        } else if (dtypep->isWide()) {
            if (arrayed) {
                otype += "VlWide<"+cvtToStr(dtypep->widthWords())+"> ";
            } else {
                otype += "WData"+bitvec;  // []'s added later
                oarray += "["+cvtToStr(dtypep->widthWords())+"]";
            }
        }

        string oref;
        if (isDpiOpenArray()
            || (forFunc && (isWritable()
                            || direction() == VDirection::REF
                            || direction() == VDirection::CONSTREF
                            || (strtype && isNonOutput())))) {
            oref = "&";
        }

        VlArgTypeRecursed info;
        info.m_oprefix = otype;
        info.m_osuffix = oarray;
        info.m_oref = oref;
        //UINFO(9, "vlArgRec "<<"oprefix="<<info.m_oprefix<<" osuffix="<<info.m_osuffix
        //      <<" oref="<<info.m_oref<<" "<<dtypep);
        return info;
    } else {
        v3fatalSrc("Unknown data type in var type emitter: "<<dtypep->prettyName());
    }
}

string AstVar::vlEnumType() const {
    string arg;
    AstBasicDType* bdtypep = basicp();
    bool strtype = bdtypep && bdtypep->keyword()==AstBasicDTypeKwd::STRING;
    if (bdtypep && bdtypep->keyword()==AstBasicDTypeKwd::CHARPTR) {
        return "VLVT_PTR";
    } else if (bdtypep && bdtypep->keyword()==AstBasicDTypeKwd::SCOPEPTR) {
        return "VLVT_PTR";
    } else if (strtype) {
        arg += "VLVT_STRING";
    } else if (widthMin() <= 8) {
        arg += "VLVT_UINT8";
    } else if (widthMin() <= 16) {
        arg += "VLVT_UINT16";
    } else if (widthMin() <= VL_IDATASIZE) {
        arg += "VLVT_UINT32";
    } else if (isQuad()) {
        arg += "VLVT_UINT64";
    } else if (isWide()) {
        arg += "VLVT_WDATA";
    }
    // else return "VLVT_UNKNOWN"
    return arg;
}

string AstVar::vlEnumDir() const {
    string out;
    if (isInoutish()) {
        out = "VLVD_INOUT";
    } else if (isWritable()) {
        out = "VLVD_OUT";
    } else if (isNonOutput()) {
        out = "VLVD_IN";
    } else {
        out = "VLVD_NODIR";
    }
    //
    if (isSigUserRWPublic()) out += "|VLVF_PUB_RW";
    else if (isSigUserRdPublic()) out += "|VLVF_PUB_RD";
    //
    if (AstBasicDType* bdtypep = basicp()) {
        if (bdtypep->keyword().isDpiCLayout()) out += "|VLVF_DPI_CLAY";
    }
    return out;
}

string AstVar::vlPropInit() const {
    string out;
    out = vlEnumType();  // VLVT_UINT32 etc
    out += ", "+vlEnumDir();  // VLVD_IN etc
    if (AstBasicDType* bdtypep = basicp()) {
        out += ", VerilatedVarProps::Packed()";
        out += ", "+cvtToStr(bdtypep->left())+", "+cvtToStr(bdtypep->right());
    }
    bool first = true;
    for (AstNodeDType* dtp=dtypep(); dtp; ) {
        dtp = dtp->skipRefp();  // Skip AstRefDType/AstTypedef, or return same node
        if (AstNodeArrayDType* adtypep = VN_CAST(dtp, NodeArrayDType)) {
            if (first) {
                out += ", VerilatedVarProps::Unpacked()";
                first = false;
            }
            out += ", "+cvtToStr(adtypep->declRange().left())+", "+cvtToStr(adtypep->declRange().right());
            dtp = adtypep->subDTypep();
        }
        else break;  // AstBasicDType - nothing below
    }
    return out;
}

string AstVar::cPubArgType(bool named, bool forReturn) const {
    if (forReturn) named = false;
    string arg;
    if (isWide() && isReadOnly()) arg += "const ";
    if (widthMin() == 1) {
        arg += "bool";
    } else if (widthMin() <= VL_IDATASIZE) {
        arg += "uint32_t";
    } else if (widthMin() <= VL_QUADSIZE) {
        arg += "vluint64_t";
    } else {
        arg += "uint32_t";  // []'s added later
    }
    if (isWide()) {
        if (forReturn) v3error("Unsupported: Public functions with >64 bit outputs; make an output of a public task instead");
        arg += " (& "+name();
        arg += ")["+cvtToStr(widthWords())+"]";
    } else {
        if (!forReturn && (isWritable()
                           || direction().isRefOrConstRef())) arg += "&";
        if (named) arg += " "+name();
    }
    return arg;
}

string AstVar::dpiArgType(bool named, bool forReturn) const {
    if (forReturn) named = false;
    string arg;
    if (isDpiOpenArray()) {
        arg = "const svOpenArrayHandle";
    } else if (!basicp()) {
        arg = "UNKNOWN";
    } else if (basicp()->keyword().isDpiBitVal()) {
        if (widthMin() == 1) {
            arg = "unsigned char";
            if (!forReturn && isWritable()) arg += "*";
        } else {
            if (forReturn) {
                arg = "svBitVecVal";
            } else if (isReadOnly()) {
                arg = "const svBitVecVal*";
            } else {
                arg = "svBitVecVal*";
            }
        }
    } else if (basicp()->keyword().isDpiLogicVal()) {
        if (widthMin() == 1) {
            arg = "unsigned char";
            if (!forReturn && isWritable()) arg += "*";
        } else {
            if (forReturn) {
                arg = "svLogicVecVal";
            } else if (isReadOnly()) {
                arg = "const svLogicVecVal*";
            } else {
                arg = "svLogicVecVal*";
            }
        }
    } else {
        arg = basicp()->keyword().dpiType();
        if (basicp()->keyword().isDpiUnsignable() && !basicp()->isSigned()) {
            arg = "unsigned "+arg;
        }
        if (!forReturn && isWritable()) arg += "*";
    }
    if (named) arg += " "+name();
    return arg;
}

string AstVar::scType() const {
    if (isScBigUint()) {
        return (string("sc_biguint<")+cvtToStr(widthMin())+"> ");  // Keep the space so don't get >>
    } else if (isScUint()) {
        return (string("sc_uint<")+cvtToStr(widthMin())+"> ");  // Keep the space so don't get >>
    } else if (isScBv()) {
        return (string("sc_bv<")+cvtToStr(widthMin())+"> ");  // Keep the space so don't get >>
    } else if (widthMin() == 1) {
        return "bool";
    } else if (widthMin() <= VL_IDATASIZE) {
        if (widthMin() <= 8 && v3Global.opt.pinsUint8()) {
            return "uint8_t";
        } else if (widthMin() <= 16 && v3Global.opt.pinsUint8()) {
            return "uint16_t";
        } else {
            return "uint32_t";
        }
    } else {
        return "vluint64_t";
    }
}

AstVar* AstVar::scVarRecurse(AstNode* nodep) {
    // See if this is a SC assignment; if so return that type
    // Historically sc variables are identified by a variable
    // attribute. TODO it would better be a data type attribute.
    if (AstVar* anodep = VN_CAST(nodep, Var)) {
        if (anodep->isSc()) return anodep;
        else return NULL;
    }
    else if (VN_IS(nodep, VarRef)) {
        if (VN_CAST(nodep, VarRef)->varp()->isSc()) return VN_CAST(nodep, VarRef)->varp();
        else return NULL;
    }
    else if (VN_IS(nodep, ArraySel)) {
        if (nodep->op1p()) if (AstVar* p = scVarRecurse(nodep->op1p())) return p;
        if (nodep->op2p()) if (AstVar* p = scVarRecurse(nodep->op2p())) return p;
        if (nodep->op3p()) if (AstVar* p = scVarRecurse(nodep->op3p())) return p;
        if (nodep->op4p()) if (AstVar* p = scVarRecurse(nodep->op4p())) return p;
    }
    return NULL;
}

string AstVar::mtasksString() const {
    std::ostringstream os;
    os<<"all: ";
    for (MTaskIdSet::const_iterator it = m_mtaskIds.begin();
         it != m_mtaskIds.end(); ++it) {
        os<<*it<<" ";
    }
    return os.str();
}

AstNodeDType* AstNodeDType::dtypeDimensionp(int dimension) {
    // dimension passed from AstArraySel::dimension
    // Dimension 0 means the VAR itself, 1 is the closest SEL to the AstVar,
    // which is the lowest in the dtype list.
    //     ref order:   a[1][2][3][4]
    //     Created as:  reg [4] a [1][2][3];
    //        *or*      reg a [1][2][3][4];
    //                  // The bit select is optional; used only if "leftover" []'s
    //     SEL:         SEL4(SEL3(SEL2(SEL1(VARREF0 a))))
    //     DECL:        VAR a (ARRAYSEL0 (ARRAYSEL1 (ARRAYSEL2 (DT RANGE3))))
    //        *or*      VAR a (ARRAYSEL0 (ARRAYSEL1 (ARRAYSEL2 (ARRAYSEL3 (DT))))
    //     SEL1 needs to select from entire variable which is a pointer to ARRAYSEL0
    // TODO this function should be removed in favor of recursing the dtype(),
    // as that allows for more complicated data types.
    int dim = 0;
    for (AstNodeDType* dtypep=this; dtypep; ) {
        dtypep = dtypep->skipRefp();  // Skip AstRefDType/AstTypedef, or return same node
        if (AstNodeArrayDType* adtypep = VN_CAST(dtypep, NodeArrayDType)) {
            if ((dim++)==dimension) {
                return dtypep;
            }
            dtypep = adtypep->subDTypep();
            continue;
        }
        else if (AstBasicDType* adtypep = VN_CAST(dtypep, BasicDType)) {
            // AstBasicDType - nothing below, return null
            if (adtypep->isRanged()) {
                if ((dim++) == dimension) {
                    return adtypep;
                }
            }
            return NULL;
        }
        else if (AstNodeUOrStructDType* adtypep = VN_CAST(dtypep, NodeUOrStructDType)) {
            if (adtypep->packed()) {
                if ((dim++) == dimension) {
                    return adtypep;
                }
            }
            return NULL;
        }
        // Node no ->next in loop; use continue where necessary
        break;
    }
    return NULL;
}

uint32_t AstNodeDType::arrayUnpackedElements() {
    uint32_t entries = 1;
    for (AstNodeDType* dtypep=this; dtypep; ) {
        dtypep = dtypep->skipRefp();  // Skip AstRefDType/AstTypedef, or return same node
        if (AstUnpackArrayDType* adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
            entries *= adtypep->elementsConst();
            dtypep = adtypep->subDTypep();
        }
        else {
            // AstBasicDType - nothing below, 1
            break;
        }
    }
    return entries;
}

std::pair<uint32_t,uint32_t> AstNodeDType::dimensions(bool includeBasic) {
    // How many array dimensions (packed,unpacked) does this Var have?
    uint32_t packed = 0;
    uint32_t unpacked = 0;
    for (AstNodeDType* dtypep=this; dtypep; ) {
        dtypep = dtypep->skipRefp();  // Skip AstRefDType/AstTypedef, or return same node
        if (const AstNodeArrayDType* adtypep = VN_CAST(dtypep, NodeArrayDType)) {
            if (VN_IS(adtypep, PackArrayDType)) packed++;
            else unpacked++;
            dtypep = adtypep->subDTypep();
            continue;
        }
        else if (const AstQueueDType* qdtypep = VN_CAST(dtypep, QueueDType)) {
            unpacked++;
            dtypep = qdtypep->subDTypep();
            continue;
        }
        else if (const AstBasicDType* adtypep = VN_CAST(dtypep, BasicDType)) {
            if (includeBasic && (adtypep->isRanged() || adtypep->isString())) packed++;
        }
        else if (const AstStructDType* sdtypep = VN_CAST(dtypep, StructDType)) {
            packed++;
        }
        break;
    }
    return make_pair(packed, unpacked);
}

int AstNodeDType::widthPow2() const {
    // I.e.  width 30 returns 32, width 32 returns 32.
    uint32_t width = this->width();
    for (int p2=30; p2>=0; p2--) {
        if (width > (1UL<<p2)) return (1UL<<(p2+1));
    }
    return 1;
}

AstNode* AstArraySel::baseFromp(AstNode* nodep) {  ///< What is the base variable (or const) this dereferences?
    // Else AstArraySel etc; search for the base
    while (nodep) {
        if (VN_IS(nodep, ArraySel)) { nodep = VN_CAST(nodep, ArraySel)->fromp(); continue; }
        else if (VN_IS(nodep, Sel)) { nodep = VN_CAST(nodep, Sel)->fromp(); continue; }
        // AstNodeSelPre stashes the associated variable under an ATTROF
        // of AstAttrType::VAR_BASE/MEMBER_BASE so it isn't constified
        else if (VN_IS(nodep, AttrOf)) { nodep = VN_CAST(nodep, AttrOf)->fromp(); continue; }
        else if (VN_IS(nodep, NodePreSel)) {
            if (VN_CAST(nodep, NodePreSel)->attrp()) {
                nodep = VN_CAST(nodep, NodePreSel)->attrp();
            } else {
                nodep = VN_CAST(nodep, NodePreSel)->lhsp();
            }
            continue;
        }
        else break;
    }
    return nodep;
}

string AstCCall::hiernameProtect() const {
    return VIdProtect::protectWordsIf(hiername(), protect());
}

const char* AstScope::broken() const {
    BROKEN_RTN(m_aboveScopep && !m_aboveScopep->brokeExists());
    BROKEN_RTN(m_aboveCellp && !m_aboveCellp->brokeExists());
    BROKEN_RTN(!m_modp);
    BROKEN_RTN(m_modp && !m_modp->brokeExists());
    return NULL;
}

void AstScope::cloneRelink() {
    if (m_aboveScopep && m_aboveScopep->clonep()) m_aboveScopep->clonep();
    if (m_aboveCellp && m_aboveCellp->clonep()) m_aboveCellp->clonep();
    if (m_modp && static_cast<AstNode*>(m_modp)->clonep()) {
        static_cast<AstNode*>(m_modp)->clonep();
    }
}

string AstScope::nameDotless() const {
    string out = shortName();
    string::size_type pos;
    while ((pos = out.find('.')) != string::npos) {
        out.replace(pos, 1, "__");
    }
    return out;
}

string AstScopeName::scopePrettyNameFormatter(AstText* scopeTextp) const {
    string out;
    for (AstText* textp=scopeTextp; textp; textp=VN_CAST(textp->nextp(), Text)) {
        out += textp->text();
    }
    // TOP will be replaced by top->name()
    if (out.substr(0, 10) == "__DOT__TOP") out.replace(0, 10, "");
    if (out.substr(0, 7) == "__DOT__") out.replace(0, 7, "");
    if (out.substr(0, 1) == ".") out.replace(0, 1, "");
    return AstNode::prettyName(out);
}

string AstScopeName::scopeNameFormatter(AstText* scopeTextp) const {
    string out;
    for (AstText* textp=scopeTextp; textp; textp=VN_CAST(textp->nextp(), Text)) {
        out += textp->text();
    }
    if (out.substr(0, 10) == "__DOT__TOP") out.replace(0, 10, "");
    if (out.substr(0, 7) == "__DOT__") out.replace(0, 7, "");
    if (out.substr(0, 1) == ".") out.replace(0, 1, "");
    string::size_type pos;
    while ((pos = out.find('.')) != string::npos) {
        out.replace(pos, 1, "__");
    }
    while ((pos = out.find("__DOT__")) != string::npos) {
        out.replace(pos, 7, "__");
    }
    return out;
}

bool AstSenTree::hasClocked() const {
    UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
    for (AstNodeSenItem* senp = sensesp(); senp; senp=VN_CAST(senp->nextp(), NodeSenItem)) {
        if (senp->isClocked()) return true;
    }
    return false;
}
bool AstSenTree::hasSettle() const {
    UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
    for (AstNodeSenItem* senp = sensesp(); senp; senp=VN_CAST(senp->nextp(), NodeSenItem)) {
        if (senp->isSettle()) return true;
    }
    return false;
}
bool AstSenTree::hasInitial() const {
    UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
    for (AstNodeSenItem* senp = sensesp(); senp; senp=VN_CAST(senp->nextp(), NodeSenItem)) {
        if (senp->isInitial()) return true;
    }
    return false;
}
bool AstSenTree::hasCombo() const {
    UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
    for (AstNodeSenItem* senp = sensesp(); senp; senp=VN_CAST(senp->nextp(), NodeSenItem)) {
        if (senp->isCombo()) return true;
    }
    return false;
}

void AstTypeTable::clearCache() {
    // When we mass-change widthMin in V3WidthCommit, we need to correct the table.
    // Just clear out the maps; the search functions will be used to rebuild the map
    for (int i=0; i < static_cast<int>(AstBasicDTypeKwd::_ENUM_MAX); ++i) {
        m_basicps[i] = NULL;
    }
    m_detailedMap.clear();
    // Clear generic()'s so dead detection will work
    for (AstNode* nodep = typesp(); nodep; nodep=nodep->nextp()) {
        if (AstBasicDType* bdtypep = VN_CAST(nodep, BasicDType)) {
            bdtypep->generic(false);
        }
    }
}

void AstTypeTable::repairCache() {
    // After we mass-change widthMin in V3WidthCommit, we need to correct the table.
    clearCache();
    for (AstNode* nodep = typesp(); nodep; nodep=nodep->nextp()) {
        if (AstBasicDType* bdtypep = VN_CAST(nodep, BasicDType)) {
            (void)findInsertSameDType(bdtypep);
        }
    }
}

AstVoidDType* AstTypeTable::findVoidDType(FileLine* fl) {
    if (VL_UNLIKELY(!m_voidp)) {
        AstVoidDType* newp = new AstVoidDType(fl);
        addTypesp(newp);
        m_voidp = newp;
    }
    return m_voidp;
}

AstBasicDType* AstTypeTable::findBasicDType(FileLine* fl, AstBasicDTypeKwd kwd) {
    if (m_basicps[kwd]) return m_basicps[kwd];
    //
    AstBasicDType* new1p = new AstBasicDType(fl, kwd);
    // Because the detailed map doesn't update this map,
    // check the detailed map for this same node
    // Also adds this new node to the detailed map
    AstBasicDType* newp = findInsertSameDType(new1p);
    if (newp != new1p) VL_DO_DANGLING(new1p->deleteTree(), new1p);
    else addTypesp(newp);
    //
    m_basicps[kwd] = newp;
    return newp;
}

AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, AstBasicDTypeKwd kwd,
                                               int width, int widthMin, AstNumeric numeric) {
    AstBasicDType* new1p = new AstBasicDType(fl, kwd, numeric, width, widthMin);
    AstBasicDType* newp = findInsertSameDType(new1p);
    if (newp != new1p) VL_DO_DANGLING(new1p->deleteTree(), new1p);
    else addTypesp(newp);
    return newp;
}

AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, AstBasicDTypeKwd kwd,
                                               VNumRange range, int widthMin, AstNumeric numeric) {
    AstBasicDType* new1p = new AstBasicDType(fl, kwd, numeric, range, widthMin);
    AstBasicDType* newp = findInsertSameDType(new1p);
    if (newp != new1p) VL_DO_DANGLING(new1p->deleteTree(), new1p);
    else addTypesp(newp);
    return newp;
}

AstBasicDType* AstTypeTable::findInsertSameDType(AstBasicDType* nodep) {
    VBasicTypeKey key (nodep->width(), nodep->widthMin(), nodep->numeric(),
                       nodep->keyword(), nodep->nrange());
    DetailedMap& mapr = m_detailedMap;
    DetailedMap::const_iterator it = mapr.find(key);
    if (it != mapr.end()) return it->second;
    mapr.insert(make_pair(key, nodep));
    nodep->generic(true);
    // No addTypesp; the upper function that called new() is responsible for adding
    return nodep;
}

//======================================================================
// Special walking tree inserters

void AstNode::addBeforeStmt(AstNode* newp, AstNode*) {
    UASSERT_OBJ(backp(), newp, "Can't find current statement to addBeforeStmt");
    // Look up; virtual call will find where to put it
    this->backp()->addBeforeStmt(newp, this);
}
void AstNode::addNextStmt(AstNode* newp, AstNode*) {
    UASSERT_OBJ(backp(), newp, "Can't find current statement to addNextStmt");
    // Look up; virtual call will find where to put it
    this->backp()->addNextStmt(newp, this);
}

void AstNodeStmt::addBeforeStmt(AstNode* newp, AstNode*) {
    // Insert newp before current node
    this->addHereThisAsNext(newp);
}
void AstNodeStmt::addNextStmt(AstNode* newp, AstNode*) {
    // Insert newp after current node
    this->addNextHere(newp);
}

void AstWhile::addBeforeStmt(AstNode* newp, AstNode* belowp) {
    // Special, as statements need to be put in different places
    // Belowp is how we came to recurse up to this point
    // Preconditions insert first just before themselves (the normal rule
    // for other statement types)
    if (belowp == precondsp()) {
        // Must have been first statement in precondsp list, so newp is new first statement
        belowp->addHereThisAsNext(newp);
    } else if (belowp == condp()) {
        // Goes before condition, IE in preconditions
        addPrecondsp(newp);
    } else if (belowp == bodysp()) {
        // Was first statement in body, so new front
        belowp->addHereThisAsNext(newp);
    } else {
        belowp->v3fatalSrc("Doesn't look like this was really under the while");
    }
}
void AstWhile::addNextStmt(AstNode* newp, AstNode* belowp) {
    // Special, as statements need to be put in different places
    // Belowp is how we came to recurse up to this point
    // Preconditions insert first just before themselves (the normal rule
    // for other statement types)
    if (belowp == precondsp()) {
        // Next in precond list
        belowp->addNextHere(newp);
    } else if (belowp == condp()) {
        // Becomes first statement in body, body may have been empty
        if (bodysp()) {
            bodysp()->addHereThisAsNext(newp);
        } else {
            addBodysp(newp);
        }
    } else if (belowp == bodysp()) {
        // Next statement in body
        belowp->addNextHere(newp);
    } else {
        belowp->v3fatalSrc("Doesn't look like this was really under the while");
    }
}

//======================================================================
// Per-type Debugging

void AstNode::dump(std::ostream& str) const {
    str<<typeName()<<" "<<cvtToHex(this)
        //<<" "<<cvtToHex(this)->m_backp
       <<" <e"<<std::dec<<editCount()
       <<((editCount()>=editCountLast())?"#>":">")
       <<" {"<<fileline()->filenameLetters()<<std::dec<<fileline()->lastLineno()<<"}";
    if (user1p()) str<<" u1="<<cvtToHex(user1p());
    if (user2p()) str<<" u2="<<cvtToHex(user2p());
    if (user3p()) str<<" u3="<<cvtToHex(user3p());
    if (user4p()) str<<" u4="<<cvtToHex(user4p());
    if (user5p()) str<<" u5="<<cvtToHex(user5p());
    if (hasDType()) {
        // Final @ so less likely to by accident read it as a nodep
        if (dtypep()==this) str<<" @dt="<<"this@";
        else str<<" @dt="<<cvtToHex(dtypep())<<"@";
        if (AstNodeDType* dtp = dtypep()) {
            dtp->dumpSmall(str);
        }
    } else {  // V3Broken will throw an error
        if (dtypep()) str<<" %Error-dtype-exp=null,got="<<cvtToHex(dtypep());
    }
    if (name()!="") {
        if (VN_IS(this, Const)) str<<"  "<<name();  // Already quoted
        else str<<"  "<<V3OutFormatter::quoteNameControls(name());
    }
}

void AstAlways::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (keyword() != VAlwaysKwd::ALWAYS) str<<" ["<<keyword().ascii()<<"]";
}

void AstAttrOf::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" ["<<attrType().ascii()<<"]";
}
void AstBasicDType::dump(std::ostream& str) const {
    this->AstNodeDType::dump(str);
    str<<" kwd="<<keyword().ascii();
    if (isRanged() && !rangep()) str<<" range=["<<left()<<":"<<right()<<"]";
}
string AstBasicDType::prettyDTypeName() const {
    std::ostringstream os;
    os << keyword().ascii();
    if (isRanged() && !rangep() && keyword().width() <= 1) {
        os << "[" << left() << ":" << right() << "]";
    }
    return os.str();
}
void AstCCast::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" sz"<<size();
}
void AstCell::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (recursive()) str<<" [RECURSIVE]";
    if (modp()) { str<<" -> "; modp()->dump(str); }
    else { str<<" ->UNLINKED:"<<modName(); }
}
void AstCellInline::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" -> "<<origModName();
}
void AstNodeCoverOrAssert::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (immediate()) str<<" [IMMEDIATE]";
}
void AstDisplay::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    //str<<" "<<displayType().ascii();
}
void AstEnumItemRef::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" -> ";
    if (itemp()) { itemp()->dump(str); }
    else { str<<"UNLINKED"; }
}
void AstIfaceRefDType::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (cellName()!="") { str<<" cell="<<cellName(); }
    if (ifaceName()!="") { str<<" if="<<ifaceName(); }
    if (modportName()!="") { str<<" mp="<<modportName(); }
    if (cellp()) { str<<" -> "; cellp()->dump(str); }
    else if (ifacep()) { str<<" -> "; ifacep()->dump(str); }
    else { str<<" -> UNLINKED"; }
}
void AstIfaceRefDType::dumpSmall(std::ostream& str) const {
    this->AstNodeDType::dumpSmall(str);
    str<<"iface";
}
void AstInitArray::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    int n = 0;
    const AstInitArray::KeyItemMap& mapr = map();
    for (AstInitArray::KeyItemMap::const_iterator it = mapr.begin(); it != mapr.end(); ++it) {
        if (n++ > 5) { str<<" ..."; break; }
        str<<" ["<<it->first<<"]="<<(void*)it->second;
    }
}
void AstJumpGo::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" -> ";
    if (labelp()) { labelp()->dump(str); }
    else { str<<"%Error:UNLINKED"; }
}
void AstMemberSel::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str << " -> ";
    if (varp()) { varp()->dump(str); }
    else { str << "%Error:UNLINKED"; }
}
void AstModportFTaskRef::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (isExport()) str<<" EXPORT";
    if (isImport()) str<<" IMPORT";
    if (ftaskp()) { str<<" -> "; ftaskp()->dump(str); }
    else { str<<" -> UNLINKED"; }
}
void AstModportVarRef::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (direction().isAny()) str<<" "<<direction();
    if (varp()) { str<<" -> "; varp()->dump(str); }
    else { str<<" -> UNLINKED"; }
}
void AstPin::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (modVarp()) { str<<" -> "; modVarp()->dump(str); }
    else { str<<" ->UNLINKED"; }
    if (svImplicit()) str<<" [.SV]";
}
void AstTypedef::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (attrPublic()) str<<" [PUBLIC]";
}
void AstRange::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (littleEndian()) str<<" [LITTLE]";
}
void AstRefDType::dump(std::ostream& str) const {
    this->AstNodeDType::dump(str);
    if (defp()) {
        static bool s_recursing = false;
        if (!s_recursing) {  // Prevent infinite dump if circular typedefs
            s_recursing = true;
            str<<" -> "; defp()->dump(str);
            s_recursing = false;
        }
    }
    else { str<<" -> UNLINKED"; }
}
void AstNodeUOrStructDType::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (packed()) str<<" [PACKED]";
    if (isFourstate()) str<<" [4STATE]";
}
void AstNodeDType::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (generic()) str<<" [GENERIC]";
    if (AstNodeDType* dtp = virtRefDTypep()) {
        str<<" refdt="<<cvtToHex(dtp);
        dtp->dumpSmall(str);
    }
}
void AstNodeDType::dumpSmall(std::ostream& str) const {
    str<<"("
       <<(generic()?"G/":"")
       <<((isSigned()&&!isDouble())?"s":"")
       <<(isNosign()?"n":"")
       <<(isDouble()?"d":"")
       <<(isString()?"str":"");
    if (!isDouble() && !isString()) {
        str<<"w"<<(widthSized()?"":"u")<<width();
    }
    if (!widthSized()) str<<"/"<<widthMin();
    str<<")";
}
void AstNodeArrayDType::dumpSmall(std::ostream& str) const {
    this->AstNodeDType::dumpSmall(str);
    if (VN_IS(this, PackArrayDType)) str<<"p"; else str<<"u";
    str<<declRange();
}
void AstNodeArrayDType::dump(std::ostream& str) const {
    this->AstNodeDType::dump(str);
    str<<" "<<declRange();
}
string AstPackArrayDType::prettyDTypeName() const {
    std::ostringstream os;
    os << subDTypep()->prettyDTypeName() << declRange();
    return os.str();
}
string AstUnpackArrayDType::prettyDTypeName() const {
    std::ostringstream os;
    string ranges = cvtToStr(declRange());
    // Unfortunately we need a single $ for the first unpacked, and all
    // dimensions shown in "reverse" order
    AstNodeDType* subp = subDTypep()->skipRefp();
    while (AstUnpackArrayDType* adtypep = VN_CAST(subp, UnpackArrayDType)) {
        ranges += cvtToStr(adtypep->declRange());
        subp = adtypep->subDTypep()->skipRefp();
    }
    os << subp->prettyDTypeName() << "$" << ranges;
    return os.str();
}
void AstNodeModule::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<"  L"<<level();
    if (modPublic()) str<<" [P]";
    if (inLibrary()) str<<" [LIB]";
    if (dead()) str<<" [DEAD]";
    if (recursiveClone()) str<<" [RECURSIVE-CLONE]";
    else if (recursive()) str<<" [RECURSIVE]";
}
void AstPackageExport::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" -> "<<packagep();
}
void AstPackageImport::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" -> "<<packagep();
}
void AstSel::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (declRange().ranged()) {
        str<<" decl"<<declRange()<<"]";
        if (declElWidth()!=1) str<<"/"<<declElWidth();
    }
}
void AstSliceSel::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (declRange().ranged()) {
        str<<" decl"<<declRange();
    }
}
void AstMTaskBody::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" ";
    m_execMTaskp->dump(str);
}
void AstTypeTable::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    for (int i=0; i < static_cast<int>(AstBasicDTypeKwd::_ENUM_MAX); ++i) {
        if (AstBasicDType* subnodep = m_basicps[i]) {
            str<<endl;  // Newline from caller, so newline first
            str<<"\t\t"<<std::setw(8)<<AstBasicDTypeKwd(i).ascii();
            str<<"  -> ";
            subnodep->dump(str);
        }
    }
    {
        const DetailedMap& mapr = m_detailedMap;
        for (DetailedMap::const_iterator it = mapr.begin(); it != mapr.end(); ++it) {
            AstBasicDType* dtypep = it->second;
            str<<endl;  // Newline from caller, so newline first
            str<<"\t\tdetailed  ->  ";
            dtypep->dump(str);
        }
    }
    // Note get newline from caller too.
}
void AstAssocArrayDType::dumpSmall(std::ostream& str) const {
    this->AstNodeDType::dumpSmall(str);
    str<<"[assoc-"<<(void*)keyDTypep()<<"]";
}
string AstAssocArrayDType::prettyDTypeName() const {
    return subDTypep()->prettyDTypeName() + "[" + keyDTypep()->prettyDTypeName() + "]";
}
void AstQueueDType::dumpSmall(std::ostream& str) const {
    this->AstNodeDType::dumpSmall(str);
    str<<"[queue]";
}
string AstQueueDType::prettyDTypeName() const {
    string str = subDTypep()->prettyDTypeName() + "[$";
    if (boundConst()) str += ":" + cvtToStr(boundConst());
    return str + "]";
}
void AstUnsizedArrayDType::dumpSmall(std::ostream& str) const {
    this->AstNodeDType::dumpSmall(str);
    str<<"[]";
}
void AstVoidDType::dumpSmall(std::ostream& str) const {
    this->AstNodeDType::dumpSmall(str);
    str<<"void";
}
void AstVarScope::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (isCircular()) str<<" [CIRC]";
    if (varp()) { str<<" -> "; varp()->dump(str); }
    else { str<<" ->UNLINKED"; }
}
void AstVarXRef::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (packagep()) { str<<" pkg="<<cvtToHex(packagep()); }
    if (lvalue()) str<<" [LV] => ";
    else          str<<" [RV] <- ";
    str<<".="<<dotted()<<" ";
    if (inlinedDots()!="") str<<" inline.="<<inlinedDots()<<" - ";
    if (varScopep()) { varScopep()->dump(str); }
    else if (varp()) { varp()->dump(str); }
    else { str<<"UNLINKED"; }
}
void AstVarRef::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (packagep()) { str<<" pkg="<<cvtToHex(packagep()); }
    if (lvalue()) str<<" [LV] => ";
    else          str<<" [RV] <- ";
    if (varScopep()) { varScopep()->dump(str); }
    else if (varp()) { varp()->dump(str); }
    else { str<<"UNLINKED"; }
}
void AstVar::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (isSc()) str<<" [SC]";
    if (isPrimaryIO()) str<<(isInoutish()?" [PIO]":(isWritable()?" [PO]":" [PI]"));
    if (isIO()) str<<" "<<direction().ascii();
    if (isConst()) str<<" [CONST]";
    if (isPullup()) str<<" [PULLUP]";
    if (isPulldown()) str<<" [PULLDOWN]";
    if (isUsedClock()) str<<" [CLK]";
    if (isSigPublic()) str<<" [P]";
    if (isUsedLoopIdx()) str<<" [LOOP]";
    if (attrClockEn()) str<<" [aCLKEN]";
    if (attrIsolateAssign()) str<<" [aISO]";
    if (attrFileDescr()) str<<" [aFD]";
    if (isFuncReturn()) str<<" [FUNCRTN]";
    else if (isFuncLocal()) str<<" [FUNC]";
    if (isDpiOpenArray()) str<<" [DPIOPENA]";
    if (!attrClocker().unknown()) str<<" ["<<attrClocker().ascii()<<"] ";
    str<<" "<<varType();
}
void AstSenTree::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (isMulti()) str<<" [MULTI]";
}
void AstSenItem::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" ["<<edgeType().ascii()<<"]";
}
void AstParseRef::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" ["<<expect().ascii()<<"]";
}
void AstPackageRef::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (packagep()) { str<<" pkg="<<cvtToHex(packagep()); }
    str<<" -> ";
    if (packagep()) { packagep()->dump(str); }
    else { str<<"UNLINKED"; }
}
void AstDot::dump(std::ostream& str) const {
    this->AstNode::dump(str);
}
void AstActive::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" => ";
    if (sensesp()) { sensesp()->dump(str); }
    else { str<<"UNLINKED"; }
}
void AstNodeFTaskRef::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (packagep()) { str<<" pkg="<<cvtToHex(packagep()); }
    str<<" -> ";
    if (dotted()!="") { str<<".="<<dotted()<<" "; }
    if (taskp()) { taskp()->dump(str); }
    else { str<<"UNLINKED"; }
}
void AstNodeFTask::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (taskPublic()) str<<" [PUBLIC]";
    if (prototype()) str<<" [PROTOTYPE]";
    if (dpiImport()) str<<" [DPII]";
    if (dpiExport()) str<<" [DPIX]";
    if (dpiOpenChild()) str<<" [DPIOPENCHILD]";
    if (dpiOpenParent()) str<<" [DPIOPENPARENT]";
    if ((dpiImport() || dpiExport()) && cname()!=name()) str<<" [c="<<cname()<<"]";
}
void AstBegin::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (unnamed()) str<<" [UNNAMED]";
    if (generate()) str<<" [GEN]";
    if (genforp()) str<<" [GENFOR]";
}
void AstCoverDecl::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (this->dataDeclNullp()) {
        str<<" -> ";
        this->dataDeclNullp()->dump(str);
    } else {
        if (binNum()) { str<<" bin"<<std::dec<<binNum(); }
    }
}
void AstCoverInc::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" -> ";
    if (declp()) { declp()->dump(str); }
    else { str<<"%Error:UNLINKED"; }
}
void AstTraceInc::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    str<<" -> ";
    if (declp()) { declp()->dump(str); }
    else { str<<"%Error:UNLINKED"; }
}
void AstNodeText::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    string out = text();
    string::size_type pos;
    if ((pos = out.find('\n')) != string::npos) {
        out.erase(pos, out.length()-pos);
        out += "...";
    }
    str<<" \""<<out<<"\"";
}

void AstVFile::dump(std::ostream& str) const {
    this->AstNode::dump(str);
}

void AstCFile::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (source()) str<<" [SRC]";
    if (slow()) str<<" [SLOW]";
}
void AstCCall::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (funcp()) {
        str<<" "<<funcp()->name()<<" => ";
        funcp()->dump(str);
    }
}
void AstCFunc::dump(std::ostream& str) const {
    this->AstNode::dump(str);
    if (slow()) str<<" [SLOW]";
    if (pure()) str<<" [PURE]";
    if (isStatic().unknown()) str<<" [STATICU]";
    else if (isStatic().trueU()) str<<" [STATIC]";
    if (dpiImport()) str<<" [DPII]";
    if (dpiExport()) str<<" [DPIX]";
    if (dpiExportWrapper()) str<<" [DPIXWR]";
}
