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 }