Assumes ALL virtual methods and constructors are scriptable, but requires @scriptable to be present on final or static methods. This may change in the future.
Note that it has zero support for @safe, pure, nothrow, and other attributes at this time and will skip that use those. I may be able to loosen this in the future as well but I have no concrete plan to at this time. You can still mark them as @scriptable to call them from the script, but they can never be overridden by script code because it cannot verify those guarantees hold true.
Ditto on const and immutable.
Its behavior on overloads is currently undefined - it may keep only any random overload as the only one and do dynamic type conversions to cram data into it. This is likely to change in the future but for now try not to use this on classes with overloaded methods.
It also does not wrap member variables unless explicitly marked @scriptable; it is meant to communicate via methods.
Demonstrates tested capabilities of subclassable
1 interface IFoo { 2 string method(); 3 int method2(); 4 int args(int, int); 5 } 6 // note the static is just here because this 7 // is written in a unittest; it shouldn't actually 8 // be necessary under normal circumstances. 9 static class Foo : IFoo { 10 ulong handle() { return cast(ulong) cast(void*) this; } 11 string method() { return "Foo"; } 12 int method2() { return 10; } 13 int args(int a, int b) { 14 //import std.stdio; writeln(a, " + ", b, " + ", member_, " on ", cast(ulong) cast(void*) this); 15 return member_+a+b; } 16 17 int member_; 18 @property int member(int i) { return member_ = i; } 19 @property int member() { return member_; } 20 21 @scriptable final int fm() { return 56; } 22 } 23 static class Bar : Foo { 24 override string method() { return "Bar"; } 25 } 26 static class Baz : Bar { 27 override int method2() { return 20; } 28 } 29 30 static class WithCtor { 31 // constructors work but are iffy with overloads.... 32 this(int arg) { this.arg = arg; } 33 @scriptable int arg; // this is accessible cuz it is @scriptable 34 int getValue() { return arg; } 35 } 36 37 var globals = var.emptyObject; 38 globals.Foo = subclassable!Foo; 39 globals.Bar = subclassable!Bar; 40 globals.Baz = subclassable!Baz; 41 globals.WithCtor = subclassable!WithCtor; 42 43 import arsd.script; 44 45 interpret(q{ 46 // can instantiate D classes added via subclassable 47 var foo = new Foo(); 48 // and call its methods... 49 assert(foo.method() == "Foo"); 50 assert(foo.method2() == 10); 51 52 foo.member(55); 53 54 // proves the new operator actually creates new D 55 // objects as well to avoid sharing instance state. 56 var foo2 = new Foo(); 57 assert(foo2.handle() != foo.handle()); 58 59 // passing arguments works 60 assert(foo.args(2, 4) == 6 + 55); // (and sanity checks operator precedence) 61 62 var bar = new Bar(); 63 assert(bar.method() == "Bar"); 64 assert(bar.method2() == 10); 65 66 // this final member is accessible because it was marked @scriptable 67 assert(bar.fm() == 56); 68 69 // the script can even subclass D classes! 70 class Amazing : Bar { 71 // and override its methods 72 var inst = 99; 73 function method() { 74 return "Amazing"; 75 } 76 77 // note: to access instance members or virtual call lookup you MUST use the `this` keyword 78 // otherwise the function will be called with scope limited to this class itself (similar to javascript) 79 function other() { 80 // this.inst is needed to get the instance variable (otherwise it would only look for a static var) 81 // and this.method triggers dynamic lookup there, so it will get children's overridden methods if there is one 82 return this.inst ~ this.method(); 83 } 84 85 function args(a, b) { 86 // calling parent class method still possible 87 return super.args(a*2, b*2); 88 } 89 } 90 91 var amazing = new Amazing(); 92 assert(amazing.method() == "Amazing"); 93 assert(amazing.method2() == 10); // calls back to the parent class 94 amazing.member(5); 95 96 // this line I can paste down to interactively debug the test btw. 97 //}, globals); repl!true(globals); interpret(q{ 98 99 assert(amazing.args(2, 4) == 12+5); 100 101 var wc = new WithCtor(5); // argument passed to constructor 102 assert(wc.getValue() == 5); 103 104 // confirm the property read works too 105 assert(wc.arg == 5); 106 107 // but property WRITING is currently not working though. 108 109 110 class DoubleChild : Amazing { 111 function method() { 112 return "DoubleChild"; 113 } 114 } 115 116 // can also do a child of a child class 117 var dc = new DoubleChild(); 118 assert(dc.method() == "DoubleChild"); 119 assert(dc.other() == "99DoubleChild"); // the `this.method` means it uses the replacement now 120 assert(dc.method2() == 10); // back to the D grandparent 121 assert(dc.args(2, 4) == 12); // but the args impl from above 122 }, globals); 123 124 Foo foo = globals.foo.get!Foo; // get the native object back out 125 assert(foo.member == 55); // and see mutation via properties proving object mutability 126 assert(globals.foo.get!Bar is null); // cannot get the wrong class out of it 127 assert(globals.foo.get!Object !is null); // but can do parent classes / interfaces 128 assert(globals.foo.get!IFoo !is null); 129 assert(globals.bar.get!Foo !is null); // the Bar can also be a Foo 130 131 Bar amazing = globals.amazing.get!Bar; // instance of the script's class is still accessible through parent D class or interface 132 assert(amazing !is null); // object exists 133 assert(amazing.method() == "Amazing"); // calls the override from the script 134 assert(amazing.method2() == 10); // non-overridden function works as expected 135 136 IFoo iamazing = globals.amazing.get!IFoo; // and through just the interface works the same way 137 assert(iamazing !is null); 138 assert(iamazing.method() == "Amazing"); 139 assert(iamazing.method2() == 10);
Added April 25, 2020
EXPERIMENTAL
Allows you to make a class available to the script rather than just class objects. You can subclass it in script and then call the methods again through the original D interface. With caveats...