package core.demangle;

private enum TypeCtor : ushort {
    None      = 0,
    //// 'x'
    Const     = (1 << 1),
    /// 'y'
    Immutable = (1 << 2),
    /// 'O'
    Shared    = (1 << 3),
    ///
    InOut     = (1 << 4),
}

private immutable ManglingFlagInfo[] typeCtors = [
    ManglingFlagInfo(TypeCtor.Immutable, "immutable"),
    ManglingFlagInfo(TypeCtor.Shared,    "shared"),
    ManglingFlagInfo(TypeCtor.InOut,     "inout"),
    ManglingFlagInfo(TypeCtor.Const,     "const"),
];

private enum FuncAttributes : ushort {
    None      = 0,
    //// 'a'
    Pure     = (1 << 1),
    //// 'b'
    Nothrow  = (1 << 2),
    //// 'c'
    Ref      = (1 << 3),
    //// 'd'
    Property = (1 << 4),
    //// 'e'
    Trusted  = (1 << 5),
    //// 'f'
    Safe     = (1 << 6),
    //// 'i'
    NoGC     = (1 << 7),
    //// 'j'
    Return   = (1 << 8),
    //// 'l'
    Scope    = (1 << 9),
    //// 'm'
    Live     = (1 << 10),

    /// Their order matter
    ReturnScope   = (1 << 11),
    ScopeReturn   = (1 << 12),
}

// The order in which we process is the same as in compiler/dmd/src/dmangle.d
private immutable ManglingFlagInfo[] funcAttrs = [
    ManglingFlagInfo(FuncAttributes.Pure,     "pure"),
    ManglingFlagInfo(FuncAttributes.Nothrow,  "nothrow"),
    ManglingFlagInfo(FuncAttributes.Ref,      "ref"),
    ManglingFlagInfo(FuncAttributes.Property, "@property"),
    ManglingFlagInfo(FuncAttributes.NoGC,     "@nogc"),

    ManglingFlagInfo(FuncAttributes.ReturnScope, "return scope"),
    ManglingFlagInfo(FuncAttributes.ScopeReturn, "scope return"),

    ManglingFlagInfo(FuncAttributes.Return,   "return"),
    ManglingFlagInfo(FuncAttributes.Scope,    "scope"),

    ManglingFlagInfo(FuncAttributes.Live,     "@live"),
    ManglingFlagInfo(FuncAttributes.Trusted,  "@trusted"),
    ManglingFlagInfo(FuncAttributes.Safe,     "@safe"),
];

private struct NoHooks
{
    // supported hooks
    // static bool parseLName(ref Demangle);
    // static char[] parseType(ref Demangle, char[])
}

private struct Demangle(Hooks = NoHooks)
{
    // NOTE: This implementation currently only works with mangled function
    //       names as they exist in an object file.  Type names mangled via
    //       the .mangleof property are effectively incomplete as far as the
    //       ABI is concerned and so are not considered to be mangled symbol
    //       names.

    // NOTE: This implementation builds the demangled buffer in place by
    //       writing data as it is decoded and then rearranging it later as
    //       needed.  In practice this results in very little data movement,
    //       and the performance cost is more than offset by the gain from
    //       not allocating dynamic memory to assemble the name piecemeal.
    //
    //       If the destination buffer is too small, parsing will restart
    //       with a larger buffer.  Since this generally means only one
    //       allocation during the course of a parsing run, this is still
    //       faster than assembling the result piecemeal.

pure @safe:
    enum AddType { no, yes }


    this( return scope const(char)[] buf_, return scope char[] dst_ = null )
    {
        this( buf_, AddType.yes, dst_ );
    }


    this( return scope const(char)[] buf_, AddType addType_, return scope char[] dst_ = null )
    {
        buf     = buf_;
        addType = addType_;
        dst.dst = dst_;
    }

    const(char)[]   buf     = null;
    Buffer          dst;
    size_t          pos     = 0;
    size_t          brp     = 0; // current back reference pos
    AddType         addType = AddType.yes;
    bool            mute    = false;
    Hooks           hooks;

    //////////////////////////////////////////////////////////////////////////
    // Type Testing and Conversion
    //////////////////////////////////////////////////////////////////////////


    static bool isAlpha( char val )
    {
        return ('a' <= val && 'z' >= val) ||
               ('A' <= val && 'Z' >= val) ||
               (0x80 & val); // treat all unicode as alphabetic
    }


    static bool isDigit( char val ) nothrow
    {
        return '0' <= val && '9' >= val;
    }


    static bool isHexDigit( char val )
    {
        return ('0' <= val && '9' >= val) ||
               ('a' <= val && 'f' >= val) ||
               ('A' <= val && 'F' >= val);
    }


    static ubyte ascii2hex( out bool errStatus, char val ) nothrow
    {
        if (val >= 'a' && val <= 'f')
            return cast(ubyte)(val - 'a' + 10);
        if (val >= 'A' && val <= 'F')
            return cast(ubyte)(val - 'A' + 10);
        if (val >= '0' && val <= '9')
            return cast(ubyte)(val - '0');

        errStatus = true;
        return 0;
    }

    BufSlice shift(scope const BufSlice val) return scope
    {
        if (mute)
            return dst.bslice_empty;
        return dst.shift(val);
    }

    void putComma(size_t n)
    {
        version (DigitalMars) pragma(inline, false);
        if (n)
            put(", ");
    }

    void put(char c) return scope
    {
        char[1] val = c;
        put(val[]);
    }

    void put(scope BufSlice val) return scope
    {
        put(val.getSlice);
    }

    void put(scope const(char)[] val) return scope nothrow
    {
        if (mute)
            return;
        dst.append(val);
    }


    void putAsHex( size_t val, int width = 0 )
    {
        import core.internal.string;

        UnsignedStringBuf buf = void;

        auto s = unsignedToTempString!16(val, buf);
        int slen = cast(int)s.length;
        if (slen < width)
        {
            foreach (i; slen .. width)
                put('0');
        }
        put(s);
    }


    void pad( const(char)[] val )
    {
        if ( val.length )
        {
            put(" ");
            put( val );
        }
    }


    void silent( out bool err_status, void delegate(out bool err_status) pure @safe nothrow dg ) nothrow
    {
        auto n = dst.length;
        dg(err_status);
        if(!err_status)
            dst.len = n;
    }


    //////////////////////////////////////////////////////////////////////////
    // Parsing Utility
    //////////////////////////////////////////////////////////////////////////

