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