/**
 * Compiler implementation of the $(LINK2 http://www.dlang.org, D programming language)
 *
 * Copyright: Copyright (c) 1999-2017 by Digital Mars, All Rights Reserved
 * Authors: Walter Bright, http://www.digitalmars.com
 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:    $(DMDSRC _dmangle.d)
 */

module ddmd.dmangle;

import core.stdc.ctype;
import core.stdc.stdio;
import core.stdc.string;

import ddmd.aggregate;
import ddmd.arraytypes;
import ddmd.cppmangle;
import ddmd.dclass;
import ddmd.declaration;
import ddmd.dmodule;
import ddmd.dsymbol;
import ddmd.dtemplate;
import ddmd.expression;
import ddmd.func;
import ddmd.globals;
import ddmd.id;
import ddmd.mtype;
import ddmd.root.ctfloat;
import ddmd.root.outbuffer;
import ddmd.utf;
import ddmd.visitor;

private immutable char[TMAX] mangleChar =
[
    Tchar        : 'a',
    Tbool        : 'b',
    Tcomplex80   : 'c',
    Tfloat64     : 'd',
    Tfloat80     : 'e',
    Tfloat32     : 'f',
    Tint8        : 'g',
    Tuns8        : 'h',
    Tint32       : 'i',
    Timaginary80 : 'j',
    Tuns32       : 'k',
    Tint64       : 'l',
    Tuns64       : 'm',
    Tnone        : 'n',
    Tnull        : 'n', // yes, same as TypeNone
    Timaginary32 : 'o',
    Timaginary64 : 'p',
    Tcomplex32   : 'q',
    Tcomplex64   : 'r',
    Tint16       : 's',
    Tuns16       : 't',
    Twchar       : 'u',
    Tvoid        : 'v',
    Tdchar       : 'w',
    //              x   // const
    //              y   // immutable
    Tint128      : 'z', // zi
    Tuns128      : 'z', // zk

    Tarray       : 'A',
    Ttuple       : 'B',
    Tclass       : 'C',
    Tdelegate    : 'D',
    Tenum        : 'E',
    Tfunction    : 'F', // D function
    Tsarray      : 'G',
    Taarray      : 'H',
    Tident       : 'I',
    //              J   // out
    //              K   // ref
    //              L   // lazy
    //              M   // has this, or scope
    //              N   // Nh:vector Ng:wild
    //              O   // shared
    Tpointer     : 'P',
    //              Q
    Treference   : 'R',
    Tstruct      : 'S',
    //              T   // Ttypedef
    //              U   // C function
    //              V   // Pascal function
    //              W   // Windows function
    //              X   // variadic T t...)
    //              Y   // variadic T t,...)
    //              Z   // not variadic, end of parameters

    // '@' shouldn't appear anywhere in the deco'd names
    Tinstance    : '@',
    Terror       : '@',
    Ttypeof      : '@',
    Tslice       : '@',
    Treturn      : '@',
    Tvector      : '@',
];

unittest
{
    foreach (i, mangle; mangleChar)
    {
        if (mangle == char.init)
        {
            fprintf(stderr, "ty = %u\n", cast(uint)i);
            assert(0);
        }
    }
}

/***********************
 * Mangle basic type ty to buf.
 */

private void tyToDecoBuffer(OutBuffer* buf, int ty)
{
    const c = mangleChar[ty];
    buf.writeByte(c);
    if (c == 'z')
        buf.writeByte(ty == Tint128 ? 'i' : 'k');
}

/*********************************
 * Mangling for mod.
 */
private void MODtoDecoBuffer(OutBuffer* buf, MOD mod)
{
    switch (mod)
    {
    case 0:
        break;
    case MODconst:
        buf.writeByte('x');
        break;
    case MODimmutable:
        buf.writeByte('y');
        break;
    case MODshared:
        buf.writeByte('O');
        break;
    case MODshared | MODconst:
        buf.writestring("Ox");
        break;
    case MODwild:
        buf.writestring("Ng");
        break;
    case MODwildconst:
        buf.writestring("Ngx");
        break;
    case MODshared | MODwild:
        buf.writestring("ONg");
        break;
    case MODshared | MODwildconst:
        buf.writestring("ONgx");
        break;
    default:
        assert(0);
    }
}

