//  Written in the D programming language 2.0
/******************************************************************************
    ClassInfoEx 0.10 alpha - Runtime Reflection for D classes.

    Author:
        Jascha Wetzel
    License:
        Public Domain

    Example usage:

    import classinfoex;
    import std.stdio;

    class Base
    {
        mixin Reflect;
        int foo() { return 5678; }
    }
    
    class Sub : Base
    {
        mixin Reflect;
        override int foo() { return 1234; }
        void bar(string s) { writefln("%s", s); }
        void bar(string s, int t) { writefln("%s %x", s, t); }
        void bar(Sub s) { s.bar("qwer"); }
    }

    void main()
    {
        Base o = new Sub;
        auto cie = ClassInfoEx(o.classinfo);
        assert(cie !is null);

        foreach ( name, mie; cie.abstract_functions )
            writefln("%s: %s %s %x", name, mie.type_info, mie.mangled, mie.address);
        foreach ( name, mie; cie.final_functions )
            writefln("%s: %s %s %x", name, mie.type_info, mie.mangled, mie.address);
        foreach ( name, mies; cie.virtual_functions )
        {
            writef("%s", name);
            foreach ( mie; mies )
                writef(": %s %s %x", mie.type_info, mie.mangled, mie.address);
            writefln;
        }
        foreach ( name, mie; cie.variables )
            writefln("%s: %s %s %x", name, mie.type_info, mie.mangled, mie.address);
        
        call(o, "bar", "argument string");
        call(o, "bar", o);
        call(o, "bar", "argument string", 0x1234);
        auto retval = call(o, "foo");
        writefln("foo returned %s", retval);
        retval = call(new Base, "foo");
        writefln("foo returned %s", retval);
    }

    Issues:
    - function prototypes without an implementation are not detected correctly.
      to avoid linker errors, virtual function info for abstract classes is not available
    - the address of a virtual function is the real function address, not the offset
      of the vtable entry
    - if there is a semantic error somewhere in the code of the class, the compiler
      will also issue verbose errors about the reflection code
    - type check for function calls isn't complete due to issues #1519 and #1520

******************************************************************************/
module classinfoex;

version(linux) static assert(0, "Linux not supported, due to issue #1522");
static assert(__VENDOR__ == "Digital Mars D" && __VERSION__ >= 2004, "ClassInfoEx requires DMD v2.004 or newer");

struct MemberInfoEx
{
    TypeInfo    type_info;
    string      mangled;    // this is needed, due to issue #1519
    size_t      address;

    const void* getPointer(Object o)
    {
        // TODO: check whether this is a variable
        return cast(void*)o + address;
    }

    const void delegate() getDelegate(Object o)
    {
        // TODO: check whether this is a function
        void delegate() dlg;
        dlg.ptr = cast(void*)o;
        dlg.funcptr = cast(void function())address;
        return dlg;
    }
}

struct ClassInfoEx
{
    static ClassInfoEx*[ClassInfo]  s_table;

    static ClassInfoEx* opCall(ClassInfo ci)
    {
        auto cie = ci in s_table;
        if ( cie is null )
            return null;
        return *cie;
    }

    ClassInfo           classinfo;

    MemberInfoEx[string]    final_functions,
                            abstract_functions,
                            variables;
    MemberInfoEx[][string]  virtual_functions;
}

string mixin_foreach(string t, string[] members, string code)
{
    string str;
    foreach ( m; members )
    {
        size_t j;
        for ( size_t i; i < code.length; ++i )
        {
            if ( code[i] == '%' )
            {
                str ~= code[j .. i];
                if ( code.length > i+1 )
                {
                    switch ( code[++i] ) {
                        case 't':   str ~= t;   break;
                        case 'm':   str ~= m;   break;
                        case '%':   str ~= '%';
                        default:    break;
                    }
                }
                j = i+1;
            }
        }
        str ~= code[j .. $];
    }
    return str;
}

