Release 1.4.1.
[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 "config.h"
20 #include "wine/port.h"
21
22 #include <math.h>
23
24 #include "jscript.h"
25
26 #include "wine/debug.h"
27
28 WINE_DEFAULT_DEBUG_CHANNEL(jscript);
29
30 typedef struct {
31     jsdisp_t dispex;
32
33     DWORD length;
34 } ArrayInstance;
35
36 static const WCHAR lengthW[] = {'l','e','n','g','t','h',0};
37 static const WCHAR concatW[] = {'c','o','n','c','a','t',0};
38 static const WCHAR joinW[] = {'j','o','i','n',0};
39 static const WCHAR popW[] = {'p','o','p',0};
40 static const WCHAR pushW[] = {'p','u','s','h',0};
41 static const WCHAR reverseW[] = {'r','e','v','e','r','s','e',0};
42 static const WCHAR shiftW[] = {'s','h','i','f','t',0};
43 static const WCHAR sliceW[] = {'s','l','i','c','e',0};
44 static const WCHAR sortW[] = {'s','o','r','t',0};
45 static const WCHAR spliceW[] = {'s','p','l','i','c','e',0};
46 static const WCHAR toStringW[] = {'t','o','S','t','r','i','n','g',0};
47 static const WCHAR toLocaleStringW[] = {'t','o','L','o','c','a','l','e','S','t','r','i','n','g',0};
48 static const WCHAR unshiftW[] = {'u','n','s','h','i','f','t',0};
49
50 static const WCHAR default_separatorW[] = {',',0};
51
52 static inline ArrayInstance *array_from_vdisp(vdisp_t *vdisp)
53 {
54     return (ArrayInstance*)vdisp->u.jsdisp;
55 }
56
57 static inline ArrayInstance *array_this(vdisp_t *jsthis)
58 {
59     return is_vclass(jsthis, JSCLASS_ARRAY) ? array_from_vdisp(jsthis) : NULL;
60 }
61
62 static HRESULT get_length(script_ctx_t *ctx, vdisp_t *vdisp, jsexcept_t *ei, jsdisp_t **jsthis, DWORD *ret)
63 {
64     ArrayInstance *array;
65     VARIANT var;
66     HRESULT hres;
67
68     array = array_this(vdisp);
69     if(array) {
70         *jsthis = &array->dispex;
71         *ret = array->length;
72         return S_OK;
73     }
74
75     if(!is_jsdisp(vdisp))
76         return throw_type_error(ctx, ei, JS_E_JSCRIPT_EXPECTED, NULL);
77
78     hres = jsdisp_propget_name(vdisp->u.jsdisp, lengthW, &var, ei, NULL/*FIXME*/);
79     if(FAILED(hres))
80         return hres;
81
82     hres = to_uint32(ctx, &var, ei, ret);
83     VariantClear(&var);
84     if(FAILED(hres))
85         return hres;
86
87     *jsthis = vdisp->u.jsdisp;
88     return S_OK;
89 }
90
91 static HRESULT set_length(jsdisp_t *obj, jsexcept_t *ei, DWORD length)
92 {
93     VARIANT var;
94
95     if(is_class(obj, JSCLASS_ARRAY)) {
96         ((ArrayInstance*)obj)->length = length;
97         return S_OK;
98     }
99
100     V_VT(&var) = VT_I4;
101     V_I4(&var) = length;
102     return jsdisp_propput_name(obj, lengthW, &var, ei, NULL/*FIXME*/);
103 }
104
105 static WCHAR *idx_to_str(DWORD idx, WCHAR *ptr)
106 {
107     if(!idx) {
108         *ptr = '0';
109         return ptr;
110     }
111
112     while(idx) {
113         *ptr-- = '0' + (idx%10);
114         idx /= 10;
115     }
116
117     return ptr+1;
118 }
119
120 static HRESULT Array_length(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, DISPPARAMS *dp,
121         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
122 {
123     ArrayInstance *This = array_from_vdisp(jsthis);
124
125     TRACE("%p %d\n", This, This->length);
126
127     switch(flags) {
128     case DISPATCH_PROPERTYGET:
129         V_VT(retv) = VT_I4;
130         V_I4(retv) = This->length;
131         break;
132     case DISPATCH_PROPERTYPUT: {
133         VARIANT num;
134         DOUBLE len = -1;
135         DWORD i;
136         HRESULT hres;
137
138         hres = to_number(ctx, get_arg(dp, 0), ei, &num);
139         if(FAILED(hres))
140             return hres;
141
142         if(V_VT(&num) == VT_I4)
143             len = V_I4(&num);
144         else
145             len = floor(V_R8(&num));
146
147         if(len!=(DWORD)len)
148             return throw_range_error(ctx, ei, JS_E_INVALID_LENGTH, NULL);
149
150         for(i=len; i<This->length; i++) {
151             hres = jsdisp_delete_idx(&This->dispex, i);
152             if(FAILED(hres))
153                 return hres;
154         }
155
156         This->length = len;
157         break;
158     }
159     default:
160         FIXME("unimplemented flags %x\n", flags);
161         return E_NOTIMPL;
162     }
163
164     return S_OK;
165 }
166
167 static HRESULT concat_array(jsdisp_t *array, ArrayInstance *obj, DWORD *len,
168         jsexcept_t *ei, IServiceProvider *caller)
169 {
170     VARIANT var;
171     DWORD i;
172     HRESULT hres;
173
174     for(i=0; i < obj->length; i++) {
175         hres = jsdisp_get_idx(&obj->dispex, i, &var, ei, caller);
176         if(hres == DISP_E_UNKNOWNNAME)
177             continue;
178         if(FAILED(hres))
179             return hres;
180
181         hres = jsdisp_propput_idx(array, *len+i, &var, ei, caller);
182         VariantClear(&var);
183         if(FAILED(hres))
184             return hres;
185     }
186
187     *len += obj->length;
188     return S_OK;
189 }
190
191 static HRESULT concat_obj(jsdisp_t *array, IDispatch *obj, DWORD *len, jsexcept_t *ei, IServiceProvider *caller)
192 {
193     jsdisp_t *jsobj;
194     VARIANT var;
195     HRESULT hres;
196
197     jsobj = iface_to_jsdisp((IUnknown*)obj);
198     if(jsobj) {
199         if(is_class(jsobj, JSCLASS_ARRAY)) {
200             hres = concat_array(array, (ArrayInstance*)jsobj, len, ei, caller);
201             jsdisp_release(jsobj);
202             return hres;
203         }
204         jsdisp_release(jsobj);
205     }
206
207     V_VT(&var) = VT_DISPATCH;
208     V_DISPATCH(&var) = obj;
209     return jsdisp_propput_idx(array, (*len)++, &var, ei, caller);
210 }
211
212 static HRESULT Array_concat(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, DISPPARAMS *dp,
213         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
214 {
215     jsdisp_t *ret;
216     DWORD len = 0;
217     HRESULT hres;
218
219     TRACE("\n");
220
221     hres = create_array(ctx, 0, &ret);
222     if(FAILED(hres))
223         return hres;
224
225     hres = concat_obj(ret, jsthis->u.disp, &len, ei, caller);
226     if(SUCCEEDED(hres)) {
227         VARIANT *arg;
228         DWORD i;
229
230         for(i=0; i < arg_cnt(dp); i++) {
231             arg = get_arg(dp, i);
232             if(V_VT(arg) == VT_DISPATCH)
233                 hres = concat_obj(ret, V_DISPATCH(arg), &len, ei, caller);
234             else
235                 hres = jsdisp_propput_idx(ret, len++, arg, ei, caller);
236             if(FAILED(hres))
237                 break;
238         }
239     }
240
241     if(FAILED(hres))
242         return hres;
243
244     if(retv)
245         var_set_jsdisp(retv, ret);
246     else
247         jsdisp_release(ret);
248     return S_OK;
249 }
250
251 static HRESULT array_join(script_ctx_t *ctx, jsdisp_t *array, DWORD length, const WCHAR *sep, VARIANT *retv,
252         jsexcept_t *ei, IServiceProvider *caller)
253 {
254     BSTR *str_tab, ret = NULL;
255     VARIANT var;
256     DWORD i;
257     HRESULT hres = E_FAIL;
258
259     if(!length) {
260         if(retv) {
261             V_VT(retv) = VT_BSTR;
262             V_BSTR(retv) = SysAllocStringLen(NULL, 0);
263             if(!V_BSTR(retv))
264                 return E_OUTOFMEMORY;
265         }
266         return S_OK;
267     }
268
269     str_tab = heap_alloc_zero(length * sizeof(BSTR));
270     if(!str_tab)
271         return E_OUTOFMEMORY;
272
273     for(i=0; i < length; i++) {
274         hres = jsdisp_get_idx(array, i, &var, ei, caller);
275         if(hres == DISP_E_UNKNOWNNAME) {
276             hres = S_OK;
277             continue;
278         } else if(FAILED(hres))
279             break;
280
281         if(V_VT(&var) != VT_EMPTY && V_VT(&var) != VT_NULL)
282             hres = to_string(ctx, &var, ei, str_tab+i);
283         VariantClear(&var);
284         if(FAILED(hres))
285             break;
286     }
287
288     if(SUCCEEDED(hres)) {
289         DWORD seplen = 0, len = 0;
290         WCHAR *ptr;
291
292         seplen = strlenW(sep);
293
294         if(str_tab[0])
295             len = SysStringLen(str_tab[0]);
296         for(i=1; i < length; i++)
297             len += seplen + SysStringLen(str_tab[i]);
298
299         ret = SysAllocStringLen(NULL, len);
300         if(ret) {
301             DWORD tmplen = 0;
302
303             if(str_tab[0]) {
304                 tmplen = SysStringLen(str_tab[0]);
305                 memcpy(ret, str_tab[0], tmplen*sizeof(WCHAR));
306             }
307
308             ptr = ret + tmplen;
309             for(i=1; i < length; i++) {
310                 if(seplen) {
311                     memcpy(ptr, sep, seplen*sizeof(WCHAR));
312                     ptr += seplen;
313                 }
314
315                 if(str_tab[i]) {
316                     tmplen = SysStringLen(str_tab[i]);
317                     memcpy(ptr, str_tab[i], tmplen*sizeof(WCHAR));
318                     ptr += tmplen;
319                 }
320             }
321             *ptr=0;
322         }else {
323             hres = E_OUTOFMEMORY;
324         }
325     }
326
327     for(i=0; i < length; i++)
328         SysFreeString(str_tab[i]);
329     heap_free(str_tab);
330     if(FAILED(hres))
331         return hres;
332
333     TRACE("= %s\n", debugstr_w(ret));
334
335     if(retv) {
336         if(!ret) {
337             ret = SysAllocStringLen(NULL, 0);
338             if(!ret)
339                 return E_OUTOFMEMORY;
340         }
341
342         V_VT(retv) = VT_BSTR;
343         V_BSTR(retv) = ret;
344     }else {
345         SysFreeString(ret);
346     }
347
348     return S_OK;
349 }
350
351 /* ECMA-262 3rd Edition    15.4.4.5 */
352 static HRESULT Array_join(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
353         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
354 {
355     jsdisp_t *jsthis;
356     DWORD length;
357     HRESULT hres;
358
359     TRACE("\n");
360
361     hres = get_length(ctx, vthis, ei, &jsthis, &length);
362     if(FAILED(hres))
363         return hres;
364
365     if(arg_cnt(dp)) {
366         BSTR sep;
367
368         hres = to_string(ctx, get_arg(dp,0), ei, &sep);
369         if(FAILED(hres))
370             return hres;
371
372         hres = array_join(ctx, jsthis, length, sep, retv, ei, caller);
373
374         SysFreeString(sep);
375     }else {
376         hres = array_join(ctx, jsthis, length, default_separatorW, retv, ei, caller);
377     }
378
379     return hres;
380 }
381
382 static HRESULT Array_pop(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
383         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
384 {
385     jsdisp_t *jsthis;
386     VARIANT val;
387     DWORD length;
388     HRESULT hres;
389
390     TRACE("\n");
391
392     hres = get_length(ctx, vthis, ei, &jsthis, &length);
393     if(FAILED(hres))
394         return hres;
395
396     if(!length) {
397         hres = set_length(jsthis, ei, 0);
398         if(FAILED(hres))
399             return hres;
400
401         if(retv)
402             V_VT(retv) = VT_EMPTY;
403         return S_OK;
404     }
405
406     length--;
407     hres = jsdisp_get_idx(jsthis, length, &val, ei, caller);
408     if(SUCCEEDED(hres)) {
409         hres = jsdisp_delete_idx(jsthis, length);
410     } else if(hres == DISP_E_UNKNOWNNAME) {
411         V_VT(&val) = VT_EMPTY;
412         hres = S_OK;
413     } else
414         return hres;
415
416     if(SUCCEEDED(hres))
417         hres = set_length(jsthis, ei, length);
418
419     if(FAILED(hres)) {
420         VariantClear(&val);
421         return hres;
422     }
423
424     if(retv)
425         *retv = val;
426     else
427         VariantClear(&val);
428
429     return S_OK;
430 }
431
432 /* ECMA-262 3rd Edition    15.4.4.7 */
433 static HRESULT Array_push(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
434         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
435 {
436     jsdisp_t *jsthis;
437     DWORD length = 0;
438     int i, n;
439     HRESULT hres;
440
441     TRACE("\n");
442
443     hres = get_length(ctx, vthis, ei, &jsthis, &length);
444     if(FAILED(hres))
445         return hres;
446
447     n = arg_cnt(dp);
448     for(i=0; i < n; i++) {
449         hres = jsdisp_propput_idx(jsthis, length+i, get_arg(dp, i), ei, sp);
450         if(FAILED(hres))
451             return hres;
452     }
453
454     hres = set_length(jsthis, ei, length+n);
455     if(FAILED(hres))
456         return hres;
457
458     if(retv) {
459         V_VT(retv) = VT_I4;
460         V_I4(retv) = length+n;
461     }
462     return S_OK;
463 }
464
465 static HRESULT Array_reverse(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
466         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
467 {
468     jsdisp_t *jsthis;
469     DWORD length, k, l;
470     VARIANT v1, v2;
471     HRESULT hres1, hres2;
472
473     TRACE("\n");
474
475     hres1 = get_length(ctx, vthis, ei, &jsthis, &length);
476     if(FAILED(hres1))
477         return hres1;
478
479     for(k=0; k<length/2; k++) {
480         l = length-k-1;
481
482         hres1 = jsdisp_get_idx(jsthis, k, &v1, ei, sp);
483         if(FAILED(hres1) && hres1!=DISP_E_UNKNOWNNAME)
484             return hres1;
485
486         hres2 = jsdisp_get_idx(jsthis, l, &v2, ei, sp);
487         if(FAILED(hres2) && hres2!=DISP_E_UNKNOWNNAME) {
488             VariantClear(&v1);
489             return hres2;
490         }
491
492         if(hres1 == DISP_E_UNKNOWNNAME)
493             hres1 = jsdisp_delete_idx(jsthis, l);
494         else
495             hres1 = jsdisp_propput_idx(jsthis, l, &v1, ei, sp);
496
497         if(FAILED(hres1)) {
498             VariantClear(&v1);
499             VariantClear(&v2);
500             return hres1;
501         }
502
503         if(hres2 == DISP_E_UNKNOWNNAME)
504             hres2 = jsdisp_delete_idx(jsthis, k);
505         else
506             hres2 = jsdisp_propput_idx(jsthis, k, &v2, ei, sp);
507
508         if(FAILED(hres2)) {
509             VariantClear(&v2);
510             return hres2;
511         }
512     }
513
514     if(retv) {
515         jsdisp_addref(jsthis);
516         var_set_jsdisp(retv, jsthis);
517     }
518
519     return S_OK;
520 }
521
522 /* ECMA-262 3rd Edition    15.4.4.9 */
523 static HRESULT Array_shift(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
524         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
525 {
526     jsdisp_t *jsthis;
527     DWORD length = 0, i;
528     VARIANT v, ret;
529     HRESULT hres;
530
531     TRACE("\n");
532
533     hres = get_length(ctx, vthis, ei, &jsthis, &length);
534     if(FAILED(hres))
535         return hres;
536
537     if(!length) {
538         hres = set_length(jsthis, ei, 0);
539         if(FAILED(hres))
540             return hres;
541     }
542
543     if(!length) {
544         if(retv)
545             V_VT(retv) = VT_EMPTY;
546         return S_OK;
547     }
548
549     hres = jsdisp_get_idx(jsthis, 0, &ret, ei, caller);
550     if(hres == DISP_E_UNKNOWNNAME) {
551         V_VT(&ret) = VT_EMPTY;
552         hres = S_OK;
553     }
554
555     for(i=1; SUCCEEDED(hres) && i<length; i++) {
556         hres = jsdisp_get_idx(jsthis, i, &v, ei, caller);
557         if(hres == DISP_E_UNKNOWNNAME)
558             hres = jsdisp_delete_idx(jsthis, i-1);
559         else if(SUCCEEDED(hres))
560             hres = jsdisp_propput_idx(jsthis, i-1, &v, ei, caller);
561     }
562
563     if(SUCCEEDED(hres)) {
564         hres = jsdisp_delete_idx(jsthis, length-1);
565         if(SUCCEEDED(hres))
566             hres = set_length(jsthis, ei, length-1);
567     }
568
569     if(SUCCEEDED(hres) && retv)
570         *retv = ret;
571     else
572         VariantClear(&ret);
573     return hres;
574 }
575
576 /* ECMA-262 3rd Edition    15.4.4.10 */
577 static HRESULT Array_slice(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
578         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
579 {
580     jsdisp_t *arr, *jsthis;
581     VARIANT v;
582     DOUBLE range;
583     DWORD length, start, end, idx;
584     HRESULT hres;
585
586     TRACE("\n");
587
588     hres = get_length(ctx, vthis, ei, &jsthis, &length);
589     if(FAILED(hres))
590         return hres;
591
592     if(arg_cnt(dp)) {
593         hres = to_number(ctx, get_arg(dp, 0), ei, &v);
594         if(FAILED(hres))
595             return hres;
596
597         if(V_VT(&v) == VT_I4)
598             range = V_I4(&v);
599         else
600             range = floor(V_R8(&v));
601
602         if(-range>length || isnan(range)) start = 0;
603         else if(range < 0) start = range+length;
604         else if(range <= length) start = range;
605         else start = length;
606     }
607     else start = 0;
608
609     if(arg_cnt(dp)>1) {
610         hres = to_number(ctx, get_arg(dp, 1), ei, &v);
611         if(FAILED(hres))
612             return hres;
613
614         if(V_VT(&v) == VT_I4)
615             range = V_I4(&v);
616         else
617             range = floor(V_R8(&v));
618
619         if(-range>length) end = 0;
620         else if(range < 0) end = range+length;
621         else if(range <= length) end = range;
622         else end = length;
623     }
624     else end = length;
625
626     hres = create_array(ctx, (end>start)?end-start:0, &arr);
627     if(FAILED(hres))
628         return hres;
629
630     for(idx=start; idx<end; idx++) {
631         hres = jsdisp_get_idx(jsthis, idx, &v, ei, sp);
632         if(hres == DISP_E_UNKNOWNNAME)
633             continue;
634
635         if(SUCCEEDED(hres)) {
636             hres = jsdisp_propput_idx(arr, idx-start, &v, ei, sp);
637             VariantClear(&v);
638         }
639
640         if(FAILED(hres)) {
641             jsdisp_release(arr);
642             return hres;
643         }
644     }
645
646     if(retv)
647         var_set_jsdisp(retv, arr);
648     else
649         jsdisp_release(arr);
650
651     return S_OK;
652 }
653
654 static HRESULT sort_cmp(script_ctx_t *ctx, jsdisp_t *cmp_func, VARIANT *v1, VARIANT *v2, jsexcept_t *ei,
655         IServiceProvider *caller, INT *cmp)
656 {
657     HRESULT hres;
658
659     if(cmp_func) {
660         VARIANTARG args[2];
661         DISPPARAMS dp = {args, NULL, 2, 0};
662         VARIANT tmp;
663         VARIANT res;
664
665         args[0] = *v2;
666         args[1] = *v1;
667
668         hres = jsdisp_call_value(cmp_func, DISPATCH_METHOD, &dp, &res, ei, caller);
669         if(FAILED(hres))
670             return hres;
671
672         hres = to_number(ctx, &res, ei, &tmp);
673         VariantClear(&res);
674         if(FAILED(hres))
675             return hres;
676
677         if(V_VT(&tmp) == VT_I4)
678             *cmp = V_I4(&tmp);
679         else
680             *cmp = V_R8(&tmp) > 0.0 ? 1 : -1;
681     }else if(V_VT(v1) == VT_EMPTY) {
682         *cmp = V_VT(v2) == VT_EMPTY ? 0 : 1;
683     }else if(V_VT(v2) == VT_EMPTY) {
684         *cmp = -1;
685     }else if(is_num_vt(V_VT(v1)) && is_num_vt(V_VT(v2))) {
686         DOUBLE d = num_val(v1)-num_val(v2);
687         if(d > 0.0)
688             *cmp = 1;
689         else
690             *cmp = d < -0.0 ? -1 : 0;
691     }else {
692         BSTR x, y;
693
694         hres = to_string(ctx, v1, ei, &x);
695         if(FAILED(hres))
696             return hres;
697
698         hres = to_string(ctx, v2, ei, &y);
699         if(SUCCEEDED(hres)) {
700             *cmp = strcmpW(x, y);
701             SysFreeString(y);
702         }
703         SysFreeString(x);
704         if(FAILED(hres))
705             return hres;
706     }
707
708     return S_OK;
709 }
710
711 /* ECMA-262 3rd Edition    15.4.4.11 */
712 static HRESULT Array_sort(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
713         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
714 {
715     jsdisp_t *jsthis, *cmp_func = NULL;
716     VARIANT *vtab, **sorttab = NULL;
717     DWORD length;
718     DWORD i;
719     HRESULT hres = S_OK;
720
721     TRACE("\n");
722
723     hres = get_length(ctx, vthis, ei, &jsthis, &length);
724     if(FAILED(hres))
725         return hres;
726
727     if(arg_cnt(dp) > 1) {
728         WARN("invalid arg_cnt %d\n", arg_cnt(dp));
729         return E_FAIL;
730     }
731
732     if(arg_cnt(dp) == 1) {
733         VARIANT *arg = get_arg(dp, 0);
734
735         if(V_VT(arg) != VT_DISPATCH) {
736             WARN("arg is not dispatch\n");
737             return E_FAIL;
738         }
739
740
741         cmp_func = iface_to_jsdisp((IUnknown*)V_DISPATCH(arg));
742         if(!cmp_func || !is_class(cmp_func, JSCLASS_FUNCTION)) {
743             WARN("cmp_func is not a function\n");
744             if(cmp_func)
745                 jsdisp_release(cmp_func);
746             return E_FAIL;
747         }
748     }
749
750     if(!length) {
751         if(cmp_func)
752             jsdisp_release(cmp_func);
753         if(retv) {
754             jsdisp_addref(jsthis);
755             var_set_jsdisp(retv, jsthis);
756         }
757         return S_OK;
758     }
759
760     vtab = heap_alloc_zero(length * sizeof(VARIANT));
761     if(vtab) {
762         for(i=0; i<length; i++) {
763             hres = jsdisp_get_idx(jsthis, i, vtab+i, ei, caller);
764             if(hres == DISP_E_UNKNOWNNAME) {
765                 V_VT(vtab+i) = VT_EMPTY;
766                 hres = S_OK;
767             } else if(FAILED(hres)) {
768                 WARN("Could not get elem %d: %08x\n", i, hres);
769                 break;
770             }
771         }
772     }else {
773         hres = E_OUTOFMEMORY;
774     }
775
776     if(SUCCEEDED(hres)) {
777         sorttab = heap_alloc(length*2*sizeof(VARIANT*));
778         if(!sorttab)
779             hres = E_OUTOFMEMORY;
780     }
781
782     /* merge-sort */
783     if(SUCCEEDED(hres)) {
784         VARIANT *tmpv, **tmpbuf;
785         INT cmp;
786
787         tmpbuf = sorttab + length;
788         for(i=0; i < length; i++)
789             sorttab[i] = vtab+i;
790
791         for(i=0; i < length/2; i++) {
792             hres = sort_cmp(ctx, cmp_func, sorttab[2*i+1], sorttab[2*i], ei, caller, &cmp);
793             if(FAILED(hres))
794                 break;
795
796             if(cmp < 0) {
797                 tmpv = sorttab[2*i];
798                 sorttab[2*i] = sorttab[2*i+1];
799                 sorttab[2*i+1] = tmpv;
800             }
801         }
802
803         if(SUCCEEDED(hres)) {
804             DWORD k, a, b, bend;
805
806             for(k=2; k < length; k *= 2) {
807                 for(i=0; i+k < length; i += 2*k) {
808                     a = b = 0;
809                     if(i+2*k <= length)
810                         bend = k;
811                     else
812                         bend = length - (i+k);
813
814                     memcpy(tmpbuf, sorttab+i, k*sizeof(VARIANT*));
815
816                     while(a < k && b < bend) {
817                         hres = sort_cmp(ctx, cmp_func, tmpbuf[a], sorttab[i+k+b], ei, caller, &cmp);
818                         if(FAILED(hres))
819                             break;
820
821                         if(cmp < 0) {
822                             sorttab[i+a+b] = tmpbuf[a];
823                             a++;
824                         }else {
825                             sorttab[i+a+b] = sorttab[i+k+b];
826                             b++;
827                         }
828                     }
829
830                     if(FAILED(hres))
831                         break;
832
833                     if(a < k)
834                         memcpy(sorttab+i+a+b, tmpbuf+a, (k-a)*sizeof(VARIANT*));
835                 }
836
837                 if(FAILED(hres))
838                     break;
839             }
840         }
841
842         for(i=0; SUCCEEDED(hres) && i < length; i++)
843             hres = jsdisp_propput_idx(jsthis, i, sorttab[i], ei, caller);
844     }
845
846     if(vtab) {
847         for(i=0; i < length; i++)
848             VariantClear(vtab+i);
849         heap_free(vtab);
850     }
851     heap_free(sorttab);
852     if(cmp_func)
853         jsdisp_release(cmp_func);
854
855     if(FAILED(hres))
856         return hres;
857
858     if(retv) {
859         jsdisp_addref(jsthis);
860         var_set_jsdisp(retv, jsthis);
861     }
862
863     return S_OK;
864 }
865
866 /* ECMA-262 3rd Edition    15.4.4.12 */
867 static HRESULT Array_splice(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
868         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
869 {
870     DWORD length, start=0, delete_cnt=0, argc, i, add_args = 0;
871     jsdisp_t *ret_array = NULL, *jsthis;
872     VARIANT v;
873     HRESULT hres = S_OK;
874
875     TRACE("\n");
876
877     hres = get_length(ctx, vthis, ei, &jsthis, &length);
878     if(FAILED(hres))
879         return hres;
880
881     argc = arg_cnt(dp);
882     if(argc >= 1) {
883         hres = to_integer(ctx, get_arg(dp,0), ei, &v);
884         if(FAILED(hres))
885             return hres;
886
887         if(V_VT(&v) == VT_I4) {
888             if(V_I4(&v) >= 0)
889                 start = min(V_I4(&v), length);
890             else
891                 start = -V_I4(&v) > length ? 0 : length + V_I4(&v);
892         }else {
893             start = V_R8(&v) < 0.0 ? 0 : length;
894         }
895     }
896
897     if(argc >= 2) {
898         hres = to_integer(ctx, get_arg(dp,1), ei, &v);
899         if(FAILED(hres))
900             return hres;
901
902         if(V_VT(&v) == VT_I4) {
903             if(V_I4(&v) > 0)
904                 delete_cnt = min(V_I4(&v), length-start);
905         }else if(V_R8(&v) > 0.0) {
906             delete_cnt = length-start;
907         }
908
909         add_args = argc-2;
910     }
911
912     if(retv) {
913         hres = create_array(ctx, 0, &ret_array);
914         if(FAILED(hres))
915             return hres;
916
917         for(i=0; SUCCEEDED(hres) && i < delete_cnt; i++) {
918             hres = jsdisp_get_idx(jsthis, start+i, &v, ei, caller);
919             if(hres == DISP_E_UNKNOWNNAME)
920                 hres = S_OK;
921             else if(SUCCEEDED(hres))
922                 hres = jsdisp_propput_idx(ret_array, i, &v, ei, caller);
923         }
924
925         if(SUCCEEDED(hres)) {
926             V_VT(&v) = VT_I4;
927             V_I4(&v) = delete_cnt;
928
929             hres = jsdisp_propput_name(ret_array, lengthW, &v, ei, caller);
930         }
931     }
932
933     if(add_args < delete_cnt) {
934         for(i = start; SUCCEEDED(hres) && i < length-delete_cnt; i++) {
935             hres = jsdisp_get_idx(jsthis, i+delete_cnt, &v, ei, caller);
936             if(hres == DISP_E_UNKNOWNNAME)
937                 hres = jsdisp_delete_idx(jsthis, i+add_args);
938             else if(SUCCEEDED(hres))
939                 hres = jsdisp_propput_idx(jsthis, i+add_args, &v, ei, caller);
940         }
941
942         for(i=length; SUCCEEDED(hres) && i != length-delete_cnt+add_args; i--)
943             hres = jsdisp_delete_idx(jsthis, i-1);
944     }else if(add_args > delete_cnt) {
945         for(i=length-delete_cnt; SUCCEEDED(hres) && i != start; i--) {
946             hres = jsdisp_get_idx(jsthis, i+delete_cnt-1, &v, ei, caller);
947             if(hres == DISP_E_UNKNOWNNAME)
948                 hres = jsdisp_delete_idx(jsthis, i+add_args-1);
949             else if(SUCCEEDED(hres))
950                 hres = jsdisp_propput_idx(jsthis, i+add_args-1, &v, ei, caller);
951         }
952     }
953
954     for(i=0; SUCCEEDED(hres) && i < add_args; i++)
955         hres = jsdisp_propput_idx(jsthis, start+i, get_arg(dp,i+2), ei, caller);
956
957     if(SUCCEEDED(hres)) {
958         V_VT(&v) = VT_I4;
959         V_I4(&v) = length-delete_cnt+add_args;
960         hres = jsdisp_propput_name(jsthis, lengthW, &v, ei, caller);
961     }
962
963     if(FAILED(hres)) {
964         if(ret_array)
965             jsdisp_release(ret_array);
966         return hres;
967     }
968
969     if(retv)
970         var_set_jsdisp(retv, ret_array);
971     return S_OK;
972 }
973
974 /* ECMA-262 3rd Edition    15.4.4.2 */
975 static HRESULT Array_toString(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, DISPPARAMS *dp,
976         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
977 {
978     ArrayInstance *array;
979
980     TRACE("\n");
981
982     array = array_this(jsthis);
983     if(!array)
984         return throw_type_error(ctx, ei, JS_E_ARRAY_EXPECTED, NULL);
985
986     return array_join(ctx, &array->dispex, array->length, default_separatorW, retv, ei, sp);
987 }
988
989 static HRESULT Array_toLocaleString(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
990         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
991 {
992     FIXME("\n");
993     return E_NOTIMPL;
994 }
995
996 /* ECMA-262 3rd Edition    15.4.4.13 */
997 static HRESULT Array_unshift(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
998         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
999 {
1000     jsdisp_t *jsthis;
1001     WCHAR buf[14], *buf_end, *str;
1002     DWORD argc, i, length;
1003     VARIANT var;
1004     DISPID id;
1005     HRESULT hres;
1006
1007     TRACE("\n");
1008
1009     hres = get_length(ctx, vthis, ei, &jsthis, &length);
1010     if(FAILED(hres))
1011         return hres;
1012
1013     argc = arg_cnt(dp);
1014     if(argc) {
1015         buf_end = buf + sizeof(buf)/sizeof(WCHAR)-1;
1016         *buf_end-- = 0;
1017         i = length;
1018
1019         while(i--) {
1020             str = idx_to_str(i, buf_end);
1021
1022             hres = jsdisp_get_id(jsthis, str, 0, &id);
1023             if(SUCCEEDED(hres)) {
1024                 hres = jsdisp_propget(jsthis, id, &var, ei, caller);
1025                 if(FAILED(hres))
1026                     return hres;
1027
1028                 hres = jsdisp_propput_idx(jsthis, i+argc, &var, ei, caller);
1029                 VariantClear(&var);
1030             }else if(hres == DISP_E_UNKNOWNNAME) {
1031                 hres = IDispatchEx_DeleteMemberByDispID(vthis->u.dispex, id);
1032             }
1033         }
1034
1035         if(FAILED(hres))
1036             return hres;
1037     }
1038
1039     for(i=0; i<argc; i++) {
1040         hres = jsdisp_propput_idx(jsthis, i, get_arg(dp,i), ei, caller);
1041         if(FAILED(hres))
1042             return hres;
1043     }
1044
1045     if(argc) {
1046         length += argc;
1047         hres = set_length(jsthis, ei, length);
1048         if(FAILED(hres))
1049             return hres;
1050     }
1051
1052     if(retv) {
1053         if(ctx->version < 2) {
1054             V_VT(retv) = VT_EMPTY;
1055         }else {
1056             V_VT(retv) = VT_I4;
1057             V_I4(retv) = length;
1058         }
1059     }
1060     return S_OK;
1061 }
1062
1063 static HRESULT Array_value(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, DISPPARAMS *dp,
1064         VARIANT *retv, jsexcept_t *ei, IServiceProvider *sp)
1065 {
1066     TRACE("\n");
1067
1068     switch(flags) {
1069     case INVOKE_FUNC:
1070         return throw_type_error(ctx, ei, JS_E_FUNCTION_EXPECTED, NULL);
1071     case INVOKE_PROPERTYGET:
1072         return array_join(ctx, jsthis->u.jsdisp, array_from_vdisp(jsthis)->length, default_separatorW, retv, ei, sp);
1073     default:
1074         FIXME("unimplemented flags %x\n", flags);
1075         return E_NOTIMPL;
1076     }
1077
1078     return S_OK;
1079 }
1080
1081 static void Array_destructor(jsdisp_t *dispex)
1082 {
1083     heap_free(dispex);
1084 }
1085
1086 static void Array_on_put(jsdisp_t *dispex, const WCHAR *name)
1087 {
1088     ArrayInstance *array = (ArrayInstance*)dispex;
1089     const WCHAR *ptr = name;
1090     DWORD id = 0;
1091
1092     if(!isdigitW(*ptr))
1093         return;
1094
1095     while(*ptr && isdigitW(*ptr)) {
1096         id = id*10 + (*ptr-'0');
1097         ptr++;
1098     }
1099
1100     if(*ptr)
1101         return;
1102
1103     if(id >= array->length)
1104         array->length = id+1;
1105 }
1106
1107 static const builtin_prop_t Array_props[] = {
1108     {concatW,                Array_concat,               PROPF_METHOD|1},
1109     {joinW,                  Array_join,                 PROPF_METHOD|1},
1110     {lengthW,                Array_length,               0},
1111     {popW,                   Array_pop,                  PROPF_METHOD},
1112     {pushW,                  Array_push,                 PROPF_METHOD|1},
1113     {reverseW,               Array_reverse,              PROPF_METHOD},
1114     {shiftW,                 Array_shift,                PROPF_METHOD},
1115     {sliceW,                 Array_slice,                PROPF_METHOD|2},
1116     {sortW,                  Array_sort,                 PROPF_METHOD|1},
1117     {spliceW,                Array_splice,               PROPF_METHOD|2},
1118     {toLocaleStringW,        Array_toLocaleString,       PROPF_METHOD},
1119     {toStringW,              Array_toString,             PROPF_METHOD},
1120     {unshiftW,               Array_unshift,              PROPF_METHOD|1},
1121 };
1122
1123 static const builtin_info_t Array_info = {
1124     JSCLASS_ARRAY,
1125     {NULL, Array_value, 0},
1126     sizeof(Array_props)/sizeof(*Array_props),
1127     Array_props,
1128     Array_destructor,
1129     Array_on_put
1130 };
1131
1132 static HRESULT ArrayConstr_value(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, DISPPARAMS *dp,
1133         VARIANT *retv, jsexcept_t *ei, IServiceProvider *caller)
1134 {
1135     jsdisp_t *obj;
1136     VARIANT *arg_var;
1137     DWORD i;
1138     HRESULT hres;
1139
1140     TRACE("\n");
1141
1142     switch(flags) {
1143     case DISPATCH_METHOD:
1144     case DISPATCH_CONSTRUCT: {
1145         if(arg_cnt(dp) == 1 && V_VT((arg_var = get_arg(dp, 0))) == VT_I4) {
1146             if(V_I4(arg_var) < 0)
1147                 return throw_range_error(ctx, ei, JS_E_INVALID_LENGTH, NULL);
1148
1149             hres = create_array(ctx, V_I4(arg_var), &obj);
1150             if(FAILED(hres))
1151                 return hres;
1152
1153             var_set_jsdisp(retv, obj);
1154             return S_OK;
1155         }
1156
1157         hres = create_array(ctx, arg_cnt(dp), &obj);
1158         if(FAILED(hres))
1159             return hres;
1160
1161         for(i=0; i < arg_cnt(dp); i++) {
1162             hres = jsdisp_propput_idx(obj, i, get_arg(dp, i), ei, caller);
1163             if(FAILED(hres))
1164                 break;
1165         }
1166         if(FAILED(hres)) {
1167             jsdisp_release(obj);
1168             return hres;
1169         }
1170
1171         var_set_jsdisp(retv, obj);
1172         break;
1173     }
1174     default:
1175         FIXME("unimplemented flags: %x\n", flags);
1176         return E_NOTIMPL;
1177     }
1178
1179     return S_OK;
1180 }
1181
1182 static HRESULT alloc_array(script_ctx_t *ctx, jsdisp_t *object_prototype, ArrayInstance **ret)
1183 {
1184     ArrayInstance *array;
1185     HRESULT hres;
1186
1187     array = heap_alloc_zero(sizeof(ArrayInstance));
1188     if(!array)
1189         return E_OUTOFMEMORY;
1190
1191     if(object_prototype)
1192         hres = init_dispex(&array->dispex, ctx, &Array_info, object_prototype);
1193     else
1194         hres = init_dispex_from_constr(&array->dispex, ctx, &Array_info, ctx->array_constr);
1195
1196     if(FAILED(hres)) {
1197         heap_free(array);
1198         return hres;
1199     }
1200
1201     *ret = array;
1202     return S_OK;
1203 }
1204
1205 HRESULT create_array_constr(script_ctx_t *ctx, jsdisp_t *object_prototype, jsdisp_t **ret)
1206 {
1207     ArrayInstance *array;
1208     HRESULT hres;
1209
1210     static const WCHAR ArrayW[] = {'A','r','r','a','y',0};
1211
1212     hres = alloc_array(ctx, object_prototype, &array);
1213     if(FAILED(hres))
1214         return hres;
1215
1216     hres = create_builtin_function(ctx, ArrayConstr_value, ArrayW, NULL, PROPF_CONSTR|1, &array->dispex, ret);
1217
1218     jsdisp_release(&array->dispex);
1219     return hres;
1220 }
1221
1222 HRESULT create_array(script_ctx_t *ctx, DWORD length, jsdisp_t **ret)
1223 {
1224     ArrayInstance *array;
1225     HRESULT hres;
1226
1227     hres = alloc_array(ctx, NULL, &array);
1228     if(FAILED(hres))
1229         return hres;
1230
1231     array->length = length;
1232
1233     *ret = &array->dispex;
1234     return S_OK;
1235 }