// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add common contents to modules
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// V3Common's Transformations:
//
//  Each class:
//      Emit to_string() if given class is printed from (used as format arg), or any of its
//      parents/children is printed from, or if any of its children has another parent that is
//      printed from. If class emits to_string(), emit to_string() for its struct/union/interface
//      fields.
//  Each struct/union:
//      Emit to_string() if given struct/union is used as format arg or is a field of a
//      class/struct/union that emits to_string(). If struct/union emits to_string(), emit
//      to_string() for its struct/union fields.
//  Each interface:
//      Emit to_string() if given interface is a field of a class that emits to_string()
//
//*************************************************************************

#include "V3PchAstNoMT.h"  // VL_MT_DISABLED_CODE_UNIT

#include "V3Common.h"

#include "V3EmitCBase.h"

VL_DEFINE_DEBUG_FUNCTIONS;

//######################################################################
// Common component builders

string V3Common::makeToStringCall(AstNodeDType* nodep, const std::string& lhs) {
    std::string stmt;
    if (VN_IS(nodep->skipRefp(), BasicDType) && nodep->isWide()) {
        stmt += "VL_TO_STRING_W(";
        stmt += cvtToStr(nodep->widthWords());
        stmt += ", ";
    } else {
        stmt += "VL_TO_STRING(";
    }
    stmt += lhs;
    stmt += ")";
    return stmt;
}

static void makeVlToString(AstIface* nodep) {
    AstCFunc* const funcp
        = new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
    funcp->argTypes("const " + EmitCUtil::prefixNameProtect(nodep) + "* obj");
    funcp->isMethod(false);
    funcp->isConst(false);
    funcp->isStatic(false);
    funcp->protect(false);
    AstNodeExpr* const exprp = new AstCExpr{nodep->fileline(), "obj ? obj->vlNamep : \"null\""};
    exprp->dtypeSetString();
    funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp});
    nodep->addStmtsp(funcp);
}
static void makeVlToString(AstNodeUOrStructDType* nodep) {
    AstNodeModule* const modp = nodep->classOrPackagep();
    UASSERT_OBJ(modp, nodep, "Unlinked struct package");
    AstCFunc* const funcp
        = new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
    funcp->argTypes("const " + EmitCUtil::prefixNameProtect(nodep) + "& obj");
    funcp->isMethod(false);
    funcp->isConst(false);
    funcp->isStatic(false);
    funcp->protect(false);
    funcp->addStmtsp(new AstCStmt{nodep->fileline(), "std::string out;"});
    for (const AstMemberDType* itemp = nodep->membersp(); itemp;
         itemp = VN_AS(itemp->nextp(), MemberDType)) {
        std::string stmt = "out += \"";
        if (itemp == nodep->membersp()) {
            stmt += "'{";
        } else {
            stmt += ", ";
        }
        stmt += VIdProtect::protect(itemp->prettyName()) + ":\" + ";
        stmt += V3Common::makeToStringCall(itemp->dtypep(), "obj."s + itemp->nameProtect());
        stmt += ";";
        funcp->addStmtsp(new AstCStmt{nodep->fileline(), stmt});
    }
    funcp->addStmtsp(new AstCStmt{nodep->fileline(), "out += \"}\";"});

    AstCExpr* const exprp = new AstCExpr{nodep->fileline(), "out"};
    exprp->dtypeSetString();
    funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp});

    modp->addStmtsp(funcp);
}
static void makeToString(AstClass* nodep) {
    AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "to_string", nullptr, "std::string"};
    funcp->isConst(true);
    funcp->isStatic(false);
    funcp->isVirtual(true);
    funcp->protect(false);
    AstCExpr* const exprp = new AstCExpr{nodep->fileline(), R"("'{"s + to_string_middle() + "}")"};
    exprp->dtypeSetString();
    funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp});
    nodep->addStmtsp(funcp);
}
static void makeToStringMiddle(AstClass* nodep) {
    AstCFunc* const funcp
        = new AstCFunc{nodep->fileline(), "to_string_middle", nullptr, "std::string"};
    funcp->isConst(true);
    funcp->isStatic(false);
    funcp->protect(false);
    AstNodeStmt* stmtsp = nullptr;
    std::string comma;
    for (AstNode* itemp = nodep->membersp(); itemp; itemp = itemp->nextp()) {
        if (const auto* const varp = VN_CAST(itemp, Var)) {
            const AstBasicDType* const basicp = varp->dtypeSkipRefp()->basicp();
            if (!varp->isParam() && !varp->isInternal()
                && !(basicp && (basicp->isRandomGenerator() || basicp->isStdRandomGenerator()))) {
                string stmt = "out += \"";
                stmt += comma;
                comma = ", ";
                stmt += itemp->origNameProtect();
                stmt += ":\" + ";
                stmt += V3Common::makeToStringCall(itemp->dtypep(), itemp->nameProtect());
                stmt += ";";
                nodep->user1(true);  // So what we extend dumps this
                stmtsp = AstNode::addNextNull(stmtsp, new AstCStmt{nodep->fileline(), stmt});
            }
        }
    }
    if (nodep->extendsp()) {
        string stmt = "out += ";
        if (!comma.empty()) stmt += "\", \" + ";
        // comma = ", ";  // Nothing further so not needed
        stmt += EmitCUtil::prefixNameProtect(nodep->extendsp()->dtypep());
        stmt += "::to_string_middle();";
        nodep->user1(true);  // So what we extend dumps this
        stmtsp = AstNode::addNextNull(stmtsp, new AstCStmt{nodep->fileline(), stmt});
    }

    AstNodeExpr* exprp;
    if (stmtsp) {
        funcp->addStmtsp(new AstCStmt{nodep->fileline(), "std::string out;"});
        funcp->addStmtsp(stmtsp);
        exprp = new AstCExpr{nodep->fileline(), "out"};
        exprp->dtypeSetString();
    } else {  // Nothing to print, return ""
        exprp = new AstConst{nodep->fileline(), AstConst::String{}, ""};
    }
    funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp});

    nodep->addStmtsp(funcp);
}