extern (C++) final class Mangler : Visitor
{
    alias visit = super.visit;
public:
    OutBuffer* buf;

    extern (D) this(OutBuffer* buf)
    {
        this.buf = buf;
    }

    ////////////////////////////////////////////////////////////////////////////
    /**************************************************
     * Type mangling
     */
    void visitWithMask(Type t, ubyte modMask)
    {
        if (t.deco && modMask == 0)
        {
            buf.writestring(t.deco);    // don't need to recreate it
            return;
        }
        if (modMask != t.mod)
        {
            MODtoDecoBuffer(buf, t.mod);
        }
        t.accept(this);
    }

    override void visit(Type t)
    {
        tyToDecoBuffer(buf, t.ty);
    }

    override void visit(TypeNext t)
    {
        visit(cast(Type)t);
        visitWithMask(t.next, t.mod);
    }

    override void visit(TypeVector t)
    {
        buf.writestring("Nh");
        visitWithMask(t.basetype, t.mod);
    }

    override void visit(TypeSArray t)
    {
        visit(cast(Type)t);
        if (t.dim)
            buf.printf("%u", cast(uint)t.dim.toInteger());
        if (t.next)
            visitWithMask(t.next, t.mod);
    }

    override void visit(TypeDArray t)
    {
        visit(cast(Type)t);
        if (t.next)
            visitWithMask(t.next, t.mod);
    }

    override void visit(TypeAArray t)
    {
        visit(cast(Type)t);
        visitWithMask(t.index, 0);
        visitWithMask(t.next, t.mod);
    }

    override void visit(TypeFunction t)
    {
        //printf("TypeFunction.toDecoBuffer() t = %p %s\n", t, t.toChars());
        //static int nest; if (++nest == 50) *(char*)0=0;
        mangleFuncType(t, t, t.mod, t.next);
    }

    void mangleFuncType(TypeFunction t, TypeFunction ta, ubyte modMask, Type tret)
    {
        //printf("mangleFuncType() %s\n", t.toChars());
        if (t.inuse)
        {
            t.inuse = 2; // flag error to caller
            return;
        }
        t.inuse++;
        if (modMask != t.mod)
            MODtoDecoBuffer(buf, t.mod);
        ubyte mc;
        switch (t.linkage)
        {
        case LINKd:
            mc = 'F';
            break;
        case LINKc:
            mc = 'U';
            break;
        case LINKwindows:
            mc = 'W';
            break;
        case LINKpascal:
            mc = 'V';
            break;
        case LINKcpp:
            mc = 'R';
            break;
        case LINKobjc:
            mc = 'Y';
            break;
        default:
            assert(0);
        }
        buf.writeByte(mc);
        if (ta.purity || ta.isnothrow || ta.isnogc || ta.isproperty || ta.isref || ta.trust || ta.isreturn || ta.isscope)
        {
            if (ta.purity)
                buf.writestring("Na");
            if (ta.isnothrow)
                buf.writestring("Nb");
            if (ta.isref)
                buf.writestring("Nc");
            if (ta.isproperty)
                buf.writestring("Nd");
            if (ta.isnogc)
                buf.writestring("Ni");
            if (ta.isreturn)
                buf.writestring("Nj");
            if (ta.isscope && !ta.isreturn)
                buf.writestring("Nl");
            switch (ta.trust)
            {
            case TRUSTtrusted:
                buf.writestring("Ne");
                break;
            case TRUSTsafe:
                buf.writestring("Nf");
                break;
            default:
                break;
            }
        }
        // Write argument types
        paramsToDecoBuffer(t.parameters);
        //if (buf.data[buf.offset - 1] == '@') assert(0);
        buf.writeByte('Z' - t.varargs); // mark end of arg list
        if (tret !is null)
            visitWithMask(tret, 0);
        t.inuse--;
    }

    override void visit(TypeIdentifier t)
    {
        visit(cast(Type)t);
        const(char)* name = t.ident.toChars();
        size_t len = strlen(name);
        buf.printf("%u%s", cast(uint)len, name);
    }

    override void visit(TypeEnum t)
    {
        visit(cast(Type)t);
        t.sym.accept(this);
    }

    override void visit(TypeStruct t)
    {
        //printf("TypeStruct.toDecoBuffer('%s') = '%s'\n", t.toChars(), name);
        visit(cast(Type)t);
        t.sym.accept(this);
    }

    override void visit(TypeClass t)
    {
        //printf("TypeClass.toDecoBuffer('%s' mod=%x) = '%s'\n", t.toChars(), mod, name);
        visit(cast(Type)t);
        t.sym.accept(this);
    }

    override void visit(TypeTuple t)
    {
        //printf("TypeTuple.toDecoBuffer() t = %p, %s\n", t, t.toChars());
        visit(cast(Type)t);
        OutBuffer buf2;
        buf2.reserve(32);
        scope Mangler v = new Mangler(&buf2);
        v.paramsToDecoBuffer(t.arguments);
        auto s = buf2.peekSlice();
        int len = cast(int)s.length;
        buf.printf("%d%.*s", len, len, s.ptr);
    }

    override void visit(TypeNull t)
    {
        visit(cast(Type)t);
    }

    ////////////////////////////////////////////////////////////////////////////
    void mangleDecl(Declaration sthis)
    {
        mangleParent(sthis);
        assert(sthis.ident);
        const(char)* id = sthis.ident.toChars();
        toBuffer(id, sthis);
        if (FuncDeclaration fd = sthis.isFuncDeclaration())
        {
            mangleFunc(fd, false);
        }
        else if (sthis.type.deco)
        {
            buf.writestring(sthis.type.deco);
        }
        else
            assert(0);
    }

    void mangleParent(Dsymbol s)
    {
        Dsymbol p;
        if (TemplateInstance ti = s.isTemplateInstance())
            p = ti.isTemplateMixin() ? ti.parent : ti.tempdecl.parent;
        else
            p = s.parent;
        if (p)
        {
            mangleParent(p);
            if (p.getIdent())
            {
                const(char)* id = p.ident.toChars();
                toBuffer(id, s);
                if (FuncDeclaration f = p.isFuncDeclaration())
                    mangleFunc(f, true);
            }
            else
                buf.writeByte('0');
        }
    }

    void mangleFunc(FuncDeclaration fd, bool inParent)
    {
        //printf("deco = '%s'\n", fd.type.deco ? fd.type.deco : "null");
        //printf("fd.type = %s\n", fd.type.toChars());
        if (fd.needThis() || fd.isNested())
            buf.writeByte('M');
        if (inParent)
        {
            TypeFunction tf = cast(TypeFunction)fd.type;
            TypeFunction tfo = cast(TypeFunction)fd.originalType;
            mangleFuncType(tf, tfo, 0, null);
        }
        else if (fd.type.deco)
        {
            buf.writestring(fd.type.deco);
        }
        else
        {
            printf("[%s] %s %s\n", fd.loc.toChars(), fd.toChars(), fd.type.toChars());
            assert(0); // don't mangle function until semantic3 done.
        }
    }

    /************************************************************
     * Write length prefixed string to buf.
     */
    void toBuffer(const(char)* id, Dsymbol s)
    {
        size_t len = strlen(id);
        if (len >= 8 * 1024 * 1024) // 8 megs ought be enough for anyone
            s.error("excessive length %llu for symbol, possible recursive expansion?", len);
        else
        {
            buf.printf("%u", cast(uint)len);
            buf.write(id, len);
        }
    }

    override void visit(Declaration d)
    {
        //printf("Declaration.mangle(this = %p, '%s', parent = '%s', linkage = %d)\n",
        //        d, d.toChars(), d.parent ? d.parent.toChars() : "null", d.linkage);
        if (!d.parent || d.parent.isModule() || d.linkage == LINKcpp) // if at global scope
        {
            switch (d.linkage)
            {
            case LINKd:
                break;
            case LINKc:
            case LINKwindows:
            case LINKpascal:
            case LINKobjc:
                buf.writestring(d.ident.toChars());
                return;
            case LINKcpp:
                buf.writestring(toCppMangle(d));
                return;
            case LINKdefault:
                d.error("forward declaration");
                buf.writestring(d.ident.toChars());
                return;
            default:
                fprintf(stderr, "'%s', linkage = %d\n", d.toChars(), d.linkage);
                assert(0);
            }
        }
        buf.writestring("_D");
        mangleDecl(d);
        debug
        {
            const slice = buf.peekSlice();
            assert(slice.length);
            foreach (const char c; slice)
            {
                assert(c == '_' || c == '@' || c == '?' || c == '$' || isalnum(c) || c & 0x80);
            }
        }
    }

    /******************************************************************************
     * Normally FuncDeclaration and FuncAliasDeclaration have overloads.
     * If and only if there is no overloads, mangle() could return
     * exact mangled name.
     *
     *      module test;
     *      void foo(long) {}           // _D4test3fooFlZv
     *      void foo(string) {}         // _D4test3fooFAyaZv
     *
     *      // from FuncDeclaration.mangle().
     *      pragma(msg, foo.mangleof);  // prints unexact mangled name "4test3foo"
     *                                  // by calling Dsymbol.mangle()
     *
     *      // from FuncAliasDeclaration.mangle()
     *      pragma(msg, __traits(getOverloads, test, "foo")[0].mangleof);  // "_D4test3fooFlZv"
     *      pragma(msg, __traits(getOverloads, test, "foo")[1].mangleof);  // "_D4test3fooFAyaZv"
     *
     * If a function has no overloads, .mangleof property still returns exact mangled name.
     *
     *      void bar() {}
     *      pragma(msg, bar.mangleof);  // still prints "_D4test3barFZv"
     *                                  // by calling FuncDeclaration.mangleExact().
     */
    override void visit(FuncDeclaration fd)
    {
        if (fd.isUnique())
            mangleExact(fd);
        else
            visit(cast(Dsymbol)fd);
    }

    // ditto
    override void visit(FuncAliasDeclaration fd)
    {
        FuncDeclaration f = fd.toAliasFunc();
        FuncAliasDeclaration fa = f.isFuncAliasDeclaration();
        if (!fd.hasOverloads && !fa)
        {
            mangleExact(f);
            return;
        }
        if (fa)
        {
            fa.accept(this);
            return;
        }
        visit(cast(Dsymbol)fd);
    }

    override void visit(OverDeclaration od)
    {
        if (od.overnext)
        {
            visit(cast(Dsymbol)od);
            return;
        }
        if (FuncDeclaration fd = od.aliassym.isFuncDeclaration())
        {
            if (!od.hasOverloads || fd.isUnique())
            {
                mangleExact(fd);
                return;
            }
        }
        if (TemplateDeclaration td = od.aliassym.isTemplateDeclaration())
        {
            if (!od.hasOverloads || td.overnext is null)
            {
                td.accept(this);
                return;
            }
        }
        visit(cast(Dsymbol)od);
    }

    void mangleExact(FuncDeclaration fd)
    {
        assert(!fd.isFuncAliasDeclaration());
        if (fd.mangleOverride)
        {
            buf.writestring(fd.mangleOverride);
            return;
        }
        if (fd.isMain())
        {
            buf.writestring("_Dmain");
            return;
        }
        if (fd.isWinMain() || fd.isDllMain() || fd.ident == Id.tls_get_addr)
        {
            buf.writestring(fd.ident.toChars());
            return;
        }
        visit(cast(Declaration)fd);
    }

    override void visit(VarDeclaration vd)
    {
        if (vd.mangleOverride)
        {
            buf.writestring(vd.mangleOverride);
            return;
        }
        visit(cast(Declaration)vd);
    }

    override void visit(AggregateDeclaration ad)
    {
        ClassDeclaration cd = ad.isClassDeclaration();
        Dsymbol parentsave = ad.parent;
        if (cd)
        {
            /* These are reserved to the compiler, so keep simple
             * names for them.
             */
            if (cd.ident == Id.Exception && cd.parent.ident == Id.object || cd.ident == Id.TypeInfo || cd.ident == Id.TypeInfo_Struct || cd.ident == Id.TypeInfo_Class || cd.ident == Id.TypeInfo_Tuple || cd == ClassDeclaration.object || cd == Type.typeinfoclass || cd == Module.moduleinfo || strncmp(cd.ident.toChars(), "TypeInfo_", 9) == 0)
            {
                // Don't mangle parent
                ad.parent = null;
            }
        }
        visit(cast(Dsymbol)ad);
        ad.parent = parentsave;
    }

    override void visit(TemplateInstance ti)
    {
        version (none)
        {
            printf("TemplateInstance.mangle() %p %s", ti, ti.toChars());
            if (ti.parent)
                printf("  parent = %s %s", ti.parent.kind(), ti.parent.toChars());
            printf("\n");
        }
        if (!ti.tempdecl)
            ti.error("is not defined");
        else
            mangleParent(ti);
        ti.getIdent();
        const(char)* id = ti.ident ? ti.ident.toChars() : ti.toChars();
        toBuffer(id, ti);
        //printf("TemplateInstance.mangle() %s = %s\n", ti.toChars(), ti.id);
    }

    override void visit(Dsymbol s)
    {
        version (none)
        {
            printf("Dsymbol.mangle() '%s'", s.toChars());
            if (s.parent)
                printf("  parent = %s %s", s.parent.kind(), s.parent.toChars());
            printf("\n");
        }
        mangleParent(s);
        auto id = s.ident ? s.ident.toChars() : s.toChars();
        toBuffer(id, s);
        //printf("Dsymbol.mangle() %s = %s\n", s.toChars(), id);
    }

    ////////////////////////////////////////////////////////////////////////////
    override void visit(Expression e)
    {
        e.error("expression %s is not a valid template value argument", e.toChars());
    }

    override void visit(IntegerExp e)
    {
        if (cast(sinteger_t)e.value < 0)
            buf.printf("N%lld", -e.value);
        else
            buf.printf("i%lld", e.value);
    }

    override void visit(RealExp e)
    {
        buf.writeByte('e');
        realToMangleBuffer(e.value);
    }

    void realToMangleBuffer(real_t value)
    {
        /* Rely on %A to get portable mangling.
         * Must munge result to get only identifier characters.
         *
         * Possible values from %A  => mangled result
         * NAN                      => NAN
         * -INF                     => NINF
         * INF                      => INF
         * -0X1.1BC18BA997B95P+79   => N11BC18BA997B95P79
         * 0X1.9P+2                 => 19P2
         */
        if (CTFloat.isNaN(value))
            buf.writestring("NAN"); // no -NAN bugs
        else if (CTFloat.isInfinity(value))
            buf.writestring(value < CTFloat.zero ? "NINF" : "INF");
        else
        {
            enum BUFFER_LEN = 36;
            char[BUFFER_LEN] buffer;
            const n = CTFloat.sprint(buffer.ptr, 'A', value);
            assert(n < BUFFER_LEN);
            for (int i = 0; i < n; i++)
            {
                char c = buffer[i];
                switch (c)
                {
                case '-':
                    buf.writeByte('N');
                    break;
                case '+':
                case 'X':
                case '.':
                    break;
                case '0':
                    if (i < 2)
                        break; // skip leading 0X
                    goto default;
                default:
                    buf.writeByte(c);
                    break;
                }
            }
        }
    }

    override void visit(ComplexExp e)
    {
        buf.writeByte('c');
        realToMangleBuffer(e.toReal());
        buf.writeByte('c'); // separate the two
        realToMangleBuffer(e.toImaginary());
    }

    override void visit(NullExp e)
    {
        buf.writeByte('n');
    }

    override void visit(StringExp e)
    {
        char m;
        OutBuffer tmp;
        const(char)[] q;
        /* Write string in UTF-8 format
         */
        switch (e.sz)
        {
        case 1:
            m = 'a';
            q = e.string[0 .. e.len];
            break;
        case 2:
            m = 'w';
            for (size_t u = 0; u < e.len;)
            {
                dchar c;
                const p = utf_decodeWchar(e.wstring, e.len, u, c);
                if (p)
                    e.error("%s", p);
                else
                    tmp.writeUTF8(c);
            }
            q = tmp.peekSlice();
            break;
        case 4:
            m = 'd';
            for (size_t u = 0; u < e.len; u++)
            {
                uint c = (cast(uint*)e.string)[u];
                if (!utf_isValidDchar(c))
                    e.error("invalid UCS-32 char \\U%08x", c);
                else
                    tmp.writeUTF8(c);
            }
            q = tmp.peekSlice();
            break;
        default:
            assert(0);
        }
        buf.reserve(1 + 11 + 2 * q.length);
        buf.writeByte(m);
        buf.printf("%d_", cast(int)q.length); // nbytes <= 11
        size_t qi = 0;
        for (char* p = cast(char*)buf.data + buf.offset, pend = p + 2 * q.length; p < pend; p += 2, ++qi)
        {
            char hi = (q[qi] >> 4) & 0xF;
            p[0] = cast(char)(hi < 10 ? hi + '0' : hi - 10 + 'a');
            char lo = q[qi] & 0xF;
            p[1] = cast(char)(lo < 10 ? lo + '0' : lo - 10 + 'a');
        }
        buf.offset += 2 * q.length;
    }

    override void visit(ArrayLiteralExp e)
    {
        size_t dim = e.elements ? e.elements.dim : 0;
        buf.printf("A%u", cast(uint)dim);
        for (size_t i = 0; i < dim; i++)
        {
            e.getElement(i).accept(this);
        }
    }

    override void visit(AssocArrayLiteralExp e)
    {
        size_t dim = e.keys.dim;
        buf.printf("A%u", cast(uint)dim);
        for (size_t i = 0; i < dim; i++)
        {
            (*e.keys)[i].accept(this);
            (*e.values)[i].accept(this);
        }
    }

    override void visit(StructLiteralExp e)
    {
        size_t dim = e.elements ? e.elements.dim : 0;
        buf.printf("S%u", cast(uint)dim);
        for (size_t i = 0; i < dim; i++)
        {
            Expression ex = (*e.elements)[i];
            if (ex)
                ex.accept(this);
            else
                buf.writeByte('v'); // 'v' for void
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    void paramsToDecoBuffer(Parameters* parameters)
    {
        //printf("Parameter.paramsToDecoBuffer()\n");

        int paramsToDecoBufferDg(size_t n, Parameter p)
        {
            p.accept(this);
            return 0;
        }

        Parameter._foreach(parameters, &paramsToDecoBufferDg);
    }

    override void visit(Parameter p)
    {
        if (p.storageClass & STCscope)
            buf.writeByte('M');
        // 'return inout ref' is the same as 'inout ref'
        if ((p.storageClass & (STCreturn | STCwild)) == STCreturn)
            buf.writestring("Nk");
        switch (p.storageClass & (STCin | STCout | STCref | STClazy))
        {
        case 0:
        case STCin:
            break;
        case STCout:
            buf.writeByte('J');
            break;
        case STCref:
            buf.writeByte('K');
            break;
        case STClazy:
            buf.writeByte('L');
            break;
        default:
            debug
            {
                printf("storageClass = x%llx\n", p.storageClass & (STCin | STCout | STCref | STClazy));
            }
            assert(0);
        }
        visitWithMask(p.type, 0);
    }
}


/******************************************************************************
 * Returns exact mangled name of function.
 */
extern (C++) const(char)* mangleExact(FuncDeclaration fd)
{
    if (!fd.mangleString)
    {
        OutBuffer buf;
        scope Mangler v = new Mangler(&buf);
        v.mangleExact(fd);
        fd.mangleString = buf.extractString();
    }
    return fd.mangleString;
}

extern (C++) void mangleToBuffer(Type t, OutBuffer* buf)
{
    if (t.deco)
        buf.writestring(t.deco);
    else
    {
        scope Mangler v = new Mangler(buf);
        v.visitWithMask(t, 0);
    }
}

extern (C++) void mangleToBuffer(Expression e, OutBuffer* buf)
{
    scope Mangler v = new Mangler(buf);
    e.accept(v);
}

extern (C++) void mangleToBuffer(Dsymbol s, OutBuffer* buf)
{
    scope Mangler v = new Mangler(buf);
    s.accept(v);
}

