1 /** 2 * This module implements ScriptObject, the base class for builtin Mildew objects. 3 */ 4 module mildew.types.object; 5 6 /** 7 * General Object class. Unlike JavaScript, the __proto__ property only shows up when asked for. This allows 8 * allows objects to be used as dictionaries without extraneous values showing up in the for-of loop. Native D objects 9 * can be stored in any ScriptObject or derived class by assigning it to its nativeObject field. 10 */ 11 class ScriptObject 12 { 13 import mildew.context: Context; 14 import mildew.types.any: ScriptAny; 15 import mildew.types.func: ScriptFunction; 16 public: 17 /** 18 * Constructs a new ScriptObject that can be stored inside ScriptValue. 19 * Params: 20 * typename = This does not have to be set to a meaningful value but constructors (calling script functions 21 * with the new keyword) set this value to the name of the function. 22 * proto = The object's __proto__ property. If a value is not found inside the current object's table, a chain 23 * of prototypes is searched until reaching a null prototype. If this parameter is null, the value is 24 * set to Object.prototype 25 * native = A ScriptObject can contain a native D object that can be accessed later. This is used for binding 26 * D classes. 27 */ 28 this(in string typename, ScriptObject proto, Object native = null) 29 { 30 import mildew.types.bindings: getObjectPrototype; 31 _name = typename; 32 if(proto !is null) 33 _prototype = proto; 34 else 35 _prototype = getObjectPrototype; 36 _nativeObject = native; 37 } 38 39 /** 40 * Empty constructor that leaves prototype, and nativeObject as null. 41 */ 42 this(in string typename) 43 { 44 _name = typename; 45 } 46 47 /// name property 48 string name() const { return _name; } 49 50 /// getters property 51 auto getters() { return _getters; } 52 /// setters property 53 auto setters() { return _setters; } 54 55 /// prototype property 56 auto prototype() { return _prototype; } 57 58 /// prototype property (setter) 59 auto prototype(ScriptObject proto) { return _prototype = proto; } 60 61 /// This property provides direct access to the dictionary 62 auto dictionary() { return _dictionary; } 63 64 /** 65 * Add a getter. Getters should be added to a constructor function's "prototype" field 66 */ 67 void addGetterProperty(in string propName, ScriptFunction getter) 68 { 69 _getters[propName] = getter; 70 } 71 72 /** 73 * Add a setter. Setters should be added to a constructor function's "prototype" field 74 */ 75 void addSetterProperty(in string propName, ScriptFunction setter) 76 { 77 _setters[propName] = setter; 78 } 79 80 /** 81 * Looks up a field through the prototype chain. Note that this does not call any getters because 82 * it is not possible to pass a Context to opIndex. 83 */ 84 ScriptAny lookupField(in string name) 85 { 86 if(name == "__proto__") 87 return ScriptAny(_prototype); 88 if(name in _dictionary) 89 return _dictionary[name]; 90 if(_prototype !is null) 91 return _prototype.lookupField(name); 92 return ScriptAny.UNDEFINED; 93 } 94 95 /** 96 * Shorthand for lookupField. 97 */ 98 ScriptAny opIndex(in string index) 99 { 100 return lookupField(index); 101 } 102 103 /** 104 * Assigns a field to the current object. This does not call any setters. 105 */ 106 ScriptAny assignField(in string name, ScriptAny value) 107 { 108 if(name == "__proto__") 109 { 110 _prototype = value.toValue!ScriptObject; 111 } 112 else 113 { 114 _dictionary[name] = value; 115 } 116 return value; 117 } 118 119 /** 120 * Determines if there is a getter for a given property 121 */ 122 bool hasGetter(in string propName) 123 { 124 auto objectToSearch = this; 125 while(objectToSearch !is null) 126 { 127 if(propName in objectToSearch._getters) 128 return true; 129 objectToSearch = objectToSearch._prototype; 130 } 131 return false; 132 } 133 134 /** 135 * Determines if there is a setter for a given property 136 */ 137 bool hasSetter(in string propName) 138 { 139 auto objectToSearch = this; 140 while(objectToSearch !is null) 141 { 142 if(propName in objectToSearch._setters) 143 return true; 144 objectToSearch = objectToSearch._prototype; 145 } 146 return false; 147 } 148 149 /** 150 * Shorthand for assignField 151 */ 152 ScriptAny opIndexAssign(T)(T value, in string index) 153 { 154 static if(is(T==ScriptAny)) 155 return assignField(index, value); 156 else 157 { 158 ScriptAny any = value; 159 return assignField(index, any); 160 } 161 } 162 163 ScriptObject getOwnPropertyDescriptor(in string propName) 164 { 165 ScriptObject property = new ScriptObject("property", null); 166 // find the getter 167 auto objectToSearch = this; 168 if(propName in objectToSearch._getters) 169 property["get"] = objectToSearch._getters[propName]; 170 if(propName in objectToSearch._setters) 171 property["set"] = objectToSearch._setters[propName]; 172 objectToSearch = objectToSearch._prototype; 173 return property; 174 } 175 176 /** 177 * If a native object was stored inside this ScriptObject, it can be retrieved with this function. 178 * Note that one must always check that the return value isn't null because all functions can be 179 * called with invalid "this" objects using functionName.call. 180 */ 181 T nativeObject(T)() const 182 { 183 static if(is(T == class) || is(T == interface)) 184 return cast(T)_nativeObject; 185 else 186 static assert(false, "This method can only be used with D classes and interfaces"); 187 } 188 189 /** 190 * Native object can also be written in case of inheritance by script 191 */ 192 T nativeObject(T)(T obj) 193 { 194 static if(is(T == class) || is(T == interface)) 195 return cast(T)(_nativeObject = obj); 196 else 197 static assert(false, "This method can only be used with D classes and interfaces"); 198 } 199 200 /** 201 * Returns a string with JSON like formatting representing the object's key-value pairs as well as 202 * any nested objects. In the future this will be replaced and an explicit function call will be 203 * required to print this detailed information. 204 */ 205 override string toString() const 206 { 207 if(nativeObject!Object !is null) 208 return nativeObject!Object.toString(); 209 return _name ~ " " ~ formattedString(); 210 } 211 protected: 212 213 /// The dictionary of key-value pairs 214 ScriptAny[string] _dictionary; 215 216 /// The lookup table for getters 217 ScriptFunction[string] _getters; 218 219 /// The lookup table for setters 220 ScriptFunction[string] _setters; 221 222 private: 223 224 string formattedString(int indent = 0) const 225 { 226 immutable indentation = " "; 227 auto result = "{"; 228 foreach(k, v ; _dictionary) 229 { 230 for(int i = 0; i < indent; ++i) 231 result ~= indentation; 232 result ~= k ~ ": "; 233 if(v.type == ScriptAny.Type.OBJECT) 234 { 235 if(!v.isNull) 236 result ~= v.toValue!ScriptObject().formattedString(indent+1); 237 else 238 result ~= "<null object>"; 239 } 240 else 241 result ~= v.toString(); 242 result ~= "\n"; 243 } 244 for(int i = 0; i < indent; ++i) 245 result ~= indentation; 246 result ~= "}"; 247 return result; 248 } 249 250 /// type name (Function or whatever) 251 string _name; 252 /// it can also hold a native object 253 Object _nativeObject; 254 /// prototype 255 ScriptObject _prototype = null; 256 }