class ToStringVisitor final : public VNVisitorConst {
    // NODE STATE
    //  AstNode::user1()  -> bool. to_string() was emitted
    //  AstClass::user2()  -> PrintedFromParent. Used to propagate emission of to_string() from
    //  parent to child
    const VNUser1InUse m_inuser1;
    const VNUser2InUse m_inuser2;
    size_t m_emitTowardsRoot
        = 0;  // class that require its ancestors to emit to_string() sets this
    size_t m_emitForMembers = 0;  // class/struct that requires its members (and their members
                                  // recursively) to emit to_string() sets this
    enum class PrintedFromParent : uint8_t {
        UNRESOLVED,
        PRINTED_FROM_PARENT,
        NO_PRINTED_FROM_PARENT
    };

    VDouble0 m_statClassToString;
    VDouble0 m_statInterfaceToString;
    VDouble0 m_statStructUnionToString;

    void markClassIterate(AstClass* classp) {
        if (classp->user1SetOnce()) return;
        VL_RESTORER(m_emitForMembers);
        ++m_emitForMembers;
        for (AstNode* stmtp = classp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
            if (AstVar* const varp = VN_CAST(stmtp, Var)) {
                AstNodeDType* nodeDtypep = varp->dtypep();
                if (AstIfaceRefDType* const ifaceRedDTypep = VN_CAST(nodeDtypep, IfaceRefDType)) {
                    if (ifaceRedDTypep->ifacep()) iterateConst(ifaceRedDTypep->ifacep());
                } else {
                    while (nodeDtypep && nodeDtypep->subDTypep()
                           && nodeDtypep->subDTypep()->skipRefp()) {
                        nodeDtypep = nodeDtypep->subDTypep()->skipRefp();
                        if (AstIfaceRefDType* const ifaceRedDTypep
                            = VN_CAST(nodeDtypep, IfaceRefDType)) {
                            if (ifaceRedDTypep->ifacep()) {
                                iterateConst(ifaceRedDTypep->ifacep());
                            }
                        } else if (AstNodeUOrStructDType* const uOrStructDTypep
                                   = VN_CAST(nodeDtypep, NodeUOrStructDType)) {
                            iterateConst(uOrStructDTypep);
                        }
                    }
                }
            }
        }

        makeToString(classp);
        makeToStringMiddle(classp);
        ++m_statClassToString;
    }
    void visit(AstNodeUOrStructDType* structp) override {
        if (structp->user1())
            return;  // if to_string() was already emitted skip the rest of the function
        if (structp->emitToString()) ++m_emitForMembers;
        if (m_emitForMembers
            > 0) {  // either this struct directly requires emission of to_string() or the
                    // class/struct that has this struct as a field requires it to emit to_string()
            for (AstMemberDType* memberp = structp->membersp(); memberp;
                 memberp = VN_AS(memberp->nextp(), MemberDType)) {
                AstNodeDType* nodeDtypep = memberp->dtypep();
                if (AstNodeUOrStructDType* const uOrStructDTypep
                    = VN_CAST(nodeDtypep, NodeUOrStructDType)) {
                    iterateConst(uOrStructDTypep);
                } else {
                    while (nodeDtypep && nodeDtypep->subDTypep()) {
                        nodeDtypep = nodeDtypep->subDTypep()->skipRefp();
                        if (AstNodeUOrStructDType* const uOrStructDTypep
                            = VN_CAST(nodeDtypep, NodeUOrStructDType)) {
                            iterateConst(uOrStructDTypep);
                        }
                    }
                }
            }
            if (structp->emitToString()) --m_emitForMembers;

            if (!structp->packed()) {
                makeVlToString(structp);
                ++m_statStructUnionToString;
            }

            structp->user1(true);  // mark that to_string() was emitted to not emit it twice (if
                                   // e.g two printed classes have this struct as a field)
        }
    }
    void visit(AstIface* ifacep) override {
        if (ifacep->user1())
            return;  // if to_string() was already emitted skip the rest of the function
        if (m_emitForMembers
            > 0) {  // class that has this iface as a field requires it to emit to_string()
            makeVlToString(ifacep);
            ++m_statInterfaceToString;
            ifacep->user1(true);  // mark that to_string() was emitted to not emit it twice (if e.g
                                  // two printed classes have this interface as a field)
        }
    }
    // go towards class hierarchy root, if there is any printed from class on the way, mark its
    // ancestors, and mark everything on the return path from the printed from class
    void visit(AstClass* classp) override {
        if (classp->user1()) return;
        if (classp->isPrintedFrom()) {
            markClassIterate(classp);
            VL_RESTORER(m_emitTowardsRoot);
            ++m_emitTowardsRoot;
            for (const AstClassExtends* extendp = classp->extendsp(); extendp;
                 extendp = VN_AS(extendp->nextp(), ClassExtends)) {
                if (extendp->classp() && !extendp->classp()->user1()) {
                    iterateConst(extendp->classp());
                }
            }
            classp->user2(static_cast<int>(PrintedFromParent::PRINTED_FROM_PARENT));
        } else {
            PrintedFromParent printed_parent = static_cast<PrintedFromParent>(classp->user2());
            if (printed_parent != PrintedFromParent::UNRESOLVED && m_emitTowardsRoot == 0) {
                return;
            }

            if (m_emitTowardsRoot > 0) markClassIterate(classp);

            bool is_root = true;
            for (const AstClassExtends* extendp = classp->extendsp(); extendp;
                 extendp = VN_AS(extendp->nextp(), ClassExtends)) {
                if (extendp->classp()) {
                    if (static_cast<PrintedFromParent>(extendp->classp()->user2())
                            == PrintedFromParent::UNRESOLVED
                        || (m_emitTowardsRoot > 0 && !extendp->classp()->user1())) {
                        iterateConst(extendp->classp());
                    }
                    if (printed_parent != PrintedFromParent::PRINTED_FROM_PARENT) {
                        printed_parent
                            = static_cast<PrintedFromParent>(extendp->classp()->user2());
                    }
                    is_root = false;
                }
            }

            if (is_root) printed_parent = PrintedFromParent::NO_PRINTED_FROM_PARENT;

            classp->user2(static_cast<int>(printed_parent));
            if (printed_parent == PrintedFromParent::PRINTED_FROM_PARENT) {
                markClassIterate(classp);

                // If one parent is printed from, other parent also has to emit to_string() to
                // handle cases like:
                // Base  IFaceClass (printed from)
                //   V    V
                //   Derived (this class)
                VL_RESTORER(m_emitTowardsRoot);
                ++m_emitTowardsRoot;
                for (const AstClassExtends* extendp = classp->extendsp(); extendp;
                     extendp = VN_AS(extendp->nextp(), ClassExtends)) {
                    if (extendp->classp() && !extendp->classp()->user1()) {
                        iterateConst(extendp->classp());
                    }
                }
            }
        }
    }
    void visit(AstConstPool*) override {}  // Accelerate
    void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }

public:
    explicit ToStringVisitor(AstNode* nodep) {
        if (v3Global.hasPrintedObjects()) {
            AstNode::user1ClearTree();
            AstNode::user2ClearTree();

            iterateConst(nodep);
        }

        V3Stats::addStat("Optimizations, Class ToString emitted", m_statClassToString);
        V3Stats::addStat("Optimizations, Interface ToString emitted", m_statInterfaceToString);
        V3Stats::addStat("Optimizations, Struct/union ToString emitted",
                         m_statStructUnionToString);
    }
    ~ToStringVisitor() override = default;
};

//######################################################################
// V3Common class functions

void V3Common::commonAll() {
    UINFO(2, __FUNCTION__ << ":");

    ToStringVisitor{v3Global.rootp()};

    V3Global::dumpCheckGlobalTree("common", 0, dumpTreeEitherLevel() >= 3);
}