    @property bool empty()
    {
        return pos >= buf.length;
    }

    @property char front()
    {
        if ( pos < buf.length )
            return buf[pos];
        return char.init;
    }

    char peek( size_t n )
    {
        if ( pos + n < buf.length )
            return buf[pos + n];
        return char.init;
    }


    bool test( char val ) nothrow
    {
        return val == front;
    }

    void popFront() nothrow
    {
        if ( pos++ >= buf.length )
            assert(false);
    }


    void popFront(int i) nothrow
    {
        while (i--)
            popFront();
    }


    bool match( char val ) nothrow
    {
        if (!test(val))
            return false;
        else
        {
            popFront();
            return true;
        }
    }

    bool match( const(char)[] val ) nothrow
    {
        foreach (char e; val )
            if (!match( e ))
                return false;

        return true;
    }


    void eat( char val )
    {
        if ( val == front )
            popFront();
    }

    bool isSymbolNameFront(out bool errStatus) nothrow
    {
        char val = front;
        if ( isDigit( val ) || val == '_' )
            return true;
        if ( val != 'Q' )
            return false;

        // check the back reference encoding after 'Q'
        val = peekBackref();
        if (val == 0)
        {
            // invalid back reference
            errStatus = true;
            return false;
        }

        return isDigit( val ); // identifier ref
    }

    // return the first character at the back reference
    char peekBackref() nothrow
    {
        assert( front == 'Q' );
        auto n = decodeBackref!1();
        if (!n || n > pos)
            return 0; // invalid back reference

        return buf[pos - n];
    }

    size_t decodeBackref(size_t peekAt = 0)() nothrow
    {
        enum base = 26;
        size_t n = 0;
        for (size_t p; ; p++)
        {
            char t;
            static if (peekAt > 0)
            {
                t = peek(peekAt + p);
            }
            else
            {
                t = front;
                popFront();
            }
            if (t < 'A' || t > 'Z')
            {
                if (t < 'a' || t > 'z')
                    return 0; // invalid back reference

                n = base * n + t - 'a';
                return n;
            }
            n = base * n + t - 'A';
        }
    }

    //////////////////////////////////////////////////////////////////////////
    // Parsing Implementation
    //////////////////////////////////////////////////////////////////////////


    /*
    Number:
        Digit
        Digit Number
    */
    const(char)[] sliceNumber() return scope
    {
        auto beg = pos;

        while ( true )
        {
            auto t = front;
            if (t >= '0' && t <= '9')
                popFront();
            else
                return buf[beg .. pos];
        }
    }


    size_t decodeNumber(out bool errStatus) scope nothrow
    {
        return decodeNumber( errStatus, sliceNumber() );
    }

    size_t decodeNumber( out bool errStatus, scope const(char)[] num ) scope nothrow
    {
        size_t val = 0;

        foreach ( c; num )
        {
            import core.checkedint : mulu, addu;

            bool overflow = false;
            val = mulu(val, 10, overflow);
            val = addu(val, c - '0',  overflow);
            if (overflow)
            {
                errStatus = true;
                return 0;
            }
        }
        return val;
    }

    void parseReal(out bool errStatus) scope nothrow
    {
        char[64] tbuf = void;
        size_t   tlen = 0;
        real     val  = void;

        void onError()
        {
            errStatus = true;
        }

        if ( 'I' == front )
        {
            if (!match("INF"))
                return onError();
            put( "real.infinity" );
            return;
        }
        if ( 'N' == front )
        {
            popFront();
            if ( 'I' == front )
            {
                if (!match("INF"))
                    return onError();
                put( "-real.infinity" );
                return;
            }
            if ( 'A' == front )
            {
                if (!match("AN"))
                    return onError();
                put( "real.nan" );
                return;
            }
            tbuf[tlen++] = '-';
        }

        tbuf[tlen++] = '0';
        tbuf[tlen++] = 'X';
        errStatus = !isHexDigit( front );
        if (errStatus)
            return; // Expected hex digit

        tbuf[tlen++] = front;
        tbuf[tlen++] = '.';
        popFront();

        while ( isHexDigit( front ) )
        {
            if (tlen >= tbuf.length)
                return onError(); // Too many hex float digits
            tbuf[tlen++] = front;
            popFront();
        }
        if (!match('P'))
            return onError();
        tbuf[tlen++] = 'p';
        if ( 'N' == front )
        {
            tbuf[tlen++] = '-';
            popFront();
        }
        else
        {
            tbuf[tlen++] = '+';
        }
        while ( isDigit( front ) )
        {
            tbuf[tlen++] = front;
            popFront();
        }

        tbuf[tlen] = 0;
        pureReprintReal( tbuf[] );
        put( tbuf[0 .. tlen] );
    }


    /*
    LName:
        Number Name

    Name:
        Namestart
        Namestart Namechars

    Namestart:
        _
        Alpha

    Namechar:
        Namestart
        Digit

    Namechars:
        Namechar
        Namechar Namechars
    */
    void parseLName(out string errMsg) scope nothrow
    {
        static if (__traits(hasMember, Hooks, "parseLName"))
        {
            auto r = hooks.parseLName(errMsg, this);
            if (errMsg !is null)
                return;
            if (r) return;
        }

        void error(string msg)
        {
            errMsg = msg;
        }

        if ( front == 'Q' )
        {
            // back reference to LName
            auto refPos = pos;
            popFront();
            size_t n = decodeBackref();
            if (!n || n > refPos)
                return error("Invalid LName back reference");

            if ( !mute )
            {
                auto savePos = pos;
                scope(exit) pos = savePos;
                pos = refPos - n;
                parseLName(errMsg);
            }
            return;
        }

        bool err_flag;
        auto n = decodeNumber(err_flag);
        if (err_flag)
            return error("Number overflow");

        if ( n == 0 )
        {
            put( "__anonymous" );
            return;
        }
        if ( n > buf.length || n > buf.length - pos )
            return error("LName must be at least 1 character");

        if ( '_' != front && !isAlpha( front ) )
            return error("Invalid character in LName");

        foreach (char e; buf[pos + 1 .. pos + n] )
        {
            if ( '_' != e && !isAlpha( e ) && !isDigit( e ) )
                return error("Invalid character in LName");
        }

        put( buf[pos .. pos + n] );
        pos += n;
    }