template Reflect()
{
    static if ( !__traits(isAbstractClass, typeof(this)) )
    static this()
    {
        auto cie = new ClassInfoEx;
        cie.classinfo = this.classinfo;
        ClassInfoEx.s_table[this.classinfo] = cie;

        mixin(mixin_foreach(
            typeof(this).stringof,
            __traits(allMembers, typeof(this)),
            q{
                static if ( is( typeof(%t.%m) ) )
                {
                    static if ( __traits(isVirtualFunction, %t.%m) )
                    {
                        foreach ( f; __traits(getVirtualFunctions, %t, "%m") )
                        {
                            cie.virtual_functions["%m"] ~= MemberInfoEx(
                                typeid(typeof(f)),
                                typeof(&f).mangleof[1 .. $],
                                cast(size_t)&f
                            );
                        }
                    }
                    else static if ( __traits(isFinalFunction, %t.%m) )
                    {
                        cie.final_functions["%m"] = MemberInfoEx(
                            typeid(typeof(%t.%m)),
                            typeof(%t.%m).mangleof,
                            cast(size_t)&%t.%m
                        );
                    }
                    else static if ( __traits(isAbstractFunction, %t.%m) )
                    {
                        cie.abstract_functions["%m"] = MemberInfoEx(
                            typeid(typeof(%t.%m)),
                            typeof(%t.%m).mangleof,
                            cast(size_t)&%t.%m
                        );
                    }
                    else static if ( is(typeof(%t.%m.offsetof)) )
                    {
                        {
                            %t o;
                            cie.variables["%m"] = MemberInfoEx(
                                typeid(typeof(%t.%m)),
                                typeof(%t.%m).mangleof,
                                cast(size_t)o.%m.offsetof
                            );
                        }
                    }
                }
            }
        ));
    }
}

public import std.boxer;

class CallException : Exception {
    this(string msg) { super(msg); }
}

/**********************************************************************************************
    Make a dynamic, typesafe call to a member function of an object.

    Params:     obj =       object to call the function on
                name =      name of the function to call
                ... =       parameters for the function
    Returns:    a std.boxer.Box that recieves the return value

    Throws:     CallException if no ClassInfoEx is available for the object's class
                or no function with the given name and signature is found
**********************************************************************************************/
import std.stdio;
Box call(Object obj, string name, ...)
{
    auto cie = ClassInfoEx(obj.classinfo);
    if ( cie is null )
        throw new CallException("No ClassInfoEx available for "~obj.classinfo.name);

    auto mies = name in cie.virtual_functions;
    if ( mies is null )
        throw new CallException("No function called "~name~" found in ClassInfoEx of "~obj.classinfo.name);

    // mangle argument types
    string mangled_args;
    void* tmp = _argptr;
    foreach ( a; _arguments ) {
        mangled_args ~= mangleTypeInfo(a, tmp);
        tmp += a.tsize;
    }
    
    // find function with matching signature and call it
    mieLoop: foreach ( mie; *mies )
    {
        auto mangled_func = mie.mangled;
        assert(mangled_func.length > 1);
        assert(mangled_func[0] == 'F', "only non-variadic D functions supported, yet");
        mangled_func = mangled_func[1 .. $];

        if ( mangled_func.length <= mangled_args.length )
            continue;

        // compare argument types and function signature
        int a, f;
        for ( ; a < mangled_args.length && mangled_func[f] != 'Z'; ++a, ++f )
        {
            if ( mangled_func[f] == mangled_args[a] )
                continue;
            if ( mangled_func[f] == 'x' )
            {
                if ( mangled_args[a] != 'y' )
                    --a;
            }
            else if ( mangled_func[f] == 'y' )
            {
                if ( mangled_args[a] != 'x' )
                    --a;
            }
            else
                continue mieLoop;
        }
        if ( a != mangled_args.length || mangled_func[f] != 'Z' )
            continue;

        // call the function
        version(X86)
        {
            foreach ( ti; _arguments )
            {
                auto len = ((ti.tsize + size_t.sizeof - 1) & ~(size_t.sizeof - 1));
                asm
                {
                    mov EAX, _argptr    ;
                    mov ECX, len        ;
                    add EAX, ECX        ;
                    mov _argptr, EAX    ;
                Largs:
                    sub EAX, 4          ;
                    push int ptr [EAX]  ;
                    sub ECX, 4          ;
                    jnz Largs           ;
                }
            }
            tmp = cast(void*)mie.address;
            asm
            {
                mov EAX, obj            ;
                call tmp                ;
                mov tmp, EAX            ;
            }
            
            auto tif = cast(TypeInfo_Function)mie.type_info;
            assert(tif !is null);
            return box(tif.next, cast(void*)&tmp);
        }
        else
            static assert(0, "only x86 targets are supported for runtime dynamic function calls");
    }

    throw new CallException("No function with matching signature found");
}

