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