    /*
    Type:
        Shared
        Const
        Immutable
        Wild
        TypeArray
        TypeVector
        TypeStaticArray
        TypeAssocArray
        TypePointer
        TypeFunction
        TypeIdent
        TypeClass
        TypeStruct
        TypeEnum
        TypeTypedef
        TypeDelegate
        TypeNone
        TypeVoid
        TypeNoreturn
        TypeByte
        TypeUbyte
        TypeShort
        TypeUshort
        TypeInt
        TypeUint
        TypeLong
        TypeUlong
        TypeCent
        TypeUcent
        TypeFloat
        TypeDouble
        TypeReal
        TypeIfloat
        TypeIdouble
        TypeIreal
        TypeCfloat
        TypeCdouble
        TypeCreal
        TypeBool
        TypeChar
        TypeWchar
        TypeDchar
        TypeTuple

    Shared:
        O Type

    Const:
        x Type

    Immutable:
        y Type

    Wild:
        Ng Type

    TypeArray:
        A Type

    TypeVector:
        Nh Type

    TypeStaticArray:
        G Number Type

    TypeAssocArray:
        H Type Type

    TypePointer:
        P Type

    TypeFunction:
        CallConvention FuncAttrs Arguments ArgClose Type

    TypeIdent:
        I LName

    TypeClass:
        C LName

    TypeStruct:
        S LName

    TypeEnum:
        E LName

    TypeTypedef:
        T LName

    TypeDelegate:
        D TypeFunction

    TypeNone:
        n

    TypeVoid:
        v

    TypeNoreturn
        Nn

    TypeByte:
        g

    TypeUbyte:
        h

    TypeShort:
        s

    TypeUshort:
        t

    TypeInt:
        i

    TypeUint:
        k

    TypeLong:
        l

    TypeUlong:
        m

    TypeCent
        zi

    TypeUcent
        zk

    TypeFloat:
        f

    TypeDouble:
        d

    TypeReal:
        e

    TypeIfloat:
        o

    TypeIdouble:
        p

    TypeIreal:
        j

    TypeCfloat:
        q

    TypeCdouble:
        r

    TypeCreal:
        c

    TypeBool:
        b

    TypeChar:
        a

    TypeWchar:
        u

    TypeDchar:
        w

    TypeTuple:
        B Number Arguments
    */
    BufSlice parseType(out bool errStatus) return scope nothrow
    {
        static immutable string[23] primitives = [
            "char", // a
            "bool", // b
            "creal", // c
            "double", // d
            "real", // e
            "float", // f
            "byte", // g
            "ubyte", // h
            "int", // i
            "ireal", // j
            "uint", // k
            "long", // l
            "ulong", // m
            null, // n
            "ifloat", // o
            "idouble", // p
            "cfloat", // q
            "cdouble", // r
            "short", // s
            "ushort", // t
            "wchar", // u
            "void", // v
            "dchar", // w
        ];

        static if (__traits(hasMember, Hooks, "parseType"))
        {
            auto n = hooks.parseType(errStatus, this, null);
            if (errStatus)
                return dst.bslice_empty;
            else
                if (n !is null)
                    return BufSlice(n, 0, n.length);
        }

        auto beg = dst.length;
        auto t = front;

        BufSlice parseBackrefType(out string errStatus, scope BufSlice delegate(bool err_flag) pure @safe nothrow parseDg) pure @safe nothrow
        {
            if (pos == brp)
            {
                errStatus = "recursive back reference";
                return dst.bslice_empty;
            }

            auto refPos = pos;
            popFront();
            auto n = decodeBackref();
            if (n == 0 || n > pos)
            {
                errStatus = "invalid back reference";
                return dst.bslice_empty;
            }

            if ( mute )
                return dst.bslice_empty;
            auto savePos = pos;
            auto saveBrp = brp;
            scope(success) { pos = savePos; brp = saveBrp; }
            pos = refPos - n;
            brp = refPos;

            bool err_flag;
            auto ret = parseDg(err_flag);
            if (err_flag)
            {
                errStatus = "parseDg error";
                return dst.bslice_empty;
            }

            return ret;
        }

        // call parseType() and return error if occured
        enum parseTypeOrF = "parseType(errStatus); if (errStatus) return dst.bslice_empty;";

        switch ( t )
        {
        case 'Q': // Type back reference
            string errMsg;
            auto r = parseBackrefType(errMsg, (e_flag) => parseType(e_flag));
            if (errMsg !is null)
                return dst.bslice_empty;
            return r;
        case 'O': // Shared (O Type)
            popFront();
            put( "shared(" );
            mixin(parseTypeOrF);
            put( ')' );
            return dst[beg .. $];
        case 'x': // Const (x Type)
            popFront();
            put( "const(" );
            mixin(parseTypeOrF);
            put( ')' );
            return dst[beg .. $];
        case 'y': // Immutable (y Type)
            popFront();
            put( "immutable(" );
            mixin(parseTypeOrF);
            put( ')' );
            return dst[beg .. $];
        case 'N':
            popFront();
            switch ( front )
            {
            case 'n': // Noreturn
                popFront();
                put("noreturn");
                return dst[beg .. $];
            case 'g': // Wild (Ng Type)
                popFront();
                // TODO: Anything needed here?
                put( "inout(" );
                mixin(parseTypeOrF);
                put( ')' );
                return dst[beg .. $];
            case 'h': // TypeVector (Nh Type)
                popFront();
                put( "__vector(" );
                mixin(parseTypeOrF);
                put( ')' );
                return dst[beg .. $];
            default:
                errStatus = true;
                return dst.bslice_empty;
            }
        case 'A': // TypeArray (A Type)
            popFront();
            mixin(parseTypeOrF);
            put( "[]" );
            return dst[beg .. $];
        case 'G': // TypeStaticArray (G Number Type)
            popFront();
            auto num = sliceNumber();
            mixin(parseTypeOrF);
            put( '[' );
            put( num );
            put( ']' );
            return dst[beg .. $];
        case 'H': // TypeAssocArray (H Type Type)
            popFront();
            // skip t1
            auto tx = parseType(errStatus);
            if (errStatus)
                return dst.bslice_empty;
            mixin(parseTypeOrF);
            put( '[' );
            shift(tx);
            put( ']' );
            return dst[beg .. $];
        case 'P': // TypePointer (P Type)
            popFront();
            mixin(parseTypeOrF);
            put( '*' );
            return dst[beg .. $];
        case 'F': case 'U': case 'W': case 'V': case 'R': // TypeFunction
            auto r = parseTypeFunction(errStatus);
            if (errStatus)
                return dst.bslice_empty;
            return r;
        case 'C': // TypeClass (C LName)
        case 'S': // TypeStruct (S LName)
        case 'E': // TypeEnum (E LName)
        case 'T': // TypeTypedef (T LName)
            popFront();
            parseQualifiedName(errStatus);
            if (errStatus)
                return dst.bslice_empty;
            return dst[beg .. $];
        case 'D': // TypeDelegate (D TypeFunction)
            popFront();
            auto modifiers = parseModifier();
            if ( front == 'Q' )
            {
                string errMsg;
                auto r = parseBackrefType(errMsg, (e_flag) => parseTypeFunction(e_flag, IsDelegate.yes));
                if (errMsg !is null)
                    return dst.bslice_empty;
                return r;
            }
            else
            {
                parseTypeFunction(errStatus, IsDelegate.yes);
                if (errStatus)
                    return dst.bslice_empty;
            }

            if (modifiers)
            {
                // write modifiers behind the function arguments
                while (auto str = typeCtors.toStringConsume(modifiers))
                {
                    put(' ');
                    put(str);
                }
            }
            return dst[beg .. $];
        case 'n': // TypeNone (n)
            popFront();
            // TODO: Anything needed here?
            return dst[beg .. $];
        case 'B': // TypeTuple (B Number Arguments)
            popFront();
            // TODO: Handle this.
            return dst[beg .. $];
        case 'Z': // Internal symbol
            // This 'type' is used for untyped internal symbols, i.e.:
            // __array
            // __init
            // __vtbl
            // __Class
            // __Interface
            // __ModuleInfo
            popFront();
            return dst[beg .. $];
        default:
            if (t >= 'a' && t <= 'w')
            {
                popFront();
                put( primitives[cast(size_t)(t - 'a')] );
                return dst[beg .. $];
            }
            else if (t == 'z')
            {
                popFront();
                switch ( front )
                {
                case 'i':
                    popFront();
                    put( "cent" );
                    return dst[beg .. $];
                case 'k':
                    popFront();
                    put( "ucent" );
                    return dst[beg .. $];
                default:
                    errStatus = true;
                    return dst.bslice_empty;
                }
            }
            errStatus = true;
            return dst.bslice_empty;
        }
    }


