Compare Revisions
Last modification
- Rev 499 2013-09-22 01:36:46
- Author: PointedEars
- Log message:
* array.js
- jsx.array.BigArray:
+ Added range checks
+ Made MAX_LENGTH public (formerly: _MAX_BIGARRAY_LENGTH)
+ True array-like object
- jsx.array.pop(): Fixed potential call bomb
- Optimized imports
/trunk/test/array.js |
File deleted |
\ No newline at end of file
|
Property changes: |
Deleted: svn:mime-type |
## -1 +0,0 ## |
-text/plain |
\ No newline at end of property |
Index: trunk/test/array-test.js |
=================================================================== |
--- trunk/test/array-test.js (nonexistent) |
+++ trunk/test/array-test.js (revision 499) |
@@ -0,0 +1,309 @@ |
+function runTests() |
+{ |
+ var _test = jsx.test; |
+ var assert = _test.assert; |
+ var assertArrayEquals = _test.assertArrayEquals; |
+ var _createComparator = jsx.array.createComparator; |
+ var BigArray = jsx.array.BigArray; |
+ |
+ _test.runner.run({ |
+ file: "array.js", |
+ tests: [ |
+ { |
+ feature: 'jsx.array.BigArray()', |
+ description: 'Return new instance (factory)', |
+ code: function () { |
+ assert(BigArray() instanceof BigArray); |
+ } |
+ }, |
+ { |
+ feature: 'new jsx.array.BigArray()', |
+ description: 'Return new empty instance (constructor)', |
+ code: function () { |
+ assert(new BigArray() instanceof BigArray); |
+ } |
+ }, |
+ { |
+ feature: 'new BigArray(BigArray)', |
+ description: 'Clone instance', |
+ code: function () { |
+ var s = (new BigArray(new BigArray("x"))).toString(); |
+ assert(s === "x"); |
+ } |
+ }, |
+ { |
+ feature: 'new BigArray("")', |
+ description: 'Return one-element array', |
+ code: function () { |
+ assertArrayEquals([""], new BigArray("").toArray()); |
+ } |
+ }, |
+ { |
+ feature: 'new BigArray("x")', |
+ description: 'Convert to <code>BigArray("x")</code>', |
+ code: function () { |
+ assertArrayEquals(["x"], new BigArray("x").toArray()); |
+ } |
+ }, |
+ { |
+ feature: 'new BigArray(int)', |
+ description: 'Create <code>BigArray</code> of specified length', |
+ code: function () { |
+ var a = []; |
+ a.length = 42; |
+ assertArrayEquals(a, new BigArray(42).toArray()); |
+ } |
+ }, |
+ |
+ { |
+ feature: 'BigArray.prototype.toArray()', |
+ description: 'Return the correct value', |
+ code: function () { |
+ assertArrayEquals([], new BigArray().toArray()); |
+ assertArrayEquals([""], new BigArray("").toArray()); |
+ assertArrayEquals(["x"], new BigArray("x").toArray()); |
+ assertArrayEquals([23, 42], new BigArray(23, 42).toArray()); |
+ } |
+ }, |
+ { |
+ feature: 'BigArray.length', |
+ description: 'Getter works', |
+ code: function () { |
+ var i = Math.pow(2, 53); |
+ assert(new BigArray(i).length == i); |
+ } |
+ }, |
+ |
+ { |
+ feature: 'BigArray.getLength()', |
+ description: 'Return the correct value', |
+ code: function () { |
+ assert(new BigArray().getLength() === 0); |
+ assert(new BigArray(42).getLength() === 42); |
+ assert(new BigArray("").getLength() === 1); |
+ assert(new BigArray("x").getLength() === 1); |
+ assert(new BigArray("x", "y").getLength() === 2); |
+ } |
+ }, |
+ |
+ { |
+ feature: 'BigArray.prototype.get()', |
+ description: 'Throw <code>jsx.InvalidArgumentError</code>', |
+ code: function () { |
+ var failure = jsx.tryThis( |
+ function () { |
+ new BigArray().get(); |
+ return false; |
+ }, |
+ function (e) { |
+ return (e instanceof jsx.InvalidArgumentError); |
+ }); |
+ assert(failure); |
+ } |
+ }, |
+ { |
+ feature: 'BigArray.prototype.get(…)', |
+ description: 'Return the correct value', |
+ code: function () { |
+ assert(new BigArray("x").get(0) === "x"); |
+ assert(typeof new BigArray("x").get(1) == "undefined"); |
+ |
+ assert(new BigArray("x", "y").get(-2) === "x"); |
+ assert(new BigArray("x", "y").get(-1) === "y"); |
+ assert(typeof new BigArray("x", "y").get(-4) == "undefined"); |
+ } |
+ }, |
+ |
+ /* TODO: Override and test Array methods that are not MAX_LENGTH-safe */ |
+ |
+ { |
+ feature: 'BigArray.prototype.toString(…)', |
+ description: 'Return the correct value', |
+ code: function () { |
+ assert(new BigArray().toString() === ""); |
+ assert(new BigArray(0).toString() === ""); |
+ assert(new BigArray(1).toString() === ""); |
+ assert(new BigArray(2).toString() === ","); |
+ } |
+ }, |
+ |
+ { |
+ feature: 'BigArray.prototype.valueOf(…)', |
+ description: 'Return the correct value', |
+ code: function () { |
+ var a = new BigArray("x\uD834\uDD1Ey"); |
+ var v = a.valueOf(); |
+ assert(v === a); |
+ } |
+ }, |
+ |
+ { |
+ feature: 'jsx.array.createComparator(aKeys)', |
+ description: 'Sort <code>Array</code> as specified', |
+ code: function () { |
+ var o1 = {x: 2}; |
+ var o2 = {x: 1}; |
+ var a = [o1, o2]; |
+ a.sort(_createComparator(["x"])); |
+ assertArrayEquals([o2, o1], a); |
+ } |
+ }, |
+ { |
+ feature: 'jsx.array.createComparator(aKeys, oOptions)', |
+ description: 'Sort <code>Array</code> as specified', |
+ code: function () { |
+ var o1 = {x: 1}; |
+ var o2 = {x: 2}; |
+ var a = [o1, o2]; |
+ a.sort(_createComparator(["x"], {descending: true})); |
+ assertArrayEquals([o2, o1], a); |
+ |
+ o1 = {x: "1 "}; |
+ o2 = {x: 1}; |
+ a = [o1, o2]; |
+ a.sort(_createComparator(["x"], {strict: true})); |
+ assertArrayEquals([o2, o1], a); |
+ |
+ o1 = {x: 1}; |
+ o2 = {x: "1 "}; |
+ a = [o1, o2]; |
+ a.sort(_createComparator(["x"], {descending: true, strict: true})); |
+ assertArrayEquals([o2, o1], a); |
+ |
+ o1 = {x: "20"}; |
+ o2 = {x: "1"}; |
+ a = [o1, o2]; |
+ a.sort(_createComparator(["x"], {numeric: true})); |
+ assertArrayEquals([o2, o1], a); |
+ |
+ o1 = {x: "1"}; |
+ o2 = {x: "1"}; |
+ a = [o1, o2]; |
+ a.sort(_createComparator(["x"], {numeric: true, strict: true})); |
+ assertArrayEquals([o1, o2], a); |
+ |
+ o1 = {x: 1}; |
+ o2 = {x: "1"}; |
+ a = [o1, o2]; |
+ a.sort(_createComparator(["x"], {numeric: true})); |
+ assertArrayEquals([o1, o2], a); |
+ |
+ o1 = {x: "1 "}; |
+ o2 = {x: 1}; |
+ a = [o1, o2]; |
+ a.sort(_createComparator(["x"], {numeric: true, strict: true})); |
+ assertArrayEquals([o1, o2], a); |
+ |
+ o1 = {x: 2}; |
+ o2 = {x: "12"}; |
+ a = [o1, o2]; |
+ a.sort(_createComparator(["x"], { |
+ descending: true, |
+ numeric: true, |
+ strict: true |
+ })); |
+ assertArrayEquals([o2, o1], a); |
+ } |
+ }, |
+ { |
+ feature: 'jsx.array.createComparator([{key: …, callback: …}])', |
+ description: 'Sort <code>Array</code> as specified', |
+ code: function () { |
+ var o1 = { |
+ x: "10" |
+ }; |
+ var o2 = { |
+ x: "2" |
+ }; |
+ var a = [o1, o2]; |
+ a.sort(_createComparator([ |
+ { |
+ key: "x", |
+ callback: Number |
+ } |
+ ])); |
+ assertArrayEquals([o2, o1], a); |
+ } |
+ }, |
+ { |
+ feature: 'jsx.array.createComparator([{key: …, constructor: …}])', |
+ description: 'Sort <code>Array</code> as specified', |
+ code: function () { |
+ var o1 = { |
+ x: "10" |
+ }; |
+ var o2 = { |
+ x: "2" |
+ }; |
+ var a = [o1, o2]; |
+ a.sort(_createComparator([ |
+ { |
+ key: "x", |
+ constructor: Number |
+ } |
+ ])); |
+ assertArrayEquals([o2, o1], a); |
+ } |
+ }, |
+ { |
+ feature: 'jsx.array.createComparator([{key: …, comparator: …}])', |
+ description: 'Sort <code>Array</code> as specified', |
+ code: function () { |
+ var o1 = { |
+ engine: { |
+ liters: 4.0, |
+ cylinders: 4 |
+ }, |
+ }; |
+ var o2 = { |
+ engine: { |
+ liters: 3.0, |
+ cylinders: 4 |
+ }, |
+ }; |
+ var a = [o1, o2]; |
+ a.sort(_createComparator([ |
+ { |
+ key: "engine", |
+ comparator: _createComparator(["cylinders", "liters"]) |
+ } |
+ ])); |
+ assertArrayEquals([o2, o1], a); |
+ } |
+ }, |
+ |
+ { |
+ feature: 'jsx.array.createComparator("…", {callback: …, constructor: …, comparator: …})', |
+ description: 'Sort <code>Array</code> as specified', |
+ code: function () { |
+ function Bar (baz) |
+ { |
+ this.baz = String(baz); |
+ } |
+ |
+ Bar.prototype.toString = function () { |
+ return this.baz; |
+ }; |
+ |
+ function Baz (bla) |
+ { |
+ this.length = bla.length; |
+ } |
+ |
+ var o1 = {"bar": new Bar("burpbarbazbla")}; |
+ var o2 = {"bar": new Bar("foo")}; |
+ |
+ var foo = [o1, o2]; |
+ |
+ foo.sort(_createComparator(["bar"], { |
+ callback: String, |
+ constructor: Baz, |
+ comparator: _createComparator(["length"], {numeric: true}) |
+ })); |
+ |
+ assertArrayEquals([o2, o1], foo); |
+ } |
+ } |
+ ] |
+ }); |
+} |
\ No newline at end of file |
/trunk/test/array-test.js |
Property changes: |
Added: svn:mime-type |
## -0,0 +1 ## |
+text/plain |
\ No newline at end of property |
Index: trunk/array.js |
=================================================================== |
--- trunk/array.js (revision 498) |
+++ trunk/array.js (revision 499) |
@@ -40,10 +40,20 @@ |
*/ |
jsx.array = (/** @constructor */ function () { |
/* Imports */ |
- var _jsx_object = jsx.object; |
+ var _jsx = jsx; |
+ var _tryThis = _jsx.tryThis; |
+ var _jsx_object = _jsx.object; |
+ var _defineProperty = _jsx_object.defineProperty; |
+ var _getClass = _jsx_object.getClass; |
var _getKeys = _jsx_object.getKeys; |
var _hasOwnProperty = _jsx_object._hasOwnProperty; |
+ var _getKeys = _jsx_object.getKeys; |
+ var _hasOwnProperty = _jsx_object._hasOwnProperty; |
var _isArray = _jsx_object.isArray; |
+ var _isNativeObject = _jsx_object.isNativeObject; |
+ |
+ /* Constant-like variables */ |
+ var _MAX_ARRAY_LENGTH = Math.pow(2, 32) - 1; |
var _isNativeMethod = _jsx_object.isNativeMethod; |
/** |
@@ -123,7 +133,327 @@ |
return []; |
} |
|
+ /** |
+ * Array index out of range |
+ * |
+ * @type jsx.array.RangeError |
+ * @extends RangeError|jsx.InvalidArgumentError |
+ * @constructor |
+ */ |
+ var _RangeError = ( |
+ function (sReason, sGot, sExpected) { |
+ _jsx.InvalidArgumentError.apply(this, arguments); |
+ } |
+ ).extend( |
+ typeof RangeError == "function" |
+ ? RangeError |
+ : _jsx.InvalidArgumentError, |
+ { |
+ name: "jsx.array.RangeError" |
+ }); |
+ |
+ /** |
+ * Array-like object which can hold up to 2<sup>53</sup> elements |
+ * |
+ * @function |
+ * @type jsx.array.BigArray |
+ * @extends Array |
+ * @constructor |
+ */ |
+ var _BigArray = jsx.object.extend( |
+ /** |
+ * @type __jsx.array.BigArray |
+ * @param {Array|Object} src (optional) |
+ * Array-like object to be converted |
+ * @return {jsx.arrayBigArray} |
+ */ |
+ function jsx_array_BigArray (src) { |
+ if (!(this instanceof jsx_array_BigArray)) |
+ { |
+ return jsx_array_BigArray.construct(arguments); |
+ } |
+ |
+ /** |
+ * @memberOf __jsx.array.BigArray |
+ * @private |
+ */ |
+ var _length = 0; |
+ |
+ /** |
+ * Removes all elements from the array |
+ * |
+ * @memberOf jsx.array.BigArray |
+ */ |
+ this.clear = function () { |
+ for (var i in this) |
+ { |
+ if (_BigArray.isIndex(i)) |
+ { |
+ delete this[i]; |
+ } |
+ } |
+ |
+ this.setLength(0); |
+ }; |
+ |
+ /** |
+ * Sets the real length of this array |
+ * |
+ * @param {int} value |
+ * @throws jsx.array.RangeError |
+ * if the value is less than <code>0</code> or |
+ * greater than {@link BigArray.MAX_LENGTH} |
+ */ |
+ this.setLength = function BigArray_setLength (value) { |
+ if (value < 0 || value > _BigArray.MAX_LENGTH) |
+ { |
+ return _jsx.throwThis(_RangeError, |
+ ["Invalid length", value, |
+ "0.." + _BigArray.MAX_LENGTH |
+ + " (2^" + Math.floor(Math.log(_BigArray.MAX_LENGTH) / Math.log(2)) + ")"], |
+ BigArray_setLength); |
+ } |
+ |
+ _length = Math.floor(value); |
+ |
+ return this; |
+ }; |
+ |
+ /** |
+ * Returns the real length of this array |
+ * |
+ * @return {int} |
+ */ |
+ this.getLength = function () { |
+ return _length; |
+ }; |
+ |
+ _defineProperty(this, "length", { |
+ /** |
+ * @memberOf jsx.array.BigArray |
+ */ |
+ "get": this.getLength, |
+ "set": this.setLength |
+ }, "jsx.array.BigArray"); |
+ |
+ /** |
+ * Sets the element at <var>index</var> to <var>value</var> |
+ * |
+ * @param {int} index |
+ * @param {any} value |
+ */ |
+ this.set = function (index, value) { |
+ if (index < 0) |
+ { |
+ index = this.getLength() + Math.ceil(index); |
+ } |
+ else |
+ { |
+ index = Math.floor(index); |
+ } |
+ |
+ if (this.getLength() < index + 1) |
+ { |
+ this.setLength(index + 1); |
+ } |
+ |
+ this[index] = value; |
+ |
+ return this; |
+ }; |
+ |
+ /** |
+ * @param {int} index |
+ * @return {any} the element of this array at <var>index</var> |
+ */ |
+ this.get = function BigArray_get (index) { |
+ if (arguments.length < 1) |
+ { |
+ return jsx.throwThis(jsx.InvalidArgumentError, |
+ ["Not enough arguments", "", "(int)"], |
+ BigArray_get); |
+ } |
+ |
+ if (index < 0) |
+ { |
+ index = this.getLength() + Math.ceil(index); |
+ } |
+ else |
+ { |
+ index = Math.floor(index); |
+ } |
+ |
+ return this[index]; |
+ }; |
+ |
+ this.clear(); |
+ |
+ var arglen = arguments.length; |
+ if (arglen > 1) |
+ { |
+ for (var i = arglen; i--;) |
+ { |
+ this.set(i, arguments[i]); |
+ } |
+ } |
+ else if (arglen == 1) |
+ { |
+ var t = typeof src; |
+ if (t == "number") |
+ { |
+ this.setLength(src); |
+ } |
+ else |
+ { |
+ if (!_isArray(src) && _isNativeObject(src) |
+ && _getClass(src) != "Object") |
+ { |
+ this.set(0, src); |
+ } |
+ else |
+ { |
+ this.setAll(src); |
+ } |
+ } |
+ } |
+ }, |
+ { |
+ /** |
+ * Maximum number of elements that can be stored in |
+ * a <code>BigArray</code>. Successor of the greatest |
+ * integer value that can be used as index. |
+ * |
+ * @memberOf __jsx.array.BigArray |
+ */ |
+ MAX_LENGTH: Math.pow(2, 53), |
+ |
+ /** |
+ * Determines if a value can be used as <code>BigArray</code> |
+ * index. |
+ * |
+ * @param {any} i |
+ */ |
+ isIndex: function (i) { |
+ return parseInt(i, 10).toString() == i |
+ && i > -1 && i < _BigArray.MAX_LENGTH; |
+ }, |
+ |
+ /** |
+ * Determines if an object is a <code>BigArray</code>. |
+ * |
+ * @param {Object} o |
+ * @return {boolean} |
+ */ |
+ isInstance: function (o) { |
+ return o.constructor == _BigArray; |
+ } |
+ } |
+ ).extend(Array, { |
+ /** |
+ * Returns the string representations of this array's |
+ * elements, separated by another string (representation). |
+ * |
+ * @memberOf jsx.array.BigArray.prototype |
+ * @param {String} glue |
+ * The separator between the string representations |
+ * @return {string} |
+ */ |
+ join: function (glue) { |
+ var len = this.getLength(); |
+ |
+ if (len <= _MAX_ARRAY_LENGTH) |
+ { |
+ return [].join.apply(this, arguments); |
+ } |
+ |
+ if (arguments.length < 1) |
+ { |
+ glue = ","; |
+ } |
+ |
+ var s = ""; |
+ |
+ for (var i = 0; i < len; ++i) |
+ { |
+ s += this.get(i); |
+ |
+ if (glue !== "" && i < len - 1) |
+ { |
+ s += glue; |
+ } |
+ } |
+ |
+ return s; |
+ }, |
+ |
+ /** |
+ * Sets all elements based on another object. |
+ * |
+ * @param {Object} src |
+ * If an <code>Array</code> or </code>BigArray</code>, |
+ * copies all array elements (enumerable properties whose |
+ * name is an array index) to this array. |
+ * Otherwise, copies all properties with numeric name, from |
+ * 0 to the name specified by the value of the object's |
+ * <code>length</code> property, to this array. |
+ */ |
+ setAll: function (src) { |
+ if (_isArray(src) || src instanceof _BigArray) |
+ { |
+ for (var i in src) |
+ { |
+ if (_BigArray.isIndex(i)) |
+ { |
+ this.set(i, src[i]); |
+ } |
+ } |
+ } |
+ else |
+ { |
+ for (i = src.length; i--;) |
+ { |
+ if (_BigArray.isIndex(i)) |
+ { |
+ this.set(i, src[i]); |
+ } |
+ } |
+ } |
+ }, |
+ |
+ /** |
+ * Returns this <code>BigArray</code> as an {@link Array} |
+ * with extra indexes. |
+ * |
+ * @return {Array} |
+ */ |
+ toArray: function () { |
+ var a = []; |
+ a.length = this.length; |
+ |
+ for (var i in this) |
+ { |
+ if (_BigArray.isIndex(i) && i < _MAX_ARRAY_LENGTH) |
+ { |
+ a[i] = this.get(i); |
+ } |
+ } |
+ |
+ return a; |
+ }, |
+ |
+ /** |
+ * Returns the string representation of this array |
+ * as a comma-separated list. |
+ */ |
+ toString: function () { |
+ return this.join(","); |
+ } |
+ }); |
+ |
return { |
+ /** |
+ * @memberOf jsx.array |
+ */ |
version: "0.1.$Rev$", |
copyright: "Copyright \xA9 2004-2013", |
author: "Thomas Lahn", |
@@ -131,7 +461,6 @@ |
path: "http://pointedears.de/scripts/", |
|
/** |
- * @memberOf jsx.array |
* @param {string} sMsg (optional) |
* @return {boolean} false |
*/ |
@@ -150,6 +479,9 @@ |
return false; |
}, |
|
+ BigArray: _BigArray, |
+ RangeError: _RangeError, |
+ |
/** |
* Splits the array <code>a</code> into several arrays with |
* <code>iSize</code> values in them. |
@@ -291,7 +623,7 @@ |
} |
else |
{ |
- jsx.throwThis('TypeError'); |
+ _jsx.throwThis('TypeError'); |
} |
} |
else |
@@ -305,7 +637,7 @@ |
|
if (typeof fCallback != "function") |
{ |
- jsx.throwThis('TypeError'); |
+ _jsx.throwThis('TypeError'); |
} |
|
var res = []; |
@@ -388,11 +720,6 @@ |
a = this; |
} |
|
- if (_isNativeMethod(a, "pop")) |
- { |
- return a.pop(); |
- } |
- |
var result = null; |
|
if (a.length > 0) |
@@ -728,9 +1055,9 @@ |
* @function |
*/ |
splice: (function() { |
- if (typeof jsx.global.array_splice != "undefined") |
+ if (typeof _jsx.global.array_splice != "undefined") |
{ |
- return jsx.global.array_splice; |
+ return _jsx.global.array_splice; |
} |
else if (typeof Array != "undefined" |
&& _jsx_object.isNativeMethod(Array, "prototype", "splice")) |