1 /** 2 * This module implements the __proto__ field given to each special object such as ScriptObject, ScriptFunction, 3 * ScriptArray, and ScriptString, as well as the static methods for Object, Array, Function, and String 4 */ 5 module mildew.types.bindings; 6 7 import mildew.context; 8 import mildew.interpreter; 9 import mildew.types.any; 10 import mildew.types.array; 11 import mildew.types.func; 12 import mildew.types..string; 13 import mildew.types.object; 14 15 package(mildew): 16 17 /** 18 * Initializes the bindings of builtin types such as Object, Function, String, and Array. This function is not 19 * required because these objects already have their __proto__ set correctly when constructed. 20 */ 21 void initializeTypesLibrary(Interpreter interpreter) 22 { 23 ScriptAny Object_ctor = new ScriptFunction("Object", &native_Object_constructor, true); 24 Object_ctor["prototype"] = getObjectPrototype(); 25 Object_ctor["prototype"]["constructor"] = Object_ctor; 26 // static Object methods 27 Object_ctor["create"] = new ScriptFunction("Object.create", &native_Object_s_create); 28 Object_ctor["entries"] = new ScriptFunction("Object.entries", &native_Object_s_entries); 29 Object_ctor["getOwnPropertyDescriptor"] = new ScriptFunction("Object.getOwnPropertyDescriptor", 30 &native_Object_s_getOwnPropertyDescriptor); 31 Object_ctor["keys"] = new ScriptFunction("Object.keys", &native_Object_s_keys); 32 Object_ctor["values"] = new ScriptFunction("Object.values", &native_Object_s_values); 33 interpreter.forceSetGlobal("Object", Object_ctor, false); // maybe should be const 34 } 35 36 ScriptObject getObjectPrototype() 37 { 38 if(_objectPrototype is null) 39 { 40 _objectPrototype = new ScriptObject("object"); // this is the base prototype for all objects 41 } 42 return _objectPrototype; 43 } 44 45 ScriptObject getArrayPrototype() 46 { 47 if(_arrayPrototype is null) 48 { 49 _arrayPrototype = new ScriptObject("array", null); 50 _arrayPrototype["concat"] = new ScriptFunction("Array.prototype.concat", &native_Array_concat); 51 _arrayPrototype["join"] = new ScriptFunction("Array.prototype.join", &native_Array_join); 52 _arrayPrototype["pop"] = new ScriptFunction("Array.prototype.pop", &native_Array_pop); 53 _arrayPrototype["push"] = new ScriptFunction("Array.prototype.push", &native_Array_push); 54 _arrayPrototype["splice"] = new ScriptFunction("Array.prototype.splice", &native_Array_splice); 55 } 56 return _arrayPrototype; 57 } 58 59 ScriptObject getFunctionPrototype() 60 { 61 import mildew.exceptions: ScriptRuntimeException; 62 if(_functionPrototype is null) 63 { 64 _functionPrototype = new ScriptObject("function", null); 65 _functionPrototype["apply"] = new ScriptFunction("Function.prototype.apply", &native_Function_apply); 66 _functionPrototype["call"] = new ScriptFunction("Function.prototype.call", &native_Function_call); 67 } 68 return _functionPrototype; 69 } 70 71 ScriptObject getStringPrototype() 72 { 73 if(_stringPrototype is null) 74 { 75 _stringPrototype = new ScriptObject("string", null); 76 _stringPrototype["charAt"] = new ScriptFunction("String.prototype.charAt", &native_String_charAt); 77 _stringPrototype["charCodeAt"] = new ScriptFunction("String.prototype.charCodeAt", 78 &native_String_charCodeAt); 79 _stringPrototype["split"] = new ScriptFunction("String.prototype.split", &native_String_split); 80 } 81 return _stringPrototype; 82 } 83 84 private ScriptObject _objectPrototype; 85 private ScriptObject _arrayPrototype; 86 private ScriptObject _functionPrototype; 87 private ScriptObject _stringPrototype; 88 89 // 90 // Object methods ///////////////////////////////////////////////////////////// 91 // 92 93 private ScriptAny native_Object_constructor(Context c, ScriptAny* thisObj, ScriptAny[] args, 94 ref NativeFunctionError nfe) 95 { 96 if(args.length >= 1) 97 { 98 if(args[0].isObject) 99 *thisObj = args[0]; 100 } 101 return ScriptAny.UNDEFINED; 102 } 103 104 /** 105 * Object.create: This can be called by the script to create a new object whose prototype is the 106 * parameter. 107 */ 108 private ScriptAny native_Object_s_create(Context context, // @suppress(dscanner.style.phobos_naming_convention) 109 ScriptAny* thisObj, 110 ScriptAny[] args, 111 ref NativeFunctionError nfe) 112 { 113 if(args.length < 1) 114 { 115 nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS; 116 return ScriptAny.UNDEFINED; 117 } 118 119 if(!args[0].isObject) 120 { 121 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 122 return ScriptAny.UNDEFINED; 123 } 124 125 auto newObj = new ScriptObject("", args[0].toValue!ScriptObject); 126 127 return ScriptAny(newObj); 128 } 129 130 /// Returns an array of 2-element arrays representing the key and value of each dictionary entry 131 private ScriptAny native_Object_s_entries(Context context, 132 ScriptAny* thisObj, 133 ScriptAny[] args, 134 ref NativeFunctionError nfe) 135 { 136 if(args.length < 1) 137 return ScriptAny.UNDEFINED; 138 139 if(!args[0].isObject) 140 return ScriptAny.UNDEFINED; 141 142 ScriptAny[][] entries; 143 foreach(key, value ; args[0].toValue!ScriptObject.dictionary) 144 { 145 entries ~= [ScriptAny(key), value]; 146 } 147 return ScriptAny(entries); 148 } 149 150 /// Returns a possible getter or setter for an object 151 private ScriptAny native_Object_s_getOwnPropertyDescriptor(Context context, 152 ScriptAny* thisObj, 153 ScriptAny[] args, 154 ref NativeFunctionError nfe) 155 { 156 if(args.length < 2) 157 return ScriptAny.UNDEFINED; 158 if(!args[0].isObject) 159 return ScriptAny.UNDEFINED; 160 auto propName = args[1].toString(); 161 return ScriptAny(args[0].toValue!ScriptObject.getOwnPropertyDescriptor(propName)); 162 } 163 164 /// returns an array of keys of an object (or function) 165 private ScriptAny native_Object_s_keys(Context context, 166 ScriptAny* thisObj, 167 ScriptAny[] args, 168 ref NativeFunctionError nfe) 169 { 170 if(args.length < 1) 171 return ScriptAny.UNDEFINED; 172 173 if(!args[0].isObject) 174 return ScriptAny.UNDEFINED; 175 176 auto sobj = args[0].toValue!ScriptObject; 177 auto keys = ScriptAny(sobj.dictionary.keys); 178 return keys; 179 } 180 181 /// returns an array of values of an object (or function) 182 private ScriptAny native_Object_s_values(Context context, 183 ScriptAny* thisObj, 184 ScriptAny[] args, 185 ref NativeFunctionError nfe) 186 { 187 if(args.length < 1) 188 return ScriptAny.UNDEFINED; 189 190 if(!args[0].isObject) 191 return ScriptAny.UNDEFINED; 192 193 auto sobj = args[0].toValue!ScriptObject; 194 auto values = ScriptAny(sobj.dictionary.values); 195 return values; 196 } 197 198 // 199 // Array methods ////////////////////////////////////////////////////////////// 200 // 201 202 private ScriptAny native_Array_concat(Context c, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 203 { 204 if(thisObj.type != ScriptAny.Type.ARRAY) 205 return ScriptAny.UNDEFINED; 206 if(args.length < 1) 207 return *thisObj; 208 ScriptAny[] result = thisObj.toValue!ScriptArray.array; 209 if(args[0].type != ScriptAny.Type.ARRAY) 210 { 211 result ~= args[0]; 212 } 213 else 214 { 215 result ~= args[0].toValue!ScriptArray.array; 216 } 217 return ScriptAny(result); 218 } 219 220 private ScriptAny native_Array_join(Context c, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 221 { 222 if(thisObj.type != ScriptAny.Type.ARRAY) 223 return ScriptAny.UNDEFINED; 224 auto join = ","; 225 if(args.length > 0) 226 join = args[0].toString(); 227 auto arr = thisObj.toValue!(string[]); 228 string result = ""; 229 for(size_t i = 0; i < arr.length; ++i) 230 { 231 result ~= arr[i]; 232 if(i < arr.length - 1) 233 result ~= join; 234 } 235 return ScriptAny(result); 236 } 237 238 private ScriptAny native_Array_push(Context c, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 239 { 240 if(thisObj.type != ScriptAny.Type.ARRAY) 241 return ScriptAny.UNDEFINED; 242 if(args.length < 0) 243 return ScriptAny.UNDEFINED; 244 auto arr = thisObj.toValue!ScriptArray; 245 arr.array ~= args[0]; 246 return ScriptAny(arr.array.length); 247 } 248 249 private ScriptAny native_Array_pop(Context c, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 250 { 251 if(thisObj.type != ScriptAny.Type.ARRAY) 252 return ScriptAny.UNDEFINED; 253 auto arr = thisObj.toValue!ScriptArray; 254 if(arr.array.length < 1) 255 return ScriptAny.UNDEFINED; 256 auto result = arr.array[$-1]; 257 arr.array = arr.array[0..$-1]; 258 return result; 259 } 260 261 private ScriptAny native_Array_splice(Context c, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 262 { 263 import std.algorithm: min; 264 if(thisObj.type != ScriptAny.Type.ARRAY) 265 return ScriptAny.UNDEFINED; 266 auto arr = thisObj.toValue!ScriptArray; 267 if(args.length < 1) 268 return ScriptAny.UNDEFINED; 269 immutable start = min(args[0].toValue!size_t, arr.array.length - 1); 270 if(start >= arr.array.length) 271 return ScriptAny.UNDEFINED; 272 immutable deleteCount = args.length > 1 ? min(args[1].toValue!size_t, arr.array.length) : arr.array.length - start; 273 ScriptAny[] removed = []; 274 if(args.length > 2) 275 args = args[2 .. $]; 276 else 277 args = []; 278 // copy elements up to start 279 ScriptAny[] result = arr.array[0 .. start]; 280 // add new elements supplied as args 281 result ~= args; 282 // copy removed items to removed array 283 removed ~= arr.array[start .. start+deleteCount]; 284 // add those after start plus delete count 285 result ~= arr.array[start+deleteCount .. $]; 286 // set the original array 287 arr.array = result; 288 // return the removed items 289 return ScriptAny(removed); 290 } 291 292 // 293 // Function methods /////////////////////////////////////////////////////////// 294 // 295 296 private ScriptAny native_Function_call(Context c, ScriptAny* thisIsFn, ScriptAny[] args, 297 ref NativeFunctionError nfe) 298 { 299 import mildew.exceptions: ScriptRuntimeException; 300 // minimum args is 1 because first arg is the this to use 301 if(args.length < 1) 302 { 303 nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS; 304 return ScriptAny.UNDEFINED; 305 } 306 // get the function 307 if(thisIsFn.type != ScriptAny.Type.FUNCTION) 308 { 309 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 310 return ScriptAny.UNDEFINED; 311 } 312 auto fn = thisIsFn.toValue!ScriptFunction; 313 // set up the "this" to use 314 auto thisToUse = args[0]; 315 // now send the remainder of the args to a called function with this setup 316 args = args[1..$]; 317 try 318 { 319 auto interpreter = c.interpreter; 320 if(c !is null) 321 return interpreter.callFunction(fn, thisToUse, args); 322 else 323 return ScriptAny.UNDEFINED; 324 } 325 catch(ScriptRuntimeException ex) 326 { 327 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 328 return ScriptAny(ex.msg); 329 } 330 } 331 332 private ScriptAny native_Function_apply(Context c, ScriptAny* thisIsFn, ScriptAny[] args, 333 ref NativeFunctionError nfe) 334 { 335 import mildew.exceptions: ScriptRuntimeException; 336 // minimum args is 2 because first arg is the this to use and the second is an array 337 if(args.length < 2) 338 { 339 nfe = NativeFunctionError.WRONG_NUMBER_OF_ARGS; 340 return ScriptAny.UNDEFINED; 341 } 342 // get the function 343 if(thisIsFn.type != ScriptAny.Type.FUNCTION) 344 { 345 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 346 return ScriptAny.UNDEFINED; 347 } 348 auto fn = thisIsFn.toValue!ScriptFunction; 349 // set up the "this" to use 350 auto thisToUse = args[0]; 351 // set up the arg array 352 if(args[1].type != ScriptAny.Type.ARRAY) 353 { 354 nfe = NativeFunctionError.WRONG_TYPE_OF_ARG; 355 return ScriptAny.UNDEFINED; 356 } 357 auto argList = args[1].toValue!(ScriptAny[]); 358 try 359 { 360 auto interpreter = c.interpreter; 361 if(interpreter !is null) 362 return interpreter.callFunction(fn, thisToUse, argList); 363 else 364 return ScriptAny.UNDEFINED; 365 } 366 catch(ScriptRuntimeException ex) 367 { 368 nfe = NativeFunctionError.RETURN_VALUE_IS_EXCEPTION; 369 return ScriptAny(ex.msg); 370 } 371 } 372 373 // 374 // String methods ///////////////////////////////////////////////////////////// 375 // 376 377 private ScriptAny native_String_charAt(Context c, ScriptAny* thisObj, 378 ScriptAny[] args, ref NativeFunctionError nfe) 379 { 380 if(thisObj.type != ScriptAny.Type.STRING) 381 return ScriptAny.UNDEFINED; 382 if(args.length < 1) 383 return ScriptAny.UNDEFINED; 384 385 auto ss = thisObj.toValue!ScriptString; 386 auto index = args[0].toValue!size_t; 387 388 if(index >= ss.getWString.length) 389 return ScriptAny(""); 390 391 return ScriptAny([ss.charAt(index)]); 392 } 393 394 private ScriptAny native_String_charCodeAt(Context c, ScriptAny* thisObj, 395 ScriptAny[] args, ref NativeFunctionError nfe) 396 { 397 if(thisObj.type != ScriptAny.Type.STRING) 398 return ScriptAny.UNDEFINED; 399 if(args.length < 1) 400 return ScriptAny.UNDEFINED; 401 402 auto ss = thisObj.toValue!ScriptString; 403 auto index = args[0].toValue!size_t; 404 405 if(index >= ss.getWString.length) 406 return ScriptAny(0); 407 408 return ScriptAny(ss.charCodeAt(index)); 409 } 410 411 private ScriptAny native_String_split(Context c, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 412 { 413 import std.array: split; 414 if(thisObj.type != ScriptAny.Type.STRING) 415 return ScriptAny.UNDEFINED; 416 auto splitter = ","; 417 if(args.length > 0) 418 splitter = args[0].toString(); 419 auto splitResult = thisObj.toString().split(splitter); 420 return ScriptAny(splitResult); 421 }