    /*
    TypeFunction:
        CallConvention FuncAttrs Arguments ArgClose Type

    CallConvention:
        F       // D
        U       // C
        W       // Windows
        R       // C++

    FuncAttrs:
        FuncAttr
        FuncAttr FuncAttrs

    FuncAttr:
        empty
        FuncAttrPure
        FuncAttrNothrow
        FuncAttrProperty
        FuncAttrRef
        FuncAttrReturn
        FuncAttrScope
        FuncAttrTrusted
        FuncAttrSafe

    FuncAttrPure:
        Na

    FuncAttrNothrow:
        Nb

    FuncAttrRef:
        Nc

    FuncAttrProperty:
        Nd

    FuncAttrTrusted:
        Ne

    FuncAttrSafe:
        Nf

    FuncAttrNogc:
        Ni

    FuncAttrReturn:
        Nj

    FuncAttrScope:
        Nl

    Arguments:
        Argument
        Argument Arguments

    Argument:
        Argument2
        M Argument2     // scope

    Argument2:
        Type
        J Type     // out
        K Type     // ref
        L Type     // lazy

    ArgClose
        X     // variadic T t,...) style
        Y     // variadic T t...) style
        Z     // not variadic
    */
    void parseCallConvention(out bool errStatus) nothrow
    {
        // CallConvention
        switch ( front )
        {
        case 'F': // D
            popFront();
            break;
        case 'U': // C
            popFront();
            put( "extern (C) " );
            break;
        case 'W': // Windows
            popFront();
            put( "extern (Windows) " );
            break;
        case 'R': // C++
            popFront();
            put( "extern (C++) " );
            break;
        default:
            errStatus = true;
        }
    }

    /// Returns: Flags of `TypeCtor`
    ushort parseModifier()
    {
        TypeCtor res = TypeCtor.None;
        switch ( front )
        {
        case 'y':
            popFront();
            return TypeCtor.Immutable;
        case 'O':
            popFront();
            res |= TypeCtor.Shared;
            if (front == 'x')
                goto case 'x';
            if (front == 'N')
                goto case 'N';
            return TypeCtor.Shared;
        case 'N':
            if (peek( 1 ) != 'g')
                return res;
            popFront();
            popFront();
            res |= TypeCtor.InOut;
            if ( front == 'x' )
                goto case 'x';
            return res;
        case 'x':
            popFront();
            res |= TypeCtor.Const;
            return res;
        default: return TypeCtor.None;
        }
    }

    ushort parseFuncAttr(out bool errStatus) nothrow
    {
        // FuncAttrs
        ushort result;
        while ('N' == front)
        {
            popFront();
            switch ( front )
            {
            case 'a': // FuncAttrPure
                popFront();
                result |= FuncAttributes.Pure;
                continue;
            case 'b': // FuncAttrNoThrow
                popFront();
                result |= FuncAttributes.Nothrow;
                continue;
            case 'c': // FuncAttrRef
                popFront();
                result |= FuncAttributes.Ref;
                continue;
            case 'd': // FuncAttrProperty
                popFront();
                result |= FuncAttributes.Property;
                continue;
            case 'e': // FuncAttrTrusted
                popFront();
                result |= FuncAttributes.Trusted;
                continue;
            case 'f': // FuncAttrSafe
                popFront();
                result |= FuncAttributes.Safe;
                continue;
            case 'g':
            case 'h':
            case 'k':
            case 'n':
                // NOTE: The inout parameter type is represented as "Ng".
                //       The vector parameter type is represented as "Nh".
                //       The return parameter type is represented as "Nk".
                //       The noreturn parameter type is represented as "Nn".
                //       These make it look like a FuncAttr, but infact
                //       if we see these, then we know we're really in
                //       the parameter list.  Rewind and break.
                pos--;
                return result;
            case 'i': // FuncAttrNogc
                popFront();
                result |= FuncAttributes.NoGC;
                continue;
            case 'j': // FuncAttrReturn
                popFront();
                if (this.peek(0) == 'N' && this.peek(1) == 'l')
                {
                    result |= FuncAttributes.ReturnScope;
                    popFront();
                    popFront();
                } else {
                    result |= FuncAttributes.Return;
                }
                continue;
            case 'l': // FuncAttrScope
                popFront();
                if (this.peek(0) == 'N' && this.peek(1) == 'j')
                {
                    result |= FuncAttributes.ScopeReturn;
                    popFront();
                    popFront();
                } else {
                    result |= FuncAttributes.Scope;
                }
                continue;
            case 'm': // FuncAttrLive
                popFront();
                result |= FuncAttributes.Live;
                continue;
            default:
                errStatus = true;
                return 0;
            }
        }
        return result;
    }

