jscript: Added Array.join implementation.
[wine] / dlls / jscript / array.c
1 /*
2  * Copyright 2008 Jacek Caban for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include "jscript.h"
20
21 #include "wine/debug.h"
22
23 WINE_DEFAULT_DEBUG_CHANNEL(jscript);
24
25 typedef struct {
26     DispatchEx dispex;
27
28     DWORD length;
29 } ArrayInstance;
30
31 static const WCHAR lengthW[] = {'l','e','n','g','t','h',0};
32 static const WCHAR concatW[] = {'c','o','n','c','a','t',0};
33 static const WCHAR joinW[] = {'j','o','i','n',0};
34 static const WCHAR popW[] = {'p','o','p',0};
35 static const WCHAR pushW[] = {'p','u','s','h',0};
36 static const WCHAR reverseW[] = {'r','e','v','e','r','s','e',0};
37 static const WCHAR shiftW[] = {'s','h','i','f','t',0};
38 static const WCHAR sliceW[] = {'s','l','i','c','e',0};
39 static const WCHAR sortW[] = {'s','o','r','t',0};
40 static const WCHAR spliceW[] = {'s','p','l','i','c','e',0};
41 static const WCHAR toStringW[] = {'t','o','S','t','r','i','n','g',0};
42 static const WCHAR toLocaleStringW[] = {'t','o','L','o','c','a','l','e','S','t','r','i','n','g',0};
43 static const WCHAR valueOfW[] = {'v','a','l','u','e','O','f',0};
44 static const WCHAR unshiftW[] = {'u','n','s','h','i','f','t',0};
45 static const WCHAR hasOwnPropertyW[] = {'h','a','s','O','w','n','P','r','o','p','e','r','t','y',0};
46 static const WCHAR propertyIsEnumerableW[] =
47     {'p','r','o','p','e','r','t','y','I','s','E','n','u','m','e','r','a','b','l','e',0};
48 static const WCHAR isPrototypeOfW[] = {'i','s','P','r','o','t','o','t','y','p','e','O','f',0};
49
50 const WCHAR default_separatorW[] = {',',0};
51
52 static HRESULT Array_length(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
53         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
54 {
55     ArrayInstance *This = (ArrayInstance*)dispex;
56
57     TRACE("%p %d\n", This, This->length);
58
59     switch(flags) {
60     case DISPATCH_PROPERTYGET:
61         V_VT(retv) = VT_I4;
62         V_I4(retv) = This->length;
63         break;
64     default:
65         FIXME("unimplemented flags %x\n", flags);
66         return E_NOTIMPL;
67     }
68
69     return S_OK;
70 }
71
72 static HRESULT Array_concat(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
73         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
74 {
75     FIXME("\n");
76     return E_NOTIMPL;
77 }
78
79 static HRESULT array_join(DispatchEx *array, LCID lcid, DWORD length, const WCHAR *sep, VARIANT *retv,
80         jsexcept_t *ei, IServiceProvider *caller)
81 {
82     BSTR *str_tab, ret = NULL;
83     VARIANT var;
84     DWORD i;
85     HRESULT hres = E_FAIL;
86
87     if(!length) {
88         if(retv) {
89             V_VT(retv) = VT_BSTR;
90             V_BSTR(retv) = SysAllocStringLen(NULL, 0);
91             if(!V_BSTR(retv))
92                 return E_OUTOFMEMORY;
93         }
94         return S_OK;
95     }
96
97     str_tab = heap_alloc_zero(length * sizeof(BSTR));
98     if(!str_tab)
99         return E_OUTOFMEMORY;
100
101     for(i=0; i < length; i++) {
102         hres = jsdisp_propget_idx(array, i, lcid, &var, ei, caller);
103         if(FAILED(hres))
104             break;
105
106         if(V_VT(&var) != VT_EMPTY && V_VT(&var) != VT_NULL)
107             hres = to_string(array->ctx, &var, ei, str_tab+i);
108         VariantClear(&var);
109         if(FAILED(hres))
110             break;
111     }
112
113     if(SUCCEEDED(hres)) {
114         DWORD seplen = 0, len = 0;
115         WCHAR *ptr;
116
117         seplen = strlenW(sep);
118
119         if(str_tab[0])
120             len = SysStringLen(str_tab[0]);
121         for(i=1; i < length; i++)
122             len += seplen + SysStringLen(str_tab[i]);
123
124         ret = SysAllocStringLen(NULL, len);
125         if(ret) {
126             DWORD tmplen = 0;
127
128             if(str_tab[0]) {
129                 tmplen = SysStringLen(str_tab[0]);
130                 memcpy(ret, str_tab[0], tmplen*sizeof(WCHAR));
131             }
132
133             ptr = ret + tmplen;
134             for(i=1; i < length; i++) {
135                 if(seplen) {
136                     memcpy(ptr, sep, seplen*sizeof(WCHAR));
137                     ptr += seplen;
138                 }
139
140                 if(str_tab[i]) {
141                     tmplen = SysStringLen(str_tab[i]);
142                     memcpy(ptr, str_tab[i], tmplen*sizeof(WCHAR));
143                     ptr += tmplen;
144                 }
145             }
146             *ptr=0;
147         }else {
148             hres = E_OUTOFMEMORY;
149         }
150     }
151
152     for(i=0; i < length; i++)
153         SysFreeString(str_tab[i]);
154     heap_free(str_tab);
155     if(FAILED(hres))
156         return hres;
157
158     TRACE("= %s\n", debugstr_w(ret));
159
160     if(retv) {
161         if(!ret) {
162             ret = SysAllocStringLen(NULL, 0);
163             if(!ret)
164                 return E_OUTOFMEMORY;
165         }
166
167         V_VT(retv) = VT_BSTR;
168         V_BSTR(retv) = ret;
169     }else {
170         SysFreeString(ret);
171     }
172
173     return S_OK;
174 }
175
176 /* ECMA-262 3rd Edition    15.4.4.5 */
177 static HRESULT Array_join(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
178         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
179 {
180     DWORD length;
181     HRESULT hres;
182
183     TRACE("\n");
184
185     if(is_class(dispex, JSCLASS_ARRAY)) {
186         length = ((ArrayInstance*)dispex)->length;
187     }else {
188         FIXME("dispid is not Array\n");
189         return E_NOTIMPL;
190     }
191
192     if(arg_cnt(dp)) {
193         BSTR sep;
194
195         hres = to_string(dispex->ctx, dp->rgvarg + dp->cArgs-1, ei, &sep);
196         if(FAILED(hres))
197             return hres;
198
199         hres = array_join(dispex, lcid, length, sep, retv, ei, caller);
200
201         SysFreeString(sep);
202     }else {
203         hres = array_join(dispex, lcid, length, default_separatorW, retv, ei, caller);
204     }
205
206     return hres;
207 }
208
209 static HRESULT Array_pop(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
210         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
211 {
212     FIXME("\n");
213     return E_NOTIMPL;
214 }
215
216 /* ECMA-262 3rd Edition    15.4.4.7 */
217 static HRESULT Array_push(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
218         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
219 {
220     DWORD length = 0;
221     int i, n;
222     HRESULT hres;
223
224     TRACE("\n");
225
226     if(dispex->builtin_info->class == JSCLASS_ARRAY) {
227         length = ((ArrayInstance*)dispex)->length;
228     }else {
229         FIXME("not Array this\n");
230         return E_NOTIMPL;
231     }
232
233     n = dp->cArgs - dp->cNamedArgs;
234     for(i=0; i < n; i++) {
235         hres = jsdisp_propput_idx(dispex, length+i, lcid, get_arg(dp, i), ei, sp);
236         if(FAILED(hres))
237             return hres;
238     }
239
240     if(retv) {
241         V_VT(retv) = VT_I4;
242         V_I4(retv) = length+n;
243     }
244     return S_OK;
245 }
246
247 static HRESULT Array_reverse(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
248         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
249 {
250     FIXME("\n");
251     return E_NOTIMPL;
252 }
253
254 static HRESULT Array_shift(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
255         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
256 {
257     FIXME("\n");
258     return E_NOTIMPL;
259 }
260
261 static HRESULT Array_slice(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
262         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
263 {
264     FIXME("\n");
265     return E_NOTIMPL;
266 }
267
268 static HRESULT Array_sort(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
269         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
270 {
271     FIXME("\n");
272     return E_NOTIMPL;
273 }
274
275 static HRESULT Array_splice(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
276         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
277 {
278     FIXME("\n");
279     return E_NOTIMPL;
280 }
281
282 static HRESULT Array_toString(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
283         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
284 {
285     FIXME("\n");
286     return E_NOTIMPL;
287 }
288
289 static HRESULT Array_toLocaleString(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
290         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
291 {
292     FIXME("\n");
293     return E_NOTIMPL;
294 }
295
296 static HRESULT Array_valueOf(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
297         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
298 {
299     FIXME("\n");
300     return E_NOTIMPL;
301 }
302
303 static HRESULT Array_unshift(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
304         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
305 {
306     FIXME("\n");
307     return E_NOTIMPL;
308 }
309
310 static HRESULT Array_hasOwnProperty(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
311         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
312 {
313     FIXME("\n");
314     return E_NOTIMPL;
315 }
316
317 static HRESULT Array_propertyIsEnumerable(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
318         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
319 {
320     FIXME("\n");
321     return E_NOTIMPL;
322 }
323
324 static HRESULT Array_isPrototypeOf(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
325         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
326 {
327     FIXME("\n");
328     return E_NOTIMPL;
329 }
330
331 static HRESULT Array_value(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
332         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
333 {
334     FIXME("\n");
335     return E_NOTIMPL;
336 }
337
338 static void Array_destructor(DispatchEx *dispex)
339 {
340     heap_free(dispex);
341 }
342
343 static void Array_on_put(DispatchEx *dispex, const WCHAR *name)
344 {
345     ArrayInstance *array = (ArrayInstance*)dispex;
346     const WCHAR *ptr = name;
347     DWORD id = 0;
348
349     if(!isdigitW(*ptr))
350         return;
351
352     while(*ptr && isdigitW(*ptr)) {
353         id = id*10 + (*ptr-'0');
354         ptr++;
355     }
356
357     if(*ptr)
358         return;
359
360     if(id >= array->length)
361         array->length = id+1;
362 }
363
364 static const builtin_prop_t Array_props[] = {
365     {concatW,                Array_concat,               PROPF_METHOD},
366     {hasOwnPropertyW,        Array_hasOwnProperty,       PROPF_METHOD},
367     {isPrototypeOfW,         Array_isPrototypeOf,        PROPF_METHOD},
368     {joinW,                  Array_join,                 PROPF_METHOD},
369     {lengthW,                Array_length,               0},
370     {popW,                   Array_pop,                  PROPF_METHOD},
371     {propertyIsEnumerableW,  Array_propertyIsEnumerable, PROPF_METHOD},
372     {pushW,                  Array_push,                 PROPF_METHOD},
373     {reverseW,               Array_reverse,              PROPF_METHOD},
374     {shiftW,                 Array_shift,                PROPF_METHOD},
375     {sliceW,                 Array_slice,                PROPF_METHOD},
376     {sortW,                  Array_sort,                 PROPF_METHOD},
377     {spliceW,                Array_splice,               PROPF_METHOD},
378     {toLocaleStringW,        Array_toLocaleString,       PROPF_METHOD},
379     {toStringW,              Array_toString,             PROPF_METHOD},
380     {unshiftW,               Array_unshift,              PROPF_METHOD},
381     {valueOfW,               Array_valueOf,              PROPF_METHOD}
382 };
383
384 static const builtin_info_t Array_info = {
385     JSCLASS_ARRAY,
386     {NULL, Array_value, 0},
387     sizeof(Array_props)/sizeof(*Array_props),
388     Array_props,
389     Array_destructor,
390     Array_on_put
391 };
392
393 static HRESULT ArrayConstr_value(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
394         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
395 {
396     DispatchEx *obj;
397     VARIANT *arg_var;
398     DWORD i;
399     HRESULT hres;
400
401     TRACE("\n");
402
403     switch(flags) {
404     case DISPATCH_CONSTRUCT: {
405         if(arg_cnt(dp) == 1 && V_VT((arg_var = get_arg(dp, 0))) == VT_I4) {
406             if(V_I4(arg_var) < 0) {
407                 FIXME("throw RangeError\n");
408                 return E_FAIL;
409             }
410
411             hres = create_array(dispex->ctx, V_I4(arg_var), &obj);
412             if(FAILED(hres))
413                 return hres;
414
415             V_VT(retv) = VT_DISPATCH;
416             V_DISPATCH(retv) = (IDispatch*)_IDispatchEx_(obj);
417             return S_OK;
418         }
419
420         hres = create_array(dispex->ctx, arg_cnt(dp), &obj);
421         if(FAILED(hres))
422             return hres;
423
424         for(i=0; i < arg_cnt(dp); i++) {
425             hres = jsdisp_propput_idx(obj, i, lcid, get_arg(dp, i), ei, caller);
426             if(FAILED(hres))
427                 break;
428         }
429         if(FAILED(hres)) {
430             jsdisp_release(obj);
431             return hres;
432         }
433
434         V_VT(retv) = VT_DISPATCH;
435         V_DISPATCH(retv) = (IDispatch*)_IDispatchEx_(obj);
436         break;
437     }
438     default:
439         FIXME("unimplemented flags: %x\n", flags);
440         return E_NOTIMPL;
441     }
442
443     return S_OK;
444 }
445
446 static HRESULT alloc_array(script_ctx_t *ctx, BOOL use_constr, ArrayInstance **ret)
447 {
448     ArrayInstance *array = heap_alloc_zero(sizeof(ArrayInstance));
449     HRESULT hres;
450
451     if(use_constr)
452         hres = init_dispex_from_constr(&array->dispex, ctx, &Array_info, ctx->array_constr);
453     else
454         hres = init_dispex(&array->dispex, ctx, &Array_info, NULL);
455
456     if(FAILED(hres)) {
457         heap_free(array);
458         return hres;
459     }
460
461     *ret = array;
462     return S_OK;
463 }
464
465 HRESULT create_array_constr(script_ctx_t *ctx, DispatchEx **ret)
466 {
467     ArrayInstance *array;
468     HRESULT hres;
469
470     hres = alloc_array(ctx, FALSE, &array);
471     if(FAILED(hres))
472         return hres;
473
474     hres = create_builtin_function(ctx, ArrayConstr_value, PROPF_CONSTR, &array->dispex, ret);
475
476     IDispatchEx_Release(_IDispatchEx_(&array->dispex));
477     return hres;
478 }
479
480 HRESULT create_array(script_ctx_t *ctx, DWORD length, DispatchEx **ret)
481 {
482     ArrayInstance *array;
483     HRESULT hres;
484
485     hres = alloc_array(ctx, TRUE, &array);
486     if(FAILED(hres))
487         return hres;
488
489     array->length = length;
490
491     *ret = &array->dispex;
492     return S_OK;
493 }