1 // -*- c-basic-offset: 2 -*-
3 * This file is part of the KDE libraries
4 * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
5 * Copyright (C) 2003, 2007 Apple Inc. All rights reserved.
6 * Copyright (C) 2003 Peter Kelly (pmk@post.com)
7 * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 #include "array_object.h"
27 #include "array_object.lut.h"
29 #include "error_object.h"
31 #include "operations.h"
32 #include "PropertyNameArray.h"
33 #include <wtf/HashSet.h>
36 // GCC cstring uses these automatically, but not all implementations do.
45 // ------------------------------ ArrayPrototype ----------------------------
47 const ClassInfo
ArrayPrototype::info
= {"Array", &ArrayInstance::info
, &arrayTable
, 0};
49 /* Source for array_object.lut.h
51 toString ArrayProtoFunc::ToString DontEnum|Function 0
52 toLocaleString ArrayProtoFunc::ToLocaleString DontEnum|Function 0
53 concat ArrayProtoFunc::Concat DontEnum|Function 1
54 join ArrayProtoFunc::Join DontEnum|Function 1
55 pop ArrayProtoFunc::Pop DontEnum|Function 0
56 push ArrayProtoFunc::Push DontEnum|Function 1
57 reverse ArrayProtoFunc::Reverse DontEnum|Function 0
58 shift ArrayProtoFunc::Shift DontEnum|Function 0
59 slice ArrayProtoFunc::Slice DontEnum|Function 2
60 sort ArrayProtoFunc::Sort DontEnum|Function 1
61 splice ArrayProtoFunc::Splice DontEnum|Function 2
62 unshift ArrayProtoFunc::UnShift DontEnum|Function 1
63 every ArrayProtoFunc::Every DontEnum|Function 1
64 forEach ArrayProtoFunc::ForEach DontEnum|Function 1
65 some ArrayProtoFunc::Some DontEnum|Function 1
66 indexOf ArrayProtoFunc::IndexOf DontEnum|Function 1
67 lastIndexOf ArrayProtoFunc::LastIndexOf DontEnum|Function 1
68 filter ArrayProtoFunc::Filter DontEnum|Function 1
69 map ArrayProtoFunc::Map DontEnum|Function 1
74 ArrayPrototype::ArrayPrototype(ExecState
*, ObjectPrototype
* objProto
)
75 : ArrayInstance(objProto
, 0)
79 bool ArrayPrototype::getOwnPropertySlot(ExecState
* exec
, const Identifier
& propertyName
, PropertySlot
& slot
)
81 return getStaticFunctionSlot
<ArrayProtoFunc
, ArrayInstance
>(exec
, &arrayTable
, this, propertyName
, slot
);
84 // ------------------------------ ArrayProtoFunc ----------------------------
86 ArrayProtoFunc::ArrayProtoFunc(ExecState
* exec
, int i
, int len
, const Identifier
& name
)
87 : InternalFunctionImp(static_cast<FunctionPrototype
*>
88 (exec
->lexicalInterpreter()->builtinFunctionPrototype()), name
)
91 put(exec
, exec
->propertyNames().length
, jsNumber(len
), DontDelete
| ReadOnly
| DontEnum
);
94 static JSValue
*getProperty(ExecState
*exec
, JSObject
*obj
, unsigned index
)
97 if (!obj
->getPropertySlot(exec
, index
, slot
))
99 return slot
.getValue(exec
, obj
, index
);
103 JSValue
* ArrayProtoFunc::callAsFunction(ExecState
* exec
, JSObject
* thisObj
, const List
& args
)
105 unsigned length
= thisObj
->get(exec
, exec
->propertyNames().length
)->toUInt32(exec
);
107 JSValue
*result
= 0; // work around gcc 4.0 bug in uninitialized variable warning
113 if (!thisObj
->inherits(&ArrayInstance::info
))
114 return throwError(exec
, TypeError
);
118 static HashSet
<JSObject
*> visitedElems
;
119 static const UString
* empty
= new UString("");
120 static const UString
* comma
= new UString(",");
121 bool alreadyVisited
= !visitedElems
.add(thisObj
).second
;
123 return jsString(*empty
);
124 UString separator
= *comma
;
125 UString str
= *empty
;
127 if (id
== Join
&& !args
[0]->isUndefined())
128 separator
= args
[0]->toString(exec
);
129 for (unsigned int k
= 0; k
< length
; k
++) {
133 JSObject
*error
= Error::create(exec
, GeneralError
, "Out of memory");
134 exec
->setException(error
);
138 JSValue
* element
= thisObj
->get(exec
, k
);
139 if (element
->isUndefinedOrNull())
142 bool fallback
= false;
143 if (id
== ToLocaleString
) {
144 JSObject
* o
= element
->toObject(exec
);
145 JSValue
* conversionFunction
= o
->get(exec
, exec
->propertyNames().toLocaleString
);
146 if (conversionFunction
->isObject() && static_cast<JSObject
*>(conversionFunction
)->implementsCall())
147 str
+= static_cast<JSObject
*>(conversionFunction
)->call(exec
, o
, List())->toString(exec
);
149 // try toString() fallback
153 if (id
== ToString
|| id
== Join
|| fallback
) {
154 if (element
->isObject()) {
155 JSObject
*o
= static_cast<JSObject
*>(element
);
156 JSValue
*conversionFunction
= o
->get(exec
, exec
->propertyNames().toString
);
157 if (conversionFunction
->isObject() && static_cast<JSObject
*>(conversionFunction
)->implementsCall()) {
158 str
+= static_cast<JSObject
*>(conversionFunction
)->call(exec
, o
, List())->toString(exec
);
160 visitedElems
.remove(thisObj
);
161 return throwError(exec
, RangeError
, "Cannot convert " + o
->className() + " object to string");
164 str
+= element
->toString(exec
);
167 JSObject
* error
= Error::create(exec
, GeneralError
, "Out of memory");
168 exec
->setException(error
);
172 if (exec
->hadException())
175 visitedElems
.remove(thisObj
);
176 result
= jsString(str
);
180 JSObject
*arr
= static_cast<JSObject
*>(exec
->lexicalInterpreter()->builtinArray()->construct(exec
,List::empty()));
182 JSValue
*curArg
= thisObj
;
183 JSObject
*curObj
= static_cast<JSObject
*>(thisObj
);
184 ListIterator it
= args
.begin();
186 if (curArg
->isObject() &&
187 curObj
->inherits(&ArrayInstance::info
)) {
189 // Older versions tried to optimize out getting the length of thisObj
190 // by checking for n != 0, but that doesn't work if thisObj is an empty array.
191 length
= curObj
->get(exec
, exec
->propertyNames().length
)->toUInt32(exec
);
193 if (JSValue
*v
= getProperty(exec
, curObj
, k
))
194 arr
->put(exec
, n
, v
);
199 arr
->put(exec
, n
, curArg
);
202 if (it
== args
.end())
205 curObj
= static_cast<JSObject
*>(it
++); // may be 0
207 arr
->put(exec
, exec
->propertyNames().length
, jsNumber(n
), DontEnum
| DontDelete
);
214 thisObj
->put(exec
, exec
->propertyNames().length
, jsNumber(length
), DontEnum
| DontDelete
);
215 result
= jsUndefined();
217 result
= thisObj
->get(exec
, length
- 1);
218 thisObj
->put(exec
, exec
->propertyNames().length
, jsNumber(length
- 1), DontEnum
| DontDelete
);
223 for (int n
= 0; n
< args
.size(); n
++)
224 thisObj
->put(exec
, length
+ n
, args
[n
]);
225 length
+= args
.size();
226 thisObj
->put(exec
, exec
->propertyNames().length
, jsNumber(length
), DontEnum
| DontDelete
);
227 result
= jsNumber(length
);
232 unsigned int middle
= length
/ 2;
234 for (unsigned int k
= 0; k
< middle
; k
++) {
235 unsigned lk1
= length
- k
- 1;
236 JSValue
*obj2
= getProperty(exec
, thisObj
, lk1
);
237 JSValue
*obj
= getProperty(exec
, thisObj
, k
);
240 thisObj
->put(exec
, k
, obj2
);
242 thisObj
->deleteProperty(exec
, k
);
245 thisObj
->put(exec
, lk1
, obj
);
247 thisObj
->deleteProperty(exec
, lk1
);
254 thisObj
->put(exec
, exec
->propertyNames().length
, jsNumber(length
), DontEnum
| DontDelete
);
255 result
= jsUndefined();
257 result
= thisObj
->get(exec
, 0);
258 for(unsigned int k
= 1; k
< length
; k
++) {
259 if (JSValue
*obj
= getProperty(exec
, thisObj
, k
))
260 thisObj
->put(exec
, k
-1, obj
);
262 thisObj
->deleteProperty(exec
, k
-1);
264 thisObj
->deleteProperty(exec
, length
- 1);
265 thisObj
->put(exec
, exec
->propertyNames().length
, jsNumber(length
- 1), DontEnum
| DontDelete
);
270 // http://developer.netscape.com/docs/manuals/js/client/jsref/array.htm#1193713 or 15.4.4.10
272 // We return a new array
273 JSObject
*resObj
= static_cast<JSObject
*>(exec
->lexicalInterpreter()->builtinArray()->construct(exec
,List::empty()));
276 if (!args
[0]->isUndefined()) {
277 begin
= args
[0]->toInteger(exec
);
278 if (begin
>= 0) { // false for NaN
283 if (!(begin
>= 0)) // true for NaN
288 if (!args
[1]->isUndefined()) {
289 end
= args
[1]->toInteger(exec
);
290 if (end
< 0) { // false for NaN
295 if (!(end
<= length
)) // true for NaN
300 //printf( "Slicing from %d to %d \n", begin, end );
302 int b
= static_cast<int>(begin
);
303 int e
= static_cast<int>(end
);
304 for(int k
= b
; k
< e
; k
++, n
++) {
305 if (JSValue
*v
= getProperty(exec
, thisObj
, k
))
306 resObj
->put(exec
, n
, v
);
308 resObj
->put(exec
, exec
->propertyNames().length
, jsNumber(n
), DontEnum
| DontDelete
);
313 printf("KJS Array::Sort length=%d\n", length
);
314 for ( unsigned int i
= 0 ; i
<length
; ++i
)
315 printf("KJS Array::Sort: %d: %s\n", i
, thisObj
->get(exec
, i
)->toString(exec
).ascii() );
317 JSObject
*sortFunction
= NULL
;
318 if (!args
[0]->isUndefined())
320 sortFunction
= args
[0]->toObject(exec
);
321 if (!sortFunction
->implementsCall())
325 if (thisObj
->classInfo() == &ArrayInstance::info
) {
327 ((ArrayInstance
*)thisObj
)->sort(exec
, sortFunction
);
329 ((ArrayInstance
*)thisObj
)->sort(exec
);
335 thisObj
->put(exec
, exec
->propertyNames().length
, jsNumber(0), DontEnum
| DontDelete
);
340 // "Min" sort. Not the fastest, but definitely less code than heapsort
341 // or quicksort, and much less swapping than bubblesort/insertionsort.
342 for ( unsigned int i
= 0 ; i
<length
-1 ; ++i
)
344 JSValue
*iObj
= thisObj
->get(exec
,i
);
345 unsigned int themin
= i
;
346 JSValue
*minObj
= iObj
;
347 for ( unsigned int j
= i
+1 ; j
<length
; ++j
)
349 JSValue
*jObj
= thisObj
->get(exec
,j
);
351 if (jObj
->isUndefined()) {
352 cmp
= 1; // don't check minObj because there's no need to differentiate == (0) from > (1)
353 } else if (minObj
->isUndefined()) {
355 } else if (sortFunction
) {
359 cmp
= sortFunction
->call(exec
, exec
->dynamicInterpreter()->globalObject(), l
)->toNumber(exec
);
361 cmp
= (jObj
->toString(exec
) < minObj
->toString(exec
)) ? -1 : 1;
372 //printf("KJS Array::Sort: swapping %d and %d\n", i, themin );
373 thisObj
->put( exec
, i
, minObj
);
374 thisObj
->put( exec
, themin
, iObj
);
378 printf("KJS Array::Sort -- Resulting array:\n");
379 for ( unsigned int i
= 0 ; i
<length
; ++i
)
380 printf("KJS Array::Sort: %d: %s\n", i
, thisObj
->get(exec
, i
)->toString(exec
).ascii() );
386 // 15.4.4.12 - oh boy this is huge
387 JSObject
*resObj
= static_cast<JSObject
*>(exec
->lexicalInterpreter()->builtinArray()->construct(exec
,List::empty()));
389 int begin
= args
[0]->toUInt32(exec
);
391 begin
= maxInt( begin
+ length
, 0 );
393 begin
= minInt( begin
, length
);
394 unsigned int deleteCount
= minInt( maxInt( args
[1]->toUInt32(exec
), 0 ), length
- begin
);
396 //printf( "Splicing from %d, deleteCount=%d \n", begin, deleteCount );
397 for(unsigned int k
= 0; k
< deleteCount
; k
++) {
398 if (JSValue
*v
= getProperty(exec
, thisObj
, k
+begin
))
399 resObj
->put(exec
, k
, v
);
401 resObj
->put(exec
, exec
->propertyNames().length
, jsNumber(deleteCount
), DontEnum
| DontDelete
);
403 unsigned int additionalArgs
= maxInt( args
.size() - 2, 0 );
404 if ( additionalArgs
!= deleteCount
)
406 if ( additionalArgs
< deleteCount
)
408 for ( unsigned int k
= begin
; k
< length
- deleteCount
; ++k
)
410 if (JSValue
*v
= getProperty(exec
, thisObj
, k
+deleteCount
))
411 thisObj
->put(exec
, k
+additionalArgs
, v
);
413 thisObj
->deleteProperty(exec
, k
+additionalArgs
);
415 for ( unsigned int k
= length
; k
> length
- deleteCount
+ additionalArgs
; --k
)
416 thisObj
->deleteProperty(exec
, k
-1);
420 for ( unsigned int k
= length
- deleteCount
; (int)k
> begin
; --k
)
422 if (JSValue
*obj
= getProperty(exec
, thisObj
, k
+ deleteCount
- 1))
423 thisObj
->put(exec
, k
+ additionalArgs
- 1, obj
);
425 thisObj
->deleteProperty(exec
, k
+additionalArgs
-1);
429 for ( unsigned int k
= 0; k
< additionalArgs
; ++k
)
431 thisObj
->put(exec
, k
+begin
, args
[k
+2]);
433 thisObj
->put(exec
, exec
->propertyNames().length
, jsNumber(length
- deleteCount
+ additionalArgs
), DontEnum
| DontDelete
);
436 case UnShift
: { // 15.4.4.13
437 unsigned int nrArgs
= args
.size();
438 for ( unsigned int k
= length
; k
> 0; --k
)
440 if (JSValue
*v
= getProperty(exec
, thisObj
, k
- 1))
441 thisObj
->put(exec
, k
+nrArgs
-1, v
);
443 thisObj
->deleteProperty(exec
, k
+nrArgs
-1);
445 for ( unsigned int k
= 0; k
< nrArgs
; ++k
)
446 thisObj
->put(exec
, k
, args
[k
]);
447 result
= jsNumber(length
+ nrArgs
);
448 thisObj
->put(exec
, exec
->propertyNames().length
, result
, DontEnum
| DontDelete
);
453 JSObject
*eachFunction
= args
[0]->toObject(exec
);
455 if (!eachFunction
->implementsCall())
456 return throwError(exec
, TypeError
);
458 JSObject
*applyThis
= args
[1]->isUndefinedOrNull() ? exec
->dynamicInterpreter()->globalObject() : args
[1]->toObject(exec
);
459 JSObject
*resultArray
;
462 resultArray
= static_cast<JSObject
*>(exec
->lexicalInterpreter()->builtinArray()->construct(exec
, List::empty()));
465 args
.append(jsNumber(length
));
466 resultArray
= static_cast<JSObject
*>(exec
->lexicalInterpreter()->builtinArray()->construct(exec
, args
));
469 unsigned filterIndex
= 0;
470 for (unsigned k
= 0; k
< length
&& !exec
->hadException(); ++k
) {
473 if (!thisObj
->getPropertySlot(exec
, k
, slot
))
476 JSValue
*v
= slot
.getValue(exec
, thisObj
, k
);
480 eachArguments
.append(v
);
481 eachArguments
.append(jsNumber(k
));
482 eachArguments
.append(thisObj
);
484 JSValue
*result
= eachFunction
->call(exec
, applyThis
, eachArguments
);
487 resultArray
->put(exec
, k
, result
);
488 else if (result
->toBoolean(exec
))
489 resultArray
->put(exec
, filterIndex
++, v
);
497 //Documentation for these three is available at:
498 //http://developer-test.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:every
499 //http://developer-test.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:forEach
500 //http://developer-test.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:some
502 JSObject
*eachFunction
= args
[0]->toObject(exec
);
504 if (!eachFunction
->implementsCall())
505 return throwError(exec
, TypeError
);
507 JSObject
*applyThis
= args
[1]->isUndefinedOrNull() ? exec
->dynamicInterpreter()->globalObject() : args
[1]->toObject(exec
);
509 if (id
== Some
|| id
== Every
)
510 result
= jsBoolean(id
== Every
);
512 result
= jsUndefined();
514 for (unsigned k
= 0; k
< length
&& !exec
->hadException(); ++k
) {
517 if (!thisObj
->getPropertySlot(exec
, k
, slot
))
522 eachArguments
.append(slot
.getValue(exec
, thisObj
, k
));
523 eachArguments
.append(jsNumber(k
));
524 eachArguments
.append(thisObj
);
526 bool predicateResult
= eachFunction
->call(exec
, applyThis
, eachArguments
)->toBoolean(exec
);
528 if (id
== Every
&& !predicateResult
) {
529 result
= jsBoolean(false);
532 if (id
== Some
&& predicateResult
) {
533 result
= jsBoolean(true);
541 // JavaScript 1.5 Extension by Mozilla
542 // Documentation: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:indexOf
545 double d
= args
[1]->toInteger(exec
);
552 index
= static_cast<unsigned>(d
);
555 JSValue
* searchElement
= args
[0];
556 for (; index
< length
; ++index
) {
557 JSValue
* e
= getProperty(exec
, thisObj
, index
);
560 if (strictEqual(exec
, searchElement
, e
))
561 return jsNumber(index
);
567 // JavaScript 1.6 Extension by Mozilla
568 // Documentation: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:lastIndexOf
570 int index
= length
- 1;
571 double d
= args
[1]->toIntegerPreserveNaN(exec
);
579 index
= static_cast<int>(d
);
581 JSValue
* searchElement
= args
[0];
582 for (; index
>= 0; --index
) {
583 JSValue
* e
= getProperty(exec
, thisObj
, index
);
586 if (strictEqual(exec
, searchElement
, e
))
587 return jsNumber(index
);
600 // ------------------------------ ArrayObjectImp -------------------------------
602 ArrayObjectImp::ArrayObjectImp(ExecState
*exec
,
603 FunctionPrototype
*funcProto
,
604 ArrayPrototype
*arrayProto
)
605 : InternalFunctionImp(funcProto
)
607 // ECMA 15.4.3.1 Array.prototype
608 put(exec
, exec
->propertyNames().prototype
, arrayProto
, DontEnum
|DontDelete
|ReadOnly
);
610 // no. of arguments for constructor
611 put(exec
, exec
->propertyNames().length
, jsNumber(1), ReadOnly
|DontDelete
|DontEnum
);
614 bool ArrayObjectImp::implementsConstruct() const
620 JSObject
*ArrayObjectImp::construct(ExecState
*exec
, const List
&args
)
622 // a single numeric argument denotes the array size (!)
623 if (args
.size() == 1 && args
[0]->isNumber()) {
624 uint32_t n
= args
[0]->toUInt32(exec
);
625 if (n
!= args
[0]->toNumber(exec
))
626 return throwError(exec
, RangeError
, "Array size is not a small enough positive integer.");
627 return new ArrayInstance(exec
->lexicalInterpreter()->builtinArrayPrototype(), n
);
630 // otherwise the array is constructed with the arguments in it
631 return new ArrayInstance(exec
->lexicalInterpreter()->builtinArrayPrototype(), args
);
635 JSValue
*ArrayObjectImp::callAsFunction(ExecState
*exec
, JSObject
* /*thisObj*/, const List
&args
)
637 // equivalent to 'new Array(....)'
638 return construct(exec
,args
);
644 // kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on; hl c++;