    void parseFuncArguments(out bool errStatus) scope nothrow
    {
        // Arguments
        for ( size_t n = 0; true; n++ )
        {
            switch ( front )
            {
            case 'X': // ArgClose (variadic T t...) style)
                popFront();
                put( "..." );
                return;
            case 'Y': // ArgClose (variadic T t,...) style)
                popFront();
                put( ", ..." );
                return;
            case 'Z': // ArgClose (not variadic)
                popFront();
                return;
            default:
                break;
            }
            putComma(n);

            /* Do special return, scope, ref, out combinations
             */
            int npops;
            if ( 'M' == front && peek(1) == 'N' && peek(2) == 'k')
            {
                const c3 = peek(3);
                if (c3 == 'J')
                {
                    put("scope return out ");   // MNkJ
                    npops = 4;
                }
                else if (c3 == 'K')
                {
                    put("scope return ref ");   // MNkK
                    npops = 4;
                }
            }
            else if ('N' == front && peek(1) == 'k')
            {
                const c2 = peek(2);
                if (c2 == 'J')
                {
                    put("return out ");         // NkJ
                    npops = 3;
                }
                else if (c2 == 'K')
                {
                    put("return ref ");         // NkK
                    npops = 3;
                }
                else if (c2 == 'M')
                {
                    const c3 = peek(3);
                    if (c3 == 'J')
                    {
                        put("return scope out ");       // NkMJ
                        npops = 4;
                    }
                    else if (c3 == 'K')
                    {
                        put("return scope ref ");       // NkMK
                        npops = 4;
                    }
                    else
                    {
                        put("return scope ");           // NkM
                        npops = 3;
                    }
                }
            }
            popFront(npops);

            if ( 'M' == front )
            {
                popFront();
                put( "scope " );
            }
            if ( 'N' == front )
            {
                popFront();
                if ( 'k' == front ) // Return (Nk Parameter2)
                {
                    popFront();
                    put( "return " );
                }
                else
                    pos--;
            }

            // call parseType() and return error if occured
            enum parseTypeOrF = "parseType(errStatus); if (errStatus) return;";

            switch ( front )
            {
            case 'I': // in  (I Type)
                popFront();
                put("in ");
                if (front == 'K')
                    goto case;
                mixin(parseTypeOrF);
                continue;
            case 'K': // ref (K Type)
                popFront();
                put( "ref " );
                mixin(parseTypeOrF);
                continue;
            case 'J': // out (J Type)
                popFront();
                put( "out " );
                mixin(parseTypeOrF);
                continue;
            case 'L': // lazy (L Type)
                popFront();
                put( "lazy " );
                mixin(parseTypeOrF);
                continue;
            default:
                mixin(parseTypeOrF);
            }
        }
    }

    enum IsDelegate { no, yes }

    /*
        TypeFunction:
            CallConvention FuncAttrs Arguments ArgClose Type
    */
    BufSlice parseTypeFunction(out bool errStatus, IsDelegate isdg = IsDelegate.no) return scope nothrow
    {
        auto beg = dst.length;

        parseCallConvention(errStatus);
        if (errStatus)
            return dst.bslice_empty;

        auto attributes = parseFuncAttr(errStatus);
        if (errStatus)
            return dst.bslice_empty;

        auto argbeg = dst.length;
        put(IsDelegate.yes == isdg ? "delegate" : "function");
        put( '(' );
        parseFuncArguments(errStatus);
        if (errStatus)
            return dst.bslice_empty;
        put( ')' );
        if (attributes)
        {
            // write function attributes behind arguments
            while (auto str = funcAttrs.toStringConsume(attributes))
            {
                put(' ');
                put(str);
            }
        }

        // A function / delegate return type is located at the end of its mangling
        // Write it in order, then shift it back to 'code order'
        // e.g. `delegate(int) @safedouble ' => 'double delegate(int) @safe'
        {
            auto retbeg = dst.length;
            parseType(errStatus);
            if (errStatus)
                return dst.bslice_empty;
            put(' ');
            shift(dst[argbeg .. retbeg]);
        }

        return dst[beg .. $];
    }

    static bool isCallConvention( char ch )
    {
        switch ( ch )
        {
            case 'F', 'U', 'V', 'W', 'R':
                return true;
            default:
                return false;
        }
    }

    /*
    Value:
        n
        Number
        i Number
        N Number
        e HexFloat
        c HexFloat c HexFloat
        A Number Value...

    HexFloat:
        NAN
        INF
        NINF
        N HexDigits P Exponent
        HexDigits P Exponent

    Exponent:
        N Number
        Number

    HexDigits:
        HexDigit
        HexDigit HexDigits

    HexDigit:
        Digit
        A
        B
        C
        D
        E
        F
    */

    void parseValue(out bool errStatus) scope nothrow
    {
        parseValue(errStatus, dst.bslice_empty);
    }