// the remaining code is a workaround to the missing parameter types in TypeInfo_Function (issue #1519)
private ubyte[ClassInfo] typeInfoIds;
static this()
{
    typeInfoIds[typeid(void).classinfo]     = 1;
    typeInfoIds[typeid(bool).classinfo]     = 2;
    typeInfoIds[typeid(byte).classinfo]     = 3;
    typeInfoIds[typeid(ubyte).classinfo]    = 4;
    typeInfoIds[typeid(short).classinfo]    = 5;
    typeInfoIds[typeid(ushort).classinfo]   = 6;
    typeInfoIds[typeid(int).classinfo]      = 7;
    typeInfoIds[typeid(uint).classinfo]     = 8;
    typeInfoIds[typeid(long).classinfo]     = 9;
    typeInfoIds[typeid(ulong).classinfo]    = 10;
    typeInfoIds[typeid(float).classinfo]    = 11;
    typeInfoIds[typeid(double).classinfo]   = 12;
    typeInfoIds[typeid(real).classinfo]     = 13;
    typeInfoIds[typeid(ifloat).classinfo]   = 14;
    typeInfoIds[typeid(idouble).classinfo]  = 15;
    typeInfoIds[typeid(ireal).classinfo]    = 16;
    typeInfoIds[typeid(cfloat).classinfo]   = 17;
    typeInfoIds[typeid(cdouble).classinfo]  = 18;
    typeInfoIds[typeid(creal).classinfo]    = 19;
    typeInfoIds[typeid(char).classinfo]     = 20;
    typeInfoIds[typeid(wchar).classinfo]    = 21;
    typeInfoIds[typeid(dchar).classinfo]    = 22;

    typeInfoIds[TypeInfo_Typedef.classinfo]     = 23;
    typeInfoIds[TypeInfo_Enum.classinfo]        = 24;
    typeInfoIds[TypeInfo_Pointer.classinfo]     = 25;
    typeInfoIds[TypeInfo_Array.classinfo]       = 26;
    typeInfoIds[TypeInfo_StaticArray.classinfo] = 27;
    typeInfoIds[TypeInfo_AssociativeArray.classinfo] = 28;
    typeInfoIds[TypeInfo_Function.classinfo]    = 29;
    typeInfoIds[TypeInfo_Delegate.classinfo]    = 30;
    typeInfoIds[TypeInfo_Class.classinfo]       = 31;
    typeInfoIds[TypeInfo_Interface.classinfo]   = 32;
    typeInfoIds[TypeInfo_Struct.classinfo]      = 33;
    typeInfoIds[TypeInfo_Tuple.classinfo]       = 34;
    typeInfoIds[TypeInfo_Const.classinfo]       = 35;
    typeInfoIds[TypeInfo_Invariant.classinfo]   = 36;
    typeInfoIds[typeid(Object).classinfo]       = 37;

    typeInfoIds[typeid(Object[]).classinfo]     = 26;
    typeInfoIds[typeid(byte[]).classinfo]       = 26;
    typeInfoIds[typeid(ubyte[]).classinfo]      = 26;
    typeInfoIds[typeid(void[]).classinfo]       = 26;
    typeInfoIds[typeid(bool[]).classinfo]       = 26;
    typeInfoIds[typeid(char[]).classinfo]       = 26;
    typeInfoIds[typeid(int[]).classinfo]        = 26;
    typeInfoIds[typeid(uint[]).classinfo]       = 26;
    typeInfoIds[typeid(dchar[]).classinfo]      = 26;
    typeInfoIds[typeid(short[]).classinfo]      = 26;
    typeInfoIds[typeid(ushort[]).classinfo]     = 26;
    typeInfoIds[typeid(wchar[]).classinfo]      = 26;
    typeInfoIds[typeid(long[]).classinfo]       = 26;
    typeInfoIds[typeid(ulong[]).classinfo]      = 26;
    typeInfoIds[typeid(float[]).classinfo]      = 26;
    typeInfoIds[typeid(ifloat[]).classinfo]     = 26;
    typeInfoIds[typeid(cfloat[]).classinfo]     = 26;
    typeInfoIds[typeid(double[]).classinfo]     = 26;
    typeInfoIds[typeid(idouble[]).classinfo]    = 26;
    typeInfoIds[typeid(cdouble[]).classinfo]    = 26;
    typeInfoIds[typeid(real[]).classinfo]       = 26;
    typeInfoIds[typeid(ireal[]).classinfo]      = 26;
    typeInfoIds[typeid(creal[]).classinfo]      = 26;
}

