jscript: Make Array.slice generic.
[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 <math.h>
20
21 #include "jscript.h"
22
23 #include "wine/debug.h"
24
25 WINE_DEFAULT_DEBUG_CHANNEL(jscript);
26
27 typedef struct {
28     DispatchEx dispex;
29
30     DWORD length;
31 } ArrayInstance;
32
33 static const WCHAR lengthW[] = {'l','e','n','g','t','h',0};
34 static const WCHAR concatW[] = {'c','o','n','c','a','t',0};
35 static const WCHAR joinW[] = {'j','o','i','n',0};
36 static const WCHAR popW[] = {'p','o','p',0};
37 static const WCHAR pushW[] = {'p','u','s','h',0};
38 static const WCHAR reverseW[] = {'r','e','v','e','r','s','e',0};
39 static const WCHAR shiftW[] = {'s','h','i','f','t',0};
40 static const WCHAR sliceW[] = {'s','l','i','c','e',0};
41 static const WCHAR sortW[] = {'s','o','r','t',0};
42 static const WCHAR spliceW[] = {'s','p','l','i','c','e',0};
43 static const WCHAR toStringW[] = {'t','o','S','t','r','i','n','g',0};
44 static const WCHAR toLocaleStringW[] = {'t','o','L','o','c','a','l','e','S','t','r','i','n','g',0};
45 static const WCHAR unshiftW[] = {'u','n','s','h','i','f','t',0};
46
47 static const WCHAR default_separatorW[] = {',',0};
48
49 static HRESULT get_jsdisp_length(DispatchEx *obj, LCID lcid, jsexcept_t *ei, DWORD *ret)
50 {
51     VARIANT var;
52     HRESULT hres;
53
54     hres = jsdisp_propget_name(obj, lengthW, lcid, &var, ei, NULL/*FIXME*/);
55     if(FAILED(hres))
56         return hres;
57
58     hres = to_uint32(obj->ctx, &var, ei, ret);
59     VariantClear(&var);
60     return hres;
61 }
62
63 static HRESULT set_jsdisp_length(DispatchEx *obj, LCID lcid, jsexcept_t *ei, DWORD length)
64 {
65     VARIANT var;
66
67     V_VT(&var) = VT_I4;
68     V_I4(&var) = length;
69     return jsdisp_propput_name(obj, lengthW, lcid, &var, ei, NULL/*FIXME*/);
70 }
71
72 static HRESULT Array_length(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
73         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
74 {
75     ArrayInstance *This = (ArrayInstance*)dispex;
76
77     TRACE("%p %d\n", This, This->length);
78
79     switch(flags) {
80     case DISPATCH_PROPERTYGET:
81         V_VT(retv) = VT_I4;
82         V_I4(retv) = This->length;
83         break;
84     case DISPATCH_PROPERTYPUT: {
85         VARIANT num;
86         DOUBLE len = -1;
87         DWORD i;
88         HRESULT hres;
89
90         hres = to_number(dispex->ctx, get_arg(dp, 0), ei, &num);
91         if(V_VT(&num) == VT_I4)
92             len = V_I4(&num);
93         else
94             len = floor(V_R8(&num));
95
96         if(len!=(DWORD)len)
97             return throw_range_error(dispex->ctx, ei, IDS_INVALID_LENGTH, NULL);
98
99         for(i=len; i<This->length; i++) {
100             hres = jsdisp_delete_idx(dispex, i);
101             if(FAILED(hres))
102                 return hres;
103         }
104
105         This->length = len;
106         break;
107     }
108     default:
109         FIXME("unimplemented flags %x\n", flags);
110         return E_NOTIMPL;
111     }
112
113     return S_OK;
114 }
115
116 static HRESULT concat_array(DispatchEx *array, ArrayInstance *obj, DWORD *len, LCID lcid,
117         jsexcept_t *ei, IServiceProvider *caller)
118 {
119     VARIANT var;
120     DWORD i;
121     HRESULT hres;
122
123     for(i=0; i < obj->length; i++) {
124         hres = jsdisp_propget_idx(&obj->dispex, i, lcid, &var, ei, caller);
125         if(hres == DISP_E_UNKNOWNNAME)
126             continue;
127         if(FAILED(hres))
128             return hres;
129
130         hres = jsdisp_propput_idx(array, *len+i, lcid, &var, ei, caller);
131         VariantClear(&var);
132         if(FAILED(hres))
133             return hres;
134     }
135
136     *len += obj->length;
137     return S_OK;
138 }
139
140 static HRESULT concat_obj(DispatchEx *array, IDispatch *obj, DWORD *len, LCID lcid, jsexcept_t *ei, IServiceProvider *caller)
141 {
142     DispatchEx *jsobj;
143     VARIANT var;
144     HRESULT hres;
145
146     jsobj = iface_to_jsdisp((IUnknown*)obj);
147     if(jsobj) {
148         if(is_class(jsobj, JSCLASS_ARRAY)) {
149             hres = concat_array(array, (ArrayInstance*)jsobj, len, lcid, ei, caller);
150             jsdisp_release(jsobj);
151             return hres;
152         }
153         jsdisp_release(jsobj);
154     }
155
156     V_VT(&var) = VT_DISPATCH;
157     V_DISPATCH(&var) = obj;
158     return jsdisp_propput_idx(array, (*len)++, lcid, &var, ei, caller);
159 }
160
161 static HRESULT Array_concat(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
162         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
163 {
164     DispatchEx *ret;
165     DWORD len = 0;
166     HRESULT hres;
167
168     TRACE("\n");
169
170     hres = create_array(dispex->ctx, 0, &ret);
171     if(FAILED(hres))
172         return hres;
173
174     hres = concat_obj(ret, (IDispatch*)_IDispatchEx_(dispex), &len, lcid, ei, caller);
175     if(SUCCEEDED(hres)) {
176         VARIANT *arg;
177         DWORD i;
178
179         for(i=0; i < arg_cnt(dp); i++) {
180             arg = get_arg(dp, i);
181             if(V_VT(arg) == VT_DISPATCH)
182                 hres = concat_obj(ret, V_DISPATCH(arg), &len, lcid, ei, caller);
183             else
184                 hres = jsdisp_propput_idx(ret, len++, lcid, arg, ei, caller);
185             if(FAILED(hres))
186                 break;
187         }
188     }
189
190     if(FAILED(hres))
191         return hres;
192
193     if(retv) {
194         V_VT(retv) = VT_DISPATCH;
195         V_DISPATCH(retv) = (IDispatch*)_IDispatchEx_(ret);
196     }else {
197         jsdisp_release(ret);
198     }
199     return S_OK;
200 }
201
202 static HRESULT array_join(DispatchEx *array, LCID lcid, DWORD length, const WCHAR *sep, VARIANT *retv,
203         jsexcept_t *ei, IServiceProvider *caller)
204 {
205     BSTR *str_tab, ret = NULL;
206     VARIANT var;
207     DWORD i;
208     HRESULT hres = E_FAIL;
209
210     if(!length) {
211         if(retv) {
212             V_VT(retv) = VT_BSTR;
213             V_BSTR(retv) = SysAllocStringLen(NULL, 0);
214             if(!V_BSTR(retv))
215                 return E_OUTOFMEMORY;
216         }
217         return S_OK;
218     }
219
220     str_tab = heap_alloc_zero(length * sizeof(BSTR));
221     if(!str_tab)
222         return E_OUTOFMEMORY;
223
224     for(i=0; i < length; i++) {
225         hres = jsdisp_propget_idx(array, i, lcid, &var, ei, caller);
226         if(FAILED(hres))
227             break;
228
229         if(V_VT(&var) != VT_EMPTY && V_VT(&var) != VT_NULL)
230             hres = to_string(array->ctx, &var, ei, str_tab+i);
231         VariantClear(&var);
232         if(FAILED(hres))
233             break;
234     }
235
236     if(SUCCEEDED(hres)) {
237         DWORD seplen = 0, len = 0;
238         WCHAR *ptr;
239
240         seplen = strlenW(sep);
241
242         if(str_tab[0])
243             len = SysStringLen(str_tab[0]);
244         for(i=1; i < length; i++)
245             len += seplen + SysStringLen(str_tab[i]);
246
247         ret = SysAllocStringLen(NULL, len);
248         if(ret) {
249             DWORD tmplen = 0;
250
251             if(str_tab[0]) {
252                 tmplen = SysStringLen(str_tab[0]);
253                 memcpy(ret, str_tab[0], tmplen*sizeof(WCHAR));
254             }
255
256             ptr = ret + tmplen;
257             for(i=1; i < length; i++) {
258                 if(seplen) {
259                     memcpy(ptr, sep, seplen*sizeof(WCHAR));
260                     ptr += seplen;
261                 }
262
263                 if(str_tab[i]) {
264                     tmplen = SysStringLen(str_tab[i]);
265                     memcpy(ptr, str_tab[i], tmplen*sizeof(WCHAR));
266                     ptr += tmplen;
267                 }
268             }
269             *ptr=0;
270         }else {
271             hres = E_OUTOFMEMORY;
272         }
273     }
274
275     for(i=0; i < length; i++)
276         SysFreeString(str_tab[i]);
277     heap_free(str_tab);
278     if(FAILED(hres))
279         return hres;
280
281     TRACE("= %s\n", debugstr_w(ret));
282
283     if(retv) {
284         if(!ret) {
285             ret = SysAllocStringLen(NULL, 0);
286             if(!ret)
287                 return E_OUTOFMEMORY;
288         }
289
290         V_VT(retv) = VT_BSTR;
291         V_BSTR(retv) = ret;
292     }else {
293         SysFreeString(ret);
294     }
295
296     return S_OK;
297 }
298
299 /* ECMA-262 3rd Edition    15.4.4.5 */
300 static HRESULT Array_join(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
301         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
302 {
303     DWORD length;
304     HRESULT hres;
305
306     TRACE("\n");
307
308     if(is_class(dispex, JSCLASS_ARRAY)) {
309         length = ((ArrayInstance*)dispex)->length;
310     }else {
311         FIXME("dispid is not Array\n");
312         return E_NOTIMPL;
313     }
314
315     if(arg_cnt(dp)) {
316         BSTR sep;
317
318         hres = to_string(dispex->ctx, get_arg(dp,0), ei, &sep);
319         if(FAILED(hres))
320             return hres;
321
322         hres = array_join(dispex, lcid, length, sep, retv, ei, caller);
323
324         SysFreeString(sep);
325     }else {
326         hres = array_join(dispex, lcid, length, default_separatorW, retv, ei, caller);
327     }
328
329     return hres;
330 }
331
332 static HRESULT Array_pop(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
333         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
334 {
335     VARIANT val;
336     DWORD length;
337     WCHAR buf[14];
338     DISPID id;
339     HRESULT hres;
340
341     static const WCHAR formatW[] = {'%','d',0};
342
343     TRACE("\n");
344
345     if(is_class(dispex, JSCLASS_ARRAY)) {
346         ArrayInstance *array = (ArrayInstance*)dispex;
347         length = array->length;
348     }else {
349         FIXME("not Array this\n");
350         return E_NOTIMPL;
351     }
352
353     if(!length) {
354         if(retv)
355             V_VT(retv) = VT_EMPTY;
356         return S_OK;
357     }
358
359     sprintfW(buf, formatW, --length);
360     hres = jsdisp_get_id(dispex, buf, 0, &id);
361     if(SUCCEEDED(hres)) {
362         hres = jsdisp_propget(dispex, id, lcid, &val, ei, caller);
363         if(FAILED(hres))
364             return hres;
365
366         hres = IDispatchEx_DeleteMemberByDispID(_IDispatchEx_(dispex), id);
367     }else if(hres == DISP_E_UNKNOWNNAME) {
368         V_VT(&val) = VT_EMPTY;
369         hres = S_OK;
370     }else {
371         return hres;
372     }
373
374     if(SUCCEEDED(hres)) {
375         if(is_class(dispex, JSCLASS_ARRAY)) {
376             ArrayInstance *array = (ArrayInstance*)dispex;
377             array->length = length;
378         }
379     }
380
381     if(FAILED(hres)) {
382         VariantClear(&val);
383         return hres;
384     }
385
386     if(retv)
387         *retv = val;
388     else
389         VariantClear(&val);
390     return S_OK;
391 }
392
393 /* ECMA-262 3rd Edition    15.4.4.7 */
394 static HRESULT Array_push(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
395         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
396 {
397     DWORD length = 0;
398     int i, n;
399     HRESULT hres;
400
401     TRACE("\n");
402
403     if(dispex->builtin_info->class == JSCLASS_ARRAY) {
404         length = ((ArrayInstance*)dispex)->length;
405     }else {
406         hres = get_jsdisp_length(dispex, lcid, ei, &length);
407         if(FAILED(hres))
408             return hres;
409     }
410
411     n = arg_cnt(dp);
412     for(i=0; i < n; i++) {
413         hres = jsdisp_propput_idx(dispex, length+i, lcid, get_arg(dp, i), ei, sp);
414         if(FAILED(hres))
415             return hres;
416     }
417
418     if(!is_class(dispex, JSCLASS_ARRAY)) {
419         hres = set_jsdisp_length(dispex, lcid, ei, length+n);
420         if(FAILED(hres))
421             return hres;
422     }
423
424     if(retv) {
425         V_VT(retv) = VT_I4;
426         V_I4(retv) = length+n;
427     }
428     return S_OK;
429 }
430
431 static HRESULT Array_reverse(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
432         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
433 {
434     FIXME("\n");
435     return E_NOTIMPL;
436 }
437
438 static HRESULT Array_shift(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
439         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
440 {
441     FIXME("\n");
442     return E_NOTIMPL;
443 }
444
445 static HRESULT Array_slice(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
446         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
447 {
448     DispatchEx *arr;
449     VARIANT v;
450     DOUBLE range;
451     DWORD length, start, end, idx;
452     HRESULT hres;
453
454     TRACE("\n");
455
456     if(is_class(dispex, JSCLASS_ARRAY)) {
457         length = ((ArrayInstance*)dispex)->length;
458     }else {
459         hres = get_jsdisp_length(dispex, lcid, ei, &length);
460         if(FAILED(hres))
461             return hres;
462     }
463
464     if(arg_cnt(dp)) {
465         hres = to_number(dispex->ctx, get_arg(dp, 0), ei, &v);
466         if(FAILED(hres))
467             return hres;
468
469         if(V_VT(&v) == VT_I4)
470             range = V_I4(&v);
471         else
472             range = floor(V_R8(&v));
473
474         if(-range>length || isnan(range)) start = 0;
475         else if(range < 0) start = range+length;
476         else if(range <= length) start = range;
477         else start = length;
478     }
479     else start = 0;
480
481     if(arg_cnt(dp)>1) {
482         hres = to_number(dispex->ctx, get_arg(dp, 1), ei, &v);
483         if(FAILED(hres))
484             return hres;
485
486         if(V_VT(&v) == VT_I4)
487             range = V_I4(&v);
488         else
489             range = floor(V_R8(&v));
490
491         if(-range>length) end = 0;
492         else if(range < 0) end = range+length;
493         else if(range <= length) end = range;
494         else end = length;
495     }
496     else end = length;
497
498     hres = create_array(dispex->ctx, (end>start)?end-start:0, &arr);
499     if(FAILED(hres))
500         return hres;
501
502     for(idx=start; idx<end; idx++) {
503         hres = jsdisp_propget_idx(dispex, idx, lcid, &v, ei, sp);
504         if(hres == DISP_E_UNKNOWNNAME)
505             continue;
506
507         if(SUCCEEDED(hres))
508             hres = jsdisp_propput_idx(arr, idx-start, lcid, &v, ei, sp);
509
510         if(FAILED(hres)) {
511             jsdisp_release(arr);
512             return hres;
513         }
514     }
515
516     if(retv) {
517         V_VT(retv) = VT_DISPATCH;
518         V_DISPATCH(retv) = (IDispatch*)_IDispatchEx_(arr);
519     }
520     else
521         jsdisp_release(arr);
522
523     return S_OK;
524 }
525
526 static HRESULT sort_cmp(script_ctx_t *ctx, DispatchEx *cmp_func, VARIANT *v1, VARIANT *v2, jsexcept_t *ei,
527         IServiceProvider *caller, INT *cmp)
528 {
529     HRESULT hres;
530
531     if(cmp_func) {
532         VARIANTARG args[2];
533         DISPPARAMS dp = {args, NULL, 2, 0};
534         VARIANT tmp;
535         VARIANT res;
536
537         args[0] = *v2;
538         args[1] = *v1;
539
540         hres = jsdisp_call_value(cmp_func, ctx->lcid, DISPATCH_METHOD, &dp, &res, ei, caller);
541         if(FAILED(hres))
542             return hres;
543
544         hres = to_number(ctx, &res, ei, &tmp);
545         VariantClear(&res);
546         if(FAILED(hres))
547             return hres;
548
549         if(V_VT(&tmp) == VT_I4)
550             *cmp = V_I4(&tmp);
551         else
552             *cmp = V_R8(&tmp) > 0.0 ? 1 : -1;
553     }else if(is_num_vt(V_VT(v1))) {
554         if(is_num_vt(V_VT(v2))) {
555             DOUBLE d = num_val(v1)-num_val(v2);
556             if(d > 0.0)
557                 *cmp = 1;
558             else if(d < -0.0)
559                 *cmp = -1;
560             else
561                 *cmp = 0;
562         }else {
563             *cmp = -1;
564         }
565     }else if(is_num_vt(V_VT(v2))) {
566         *cmp = 1;
567     }else if(V_VT(v1) == VT_BSTR) {
568         if(V_VT(v2) == VT_BSTR)
569             *cmp = strcmpW(V_BSTR(v1), V_BSTR(v2));
570         else
571             *cmp = -1;
572     }else if(V_VT(v2) == VT_BSTR) {
573         *cmp = 1;
574     }else {
575         *cmp = 0;
576     }
577
578     return S_OK;
579 }
580
581 /* ECMA-262 3rd Edition    15.4.4.11 */
582 static HRESULT Array_sort(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
583         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
584 {
585     DispatchEx *cmp_func = NULL;
586     VARIANT *vtab, **sorttab = NULL;
587     DWORD length;
588     DWORD i;
589     HRESULT hres = S_OK;
590
591     TRACE("\n");
592
593     if(is_class(dispex, JSCLASS_ARRAY)) {
594         length = ((ArrayInstance*)dispex)->length;
595     }else {
596         FIXME("unsupported this not array\n");
597         return E_NOTIMPL;
598     }
599
600     if(arg_cnt(dp) > 1) {
601         WARN("invalid arg_cnt %d\n", arg_cnt(dp));
602         return E_FAIL;
603     }
604
605     if(arg_cnt(dp) == 1) {
606         VARIANT *arg = get_arg(dp, 0);
607
608         if(V_VT(arg) != VT_DISPATCH) {
609             WARN("arg is not dispatch\n");
610             return E_FAIL;
611         }
612
613
614         cmp_func = iface_to_jsdisp((IUnknown*)V_DISPATCH(arg));
615         if(!cmp_func || !is_class(cmp_func, JSCLASS_FUNCTION)) {
616             WARN("cmp_func is not a function\n");
617             if(cmp_func)
618                 jsdisp_release(cmp_func);
619             return E_FAIL;
620         }
621     }
622
623     if(!length) {
624         if(cmp_func)
625             jsdisp_release(cmp_func);
626         if(retv) {
627             V_VT(retv) = VT_DISPATCH;
628             V_DISPATCH(retv) = (IDispatch*)_IDispatchEx_(dispex);
629             IDispatchEx_AddRef(_IDispatchEx_(dispex));
630         }
631         return S_OK;
632     }
633
634     vtab = heap_alloc_zero(length * sizeof(VARIANT));
635     if(vtab) {
636         for(i=0; i<length; i++) {
637             hres = jsdisp_propget_idx(dispex, i, lcid, vtab+i, ei, caller);
638             if(FAILED(hres) && hres != DISP_E_UNKNOWNNAME) {
639                 WARN("Could not get elem %d: %08x\n", i, hres);
640                 break;
641             }
642         }
643     }else {
644         hres = E_OUTOFMEMORY;
645     }
646
647     if(SUCCEEDED(hres)) {
648         sorttab = heap_alloc(length*2*sizeof(VARIANT*));
649         if(!sorttab)
650             hres = E_OUTOFMEMORY;
651     }
652
653     /* merge-sort */
654     if(SUCCEEDED(hres)) {
655         VARIANT *tmpv, **tmpbuf;
656         INT cmp;
657
658         tmpbuf = sorttab + length;
659         for(i=0; i < length; i++)
660             sorttab[i] = vtab+i;
661
662         for(i=0; i < length/2; i++) {
663             hres = sort_cmp(dispex->ctx, cmp_func, sorttab[2*i+1], sorttab[2*i], ei, caller, &cmp);
664             if(FAILED(hres))
665                 break;
666
667             if(cmp < 0) {
668                 tmpv = sorttab[2*i];
669                 sorttab[2*i] = sorttab[2*i+1];
670                 sorttab[2*i+1] = tmpv;
671             }
672         }
673
674         if(SUCCEEDED(hres)) {
675             DWORD k, a, b, bend;
676
677             for(k=2; k < length; k *= 2) {
678                 for(i=0; i+k < length; i += 2*k) {
679                     a = b = 0;
680                     if(i+2*k <= length)
681                         bend = k;
682                     else
683                         bend = length - (i+k);
684
685                     memcpy(tmpbuf, sorttab+i, k*sizeof(VARIANT*));
686
687                     while(a < k && b < bend) {
688                         hres = sort_cmp(dispex->ctx, cmp_func, tmpbuf[a], sorttab[i+k+b], ei, caller, &cmp);
689                         if(FAILED(hres))
690                             break;
691
692                         if(cmp < 0) {
693                             sorttab[i+a+b] = tmpbuf[a];
694                             a++;
695                         }else {
696                             sorttab[i+a+b] = sorttab[i+k+b];
697                             b++;
698                         }
699                     }
700
701                     if(FAILED(hres))
702                         break;
703
704                     if(a < k)
705                         memcpy(sorttab+i+a+b, tmpbuf+a, (k-a)*sizeof(VARIANT*));
706                 }
707
708                 if(FAILED(hres))
709                     break;
710             }
711         }
712
713         for(i=0; SUCCEEDED(hres) && i < length; i++)
714             hres = jsdisp_propput_idx(dispex, i, lcid, sorttab[i], ei, caller);
715     }
716
717     if(vtab) {
718         for(i=0; i < length; i++)
719             VariantClear(vtab+i);
720         heap_free(vtab);
721     }
722     heap_free(sorttab);
723     if(cmp_func)
724         jsdisp_release(cmp_func);
725
726     if(FAILED(hres))
727         return hres;
728
729     if(retv) {
730         V_VT(retv) = VT_DISPATCH;
731         V_DISPATCH(retv) = (IDispatch*)_IDispatchEx_(dispex);
732         IDispatch_AddRef(_IDispatchEx_(dispex));
733     }
734
735     return S_OK;
736 }
737
738 static HRESULT Array_splice(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
739         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
740 {
741     FIXME("\n");
742     return E_NOTIMPL;
743 }
744
745 /* ECMA-262 3rd Edition    15.4.4.2 */
746 static HRESULT Array_toString(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
747         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
748 {
749     TRACE("\n");
750
751     if(!is_class(dispex, JSCLASS_ARRAY)) {
752         WARN("not Array object\n");
753         return E_FAIL;
754     }
755
756     return array_join(dispex, lcid, ((ArrayInstance*)dispex)->length, default_separatorW, retv, ei, sp);
757 }
758
759 static HRESULT Array_toLocaleString(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
760         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
761 {
762     FIXME("\n");
763     return E_NOTIMPL;
764 }
765
766 static HRESULT Array_unshift(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
767         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
768 {
769     FIXME("\n");
770     return E_NOTIMPL;
771 }
772
773 static HRESULT Array_value(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
774         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
775 {
776     TRACE("\n");
777
778     switch(flags) {
779     case INVOKE_FUNC:
780         return throw_type_error(dispex->ctx, ei, IDS_NOT_FUNC, NULL);
781     case INVOKE_PROPERTYGET:
782         return array_join(dispex, lcid, ((ArrayInstance*)dispex)->length, default_separatorW, retv, ei, sp);
783     default:
784         FIXME("unimplemented flags %x\n", flags);
785         return E_NOTIMPL;
786     }
787
788     return S_OK;
789 }
790
791 static void Array_destructor(DispatchEx *dispex)
792 {
793     heap_free(dispex);
794 }
795
796 static void Array_on_put(DispatchEx *dispex, const WCHAR *name)
797 {
798     ArrayInstance *array = (ArrayInstance*)dispex;
799     const WCHAR *ptr = name;
800     DWORD id = 0;
801
802     if(!isdigitW(*ptr))
803         return;
804
805     while(*ptr && isdigitW(*ptr)) {
806         id = id*10 + (*ptr-'0');
807         ptr++;
808     }
809
810     if(*ptr)
811         return;
812
813     if(id >= array->length)
814         array->length = id+1;
815 }
816
817 static const builtin_prop_t Array_props[] = {
818     {concatW,                Array_concat,               PROPF_METHOD|1},
819     {joinW,                  Array_join,                 PROPF_METHOD|1},
820     {lengthW,                Array_length,               0},
821     {popW,                   Array_pop,                  PROPF_METHOD},
822     {pushW,                  Array_push,                 PROPF_METHOD|1},
823     {reverseW,               Array_reverse,              PROPF_METHOD},
824     {shiftW,                 Array_shift,                PROPF_METHOD},
825     {sliceW,                 Array_slice,                PROPF_METHOD|2},
826     {sortW,                  Array_sort,                 PROPF_METHOD|1},
827     {spliceW,                Array_splice,               PROPF_METHOD|2},
828     {toLocaleStringW,        Array_toLocaleString,       PROPF_METHOD},
829     {toStringW,              Array_toString,             PROPF_METHOD},
830     {unshiftW,               Array_unshift,              PROPF_METHOD|1},
831 };
832
833 static const builtin_info_t Array_info = {
834     JSCLASS_ARRAY,
835     {NULL, Array_value, 0},
836     sizeof(Array_props)/sizeof(*Array_props),
837     Array_props,
838     Array_destructor,
839     Array_on_put
840 };
841
842 static HRESULT ArrayConstr_value(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *dp,
843         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
844 {
845     DispatchEx *obj;
846     VARIANT *arg_var;
847     DWORD i;
848     HRESULT hres;
849
850     TRACE("\n");
851
852     switch(flags) {
853     case DISPATCH_METHOD:
854     case DISPATCH_CONSTRUCT: {
855         if(arg_cnt(dp) == 1 && V_VT((arg_var = get_arg(dp, 0))) == VT_I4) {
856             if(V_I4(arg_var) < 0)
857                 return throw_range_error(dispex->ctx, ei, IDS_INVALID_LENGTH, NULL);
858
859             hres = create_array(dispex->ctx, V_I4(arg_var), &obj);
860             if(FAILED(hres))
861                 return hres;
862
863             V_VT(retv) = VT_DISPATCH;
864             V_DISPATCH(retv) = (IDispatch*)_IDispatchEx_(obj);
865             return S_OK;
866         }
867
868         hres = create_array(dispex->ctx, arg_cnt(dp), &obj);
869         if(FAILED(hres))
870             return hres;
871
872         for(i=0; i < arg_cnt(dp); i++) {
873             hres = jsdisp_propput_idx(obj, i, lcid, get_arg(dp, i), ei, caller);
874             if(FAILED(hres))
875                 break;
876         }
877         if(FAILED(hres)) {
878             jsdisp_release(obj);
879             return hres;
880         }
881
882         V_VT(retv) = VT_DISPATCH;
883         V_DISPATCH(retv) = (IDispatch*)_IDispatchEx_(obj);
884         break;
885     }
886     default:
887         FIXME("unimplemented flags: %x\n", flags);
888         return E_NOTIMPL;
889     }
890
891     return S_OK;
892 }
893
894 static HRESULT alloc_array(script_ctx_t *ctx, DispatchEx *object_prototype, ArrayInstance **ret)
895 {
896     ArrayInstance *array;
897     HRESULT hres;
898
899     array = heap_alloc_zero(sizeof(ArrayInstance));
900     if(!array)
901         return E_OUTOFMEMORY;
902
903     if(object_prototype)
904         hres = init_dispex(&array->dispex, ctx, &Array_info, object_prototype);
905     else
906         hres = init_dispex_from_constr(&array->dispex, ctx, &Array_info, ctx->array_constr);
907
908     if(FAILED(hres)) {
909         heap_free(array);
910         return hres;
911     }
912
913     *ret = array;
914     return S_OK;
915 }
916
917 HRESULT create_array_constr(script_ctx_t *ctx, DispatchEx *object_prototype, DispatchEx **ret)
918 {
919     ArrayInstance *array;
920     HRESULT hres;
921
922     hres = alloc_array(ctx, object_prototype, &array);
923     if(FAILED(hres))
924         return hres;
925
926     hres = create_builtin_function(ctx, ArrayConstr_value, NULL, PROPF_CONSTR, &array->dispex, ret);
927
928     jsdisp_release(&array->dispex);
929     return hres;
930 }
931
932 HRESULT create_array(script_ctx_t *ctx, DWORD length, DispatchEx **ret)
933 {
934     ArrayInstance *array;
935     HRESULT hres;
936
937     hres = alloc_array(ctx, NULL, &array);
938     if(FAILED(hres))
939         return hres;
940
941     array->length = length;
942
943     *ret = &array->dispex;
944     return S_OK;
945 }