    void parseValue(out bool errStatus, scope BufSlice name, char type = '\0' ) scope nothrow
    {
        void onError()
        {
            errStatus = true;
        }

        switch ( front )
        {
        case 'n':
            popFront();
            put( "null" );
            return;
        case 'i':
            popFront();
            if ('0' > front || '9' < front)
                return onError(); // Number expected
            goto case;
        case '0': .. case '9':
            parseIntegerValue( errStatus, name, type );
            return;
        case 'N':
            popFront();
            put( '-' );
            parseIntegerValue( errStatus, name, type );
            return;
        case 'e':
            popFront();
            parseReal(errStatus);
            return;
        case 'c':
            popFront();
            parseReal(errStatus);
            if (errStatus)
                return;
            put( '+' );
            if (!match('c'))
                return onError();
            parseReal(errStatus);
            if (errStatus)
                return;
            put( 'i' );
            return;
        case 'a': case 'w': case 'd':
            char t = front;
            popFront();
            auto n = decodeNumber(errStatus);
            if (errStatus)
                return;
            if (!match('_'))
                return onError();
            put( '"' );
            foreach (i; 0..n)
            {
                auto a = ascii2hex( errStatus, front );
                if (errStatus)
                    return;
                popFront();

                auto b = ascii2hex( errStatus, front );
                if (errStatus)
                    return;
                popFront();

                auto v = cast(char)((a << 4) | b);
                if (' ' <= v && v <= '~')   // ASCII printable
                {
                    put(v);
                }
                else
                {
                    put("\\x");
                    putAsHex(v, 2);
                }
            }
            put( '"' );
            if ( 'a' != t )
                put(t);
            return;
        case 'A':
            // NOTE: This is kind of a hack.  An associative array literal
            //       [1:2, 3:4] is represented as HiiA2i1i2i3i4, so the type
            //       is "Hii" and the value is "A2i1i2i3i4".  Thus the only
            //       way to determine that this is an AA value rather than an
            //       array value is for the caller to supply the type char.
            //       Hopefully, this will change so that the value is
            //       "H2i1i2i3i4", rendering this unnecesary.
            if ( 'H' == type )
                goto LassocArray;
            // A Number Value...
            // An array literal. Value is repeated Number times.
            popFront();
            put( '[' );
            auto n = decodeNumber(errStatus);
            if (errStatus)
                return;
            foreach ( i; 0 .. n )
            {
                putComma(i);
                parseValue(errStatus);
                if (errStatus)
                    return;
            }
            put( ']' );
            return;
        case 'H':
        LassocArray:
            // H Number Value...
            // An associative array literal. Value is repeated 2*Number times.
            popFront();
            put( '[' );
            auto n = decodeNumber(errStatus);
            if (errStatus)
                return;
            foreach ( i; 0 .. n )
            {
                putComma(i);
                parseValue(errStatus);
                if (errStatus)
                    return;
                put(':');
                parseValue(errStatus);
                if (errStatus)
                    return;
            }
            put( ']' );
            return;
        case 'S':
            // S Number Value...
            // A struct literal. Value is repeated Number times.
            popFront();
            if ( name.length )
                put( name );
            put( '(' );
            auto n = decodeNumber(errStatus);
            if (errStatus)
                return;
            foreach ( i; 0 .. n )
            {
                putComma(i);
                parseValue(errStatus);
                if (errStatus)
                    return;
            }
            put( ')' );
            return;
        case 'f':
            // f MangledName
            // A function literal symbol
            popFront();
            parseMangledName(errStatus, false, 1);
            return;
        default:
            errStatus = true;
        }
    }

    void parseIntegerValue( out bool errStatus, scope BufSlice name, char type = '\0' ) scope nothrow
    {
        switch ( type )
        {
        case 'a': // char
        case 'u': // wchar
        case 'w': // dchar
        {
            auto val = sliceNumber();
            auto num = decodeNumber( errStatus, val );
            if (errStatus)
                return;

            switch ( num )
            {
            case '\'':
                put( "'\\''" );
                return;
            // \", \?
            case '\\':
                put( "'\\\\'" );
                return;
            case '\a':
                put( "'\\a'" );
                return;
            case '\b':
                put( "'\\b'" );
                return;
            case '\f':
                put( "'\\f'" );
                return;
            case '\n':
                put( "'\\n'" );
                return;
            case '\r':
                put( "'\\r'" );
                return;
            case '\t':
                put( "'\\t'" );
                return;
            case '\v':
                put( "'\\v'" );
                return;
            default:
                switch ( type )
                {
                case 'a':
                    if ( num >= 0x20 && num < 0x7F )
                    {
                        put( '\'' );
                        put( cast(char)num );
                        put( '\'' );
                        return;
                    }
                    put( "\\x" );
                    putAsHex( num, 2 );
                    return;
                case 'u':
                    put( "'\\u" );
                    putAsHex( num, 4 );
                    put( '\'' );
                    return;
                case 'w':
                    put( "'\\U" );
                    putAsHex( num, 8 );
                    put( '\'' );
                    return;
                default:
                    assert( 0 );
                }
            }
        }
        case 'b': // bool
            auto d = decodeNumber(errStatus);
            if (errStatus)
                return;
            put( d ? "true" : "false" );
            return;
        case 'h', 't', 'k': // ubyte, ushort, uint
            put( sliceNumber() );
            put( 'u' );
            return;
        case 'l': // long
            put( sliceNumber() );
            put( 'L' );
            return;
        case 'm': // ulong
            put( sliceNumber() );
            put( "uL" );
            return;
        default:
            put( sliceNumber() );
            return;
        }
    }


    /*
    TemplateArgs:
        TemplateArg
        TemplateArg TemplateArgs

    TemplateArg:
        TemplateArgX
        H TemplateArgX

    TemplateArgX:
        T Type
        V Type Value
        S Number_opt QualifiedName
        X ExternallyMangledName
    */
    void parseTemplateArgs(out bool errStatus) scope nothrow
    {
    L_nextArg:
        for ( size_t n = 0; true; n++ )
        {
            if ( front == 'H' )
                popFront();

            switch ( front )
            {
            case 'T':
                popFront();
                putComma(n);
                parseType(errStatus);
                if (errStatus)
                    return;
                continue;
            case 'V':
                popFront();
                putComma(n);
                // NOTE: In the few instances where the type is actually
                //       desired in the output it should precede the value
                //       generated by parseValue, so it is safe to simply
                //       decrement len and let put/append do its thing.
                char t = front; // peek at type for parseValue
                if ( t == 'Q' )
                {
                    t = peekBackref();
                    if (t == 0)
                    {
                        // invalid back reference
                        errStatus = true;
                        return;
                    }
                }
                BufSlice name = dst.bslice_empty;
                silent( errStatus, delegate void(out bool e_flg) nothrow { name = parseType(e_flg); } );
                if (errStatus)
                    return;
                parseValue( errStatus, name, t );
                if (errStatus)
                    return;
                continue;
            case 'S':
                popFront();
                putComma(n);

                if ( mayBeMangledNameArg() )
                {
                    auto l = dst.length;
                    auto p = pos;
                    auto b = brp;

                    if (parseMangledNameArg())
                        continue;
                    dst.len = l;
                    pos = p;
                    brp = b;
                }
                if ( isDigit( front ) && isDigit( peek( 1 ) ) )
                {
                    // ambiguity: length followed by qualified name (starting with number)
                    // try all possible pairs of numbers
                    auto qlen = decodeNumber(errStatus);
                    if (errStatus)
                        return;

                    qlen /= 10; // last digit needed for QualifiedName
                    pos--;
                    auto l = dst.length;
                    auto p = pos;
                    auto b = brp;
                    while ( qlen > 0 )
                    {
                        errStatus = false;
                        parseQualifiedName(errStatus);

                        if (!errStatus)
                        {
                            if ( pos == p + qlen )
                                continue L_nextArg;
                        }

                        qlen /= 10; // retry with one digit less
                        pos = --p;
                        dst.len = l;
                        brp = b;
                    }
                }

                parseQualifiedName(errStatus);
                if (errStatus)
                    return;
                continue;
            case 'X':
                popFront();
                putComma(n);
                {
                    string errMsg;
                    parseLName(errMsg);
                    if (errMsg)
                        return;
                }
                continue;
            default:
                return;
            }
        }
    }