import std.string;

string mangleTypeInfo(TypeInfo ti, void* ptr=null)
{
    string mangleName(string name)
    {
        string mangled;
        size_t s;
        foreach ( i, c; name )
        {
            if ( c == '.' ) {
                mangled ~= toString(i-s);
                mangled ~= name[s .. i];
                s = i+1;
            }
        }
        mangled ~= toString(name.length-s);
        mangled ~= name[s .. $];
        return mangled;
    }
    
    string mangled;
    loop: while ( ti !is null )
    {
        auto id = ti.classinfo in typeInfoIds;
        if ( id is null )
            return null;
        switch ( *id )
        {
            case 1:     mangled ~= "v"; break;
            case 2:     mangled ~= "b"; break;
            case 3:     mangled ~= "g"; break;
            case 4:     mangled ~= "h"; break;
            case 5:     mangled ~= "s"; break;
            case 6:     mangled ~= "t"; break;
            case 7:     mangled ~= "i"; break;
            case 8:     mangled ~= "k"; break;
            case 9:     mangled ~= "l"; break;
            case 10:    mangled ~= "m"; break;
            case 11:    mangled ~= "f"; break;
            case 12:    mangled ~= "d"; break;
            case 13:    mangled ~= "e"; break;
            case 14:    mangled ~= "o"; break;
            case 15:    mangled ~= "p"; break;
            case 16:    mangled ~= "j"; break;
            case 17:    mangled ~= "q"; break;
            case 18:    mangled ~= "r"; break;
            case 19:    mangled ~= "c"; break;
            case 20:    mangled ~= "a"; break;
            case 21:    mangled ~= "u"; break;
            case 22:    mangled ~= "w"; break;
            case 23:    mangled ~= "T"; mangled ~= mangleName((cast(TypeInfo_Typedef)ti).name); break loop;
            case 24:    mangled ~= "E"; mangled ~= mangleName((cast(TypeInfo_Typedef)ti).name); break loop;
            case 25:    mangled ~= "P"; break;
            case 26:    mangled ~= "A"; break;
            case 27:    mangled ~= "G"; mangled ~= toString((cast(TypeInfo_StaticArray)ti).len); break;
            case 28:
                mangled ~= "H";
                mangled ~= mangleTypeInfo((cast(TypeInfo_AssociativeArray)ti).key);
                ti = (cast(TypeInfo_AssociativeArray)ti).value;
                continue loop;
            case 29:
            case 31:    mangled ~= "C"; mangled ~= mangleName((cast(TypeInfo_Class)ti).info.name); break loop;
            case 32:    mangled ~= "I"; mangled ~= mangleName((cast(TypeInfo_Interface)ti).info.name); break loop;
            case 33:    mangled ~= "S"; mangled ~= mangleName((cast(TypeInfo_Struct)ti).name); break loop;
            case 35:    mangled ~= "x"; ti = (cast(TypeInfo_Const)ti).next; continue loop;
            case 36:    mangled ~= "y"; ti = (cast(TypeInfo_Const)ti).next; continue loop;
            case 37:    mangled ~= "C"; mangled ~= mangleName((cast(Object*)ptr).classinfo.name); break loop;

            case 30: case 34: default:
                assert(0);
        }
        ti = ti.next;
    }
    return mangled;
}