    bool mayBeMangledNameArg() nothrow
    {
        bool errStatus;
        auto p = pos;
        scope(exit) pos = p;

        if ( isDigit( buf[pos] ) )
        {
            auto n = decodeNumber(errStatus);

            return !errStatus && n >= 4 &&
                pos < buf.length && '_' == buf[pos++] &&
                pos < buf.length && 'D' == buf[pos++] &&
                isDigit( buf[pos] );
        }
        else
        {
            const isSNF = isSymbolNameFront(errStatus);

            return !errStatus &&
                   pos < buf.length && '_' == buf[pos++] &&
                   pos < buf.length && 'D' == buf[pos++] &&
                   isSNF;
        }
    }

    bool parseMangledNameArg() nothrow
    {
        bool errStatus;

        size_t n = 0;
        if ( isDigit( front ) )
        {
            n = decodeNumber(errStatus);

            if (errStatus)
                return false;
        }

        parseMangledName(errStatus, false, n );

        return !errStatus;
    }

    /*
    TemplateInstanceName:
        Number __T LName TemplateArgs Z
    */
    void parseTemplateInstanceName(out bool errStatus, bool hasNumber) scope nothrow
    {
        auto sav = pos;
        auto saveBrp = brp;

        void onError()
        {
            errStatus = true;
            pos = sav;
            brp = saveBrp;
        }

        size_t n = 0;
        if (hasNumber)
        {
            n = decodeNumber(errStatus);
            if (errStatus)
                return onError();
        }

        auto beg = pos;
        errStatus = !match( "__T" );
        if (errStatus)
            return onError();

        {
            string errMsg;
            parseLName(errMsg);
            if (errMsg !is null)
                return onError();
        }

        put( "!(" );

        parseTemplateArgs(errStatus);
        if (errStatus)
            return onError();

        if (!match('Z'))
            return onError();

        if ( hasNumber && pos - beg != n )
        {
            // Template name length mismatch
            return onError();
        }

        put( ')' );
    }


    bool mayBeTemplateInstanceName() scope nothrow
    {
        auto p = pos;
        scope(exit) pos = p;

        bool errStatus;
        auto n = decodeNumber(errStatus);
        if (errStatus)
            return false;

        return n >= 5 &&
               pos < buf.length && '_' == buf[pos++] &&
               pos < buf.length && '_' == buf[pos++] &&
               pos < buf.length && 'T' == buf[pos++];
    }


    /*
    SymbolName:
        LName
        TemplateInstanceName
    */
    void parseSymbolName(out bool errStatus) scope nothrow
    {
        // LName -> Number
        // TemplateInstanceName -> Number "__T"
        switch ( front )
        {
        case '_':
            // no length encoding for templates for new mangling
            parseTemplateInstanceName(errStatus, false);
            return;

        case '0': .. case '9':
            if ( mayBeTemplateInstanceName() )
            {
                auto t = dst.length;

                parseTemplateInstanceName(errStatus, true);
                if (!errStatus)
                    return;
                else
                {
                    dst.len = t;
                }
            }
            goto case;
        case 'Q':
            string errMsg;
            parseLName(errMsg);
            errStatus = errMsg !is null;
            return;
        default:
            errStatus = true;
        }
    }

    // parse optional function arguments as part of a symbol name, i.e without return type
    // if keepAttr, the calling convention and function attributes are not discarded, but returned
    BufSlice parseFunctionTypeNoReturn( bool keepAttr = false ) return scope nothrow
    {
        // try to demangle a function, in case we are pointing to some function local
        auto prevpos = pos;
        auto prevlen = dst.length;
        auto prevbrp = brp;

        if ( 'M' == front )
        {
            // do not emit "needs this"
            popFront();
            auto modifiers = parseModifier();
            while (auto str = typeCtors.toStringConsume(modifiers))
            {
                put(str);
                put(' ');
            }
        }
        if ( isCallConvention( front ) )
        {
            BufSlice attr = dst.bslice_empty;
            // we don't want calling convention and attributes in the qualified name
            bool errStatus;
            parseCallConvention(errStatus);
            if (!errStatus)
            {
                auto attributes = parseFuncAttr(errStatus);
                if (!errStatus)
                {
                    if (keepAttr) {
                        while (auto str = funcAttrs.toStringConsume(attributes))
                        {
                            put(str);
                            put(' ');
                        }
                        attr = dst[prevlen .. $];
                    }

                    put( '(' );
                    parseFuncArguments(errStatus);
                    if (errStatus)
                        return dst.bslice_empty;
                    put( ')' );
                    return attr;
                }
            }

            // not part of a qualified name, so back up
            pos = prevpos;
            dst.len = prevlen;
            brp = prevbrp;
        }

        return dst.bslice_empty;
    }

    /*
    QualifiedName:
        SymbolName
        SymbolName QualifiedName
    */
    void parseQualifiedName(out bool errStatus) return scope nothrow
    {
        size_t  n   = 0;
        bool is_sym_name_front;

        do
        {
            if ( n++ )
                put( '.' );

            parseSymbolName(errStatus);
            if (errStatus)
                return;

            parseFunctionTypeNoReturn();

            is_sym_name_front = isSymbolNameFront(errStatus);
            if (errStatus)
                return;

        } while ( is_sym_name_front );
    }


    /*
    MangledName:
        _D QualifiedName Type
        _D QualifiedName M Type
    */
    void parseMangledName( out bool errStatus, bool displayType, size_t n = 0 ) scope nothrow
    {
        BufSlice name = dst.bslice_empty;

        auto end = pos + n;

        eat( '_' );
        errStatus = !match( 'D' );
        if (errStatus)
            return;

        do
        {
            size_t  beg = dst.length;
            size_t  nameEnd = dst.length;
            BufSlice attr = dst.bslice_empty;
            bool is_sym_name_front;

            do
            {
                if ( attr.length )
                    dst.remove(attr); // dump attributes of parent symbols
                if (beg != dst.length)
                    put( '.' );

                parseSymbolName(errStatus);
                if (errStatus)
                    return;

                nameEnd = dst.length;
                attr = parseFunctionTypeNoReturn( displayType );

                is_sym_name_front = isSymbolNameFront(errStatus);
                if (errStatus)
                    return;
            } while (is_sym_name_front);

            if ( displayType )
            {
                attr = shift( attr );
                nameEnd = dst.length - attr.length;  // name includes function arguments
            }
            name = dst[beg .. nameEnd];

            if ( 'M' == front )
                popFront(); // has 'this' pointer

            auto lastlen = dst.length;
            auto type = parseType(errStatus);
            if (errStatus)
                return;

            if ( displayType )
            {
                if ( type.length )
                    put( ' ' );
                // sort (name,attr,type) -> (attr,type,name)
                shift( name );
            }
            else
            {
                // remove type
                assert( attr.length == 0 );
                dst.len = lastlen;
            }
            if ( pos >= buf.length || (n != 0 && pos >= end) )
                return;

            switch ( front )
            {
            case 'T': // terminators when used as template alias parameter
            case 'V':
            case 'S':
            case 'Z':
                return;
            default:
            }
            put( '.' );

        } while ( true );
    }

    void parseMangledName(out bool errStatus) nothrow
    {
        parseMangledName(errStatus, AddType.yes == addType);
    }

    char[] doDemangle(alias FUNC)() return scope nothrow
    {
        while ( true )
        {
            bool errStatus;
            FUNC(errStatus);
            if (!errStatus)
            {
                return dst[0 .. $].getSlice;
            }
            else
            {
                return dst.copyInput(buf);
            }
        }
    }

    char[] demangleName() nothrow
    {
        return doDemangle!parseMangledName();
    }

    char[] demangleType() nothrow
    {
        return doDemangle!parseType();
    }
}

private struct Buffer
{
    enum size_t minSize = 4000;

    @safe pure:

    private char[] dst;
    private size_t len;

    public alias opDollar = len;

    public size_t length () const scope @safe pure nothrow @nogc
    {
        return this.len;
    }

    public BufSlice opSlice (size_t from, size_t to)
        return scope @safe pure nothrow @nogc
    {
        return bslice(from, to);
    }

    static bool contains(scope const(char)[] a, scope const BufSlice b) @safe nothrow
    {
        return
            b.from < a.length &&
            b.to <= a.length;
    }

    char[] copyInput(scope const(char)[] buf)
        return scope nothrow
    {
        if (dst.length < buf.length)
            dst.length = buf.length;
        char[] r = dst[0 .. buf.length];
        r[] = buf[];
        return r;
    }

    private void checkAndStretchBuf(size_t len_to_add) scope nothrow
    {
        const required = len + len_to_add;

        if (required > dst.length)
            dst.length = dst.length + len_to_add;
    }

    // move val to the end of the dst buffer
    BufSlice shift(scope const BufSlice val) return scope nothrow
    {
        version (DigitalMars) pragma(inline, false); // tame dmd inliner

        if (val.length)
        {
            const ptrdiff_t s = val.from;
            const size_t f = len;

            assert(contains( dst[0 .. len], val ),
                "\ndst=\""~dst[0 .. len]~"\"\n"~
                "val=\""~val.getSlice~"\"\n"
            );

            checkAndStretchBuf(val.length);

            // store value temporary over len index
            dst[len .. len + val.length] = val.getSlice();

            // shift all chars including temporary saved above
            // if buf was allocated above it will be leave for further usage
            for (size_t p = s; p < f; p++)
                dst[p] = dst[p + val.length];

            return bslice(len - val.length, len);
        }

        return bslice_empty;
    }

    // remove val from dst buffer
    void remove(scope BufSlice val) scope nothrow
    {
        version (DigitalMars) pragma(inline, false); // tame dmd inliner

        if ( val.length )
        {
            assert( contains( dst[0 .. len], val ) );

            assert( len >= val.length && len <= dst.length );
            len -= val.length;
            for (size_t p = val.from; p < len; p++)
                dst[p] = dst[p + val.length];
        }
    }

    void append(scope const(char)[] val) scope nothrow
    {
        version (DigitalMars) pragma(inline, false); // tame dmd inliner

        if (val.length)
        {
            if ( !dst.length )
                dst.length = minSize;

            checkAndStretchBuf(val.length);

            // data is already not in place?
            if ( &dst[len] != &val[0] )
                dst[len .. len + val.length] = val[];

            len += val.length;
        }
    }

    @nogc:

    private scope bslice(size_t from, size_t to) nothrow
    {
        return BufSlice(dst, from, to);
    }

    private static scope bslice_empty() nothrow
    {
        return BufSlice.init;
    }
}

private struct BufSlice
{
    char[] buf;
    size_t from;
    size_t to;

    @safe pure nothrow:

    @disable this();

    this(return scope char[] buf) scope nothrow @nogc
    {
        this(buf, 0, 0);
    }

    this(return scope char[] buf, size_t from, size_t to, bool lastArgIsLen = false) scope nothrow @nogc
    {
        this.buf = buf;
        this.from = from;

        if (lastArgIsLen)
            this.to = from + to;
        else
            this.to = to;
    }

    auto getSlice() inout nothrow scope { return buf[from .. to]; }
    size_t length() const scope { return to - from; }
}

/**
 * Demangles a D mangled type.
 *
 * Params:
 *  buf = The string to demangle.
 *  dst = An optional destination buffer.
 *
 * Returns:
 *  The demangled type name or the original string if the name is not a
 *  mangled D type.
*/
char[] demangleType( const(char)[] buf, char[] dst = null ) nothrow pure @safe
{
    auto d = Demangle!()(buf, dst);
    return d.demangleType();
}

extern (C) private
{
    pure @trusted @nogc nothrow pragma(mangle, "fakePureReprintReal") void pureReprintReal(char[] nptr);

    void fakePureReprintReal(char[] nptr)
    {
        import core.stdc.stdlib : strtold;
        import core.stdc.stdio : snprintf;
        import core.stdc.errno : errno;

        const err = errno;
        real val = strtold(nptr.ptr, null);
        snprintf(nptr.ptr, nptr.length, "%#Lg", val);
        errno = err;
    }
}
