OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2007 Apple Inc. All rights reserved. | 2 * Copyright (C) 2007 Apple Inc. All rights reserved. |
3 * Copyright (C) 2012 Google Inc. All rights reserved. | 3 * Copyright (C) 2012 Google Inc. All rights reserved. |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions | 6 * modification, are permitted provided that the following conditions |
7 * are met: | 7 * are met: |
8 * | 8 * |
9 * 1. Redistributions of source code must retain the above copyright | 9 * 1. Redistributions of source code must retain the above copyright |
10 * notice, this list of conditions and the following disclaimer. | 10 * notice, this list of conditions and the following disclaimer. |
11 * 2. Redistributions in binary form must reproduce the above copyright | 11 * 2. Redistributions in binary form must reproduce the above copyright |
12 * notice, this list of conditions and the following disclaimer in the | 12 * notice, this list of conditions and the following disclaimer in the |
13 * documentation and/or other materials provided with the distribution. | 13 * documentation and/or other materials provided with the distribution. |
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of | 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
15 * its contributors may be used to endorse or promote products derived | 15 * its contributors may be used to endorse or promote products derived |
16 * from this software without specific prior written permission. | 16 * from this software without specific prior written permission. |
17 * | 17 * |
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 */ | 28 */ |
29 | |
30 // FIXME: This performance optimization should be moved to blink so that all dev
elopers could enjoy it. | 29 // FIXME: This performance optimization should be moved to blink so that all dev
elopers could enjoy it. |
31 // console is retrieved with V8Window.getAttribute method which is slow. Here we
copy it to a js variable for faster access. | 30 // console is retrieved with V8Window.getAttribute method which is slow. Here we
copy it to a js variable for faster access. |
32 console = console; | 31 console = console; |
33 console.__originalAssert = console.assert; | 32 console.__originalAssert = console.assert; |
34 console.assert = function(value, message) | 33 console.assert = function(value, message) { |
35 { | 34 if (value) |
36 if (value) | 35 return; |
37 return; | 36 console.__originalAssert(value, message); |
38 console.__originalAssert(value, message); | |
39 }; | 37 }; |
40 | 38 |
41 /** @typedef {Array|NodeList|Arguments|{length: number}} */ | 39 /** @typedef {Array|NodeList|Arguments|{length: number}} */ |
42 var ArrayLike; | 40 var ArrayLike; |
43 | 41 |
44 /** | 42 /** |
45 * @param {number} m | 43 * @param {number} m |
46 * @param {number} n | 44 * @param {number} n |
47 * @return {number} | 45 * @return {number} |
48 */ | 46 */ |
49 function mod(m, n) | 47 function mod(m, n) { |
50 { | 48 return ((m % n) + n) % n; |
51 return ((m % n) + n) % n; | |
52 } | 49 } |
53 | 50 |
54 /** | 51 /** |
55 * @param {string} string | 52 * @param {string} string |
56 * @return {!Array.<number>} | 53 * @return {!Array.<number>} |
57 */ | 54 */ |
58 String.prototype.findAll = function(string) | 55 String.prototype.findAll = function(string) { |
59 { | 56 var matches = []; |
60 var matches = []; | 57 var i = this.indexOf(string); |
61 var i = this.indexOf(string); | 58 while (i !== -1) { |
62 while (i !== -1) { | 59 matches.push(i); |
63 matches.push(i); | 60 i = this.indexOf(string, i + string.length); |
64 i = this.indexOf(string, i + string.length); | 61 } |
| 62 return matches; |
| 63 }; |
| 64 |
| 65 /** |
| 66 * @return {string} |
| 67 */ |
| 68 String.prototype.reverse = function() { |
| 69 return this.split('').reverse().join(''); |
| 70 }; |
| 71 |
| 72 /** |
| 73 * @return {string} |
| 74 */ |
| 75 String.prototype.replaceControlCharacters = function() { |
| 76 // Replace C0 and C1 control character sets with printable character. |
| 77 // Do not replace '\t', \n' and '\r'. |
| 78 return this.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u0080-\u009f]/g,
'�'); |
| 79 }; |
| 80 |
| 81 /** |
| 82 * @return {boolean} |
| 83 */ |
| 84 String.prototype.isWhitespace = function() { |
| 85 return /^\s*$/.test(this); |
| 86 }; |
| 87 |
| 88 /** |
| 89 * @return {!Array.<number>} |
| 90 */ |
| 91 String.prototype.computeLineEndings = function() { |
| 92 var endings = this.findAll('\n'); |
| 93 endings.push(this.length); |
| 94 return endings; |
| 95 }; |
| 96 |
| 97 /** |
| 98 * @param {string} chars |
| 99 * @return {string} |
| 100 */ |
| 101 String.prototype.escapeCharacters = function(chars) { |
| 102 var foundChar = false; |
| 103 for (var i = 0; i < chars.length; ++i) { |
| 104 if (this.indexOf(chars.charAt(i)) !== -1) { |
| 105 foundChar = true; |
| 106 break; |
65 } | 107 } |
66 return matches; | 108 } |
67 }; | 109 |
68 | 110 if (!foundChar) |
69 /** | 111 return String(this); |
70 * @return {string} | 112 |
71 */ | 113 var result = ''; |
72 String.prototype.reverse = function() | 114 for (var i = 0; i < this.length; ++i) { |
73 { | 115 if (chars.indexOf(this.charAt(i)) !== -1) |
74 return this.split("").reverse().join(""); | 116 result += '\\'; |
75 }; | 117 result += this.charAt(i); |
76 | 118 } |
77 /** | 119 |
78 * @return {string} | 120 return result; |
79 */ | 121 }; |
80 String.prototype.replaceControlCharacters = function() | 122 |
81 { | 123 /** |
82 // Replace C0 and C1 control character sets with printable character. | 124 * @return {string} |
83 // Do not replace '\t', \n' and '\r'. | 125 */ |
84 return this.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u0080-\u009f]/g
, "�"); | 126 String.regexSpecialCharacters = function() { |
85 }; | 127 return '^[]{}()\\.^$*+?|-,'; |
86 | 128 }; |
87 /** | 129 |
88 * @return {boolean} | 130 /** |
89 */ | 131 * @return {string} |
90 String.prototype.isWhitespace = function() | 132 */ |
91 { | 133 String.prototype.escapeForRegExp = function() { |
92 return /^\s*$/.test(this); | 134 return this.escapeCharacters(String.regexSpecialCharacters()); |
93 }; | 135 }; |
94 | 136 |
95 /** | 137 /** |
96 * @return {!Array.<number>} | 138 * @return {string} |
97 */ | 139 */ |
98 String.prototype.computeLineEndings = function() | 140 String.prototype.escapeHTML = function() { |
99 { | 141 return this.replace(/&/g, '&') |
100 var endings = this.findAll("\n"); | 142 .replace(/</g, '<') |
101 endings.push(this.length); | 143 .replace(/>/g, '>') |
102 return endings; | 144 .replace(/"/g, '"'); // " doublequotes just for editor |
103 }; | 145 }; |
104 | 146 |
105 /** | 147 /** |
106 * @param {string} chars | 148 * @return {string} |
107 * @return {string} | 149 */ |
108 */ | 150 String.prototype.unescapeHTML = function() { |
109 String.prototype.escapeCharacters = function(chars) | 151 return this.replace(/</g, '<') |
110 { | 152 .replace(/>/g, '>') |
111 var foundChar = false; | 153 .replace(/:/g, ':') |
112 for (var i = 0; i < chars.length; ++i) { | 154 .replace(/"/g, '"') |
113 if (this.indexOf(chars.charAt(i)) !== -1) { | 155 .replace(/</g, '<') |
114 foundChar = true; | 156 .replace(/>/g, '>') |
115 break; | 157 .replace(/&/g, '&'); |
116 } | 158 }; |
117 } | 159 |
118 | 160 /** |
119 if (!foundChar) | 161 * @return {string} |
120 return String(this); | 162 */ |
121 | 163 String.prototype.collapseWhitespace = function() { |
122 var result = ""; | 164 return this.replace(/[\s\xA0]+/g, ' '); |
123 for (var i = 0; i < this.length; ++i) { | |
124 if (chars.indexOf(this.charAt(i)) !== -1) | |
125 result += "\\"; | |
126 result += this.charAt(i); | |
127 } | |
128 | |
129 return result; | |
130 }; | |
131 | |
132 /** | |
133 * @return {string} | |
134 */ | |
135 String.regexSpecialCharacters = function() | |
136 { | |
137 return "^[]{}()\\.^$*+?|-,"; | |
138 }; | |
139 | |
140 /** | |
141 * @return {string} | |
142 */ | |
143 String.prototype.escapeForRegExp = function() | |
144 { | |
145 return this.escapeCharacters(String.regexSpecialCharacters()); | |
146 }; | |
147 | |
148 /** | |
149 * @return {string} | |
150 */ | |
151 String.prototype.escapeHTML = function() | |
152 { | |
153 return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">
").replace(/"/g, """); // " doublequotes just for editor | |
154 }; | |
155 | |
156 /** | |
157 * @return {string} | |
158 */ | |
159 String.prototype.unescapeHTML = function() | |
160 { | |
161 return this.replace(/</g, "<") | |
162 .replace(/>/g, ">") | |
163 .replace(/:/g, ":") | |
164 .replace(/"/g, "\"") | |
165 .replace(/</g, "<") | |
166 .replace(/>/g, ">") | |
167 .replace(/&/g, "&"); | |
168 }; | |
169 | |
170 /** | |
171 * @return {string} | |
172 */ | |
173 String.prototype.collapseWhitespace = function() | |
174 { | |
175 return this.replace(/[\s\xA0]+/g, " "); | |
176 }; | 165 }; |
177 | 166 |
178 /** | 167 /** |
179 * @param {number} maxLength | 168 * @param {number} maxLength |
180 * @return {string} | 169 * @return {string} |
181 */ | 170 */ |
182 String.prototype.trimMiddle = function(maxLength) | 171 String.prototype.trimMiddle = function(maxLength) { |
183 { | 172 if (this.length <= maxLength) |
184 if (this.length <= maxLength) | 173 return String(this); |
185 return String(this); | 174 var leftHalf = maxLength >> 1; |
186 var leftHalf = maxLength >> 1; | 175 var rightHalf = maxLength - leftHalf - 1; |
187 var rightHalf = maxLength - leftHalf - 1; | 176 return this.substr(0, leftHalf) + '\u2026' + this.substr(this.length - rightHa
lf, rightHalf); |
188 return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - right
Half, rightHalf); | |
189 }; | 177 }; |
190 | 178 |
191 /** | 179 /** |
192 * @param {number} maxLength | 180 * @param {number} maxLength |
193 * @return {string} | 181 * @return {string} |
194 */ | 182 */ |
195 String.prototype.trimEnd = function(maxLength) | 183 String.prototype.trimEnd = function(maxLength) { |
196 { | 184 if (this.length <= maxLength) |
197 if (this.length <= maxLength) | 185 return String(this); |
198 return String(this); | 186 return this.substr(0, maxLength - 1) + '\u2026'; |
199 return this.substr(0, maxLength - 1) + "\u2026"; | |
200 }; | 187 }; |
201 | 188 |
202 /** | 189 /** |
203 * @param {?string=} baseURLDomain | 190 * @param {?string=} baseURLDomain |
204 * @return {string} | 191 * @return {string} |
205 */ | 192 */ |
206 String.prototype.trimURL = function(baseURLDomain) | 193 String.prototype.trimURL = function(baseURLDomain) { |
207 { | 194 var result = this.replace(/^(https|http|file):\/\//i, ''); |
208 var result = this.replace(/^(https|http|file):\/\//i, ""); | 195 if (baseURLDomain) { |
209 if (baseURLDomain) { | 196 if (result.toLowerCase().startsWith(baseURLDomain.toLowerCase())) |
210 if (result.toLowerCase().startsWith(baseURLDomain.toLowerCase())) | 197 result = result.substr(baseURLDomain.length); |
211 result = result.substr(baseURLDomain.length); | 198 } |
212 } | 199 return result; |
213 return result; | 200 }; |
214 }; | 201 |
215 | 202 /** |
216 /** | 203 * @return {string} |
217 * @return {string} | 204 */ |
218 */ | 205 String.prototype.toTitleCase = function() { |
219 String.prototype.toTitleCase = function() | 206 return this.substring(0, 1).toUpperCase() + this.substring(1); |
220 { | |
221 return this.substring(0, 1).toUpperCase() + this.substring(1); | |
222 }; | 207 }; |
223 | 208 |
224 /** | 209 /** |
225 * @param {string} other | 210 * @param {string} other |
226 * @return {number} | 211 * @return {number} |
227 */ | 212 */ |
228 String.prototype.compareTo = function(other) | 213 String.prototype.compareTo = function(other) { |
229 { | 214 if (this > other) |
230 if (this > other) | 215 return 1; |
231 return 1; | 216 if (this < other) |
232 if (this < other) | 217 return -1; |
233 return -1; | 218 return 0; |
234 return 0; | 219 }; |
235 }; | 220 |
236 | 221 /** |
237 /** | 222 * @return {string} |
238 * @return {string} | 223 */ |
239 */ | 224 String.prototype.removeURLFragment = function() { |
240 String.prototype.removeURLFragment = function() | 225 var fragmentIndex = this.indexOf('#'); |
241 { | 226 if (fragmentIndex === -1) |
242 var fragmentIndex = this.indexOf("#"); | 227 fragmentIndex = this.length; |
243 if (fragmentIndex === -1) | 228 return this.substring(0, fragmentIndex); |
244 fragmentIndex = this.length; | |
245 return this.substring(0, fragmentIndex); | |
246 }; | 229 }; |
247 | 230 |
248 /** | 231 /** |
249 * @param {string|undefined} string | 232 * @param {string|undefined} string |
250 * @return {number} | 233 * @return {number} |
251 */ | 234 */ |
252 String.hashCode = function(string) | 235 String.hashCode = function(string) { |
253 { | 236 if (!string) |
254 if (!string) | 237 return 0; |
255 return 0; | 238 // Hash algorithm for substrings is described in "Über die Komplexität der Mul
tiplikation in |
256 // Hash algorithm for substrings is described in "Über die Komplexität der M
ultiplikation in | 239 // eingeschränkten Branchingprogrammmodellen" by Woelfe. |
257 // eingeschränkten Branchingprogrammmodellen" by Woelfe. | 240 // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SE
CTION00832000000000000000 |
258 // http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#
SECTION00832000000000000000 | 241 var p = ((1 << 30) * 4 - 5); // prime: 2^32 - 5 |
259 var p = ((1 << 30) * 4 - 5); // prime: 2^32 - 5 | 242 var z = 0x5033d967; // 32 bits from random.org |
260 var z = 0x5033d967; // 32 bits from random.org | 243 var z2 = 0x59d2f15d; // random odd 32 bit number |
261 var z2 = 0x59d2f15d; // random odd 32 bit number | 244 var s = 0; |
262 var s = 0; | 245 var zi = 1; |
263 var zi = 1; | 246 for (var i = 0; i < string.length; i++) { |
264 for (var i = 0; i < string.length; i++) { | 247 var xi = string.charCodeAt(i) * z2; |
265 var xi = string.charCodeAt(i) * z2; | 248 s = (s + zi * xi) % p; |
266 s = (s + zi * xi) % p; | 249 zi = (zi * z) % p; |
267 zi = (zi * z) % p; | 250 } |
268 } | 251 s = (s + zi * (p - 1)) % p; |
269 s = (s + zi * (p - 1)) % p; | 252 return Math.abs(s | 0); |
270 return Math.abs(s | 0); | 253 }; |
271 }; | 254 |
272 | 255 /** |
273 /** | |
274 * @param {string} string | 256 * @param {string} string |
275 * @param {number} index | 257 * @param {number} index |
276 * @return {boolean} | 258 * @return {boolean} |
277 */ | 259 */ |
278 String.isDigitAt = function(string, index) | 260 String.isDigitAt = function(string, index) { |
279 { | 261 var c = string.charCodeAt(index); |
280 var c = string.charCodeAt(index); | 262 return (48 <= c && c <= 57); |
281 return (48 <= c && c <= 57); | |
282 }; | 263 }; |
283 | 264 |
284 /** | 265 /** |
285 * @return {string} | 266 * @return {string} |
286 */ | 267 */ |
287 String.prototype.toBase64 = function() | 268 String.prototype.toBase64 = function() { |
288 { | 269 /** |
289 /** | 270 * @param {number} b |
290 * @param {number} b | 271 * @return {number} |
291 * @return {number} | 272 */ |
292 */ | 273 function encodeBits(b) { |
293 function encodeBits(b) | 274 return b < 26 ? b + 65 : b < 52 ? b + 71 : b < 62 ? b - 4 : b === 62 ? 43 :
b === 63 ? 47 : 65; |
294 { | 275 } |
295 return b < 26 ? b + 65 : b < 52 ? b + 71 : b < 62 ? b - 4 : b === 62 ? 4
3 : b === 63 ? 47 : 65; | 276 var encoder = new TextEncoder(); |
| 277 var data = encoder.encode(this.toString()); |
| 278 var n = data.length; |
| 279 var encoded = ''; |
| 280 if (n === 0) |
| 281 return encoded; |
| 282 var shift; |
| 283 var v = 0; |
| 284 for (var i = 0; i < n; i++) { |
| 285 shift = i % 3; |
| 286 v |= data[i] << (16 >>> shift & 24); |
| 287 if (shift === 2) { |
| 288 encoded += String.fromCharCode( |
| 289 encodeBits(v >>> 18 & 63), encodeBits(v >>> 12 & 63), encodeBits(v >>>
6 & 63), encodeBits(v & 63)); |
| 290 v = 0; |
296 } | 291 } |
297 var encoder = new TextEncoder(); | 292 } |
298 var data = encoder.encode(this.toString()); | 293 if (shift === 0) |
299 var n = data.length; | 294 encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 1
2 & 63), 61, 61); |
300 var encoded = ""; | 295 else if (shift === 1) |
301 if (n === 0) | 296 encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >>> 1
2 & 63), encodeBits(v >>> 6 & 63), 61); |
302 return encoded; | 297 return encoded; |
303 var shift; | |
304 var v = 0; | |
305 for (var i = 0; i < n; i++) { | |
306 shift = i % 3; | |
307 v |= data[i] << (16 >>> shift & 24); | |
308 if (shift === 2) { | |
309 encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits
(v >>> 12 & 63), encodeBits(v >>> 6 & 63), encodeBits(v & 63)); | |
310 v = 0; | |
311 } | |
312 } | |
313 if (shift === 0) | |
314 encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >
>> 12 & 63), 61, 61); | |
315 else if (shift === 1) | |
316 encoded += String.fromCharCode(encodeBits(v >>> 18 & 63), encodeBits(v >
>> 12 & 63), encodeBits(v >>> 6 & 63), 61); | |
317 return encoded; | |
318 }; | 298 }; |
319 | 299 |
320 /** | 300 /** |
321 * @param {string} a | 301 * @param {string} a |
322 * @param {string} b | 302 * @param {string} b |
323 * @return {number} | 303 * @return {number} |
324 */ | 304 */ |
325 String.naturalOrderComparator = function(a, b) | 305 String.naturalOrderComparator = function(a, b) { |
326 { | 306 var chunk = /^\d+|^\D+/; |
327 var chunk = /^\d+|^\D+/; | 307 var chunka, chunkb, anum, bnum; |
328 var chunka, chunkb, anum, bnum; | 308 while (1) { |
329 while (1) { | 309 if (a) { |
330 if (a) { | 310 if (!b) |
331 if (!b) | 311 return 1; |
332 return 1; | 312 } else { |
333 } else { | 313 if (b) |
334 if (b) | 314 return -1; |
335 return -1; | 315 else |
336 else | 316 return 0; |
337 return 0; | |
338 } | |
339 chunka = a.match(chunk)[0]; | |
340 chunkb = b.match(chunk)[0]; | |
341 anum = !isNaN(chunka); | |
342 bnum = !isNaN(chunkb); | |
343 if (anum && !bnum) | |
344 return -1; | |
345 if (bnum && !anum) | |
346 return 1; | |
347 if (anum && bnum) { | |
348 var diff = chunka - chunkb; | |
349 if (diff) | |
350 return diff; | |
351 if (chunka.length !== chunkb.length) { | |
352 if (!+chunka && !+chunkb) // chunks are strings of all 0s (speci
al case) | |
353 return chunka.length - chunkb.length; | |
354 else | |
355 return chunkb.length - chunka.length; | |
356 } | |
357 } else if (chunka !== chunkb) | |
358 return (chunka < chunkb) ? -1 : 1; | |
359 a = a.substring(chunka.length); | |
360 b = b.substring(chunkb.length); | |
361 } | 317 } |
| 318 chunka = a.match(chunk)[0]; |
| 319 chunkb = b.match(chunk)[0]; |
| 320 anum = !isNaN(chunka); |
| 321 bnum = !isNaN(chunkb); |
| 322 if (anum && !bnum) |
| 323 return -1; |
| 324 if (bnum && !anum) |
| 325 return 1; |
| 326 if (anum && bnum) { |
| 327 var diff = chunka - chunkb; |
| 328 if (diff) |
| 329 return diff; |
| 330 if (chunka.length !== chunkb.length) { |
| 331 if (! + chunka && ! + chunkb) // chunks are strings of all 0s (special
case) |
| 332 return chunka.length - chunkb.length; |
| 333 else |
| 334 return chunkb.length - chunka.length; |
| 335 } |
| 336 } else if (chunka !== chunkb) |
| 337 return (chunka < chunkb) ? -1 : 1; |
| 338 a = a.substring(chunka.length); |
| 339 b = b.substring(chunkb.length); |
| 340 } |
362 }; | 341 }; |
363 | 342 |
364 /** | 343 /** |
365 * @param {string} a | 344 * @param {string} a |
366 * @param {string} b | 345 * @param {string} b |
367 * @return {number} | 346 * @return {number} |
368 */ | 347 */ |
369 String.caseInsensetiveComparator = function(a, b) | 348 String.caseInsensetiveComparator = function(a, b) { |
370 { | 349 a = a.toUpperCase(); |
371 a = a.toUpperCase(); | 350 b = b.toUpperCase(); |
372 b = b.toUpperCase(); | 351 if (a === b) |
373 if (a === b) | 352 return 0; |
374 return 0; | 353 return a > b ? 1 : -1; |
375 return a > b ? 1 : -1; | |
376 }; | 354 }; |
377 | 355 |
378 /** | 356 /** |
379 * @param {number} num | 357 * @param {number} num |
380 * @param {number} min | 358 * @param {number} min |
381 * @param {number} max | 359 * @param {number} max |
382 * @return {number} | 360 * @return {number} |
383 */ | 361 */ |
384 Number.constrain = function(num, min, max) | 362 Number.constrain = function(num, min, max) { |
385 { | 363 if (num < min) |
386 if (num < min) | 364 num = min; |
387 num = min; | 365 else if (num > max) |
388 else if (num > max) | 366 num = max; |
389 num = max; | 367 return num; |
390 return num; | |
391 }; | 368 }; |
392 | 369 |
393 /** | 370 /** |
394 * @param {number} a | 371 * @param {number} a |
395 * @param {number} b | 372 * @param {number} b |
396 * @return {number} | 373 * @return {number} |
397 */ | 374 */ |
398 Number.gcd = function(a, b) | 375 Number.gcd = function(a, b) { |
399 { | 376 if (b === 0) |
400 if (b === 0) | 377 return a; |
401 return a; | 378 else |
402 else | 379 return Number.gcd(b, a % b); |
403 return Number.gcd(b, a % b); | |
404 }; | 380 }; |
405 | 381 |
406 /** | 382 /** |
407 * @param {string} value | 383 * @param {string} value |
408 * @return {string} | 384 * @return {string} |
409 */ | 385 */ |
410 Number.toFixedIfFloating = function(value) | 386 Number.toFixedIfFloating = function(value) { |
411 { | 387 if (!value || isNaN(value)) |
412 if (!value || isNaN(value)) | 388 return value; |
413 return value; | 389 var number = Number(value); |
414 var number = Number(value); | 390 return number % 1 ? number.toFixed(3) : String(number); |
415 return number % 1 ? number.toFixed(3) : String(number); | |
416 }; | 391 }; |
417 | 392 |
418 /** | 393 /** |
419 * @return {boolean} | 394 * @return {boolean} |
420 */ | 395 */ |
421 Date.prototype.isValid = function() | 396 Date.prototype.isValid = function() { |
422 { | 397 return !isNaN(this.getTime()); |
423 return !isNaN(this.getTime()); | |
424 }; | 398 }; |
425 | 399 |
426 /** | 400 /** |
427 * @return {string} | 401 * @return {string} |
428 */ | 402 */ |
429 Date.prototype.toISO8601Compact = function() | 403 Date.prototype.toISO8601Compact = function() { |
430 { | 404 /** |
431 /** | 405 * @param {number} x |
432 * @param {number} x | 406 * @return {string} |
433 * @return {string} | 407 */ |
434 */ | 408 function leadZero(x) { |
435 function leadZero(x) | 409 return (x > 9 ? '' : '0') + x; |
436 { | 410 } |
437 return (x > 9 ? "" : "0") + x; | 411 return this.getFullYear() + leadZero(this.getMonth() + 1) + leadZero(this.getD
ate()) + 'T' + |
438 } | 412 leadZero(this.getHours()) + leadZero(this.getMinutes()) + leadZero(this.ge
tSeconds()); |
439 return this.getFullYear() + | |
440 leadZero(this.getMonth() + 1) + | |
441 leadZero(this.getDate()) + "T" + | |
442 leadZero(this.getHours()) + | |
443 leadZero(this.getMinutes()) + | |
444 leadZero(this.getSeconds()); | |
445 }; | 413 }; |
446 | 414 |
447 /** | 415 /** |
448 * @return {string} | 416 * @return {string} |
449 */ | 417 */ |
450 Date.prototype.toConsoleTime = function() | 418 Date.prototype.toConsoleTime = function() { |
451 { | 419 /** |
452 /** | 420 * @param {number} x |
453 * @param {number} x | 421 * @return {string} |
454 * @return {string} | 422 */ |
455 */ | 423 function leadZero2(x) { |
456 function leadZero2(x) | 424 return (x > 9 ? '' : '0') + x; |
457 { | 425 } |
458 return (x > 9 ? "" : "0") + x; | 426 |
459 } | 427 /** |
460 | 428 * @param {number} x |
461 /** | 429 * @return {string} |
462 * @param {number} x | 430 */ |
463 * @return {string} | 431 function leadZero3(x) { |
464 */ | 432 return '0'.repeat(3 - x.toString().length) + x; |
465 function leadZero3(x) | 433 } |
466 { | 434 |
467 return "0".repeat(3 - x.toString().length) + x; | 435 return this.getFullYear() + '-' + leadZero2(this.getMonth() + 1) + '-' + leadZ
ero2(this.getDate()) + ' ' + |
468 } | 436 leadZero2(this.getHours()) + ':' + leadZero2(this.getMinutes()) + ':' + le
adZero2(this.getSeconds()) + '.' + |
469 | 437 leadZero3(this.getMilliseconds()); |
470 return this.getFullYear() + "-" + | |
471 leadZero2(this.getMonth() + 1) + "-" + | |
472 leadZero2(this.getDate()) + " " + | |
473 leadZero2(this.getHours()) + ":" + | |
474 leadZero2(this.getMinutes()) + ":" + | |
475 leadZero2(this.getSeconds()) + "." + | |
476 leadZero3(this.getMilliseconds()); | |
477 }; | 438 }; |
478 | 439 |
479 Object.defineProperty(Array.prototype, "remove", { | 440 Object.defineProperty(Array.prototype, 'remove', { |
480 /** | 441 /** |
481 * @param {!T} value | 442 * @param {!T} value |
482 * @param {boolean=} firstOnly | 443 * @param {boolean=} firstOnly |
483 * @return {boolean} | 444 * @return {boolean} |
484 * @this {Array.<!T>} | 445 * @this {Array.<!T>} |
| 446 * @template T |
| 447 */ |
| 448 value: function(value, firstOnly) { |
| 449 var index = this.indexOf(value); |
| 450 if (index === -1) |
| 451 return false; |
| 452 if (firstOnly) { |
| 453 this.splice(index, 1); |
| 454 return true; |
| 455 } |
| 456 for (var i = index + 1, n = this.length; i < n; ++i) { |
| 457 if (this[i] !== value) |
| 458 this[index++] = this[i]; |
| 459 } |
| 460 this.length = index; |
| 461 return true; |
| 462 } |
| 463 }); |
| 464 |
| 465 Object.defineProperty(Array.prototype, 'pushAll', { |
| 466 /** |
| 467 * @param {!Array<!T>} array |
| 468 * @this {Array<!T>} |
| 469 * @template T |
| 470 */ |
| 471 value: function(array) { |
| 472 for (var i = 0; i < array.length; ++i) |
| 473 this.push(array[i]); |
| 474 } |
| 475 }); |
| 476 |
| 477 Object.defineProperty(Array.prototype, 'rotate', { |
| 478 /** |
| 479 * @param {number} index |
| 480 * @return {!Array.<!T>} |
| 481 * @this {Array.<!T>} |
| 482 * @template T |
| 483 */ |
| 484 value: function(index) { |
| 485 var result = []; |
| 486 for (var i = index; i < index + this.length; ++i) |
| 487 result.push(this[i % this.length]); |
| 488 return result; |
| 489 } |
| 490 }); |
| 491 |
| 492 Object.defineProperty(Array.prototype, 'sortNumbers', { |
| 493 /** |
| 494 * @this {Array.<number>} |
| 495 */ |
| 496 value: function() { |
| 497 /** |
| 498 * @param {number} a |
| 499 * @param {number} b |
| 500 * @return {number} |
| 501 */ |
| 502 function numericComparator(a, b) { |
| 503 return a - b; |
| 504 } |
| 505 |
| 506 this.sort(numericComparator); |
| 507 } |
| 508 }); |
| 509 |
| 510 Object.defineProperty(Uint32Array.prototype, 'sort', {value: Array.prototype.sor
t}); |
| 511 |
| 512 (function() { |
| 513 var partition = { |
| 514 /** |
| 515 * @this {Array.<number>} |
| 516 * @param {function(number, number): number} comparator |
| 517 * @param {number} left |
| 518 * @param {number} right |
| 519 * @param {number} pivotIndex |
| 520 */ |
| 521 value: function(comparator, left, right, pivotIndex) { |
| 522 function swap(array, i1, i2) { |
| 523 var temp = array[i1]; |
| 524 array[i1] = array[i2]; |
| 525 array[i2] = temp; |
| 526 } |
| 527 |
| 528 var pivotValue = this[pivotIndex]; |
| 529 swap(this, right, pivotIndex); |
| 530 var storeIndex = left; |
| 531 for (var i = left; i < right; ++i) { |
| 532 if (comparator(this[i], pivotValue) < 0) { |
| 533 swap(this, storeIndex, i); |
| 534 ++storeIndex; |
| 535 } |
| 536 } |
| 537 swap(this, right, storeIndex); |
| 538 return storeIndex; |
| 539 } |
| 540 }; |
| 541 Object.defineProperty(Array.prototype, 'partition', partition); |
| 542 Object.defineProperty(Uint32Array.prototype, 'partition', partition); |
| 543 |
| 544 var sortRange = { |
| 545 /** |
| 546 * @param {function(number, number): number} comparator |
| 547 * @param {number} leftBound |
| 548 * @param {number} rightBound |
| 549 * @param {number} sortWindowLeft |
| 550 * @param {number} sortWindowRight |
| 551 * @return {!Array.<number>} |
| 552 * @this {Array.<number>} |
| 553 */ |
| 554 value: function(comparator, leftBound, rightBound, sortWindowLeft, sortWindo
wRight) { |
| 555 function quickSortRange(array, comparator, left, right, sortWindowLeft, so
rtWindowRight) { |
| 556 if (right <= left) |
| 557 return; |
| 558 var pivotIndex = Math.floor(Math.random() * (right - left)) + left; |
| 559 var pivotNewIndex = array.partition(comparator, left, right, pivotIndex)
; |
| 560 if (sortWindowLeft < pivotNewIndex) |
| 561 quickSortRange(array, comparator, left, pivotNewIndex - 1, sortWindowL
eft, sortWindowRight); |
| 562 if (pivotNewIndex < sortWindowRight) |
| 563 quickSortRange(array, comparator, pivotNewIndex + 1, right, sortWindow
Left, sortWindowRight); |
| 564 } |
| 565 if (leftBound === 0 && rightBound === (this.length - 1) && sortWindowLeft
=== 0 && sortWindowRight >= rightBound) |
| 566 this.sort(comparator); |
| 567 else |
| 568 quickSortRange(this, comparator, leftBound, rightBound, sortWindowLeft,
sortWindowRight); |
| 569 return this; |
| 570 } |
| 571 }; |
| 572 Object.defineProperty(Array.prototype, 'sortRange', sortRange); |
| 573 Object.defineProperty(Uint32Array.prototype, 'sortRange', sortRange); |
| 574 })(); |
| 575 |
| 576 Object.defineProperty(Array.prototype, 'stableSort', { |
| 577 /** |
| 578 * @param {function(?T, ?T): number=} comparator |
| 579 * @return {!Array.<?T>} |
| 580 * @this {Array.<?T>} |
| 581 * @template T |
| 582 */ |
| 583 value: function(comparator) { |
| 584 function defaultComparator(a, b) { |
| 585 return a < b ? -1 : (a > b ? 1 : 0); |
| 586 } |
| 587 comparator = comparator || defaultComparator; |
| 588 |
| 589 var indices = new Array(this.length); |
| 590 for (var i = 0; i < this.length; ++i) |
| 591 indices[i] = i; |
| 592 var self = this; |
| 593 /** |
| 594 * @param {number} a |
| 595 * @param {number} b |
| 596 * @return {number} |
| 597 */ |
| 598 function indexComparator(a, b) { |
| 599 var result = comparator(self[a], self[b]); |
| 600 return result ? result : a - b; |
| 601 } |
| 602 indices.sort(indexComparator); |
| 603 |
| 604 for (var i = 0; i < this.length; ++i) { |
| 605 if (indices[i] < 0 || i === indices[i]) |
| 606 continue; |
| 607 var cyclical = i; |
| 608 var saved = this[i]; |
| 609 while (true) { |
| 610 var next = indices[cyclical]; |
| 611 indices[cyclical] = -1; |
| 612 if (next === i) { |
| 613 this[cyclical] = saved; |
| 614 break; |
| 615 } else { |
| 616 this[cyclical] = this[next]; |
| 617 cyclical = next; |
| 618 } |
| 619 } |
| 620 } |
| 621 return this; |
| 622 } |
| 623 }); |
| 624 |
| 625 Object.defineProperty(Array.prototype, 'qselect', { |
| 626 /** |
| 627 * @param {number} k |
| 628 * @param {function(number, number): number=} comparator |
| 629 * @return {number|undefined} |
| 630 * @this {Array.<number>} |
| 631 */ |
| 632 value: function(k, comparator) { |
| 633 if (k < 0 || k >= this.length) |
| 634 return; |
| 635 if (!comparator) |
| 636 comparator = function(a, b) { |
| 637 return a - b; |
| 638 }; |
| 639 |
| 640 var low = 0; |
| 641 var high = this.length - 1; |
| 642 for (;;) { |
| 643 var pivotPosition = this.partition(comparator, low, high, Math.floor((high
+ low) / 2)); |
| 644 if (pivotPosition === k) |
| 645 return this[k]; |
| 646 else if (pivotPosition > k) |
| 647 high = pivotPosition - 1; |
| 648 else |
| 649 low = pivotPosition + 1; |
| 650 } |
| 651 } |
| 652 }); |
| 653 |
| 654 Object.defineProperty(Array.prototype, 'lowerBound', { |
| 655 /** |
| 656 * Return index of the leftmost element that is equal or greater |
| 657 * than the specimen object. If there's no such element (i.e. all |
| 658 * elements are smaller than the specimen) returns right bound. |
| 659 * The function works for sorted array. |
| 660 * When specified, |left| (inclusive) and |right| (exclusive) indices |
| 661 * define the search window. |
| 662 * |
| 663 * @param {!T} object |
| 664 * @param {function(!T,!S):number=} comparator |
| 665 * @param {number=} left |
| 666 * @param {number=} right |
| 667 * @return {number} |
| 668 * @this {Array.<!S>} |
| 669 * @template T,S |
| 670 */ |
| 671 value: function(object, comparator, left, right) { |
| 672 function defaultComparator(a, b) { |
| 673 return a < b ? -1 : (a > b ? 1 : 0); |
| 674 } |
| 675 comparator = comparator || defaultComparator; |
| 676 var l = left || 0; |
| 677 var r = right !== undefined ? right : this.length; |
| 678 while (l < r) { |
| 679 var m = (l + r) >> 1; |
| 680 if (comparator(object, this[m]) > 0) |
| 681 l = m + 1; |
| 682 else |
| 683 r = m; |
| 684 } |
| 685 return r; |
| 686 } |
| 687 }); |
| 688 |
| 689 Object.defineProperty(Array.prototype, 'upperBound', { |
| 690 /** |
| 691 * Return index of the leftmost element that is greater |
| 692 * than the specimen object. If there's no such element (i.e. all |
| 693 * elements are smaller or equal to the specimen) returns right bound. |
| 694 * The function works for sorted array. |
| 695 * When specified, |left| (inclusive) and |right| (exclusive) indices |
| 696 * define the search window. |
| 697 * |
| 698 * @param {!T} object |
| 699 * @param {function(!T,!S):number=} comparator |
| 700 * @param {number=} left |
| 701 * @param {number=} right |
| 702 * @return {number} |
| 703 * @this {Array.<!S>} |
| 704 * @template T,S |
| 705 */ |
| 706 value: function(object, comparator, left, right) { |
| 707 function defaultComparator(a, b) { |
| 708 return a < b ? -1 : (a > b ? 1 : 0); |
| 709 } |
| 710 comparator = comparator || defaultComparator; |
| 711 var l = left || 0; |
| 712 var r = right !== undefined ? right : this.length; |
| 713 while (l < r) { |
| 714 var m = (l + r) >> 1; |
| 715 if (comparator(object, this[m]) >= 0) |
| 716 l = m + 1; |
| 717 else |
| 718 r = m; |
| 719 } |
| 720 return r; |
| 721 } |
| 722 }); |
| 723 |
| 724 Object.defineProperty(Uint32Array.prototype, 'lowerBound', {value: Array.prototy
pe.lowerBound}); |
| 725 |
| 726 Object.defineProperty(Uint32Array.prototype, 'upperBound', {value: Array.prototy
pe.upperBound}); |
| 727 |
| 728 Object.defineProperty(Float64Array.prototype, 'lowerBound', {value: Array.protot
ype.lowerBound}); |
| 729 |
| 730 Object.defineProperty(Array.prototype, 'binaryIndexOf', { |
| 731 /** |
| 732 * @param {!T} value |
| 733 * @param {function(!T,!S):number} comparator |
| 734 * @return {number} |
| 735 * @this {Array.<!S>} |
| 736 * @template T,S |
| 737 */ |
| 738 value: function(value, comparator) { |
| 739 var index = this.lowerBound(value, comparator); |
| 740 return index < this.length && comparator(value, this[index]) === 0 ? index :
-1; |
| 741 } |
| 742 }); |
| 743 |
| 744 Object.defineProperty(Array.prototype, 'select', { |
| 745 /** |
| 746 * @param {string} field |
| 747 * @return {!Array.<!T>} |
| 748 * @this {Array.<!Object.<string,!T>>} |
| 749 * @template T |
| 750 */ |
| 751 value: function(field) { |
| 752 var result = new Array(this.length); |
| 753 for (var i = 0; i < this.length; ++i) |
| 754 result[i] = this[i][field]; |
| 755 return result; |
| 756 } |
| 757 }); |
| 758 |
| 759 Object.defineProperty(Array.prototype, 'peekLast', { |
| 760 /** |
| 761 * @return {!T|undefined} |
| 762 * @this {Array.<!T>} |
| 763 * @template T |
| 764 */ |
| 765 value: function() { |
| 766 return this[this.length - 1]; |
| 767 } |
| 768 }); |
| 769 |
| 770 (function() { |
| 771 /** |
| 772 * @param {!Array.<T>} array1 |
| 773 * @param {!Array.<T>} array2 |
| 774 * @param {function(T,T):number} comparator |
| 775 * @param {boolean} mergeNotIntersect |
| 776 * @return {!Array.<T>} |
| 777 * @template T |
| 778 */ |
| 779 function mergeOrIntersect(array1, array2, comparator, mergeNotIntersect) { |
| 780 var result = []; |
| 781 var i = 0; |
| 782 var j = 0; |
| 783 while (i < array1.length && j < array2.length) { |
| 784 var compareValue = comparator(array1[i], array2[j]); |
| 785 if (mergeNotIntersect || !compareValue) |
| 786 result.push(compareValue <= 0 ? array1[i] : array2[j]); |
| 787 if (compareValue <= 0) |
| 788 i++; |
| 789 if (compareValue >= 0) |
| 790 j++; |
| 791 } |
| 792 if (mergeNotIntersect) { |
| 793 while (i < array1.length) |
| 794 result.push(array1[i++]); |
| 795 while (j < array2.length) |
| 796 result.push(array2[j++]); |
| 797 } |
| 798 return result; |
| 799 } |
| 800 |
| 801 Object.defineProperty(Array.prototype, 'intersectOrdered', { |
| 802 /** |
| 803 * @param {!Array.<T>} array |
| 804 * @param {function(T,T):number} comparator |
| 805 * @return {!Array.<T>} |
| 806 * @this {!Array.<T>} |
485 * @template T | 807 * @template T |
486 */ | 808 */ |
487 value: function(value, firstOnly) | 809 value: function(array, comparator) { |
488 { | 810 return mergeOrIntersect(this, array, comparator, false); |
489 var index = this.indexOf(value); | 811 } |
490 if (index === -1) | 812 }); |
491 return false; | 813 |
492 if (firstOnly) { | 814 Object.defineProperty(Array.prototype, 'mergeOrdered', { |
493 this.splice(index, 1); | 815 /** |
494 return true; | 816 * @param {!Array.<T>} array |
495 } | 817 * @param {function(T,T):number} comparator |
496 for (var i = index + 1, n = this.length; i < n; ++i) { | 818 * @return {!Array.<T>} |
497 if (this[i] !== value) | 819 * @this {!Array.<T>} |
498 this[index++] = this[i]; | |
499 } | |
500 this.length = index; | |
501 return true; | |
502 } | |
503 }); | |
504 | |
505 Object.defineProperty(Array.prototype, "pushAll", { | |
506 /** | |
507 * @param {!Array<!T>} array | |
508 * @this {Array<!T>} | |
509 * @template T | 820 * @template T |
510 */ | 821 */ |
511 value: function(array) | 822 value: function(array, comparator) { |
512 { | 823 return mergeOrIntersect(this, array, comparator, true); |
513 for (var i = 0; i < array.length; ++i) | 824 } |
514 this.push(array[i]); | 825 }); |
515 } | |
516 }); | |
517 | |
518 Object.defineProperty(Array.prototype, "rotate", { | |
519 /** | |
520 * @param {number} index | |
521 * @return {!Array.<!T>} | |
522 * @this {Array.<!T>} | |
523 * @template T | |
524 */ | |
525 value: function(index) | |
526 { | |
527 var result = []; | |
528 for (var i = index; i < index + this.length; ++i) | |
529 result.push(this[i % this.length]); | |
530 return result; | |
531 } | |
532 }); | |
533 | |
534 Object.defineProperty(Array.prototype, "sortNumbers", { | |
535 /** | |
536 * @this {Array.<number>} | |
537 */ | |
538 value: function() | |
539 { | |
540 /** | |
541 * @param {number} a | |
542 * @param {number} b | |
543 * @return {number} | |
544 */ | |
545 function numericComparator(a, b) | |
546 { | |
547 return a - b; | |
548 } | |
549 | |
550 this.sort(numericComparator); | |
551 } | |
552 }); | |
553 | |
554 Object.defineProperty(Uint32Array.prototype, "sort", { | |
555 value: Array.prototype.sort | |
556 }); | |
557 | |
558 (function() { | |
559 var partition = { | |
560 /** | |
561 * @this {Array.<number>} | |
562 * @param {function(number, number): number} comparator | |
563 * @param {number} left | |
564 * @param {number} right | |
565 * @param {number} pivotIndex | |
566 */ | |
567 value: function(comparator, left, right, pivotIndex) | |
568 { | |
569 function swap(array, i1, i2) | |
570 { | |
571 var temp = array[i1]; | |
572 array[i1] = array[i2]; | |
573 array[i2] = temp; | |
574 } | |
575 | |
576 var pivotValue = this[pivotIndex]; | |
577 swap(this, right, pivotIndex); | |
578 var storeIndex = left; | |
579 for (var i = left; i < right; ++i) { | |
580 if (comparator(this[i], pivotValue) < 0) { | |
581 swap(this, storeIndex, i); | |
582 ++storeIndex; | |
583 } | |
584 } | |
585 swap(this, right, storeIndex); | |
586 return storeIndex; | |
587 } | |
588 }; | |
589 Object.defineProperty(Array.prototype, "partition", partition); | |
590 Object.defineProperty(Uint32Array.prototype, "partition", partition); | |
591 | |
592 var sortRange = { | |
593 /** | |
594 * @param {function(number, number): number} comparator | |
595 * @param {number} leftBound | |
596 * @param {number} rightBound | |
597 * @param {number} sortWindowLeft | |
598 * @param {number} sortWindowRight | |
599 * @return {!Array.<number>} | |
600 * @this {Array.<number>} | |
601 */ | |
602 value: function(comparator, leftBound, rightBound, sortWindowLeft, sortW
indowRight) | |
603 { | |
604 function quickSortRange(array, comparator, left, right, sortWindowLe
ft, sortWindowRight) | |
605 { | |
606 if (right <= left) | |
607 return; | |
608 var pivotIndex = Math.floor(Math.random() * (right - left)) + le
ft; | |
609 var pivotNewIndex = array.partition(comparator, left, right, piv
otIndex); | |
610 if (sortWindowLeft < pivotNewIndex) | |
611 quickSortRange(array, comparator, left, pivotNewIndex - 1, s
ortWindowLeft, sortWindowRight); | |
612 if (pivotNewIndex < sortWindowRight) | |
613 quickSortRange(array, comparator, pivotNewIndex + 1, right,
sortWindowLeft, sortWindowRight); | |
614 } | |
615 if (leftBound === 0 && rightBound === (this.length - 1) && sortWindo
wLeft === 0 && sortWindowRight >= rightBound) | |
616 this.sort(comparator); | |
617 else | |
618 quickSortRange(this, comparator, leftBound, rightBound, sortWind
owLeft, sortWindowRight); | |
619 return this; | |
620 } | |
621 }; | |
622 Object.defineProperty(Array.prototype, "sortRange", sortRange); | |
623 Object.defineProperty(Uint32Array.prototype, "sortRange", sortRange); | |
624 })(); | 826 })(); |
625 | 827 |
626 Object.defineProperty(Array.prototype, "stableSort", { | |
627 /** | |
628 * @param {function(?T, ?T): number=} comparator | |
629 * @return {!Array.<?T>} | |
630 * @this {Array.<?T>} | |
631 * @template T | |
632 */ | |
633 value: function(comparator) | |
634 { | |
635 function defaultComparator(a, b) | |
636 { | |
637 return a < b ? -1 : (a > b ? 1 : 0); | |
638 } | |
639 comparator = comparator || defaultComparator; | |
640 | |
641 var indices = new Array(this.length); | |
642 for (var i = 0; i < this.length; ++i) | |
643 indices[i] = i; | |
644 var self = this; | |
645 /** | |
646 * @param {number} a | |
647 * @param {number} b | |
648 * @return {number} | |
649 */ | |
650 function indexComparator(a, b) | |
651 { | |
652 var result = comparator(self[a], self[b]); | |
653 return result ? result : a - b; | |
654 } | |
655 indices.sort(indexComparator); | |
656 | |
657 for (var i = 0; i < this.length; ++i) { | |
658 if (indices[i] < 0 || i === indices[i]) | |
659 continue; | |
660 var cyclical = i; | |
661 var saved = this[i]; | |
662 while (true) { | |
663 var next = indices[cyclical]; | |
664 indices[cyclical] = -1; | |
665 if (next === i) { | |
666 this[cyclical] = saved; | |
667 break; | |
668 } else { | |
669 this[cyclical] = this[next]; | |
670 cyclical = next; | |
671 } | |
672 } | |
673 } | |
674 return this; | |
675 } | |
676 }); | |
677 | |
678 Object.defineProperty(Array.prototype, "qselect", { | |
679 /** | |
680 * @param {number} k | |
681 * @param {function(number, number): number=} comparator | |
682 * @return {number|undefined} | |
683 * @this {Array.<number>} | |
684 */ | |
685 value: function(k, comparator) | |
686 { | |
687 if (k < 0 || k >= this.length) | |
688 return; | |
689 if (!comparator) | |
690 comparator = function(a, b) { return a - b; }; | |
691 | |
692 var low = 0; | |
693 var high = this.length - 1; | |
694 for (;;) { | |
695 var pivotPosition = this.partition(comparator, low, high, Math.floor
((high + low) / 2)); | |
696 if (pivotPosition === k) | |
697 return this[k]; | |
698 else if (pivotPosition > k) | |
699 high = pivotPosition - 1; | |
700 else | |
701 low = pivotPosition + 1; | |
702 } | |
703 } | |
704 }); | |
705 | |
706 Object.defineProperty(Array.prototype, "lowerBound", { | |
707 /** | |
708 * Return index of the leftmost element that is equal or greater | |
709 * than the specimen object. If there's no such element (i.e. all | |
710 * elements are smaller than the specimen) returns right bound. | |
711 * The function works for sorted array. | |
712 * When specified, |left| (inclusive) and |right| (exclusive) indices | |
713 * define the search window. | |
714 * | |
715 * @param {!T} object | |
716 * @param {function(!T,!S):number=} comparator | |
717 * @param {number=} left | |
718 * @param {number=} right | |
719 * @return {number} | |
720 * @this {Array.<!S>} | |
721 * @template T,S | |
722 */ | |
723 value: function(object, comparator, left, right) | |
724 { | |
725 function defaultComparator(a, b) | |
726 { | |
727 return a < b ? -1 : (a > b ? 1 : 0); | |
728 } | |
729 comparator = comparator || defaultComparator; | |
730 var l = left || 0; | |
731 var r = right !== undefined ? right : this.length; | |
732 while (l < r) { | |
733 var m = (l + r) >> 1; | |
734 if (comparator(object, this[m]) > 0) | |
735 l = m + 1; | |
736 else | |
737 r = m; | |
738 } | |
739 return r; | |
740 } | |
741 }); | |
742 | |
743 Object.defineProperty(Array.prototype, "upperBound", { | |
744 /** | |
745 * Return index of the leftmost element that is greater | |
746 * than the specimen object. If there's no such element (i.e. all | |
747 * elements are smaller or equal to the specimen) returns right bound. | |
748 * The function works for sorted array. | |
749 * When specified, |left| (inclusive) and |right| (exclusive) indices | |
750 * define the search window. | |
751 * | |
752 * @param {!T} object | |
753 * @param {function(!T,!S):number=} comparator | |
754 * @param {number=} left | |
755 * @param {number=} right | |
756 * @return {number} | |
757 * @this {Array.<!S>} | |
758 * @template T,S | |
759 */ | |
760 value: function(object, comparator, left, right) | |
761 { | |
762 function defaultComparator(a, b) | |
763 { | |
764 return a < b ? -1 : (a > b ? 1 : 0); | |
765 } | |
766 comparator = comparator || defaultComparator; | |
767 var l = left || 0; | |
768 var r = right !== undefined ? right : this.length; | |
769 while (l < r) { | |
770 var m = (l + r) >> 1; | |
771 if (comparator(object, this[m]) >= 0) | |
772 l = m + 1; | |
773 else | |
774 r = m; | |
775 } | |
776 return r; | |
777 } | |
778 }); | |
779 | |
780 Object.defineProperty(Uint32Array.prototype, "lowerBound", { | |
781 value: Array.prototype.lowerBound | |
782 }); | |
783 | |
784 Object.defineProperty(Uint32Array.prototype, "upperBound", { | |
785 value: Array.prototype.upperBound | |
786 }); | |
787 | |
788 Object.defineProperty(Float64Array.prototype, "lowerBound", { | |
789 value: Array.prototype.lowerBound | |
790 }); | |
791 | |
792 Object.defineProperty(Array.prototype, "binaryIndexOf", { | |
793 /** | |
794 * @param {!T} value | |
795 * @param {function(!T,!S):number} comparator | |
796 * @return {number} | |
797 * @this {Array.<!S>} | |
798 * @template T,S | |
799 */ | |
800 value: function(value, comparator) | |
801 { | |
802 var index = this.lowerBound(value, comparator); | |
803 return index < this.length && comparator(value, this[index]) === 0 ? ind
ex : -1; | |
804 } | |
805 }); | |
806 | |
807 Object.defineProperty(Array.prototype, "select", { | |
808 /** | |
809 * @param {string} field | |
810 * @return {!Array.<!T>} | |
811 * @this {Array.<!Object.<string,!T>>} | |
812 * @template T | |
813 */ | |
814 value: function(field) | |
815 { | |
816 var result = new Array(this.length); | |
817 for (var i = 0; i < this.length; ++i) | |
818 result[i] = this[i][field]; | |
819 return result; | |
820 } | |
821 }); | |
822 | |
823 Object.defineProperty(Array.prototype, "peekLast", { | |
824 /** | |
825 * @return {!T|undefined} | |
826 * @this {Array.<!T>} | |
827 * @template T | |
828 */ | |
829 value: function() | |
830 { | |
831 return this[this.length - 1]; | |
832 } | |
833 }); | |
834 | |
835 (function(){ | |
836 /** | |
837 * @param {!Array.<T>} array1 | |
838 * @param {!Array.<T>} array2 | |
839 * @param {function(T,T):number} comparator | |
840 * @param {boolean} mergeNotIntersect | |
841 * @return {!Array.<T>} | |
842 * @template T | |
843 */ | |
844 function mergeOrIntersect(array1, array2, comparator, mergeNotIntersect) | |
845 { | |
846 var result = []; | |
847 var i = 0; | |
848 var j = 0; | |
849 while (i < array1.length && j < array2.length) { | |
850 var compareValue = comparator(array1[i], array2[j]); | |
851 if (mergeNotIntersect || !compareValue) | |
852 result.push(compareValue <= 0 ? array1[i] : array2[j]); | |
853 if (compareValue <= 0) | |
854 i++; | |
855 if (compareValue >= 0) | |
856 j++; | |
857 } | |
858 if (mergeNotIntersect) { | |
859 while (i < array1.length) | |
860 result.push(array1[i++]); | |
861 while (j < array2.length) | |
862 result.push(array2[j++]); | |
863 } | |
864 return result; | |
865 } | |
866 | |
867 Object.defineProperty(Array.prototype, "intersectOrdered", { | |
868 /** | |
869 * @param {!Array.<T>} array | |
870 * @param {function(T,T):number} comparator | |
871 * @return {!Array.<T>} | |
872 * @this {!Array.<T>} | |
873 * @template T | |
874 */ | |
875 value: function(array, comparator) | |
876 { | |
877 return mergeOrIntersect(this, array, comparator, false); | |
878 } | |
879 }); | |
880 | |
881 Object.defineProperty(Array.prototype, "mergeOrdered", { | |
882 /** | |
883 * @param {!Array.<T>} array | |
884 * @param {function(T,T):number} comparator | |
885 * @return {!Array.<T>} | |
886 * @this {!Array.<T>} | |
887 * @template T | |
888 */ | |
889 value: function(array, comparator) | |
890 { | |
891 return mergeOrIntersect(this, array, comparator, true); | |
892 } | |
893 }); | |
894 })(); | |
895 | |
896 /** | 828 /** |
897 * @param {string} format | 829 * @param {string} format |
898 * @param {...*} var_arg | 830 * @param {...*} var_arg |
899 * @return {string} | 831 * @return {string} |
900 */ | 832 */ |
901 String.sprintf = function(format, var_arg) | 833 String.sprintf = function(format, var_arg) { |
902 { | 834 return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); |
903 return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); | |
904 }; | 835 }; |
905 | 836 |
906 /** | 837 /** |
907 * @param {string} format | 838 * @param {string} format |
908 * @param {!Object.<string, function(string, ...):*>} formatters | 839 * @param {!Object.<string, function(string, ...):*>} formatters |
909 * @return {!Array.<!Object>} | 840 * @return {!Array.<!Object>} |
910 */ | 841 */ |
911 String.tokenizeFormatString = function(format, formatters) | 842 String.tokenizeFormatString = function(format, formatters) { |
912 { | 843 var tokens = []; |
913 var tokens = []; | 844 var substitutionIndex = 0; |
914 var substitutionIndex = 0; | |
915 | 845 |
916 function addStringToken(str) | 846 function addStringToken(str) { |
917 { | 847 if (tokens.length && tokens[tokens.length - 1].type === 'string') |
918 if (tokens.length && tokens[tokens.length - 1].type === "string") | 848 tokens[tokens.length - 1].value += str; |
919 tokens[tokens.length - 1].value += str; | 849 else |
920 else | 850 tokens.push({type: 'string', value: str}); |
921 tokens.push({ type: "string", value: str }); | 851 } |
| 852 |
| 853 function addSpecifierToken(specifier, precision, substitutionIndex) { |
| 854 tokens.push({type: 'specifier', specifier: specifier, precision: precision,
substitutionIndex: substitutionIndex}); |
| 855 } |
| 856 |
| 857 var index = 0; |
| 858 for (var precentIndex = format.indexOf('%', index); precentIndex !== -1; prece
ntIndex = format.indexOf('%', index)) { |
| 859 if (format.length === index) // unescaped % sign at the end of the format s
tring. |
| 860 break; |
| 861 addStringToken(format.substring(index, precentIndex)); |
| 862 index = precentIndex + 1; |
| 863 |
| 864 if (format[index] === '%') { |
| 865 // %% escape sequence. |
| 866 addStringToken('%'); |
| 867 ++index; |
| 868 continue; |
922 } | 869 } |
923 | 870 |
924 function addSpecifierToken(specifier, precision, substitutionIndex) | 871 if (String.isDigitAt(format, index)) { |
925 { | 872 // The first character is a number, it might be a substitution index. |
926 tokens.push({ type: "specifier", specifier: specifier, precision: precis
ion, substitutionIndex: substitutionIndex }); | 873 var number = parseInt(format.substring(index), 10); |
| 874 while (String.isDigitAt(format, index)) |
| 875 ++index; |
| 876 |
| 877 // If the number is greater than zero and ends with a "$", |
| 878 // then this is a substitution index. |
| 879 if (number > 0 && format[index] === '$') { |
| 880 substitutionIndex = (number - 1); |
| 881 ++index; |
| 882 } |
927 } | 883 } |
928 | 884 |
929 var index = 0; | 885 var precision = -1; |
930 for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; pre
centIndex = format.indexOf("%", index)) { | 886 if (format[index] === '.') { |
931 if (format.length === index) // unescaped % sign at the end of the form
at string. | 887 // This is a precision specifier. If no digit follows the ".", |
932 break; | 888 // then the precision should be zero. |
933 addStringToken(format.substring(index, precentIndex)); | 889 ++index; |
934 index = precentIndex + 1; | 890 precision = parseInt(format.substring(index), 10); |
| 891 if (isNaN(precision)) |
| 892 precision = 0; |
935 | 893 |
936 if (format[index] === "%") { | 894 while (String.isDigitAt(format, index)) |
937 // %% escape sequence. | |
938 addStringToken("%"); | |
939 ++index; | |
940 continue; | |
941 } | |
942 | |
943 if (String.isDigitAt(format, index)) { | |
944 // The first character is a number, it might be a substitution index
. | |
945 var number = parseInt(format.substring(index), 10); | |
946 while (String.isDigitAt(format, index)) | |
947 ++index; | |
948 | |
949 // If the number is greater than zero and ends with a "$", | |
950 // then this is a substitution index. | |
951 if (number > 0 && format[index] === "$") { | |
952 substitutionIndex = (number - 1); | |
953 ++index; | |
954 } | |
955 } | |
956 | |
957 var precision = -1; | |
958 if (format[index] === ".") { | |
959 // This is a precision specifier. If no digit follows the ".", | |
960 // then the precision should be zero. | |
961 ++index; | |
962 precision = parseInt(format.substring(index), 10); | |
963 if (isNaN(precision)) | |
964 precision = 0; | |
965 | |
966 while (String.isDigitAt(format, index)) | |
967 ++index; | |
968 } | |
969 | |
970 if (!(format[index] in formatters)) { | |
971 addStringToken(format.substring(precentIndex, index + 1)); | |
972 ++index; | |
973 continue; | |
974 } | |
975 | |
976 addSpecifierToken(format[index], precision, substitutionIndex); | |
977 | |
978 ++substitutionIndex; | |
979 ++index; | 895 ++index; |
980 } | 896 } |
981 | 897 |
982 addStringToken(format.substring(index)); | 898 if (!(format[index] in formatters)) { |
| 899 addStringToken(format.substring(precentIndex, index + 1)); |
| 900 ++index; |
| 901 continue; |
| 902 } |
983 | 903 |
984 return tokens; | 904 addSpecifierToken(format[index], precision, substitutionIndex); |
| 905 |
| 906 ++substitutionIndex; |
| 907 ++index; |
| 908 } |
| 909 |
| 910 addStringToken(format.substring(index)); |
| 911 |
| 912 return tokens; |
985 }; | 913 }; |
986 | 914 |
987 String.standardFormatters = { | 915 String.standardFormatters = { |
988 /** | 916 /** |
989 * @return {number} | 917 * @return {number} |
990 */ | 918 */ |
991 d: function(substitution) | 919 d: function(substitution) { |
992 { | 920 return !isNaN(substitution) ? substitution : 0; |
993 return !isNaN(substitution) ? substitution : 0; | 921 }, |
994 }, | |
995 | 922 |
996 /** | 923 /** |
997 * @return {number} | 924 * @return {number} |
998 */ | 925 */ |
999 f: function(substitution, token) | 926 f: function(substitution, token) { |
1000 { | 927 if (substitution && token.precision > -1) |
1001 if (substitution && token.precision > -1) | 928 substitution = substitution.toFixed(token.precision); |
1002 substitution = substitution.toFixed(token.precision); | 929 return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(
0).toFixed(token.precision) : 0); |
1003 return !isNaN(substitution) ? substitution : (token.precision > -1 ? Num
ber(0).toFixed(token.precision) : 0); | 930 }, |
1004 }, | |
1005 | 931 |
1006 /** | 932 /** |
1007 * @return {string} | 933 * @return {string} |
1008 */ | 934 */ |
1009 s: function(substitution) | 935 s: function(substitution) { |
1010 { | 936 return substitution; |
1011 return substitution; | 937 } |
1012 } | |
1013 }; | 938 }; |
1014 | 939 |
1015 /** | 940 /** |
1016 * @param {string} format | 941 * @param {string} format |
1017 * @param {!Array.<*>} substitutions | 942 * @param {!Array.<*>} substitutions |
1018 * @return {string} | 943 * @return {string} |
1019 */ | 944 */ |
1020 String.vsprintf = function(format, substitutions) | 945 String.vsprintf = function(format, substitutions) { |
1021 { | 946 return String |
1022 return String.format(format, substitutions, String.standardFormatters, "", f
unction(a, b) { return a + b; }).formattedResult; | 947 .format( |
| 948 format, substitutions, String.standardFormatters, '', |
| 949 function(a, b) { |
| 950 return a + b; |
| 951 }) |
| 952 .formattedResult; |
1023 }; | 953 }; |
1024 | 954 |
1025 /** | 955 /** |
1026 * @param {string} format | 956 * @param {string} format |
1027 * @param {?ArrayLike} substitutions | 957 * @param {?ArrayLike} substitutions |
1028 * @param {!Object.<string, function(string, ...):Q>} formatters | 958 * @param {!Object.<string, function(string, ...):Q>} formatters |
1029 * @param {!T} initialValue | 959 * @param {!T} initialValue |
1030 * @param {function(T, Q): T|undefined} append | 960 * @param {function(T, Q): T|undefined} append |
1031 * @param {!Array.<!Object>=} tokenizedFormat | 961 * @param {!Array.<!Object>=} tokenizedFormat |
1032 * @return {!{formattedResult: T, unusedSubstitutions: ?ArrayLike}}; | 962 * @return {!{formattedResult: T, unusedSubstitutions: ?ArrayLike}}; |
1033 * @template T, Q | 963 * @template T, Q |
1034 */ | 964 */ |
1035 String.format = function(format, substitutions, formatters, initialValue, append
, tokenizedFormat) | 965 String.format = function(format, substitutions, formatters, initialValue, append
, tokenizedFormat) { |
1036 { | 966 if (!format || !substitutions || !substitutions.length) |
1037 if (!format || !substitutions || !substitutions.length) | 967 return {formattedResult: append(initialValue, format), unusedSubstitutions:
substitutions}; |
1038 return { formattedResult: append(initialValue, format), unusedSubstituti
ons: substitutions }; | |
1039 | 968 |
1040 function prettyFunctionName() | 969 function prettyFunctionName() { |
1041 { | 970 return 'String.format("' + format + '", "' + Array.prototype.join.call(subst
itutions, '", "') + '")'; |
1042 return "String.format(\"" + format + "\", \"" + Array.prototype.join.cal
l(substitutions, "\", \"") + "\")"; | 971 } |
| 972 |
| 973 function warn(msg) { |
| 974 console.warn(prettyFunctionName() + ': ' + msg); |
| 975 } |
| 976 |
| 977 function error(msg) { |
| 978 console.error(prettyFunctionName() + ': ' + msg); |
| 979 } |
| 980 |
| 981 var result = initialValue; |
| 982 var tokens = tokenizedFormat || String.tokenizeFormatString(format, formatters
); |
| 983 var usedSubstitutionIndexes = {}; |
| 984 |
| 985 for (var i = 0; i < tokens.length; ++i) { |
| 986 var token = tokens[i]; |
| 987 |
| 988 if (token.type === 'string') { |
| 989 result = append(result, token.value); |
| 990 continue; |
1043 } | 991 } |
1044 | 992 |
1045 function warn(msg) | 993 if (token.type !== 'specifier') { |
1046 { | 994 error('Unknown token type "' + token.type + '" found.'); |
1047 console.warn(prettyFunctionName() + ": " + msg); | 995 continue; |
1048 } | 996 } |
1049 | 997 |
1050 function error(msg) | 998 if (token.substitutionIndex >= substitutions.length) { |
1051 { | 999 // If there are not enough substitutions for the current substitutionIndex |
1052 console.error(prettyFunctionName() + ": " + msg); | 1000 // just output the format specifier literally and move on. |
| 1001 error( |
| 1002 'not enough substitution arguments. Had ' + substitutions.length + ' b
ut needed ' + |
| 1003 (token.substitutionIndex + 1) + ', so substitution was skipped.'); |
| 1004 result = append(result, '%' + (token.precision > -1 ? token.precision : ''
) + token.specifier); |
| 1005 continue; |
1053 } | 1006 } |
1054 | 1007 |
1055 var result = initialValue; | 1008 usedSubstitutionIndexes[token.substitutionIndex] = true; |
1056 var tokens = tokenizedFormat || String.tokenizeFormatString(format, formatte
rs); | |
1057 var usedSubstitutionIndexes = {}; | |
1058 | 1009 |
1059 for (var i = 0; i < tokens.length; ++i) { | 1010 if (!(token.specifier in formatters)) { |
1060 var token = tokens[i]; | 1011 // Encountered an unsupported format character, treat as a string. |
1061 | 1012 warn('unsupported format character \u201C' + token.specifier + '\u201D. Tr
eating as a string.'); |
1062 if (token.type === "string") { | 1013 result = append(result, substitutions[token.substitutionIndex]); |
1063 result = append(result, token.value); | 1014 continue; |
1064 continue; | |
1065 } | |
1066 | |
1067 if (token.type !== "specifier") { | |
1068 error("Unknown token type \"" + token.type + "\" found."); | |
1069 continue; | |
1070 } | |
1071 | |
1072 if (token.substitutionIndex >= substitutions.length) { | |
1073 // If there are not enough substitutions for the current substitutio
nIndex | |
1074 // just output the format specifier literally and move on. | |
1075 error("not enough substitution arguments. Had " + substitutions.leng
th + " but needed " + (token.substitutionIndex + 1) + ", so substitution was ski
pped."); | |
1076 result = append(result, "%" + (token.precision > -1 ? token.precisio
n : "") + token.specifier); | |
1077 continue; | |
1078 } | |
1079 | |
1080 usedSubstitutionIndexes[token.substitutionIndex] = true; | |
1081 | |
1082 if (!(token.specifier in formatters)) { | |
1083 // Encountered an unsupported format character, treat as a string. | |
1084 warn("unsupported format character \u201C" + token.specifier + "\u20
1D. Treating as a string."); | |
1085 result = append(result, substitutions[token.substitutionIndex]); | |
1086 continue; | |
1087 } | |
1088 | |
1089 result = append(result, formatters[token.specifier](substitutions[token.
substitutionIndex], token)); | |
1090 } | 1015 } |
1091 | 1016 |
1092 var unusedSubstitutions = []; | 1017 result = append(result, formatters[token.specifier](substitutions[token.subs
titutionIndex], token)); |
1093 for (var i = 0; i < substitutions.length; ++i) { | 1018 } |
1094 if (i in usedSubstitutionIndexes) | |
1095 continue; | |
1096 unusedSubstitutions.push(substitutions[i]); | |
1097 } | |
1098 | 1019 |
1099 return { formattedResult: result, unusedSubstitutions: unusedSubstitutions }
; | 1020 var unusedSubstitutions = []; |
| 1021 for (var i = 0; i < substitutions.length; ++i) { |
| 1022 if (i in usedSubstitutionIndexes) |
| 1023 continue; |
| 1024 unusedSubstitutions.push(substitutions[i]); |
| 1025 } |
| 1026 |
| 1027 return {formattedResult: result, unusedSubstitutions: unusedSubstitutions}; |
1100 }; | 1028 }; |
1101 | 1029 |
1102 /** | 1030 /** |
1103 * @param {string} query | 1031 * @param {string} query |
1104 * @param {boolean} caseSensitive | 1032 * @param {boolean} caseSensitive |
1105 * @param {boolean} isRegex | 1033 * @param {boolean} isRegex |
1106 * @return {!RegExp} | 1034 * @return {!RegExp} |
1107 */ | 1035 */ |
1108 function createSearchRegex(query, caseSensitive, isRegex) | 1036 function createSearchRegex(query, caseSensitive, isRegex) { |
1109 { | 1037 var regexFlags = caseSensitive ? 'g' : 'gi'; |
1110 var regexFlags = caseSensitive ? "g" : "gi"; | 1038 var regexObject; |
1111 var regexObject; | |
1112 | 1039 |
1113 if (isRegex) { | 1040 if (isRegex) { |
1114 try { | 1041 try { |
1115 regexObject = new RegExp(query, regexFlags); | 1042 regexObject = new RegExp(query, regexFlags); |
1116 } catch (e) { | 1043 } catch (e) { |
1117 // Silent catch. | 1044 // Silent catch. |
1118 } | |
1119 } | 1045 } |
| 1046 } |
1120 | 1047 |
1121 if (!regexObject) | 1048 if (!regexObject) |
1122 regexObject = createPlainTextSearchRegex(query, regexFlags); | 1049 regexObject = createPlainTextSearchRegex(query, regexFlags); |
1123 | 1050 |
1124 return regexObject; | 1051 return regexObject; |
1125 } | 1052 } |
1126 | 1053 |
1127 /** | 1054 /** |
1128 * @param {string} query | 1055 * @param {string} query |
1129 * @param {string=} flags | 1056 * @param {string=} flags |
1130 * @return {!RegExp} | 1057 * @return {!RegExp} |
1131 */ | 1058 */ |
1132 function createPlainTextSearchRegex(query, flags) | 1059 function createPlainTextSearchRegex(query, flags) { |
1133 { | 1060 // This should be kept the same as the one in StringUtil.cpp. |
1134 // This should be kept the same as the one in StringUtil.cpp. | 1061 var regexSpecialCharacters = String.regexSpecialCharacters(); |
1135 var regexSpecialCharacters = String.regexSpecialCharacters(); | 1062 var regex = ''; |
1136 var regex = ""; | 1063 for (var i = 0; i < query.length; ++i) { |
1137 for (var i = 0; i < query.length; ++i) { | 1064 var c = query.charAt(i); |
1138 var c = query.charAt(i); | 1065 if (regexSpecialCharacters.indexOf(c) !== -1) |
1139 if (regexSpecialCharacters.indexOf(c) !== -1) | 1066 regex += '\\'; |
1140 regex += "\\"; | 1067 regex += c; |
1141 regex += c; | 1068 } |
1142 } | 1069 return new RegExp(regex, flags || ''); |
1143 return new RegExp(regex, flags || ""); | |
1144 } | 1070 } |
1145 | 1071 |
1146 /** | 1072 /** |
1147 * @param {!RegExp} regex | 1073 * @param {!RegExp} regex |
1148 * @param {string} content | 1074 * @param {string} content |
1149 * @return {number} | 1075 * @return {number} |
1150 */ | 1076 */ |
1151 function countRegexMatches(regex, content) | 1077 function countRegexMatches(regex, content) { |
1152 { | 1078 var text = content; |
1153 var text = content; | 1079 var result = 0; |
1154 var result = 0; | 1080 var match; |
1155 var match; | 1081 while (text && (match = regex.exec(text))) { |
1156 while (text && (match = regex.exec(text))) { | 1082 if (match[0].length > 0) |
1157 if (match[0].length > 0) | 1083 ++result; |
1158 ++result; | 1084 text = text.substring(match.index + 1); |
1159 text = text.substring(match.index + 1); | 1085 } |
1160 } | 1086 return result; |
1161 return result; | |
1162 } | 1087 } |
1163 | 1088 |
1164 /** | 1089 /** |
1165 * @param {number} spacesCount | 1090 * @param {number} spacesCount |
1166 * @return {string} | 1091 * @return {string} |
1167 */ | 1092 */ |
1168 function spacesPadding(spacesCount) | 1093 function spacesPadding(spacesCount) { |
1169 { | 1094 return '\u00a0'.repeat(spacesCount); |
1170 return "\u00a0".repeat(spacesCount); | |
1171 } | 1095 } |
1172 | 1096 |
1173 /** | 1097 /** |
1174 * @param {number} value | 1098 * @param {number} value |
1175 * @param {number} symbolsCount | 1099 * @param {number} symbolsCount |
1176 * @return {string} | 1100 * @return {string} |
1177 */ | 1101 */ |
1178 function numberToStringWithSpacesPadding(value, symbolsCount) | 1102 function numberToStringWithSpacesPadding(value, symbolsCount) { |
1179 { | 1103 var numberString = value.toString(); |
1180 var numberString = value.toString(); | 1104 var paddingLength = Math.max(0, symbolsCount - numberString.length); |
1181 var paddingLength = Math.max(0, symbolsCount - numberString.length); | 1105 return spacesPadding(paddingLength) + numberString; |
1182 return spacesPadding(paddingLength) + numberString; | |
1183 } | 1106 } |
1184 | 1107 |
1185 /** | 1108 /** |
1186 * @return {!Array.<T>} | 1109 * @return {!Array.<T>} |
1187 * @template T | 1110 * @template T |
1188 */ | 1111 */ |
1189 Set.prototype.valuesArray = function() | 1112 Set.prototype.valuesArray = function() { |
1190 { | 1113 return Array.from(this.values()); |
1191 return Array.from(this.values()); | |
1192 }; | 1114 }; |
1193 | 1115 |
1194 /** | 1116 /** |
1195 * @param {!Iterable<T>|!Array<!T>} iterable | 1117 * @param {!Iterable<T>|!Array<!T>} iterable |
1196 * @template T | 1118 * @template T |
1197 */ | 1119 */ |
1198 Set.prototype.addAll = function(iterable) | 1120 Set.prototype.addAll = function(iterable) { |
1199 { | 1121 for (var e of iterable) |
1200 for (var e of iterable) | 1122 this.add(e); |
1201 this.add(e); | |
1202 }; | 1123 }; |
1203 | 1124 |
1204 /** | 1125 /** |
1205 * @param {!Iterable<T>|!Array<!T>} iterable | 1126 * @param {!Iterable<T>|!Array<!T>} iterable |
1206 * @return {boolean} | 1127 * @return {boolean} |
1207 * @template T | 1128 * @template T |
1208 */ | 1129 */ |
1209 Set.prototype.containsAll = function(iterable) | 1130 Set.prototype.containsAll = function(iterable) { |
1210 { | 1131 for (var e of iterable) { |
1211 for (var e of iterable) { | 1132 if (!this.has(e)) |
1212 if (!this.has(e)) | 1133 return false; |
1213 return false; | 1134 } |
1214 } | 1135 return true; |
1215 return true; | |
1216 }; | 1136 }; |
1217 | 1137 |
1218 /** | 1138 /** |
1219 * @return {T} | 1139 * @return {T} |
1220 * @template T | 1140 * @template T |
1221 */ | 1141 */ |
1222 Map.prototype.remove = function(key) | 1142 Map.prototype.remove = function(key) { |
1223 { | 1143 var value = this.get(key); |
| 1144 this.delete(key); |
| 1145 return value; |
| 1146 }; |
| 1147 |
| 1148 /** |
| 1149 * @return {!Array<!VALUE>} |
| 1150 */ |
| 1151 Map.prototype.valuesArray = function() { |
| 1152 return Array.from(this.values()); |
| 1153 }; |
| 1154 |
| 1155 /** |
| 1156 * @return {!Array<!KEY>} |
| 1157 */ |
| 1158 Map.prototype.keysArray = function() { |
| 1159 return Array.from(this.keys()); |
| 1160 }; |
| 1161 |
| 1162 /** |
| 1163 * @return {!Multimap<!KEY, !VALUE>} |
| 1164 */ |
| 1165 Map.prototype.inverse = function() { |
| 1166 var result = new Multimap(); |
| 1167 for (var key of this.keys()) { |
1224 var value = this.get(key); | 1168 var value = this.get(key); |
1225 this.delete(key); | 1169 result.set(value, key); |
1226 return value; | 1170 } |
1227 }; | 1171 return result; |
1228 | |
1229 /** | |
1230 * @return {!Array<!VALUE>} | |
1231 */ | |
1232 Map.prototype.valuesArray = function() | |
1233 { | |
1234 return Array.from(this.values()); | |
1235 }; | |
1236 | |
1237 /** | |
1238 * @return {!Array<!KEY>} | |
1239 */ | |
1240 Map.prototype.keysArray = function() | |
1241 { | |
1242 return Array.from(this.keys()); | |
1243 }; | |
1244 | |
1245 /** | |
1246 * @return {!Multimap<!KEY, !VALUE>} | |
1247 */ | |
1248 Map.prototype.inverse = function() | |
1249 { | |
1250 var result = new Multimap(); | |
1251 for (var key of this.keys()) { | |
1252 var value = this.get(key); | |
1253 result.set(value, key); | |
1254 } | |
1255 return result; | |
1256 }; | 1172 }; |
1257 | 1173 |
1258 /** | 1174 /** |
1259 * @constructor | 1175 * @constructor |
1260 * @template K, V | 1176 * @template K, V |
1261 */ | 1177 */ |
1262 var Multimap = function() | 1178 var Multimap = function() { |
1263 { | 1179 /** @type {!Map.<K, !Set.<!V>>} */ |
1264 /** @type {!Map.<K, !Set.<!V>>} */ | 1180 this._map = new Map(); |
1265 this._map = new Map(); | |
1266 }; | 1181 }; |
1267 | 1182 |
1268 Multimap.prototype = { | 1183 Multimap.prototype = { |
1269 /** | 1184 /** |
1270 * @param {K} key | 1185 * @param {K} key |
1271 * @param {V} value | 1186 * @param {V} value |
1272 */ | 1187 */ |
1273 set: function(key, value) | 1188 set: function(key, value) { |
1274 { | 1189 var set = this._map.get(key); |
1275 var set = this._map.get(key); | 1190 if (!set) { |
1276 if (!set) { | 1191 set = new Set(); |
1277 set = new Set(); | 1192 this._map.set(key, set); |
1278 this._map.set(key, set); | 1193 } |
1279 } | 1194 set.add(value); |
1280 set.add(value); | 1195 }, |
1281 }, | 1196 |
1282 | 1197 /** |
1283 /** | 1198 * @param {K} key |
1284 * @param {K} key | 1199 * @return {!Set.<!V>} |
1285 * @return {!Set.<!V>} | 1200 */ |
1286 */ | 1201 get: function(key) { |
1287 get: function(key) | 1202 var result = this._map.get(key); |
1288 { | 1203 if (!result) |
1289 var result = this._map.get(key); | 1204 result = new Set(); |
1290 if (!result) | 1205 return result; |
1291 result = new Set(); | 1206 }, |
1292 return result; | 1207 |
1293 }, | 1208 /** |
1294 | 1209 * @param {K} key |
1295 /** | 1210 * @return {boolean} |
1296 * @param {K} key | 1211 */ |
1297 * @return {boolean} | 1212 has: function(key) { |
1298 */ | 1213 return this._map.has(key); |
1299 has: function(key) | 1214 }, |
1300 { | 1215 |
1301 return this._map.has(key); | 1216 /** |
1302 }, | 1217 * @param {K} key |
1303 | 1218 * @param {V} value |
1304 /** | 1219 * @return {boolean} |
1305 * @param {K} key | 1220 */ |
1306 * @param {V} value | 1221 hasValue: function(key, value) { |
1307 * @return {boolean} | 1222 var set = this._map.get(key); |
1308 */ | 1223 if (!set) |
1309 hasValue: function(key, value) | 1224 return false; |
1310 { | 1225 return set.has(value); |
1311 var set = this._map.get(key); | 1226 }, |
1312 if (!set) | 1227 |
1313 return false; | 1228 /** |
1314 return set.has(value); | 1229 * @return {number} |
1315 }, | 1230 */ |
1316 | 1231 get size() { |
1317 /** | 1232 return this._map.size; |
1318 * @return {number} | 1233 }, |
1319 */ | 1234 |
1320 get size() | 1235 /** |
1321 { | 1236 * @param {K} key |
1322 return this._map.size; | 1237 * @param {V} value |
1323 }, | 1238 */ |
1324 | 1239 remove: function(key, value) { |
1325 /** | 1240 var values = this.get(key); |
1326 * @param {K} key | 1241 values.delete(value); |
1327 * @param {V} value | 1242 if (!values.size) |
1328 */ | 1243 this._map.delete(key); |
1329 remove: function(key, value) | 1244 }, |
1330 { | 1245 |
1331 var values = this.get(key); | 1246 /** |
1332 values.delete(value); | 1247 * @param {K} key |
1333 if (!values.size) | 1248 */ |
1334 this._map.delete(key); | 1249 removeAll: function(key) { |
1335 }, | 1250 this._map.delete(key); |
1336 | 1251 }, |
1337 /** | 1252 |
1338 * @param {K} key | 1253 /** |
1339 */ | 1254 * @return {!Array.<K>} |
1340 removeAll: function(key) | 1255 */ |
1341 { | 1256 keysArray: function() { |
1342 this._map.delete(key); | 1257 return this._map.keysArray(); |
1343 }, | 1258 }, |
1344 | 1259 |
1345 /** | 1260 /** |
1346 * @return {!Array.<K>} | 1261 * @return {!Array.<!V>} |
1347 */ | 1262 */ |
1348 keysArray: function() | 1263 valuesArray: function() { |
1349 { | 1264 var result = []; |
1350 return this._map.keysArray(); | 1265 var keys = this.keysArray(); |
1351 }, | 1266 for (var i = 0; i < keys.length; ++i) |
1352 | 1267 result.pushAll(this.get(keys[i]).valuesArray()); |
1353 /** | 1268 return result; |
1354 * @return {!Array.<!V>} | 1269 }, |
1355 */ | 1270 |
1356 valuesArray: function() | 1271 clear: function() { |
1357 { | 1272 this._map.clear(); |
1358 var result = []; | 1273 } |
1359 var keys = this.keysArray(); | |
1360 for (var i = 0; i < keys.length; ++i) | |
1361 result.pushAll(this.get(keys[i]).valuesArray()); | |
1362 return result; | |
1363 }, | |
1364 | |
1365 clear: function() | |
1366 { | |
1367 this._map.clear(); | |
1368 } | |
1369 }; | 1274 }; |
1370 | 1275 |
1371 /** | 1276 /** |
1372 * @param {string} url | 1277 * @param {string} url |
1373 * @return {!Promise.<string>} | 1278 * @return {!Promise.<string>} |
1374 */ | 1279 */ |
1375 function loadXHR(url) | 1280 function loadXHR(url) { |
1376 { | 1281 return new Promise(load); |
1377 return new Promise(load); | 1282 |
1378 | 1283 function load(successCallback, failureCallback) { |
1379 function load(successCallback, failureCallback) | 1284 function onReadyStateChanged() { |
1380 { | 1285 if (xhr.readyState !== XMLHttpRequest.DONE) |
1381 function onReadyStateChanged() | 1286 return; |
1382 { | 1287 if (xhr.status !== 200) { |
1383 if (xhr.readyState !== XMLHttpRequest.DONE) | 1288 xhr.onreadystatechange = null; |
1384 return; | 1289 failureCallback(new Error(xhr.status)); |
1385 if (xhr.status !== 200) { | 1290 return; |
1386 xhr.onreadystatechange = null; | 1291 } |
1387 failureCallback(new Error(xhr.status)); | 1292 xhr.onreadystatechange = null; |
1388 return; | 1293 successCallback(xhr.responseText); |
1389 } | 1294 } |
1390 xhr.onreadystatechange = null; | 1295 |
1391 successCallback(xhr.responseText); | 1296 var xhr = new XMLHttpRequest(); |
1392 } | 1297 xhr.withCredentials = false; |
1393 | 1298 xhr.open('GET', url, true); |
1394 var xhr = new XMLHttpRequest(); | 1299 xhr.onreadystatechange = onReadyStateChanged; |
1395 xhr.withCredentials = false; | 1300 xhr.send(null); |
1396 xhr.open("GET", url, true); | 1301 } |
1397 xhr.onreadystatechange = onReadyStateChanged; | |
1398 xhr.send(null); | |
1399 } | |
1400 } | 1302 } |
1401 | 1303 |
1402 /** | 1304 /** |
1403 * @constructor | 1305 * @unrestricted |
1404 */ | 1306 */ |
1405 function CallbackBarrier() | 1307 var CallbackBarrier = class { |
1406 { | 1308 constructor() { |
1407 this._pendingIncomingCallbacksCount = 0; | 1309 this._pendingIncomingCallbacksCount = 0; |
1408 } | 1310 } |
1409 | 1311 |
1410 CallbackBarrier.prototype = { | 1312 /** |
| 1313 * @param {function(...)=} userCallback |
| 1314 * @return {function(...)} |
| 1315 */ |
| 1316 createCallback(userCallback) { |
| 1317 console.assert( |
| 1318 !this._outgoingCallback, 'CallbackBarrier.createCallback() is called aft
er CallbackBarrier.callWhenDone()'); |
| 1319 ++this._pendingIncomingCallbacksCount; |
| 1320 return this._incomingCallback.bind(this, userCallback); |
| 1321 } |
| 1322 |
| 1323 /** |
| 1324 * @param {function()} callback |
| 1325 */ |
| 1326 callWhenDone(callback) { |
| 1327 console.assert(!this._outgoingCallback, 'CallbackBarrier.callWhenDone() is c
alled multiple times'); |
| 1328 this._outgoingCallback = callback; |
| 1329 if (!this._pendingIncomingCallbacksCount) |
| 1330 this._outgoingCallback(); |
| 1331 } |
| 1332 |
| 1333 /** |
| 1334 * @return {!Promise.<undefined>} |
| 1335 */ |
| 1336 donePromise() { |
| 1337 return new Promise(promiseConstructor.bind(this)); |
| 1338 |
1411 /** | 1339 /** |
1412 * @param {function(...)=} userCallback | 1340 * @param {function()} success |
1413 * @return {function(...)} | 1341 * @this {CallbackBarrier} |
1414 */ | 1342 */ |
1415 createCallback: function(userCallback) | 1343 function promiseConstructor(success) { |
1416 { | 1344 this.callWhenDone(success); |
1417 console.assert(!this._outgoingCallback, "CallbackBarrier.createCallback(
) is called after CallbackBarrier.callWhenDone()"); | 1345 } |
1418 ++this._pendingIncomingCallbacksCount; | 1346 } |
1419 return this._incomingCallback.bind(this, userCallback); | 1347 |
1420 }, | 1348 /** |
1421 | 1349 * @param {function(...)=} userCallback |
1422 /** | 1350 */ |
1423 * @param {function()} callback | 1351 _incomingCallback(userCallback) { |
1424 */ | 1352 console.assert(this._pendingIncomingCallbacksCount > 0); |
1425 callWhenDone: function(callback) | 1353 if (userCallback) { |
1426 { | 1354 var args = Array.prototype.slice.call(arguments, 1); |
1427 console.assert(!this._outgoingCallback, "CallbackBarrier.callWhenDone()
is called multiple times"); | 1355 userCallback.apply(null, args); |
1428 this._outgoingCallback = callback; | 1356 } |
1429 if (!this._pendingIncomingCallbacksCount) | 1357 if (!--this._pendingIncomingCallbacksCount && this._outgoingCallback) |
1430 this._outgoingCallback(); | 1358 this._outgoingCallback(); |
1431 }, | 1359 } |
1432 | |
1433 /** | |
1434 * @return {!Promise.<undefined>} | |
1435 */ | |
1436 donePromise: function() | |
1437 { | |
1438 return new Promise(promiseConstructor.bind(this)); | |
1439 | |
1440 /** | |
1441 * @param {function()} success | |
1442 * @this {CallbackBarrier} | |
1443 */ | |
1444 function promiseConstructor(success) | |
1445 { | |
1446 this.callWhenDone(success); | |
1447 } | |
1448 }, | |
1449 | |
1450 /** | |
1451 * @param {function(...)=} userCallback | |
1452 */ | |
1453 _incomingCallback: function(userCallback) | |
1454 { | |
1455 console.assert(this._pendingIncomingCallbacksCount > 0); | |
1456 if (userCallback) { | |
1457 var args = Array.prototype.slice.call(arguments, 1); | |
1458 userCallback.apply(null, args); | |
1459 } | |
1460 if (!--this._pendingIncomingCallbacksCount && this._outgoingCallback) | |
1461 this._outgoingCallback(); | |
1462 } | |
1463 }; | 1360 }; |
1464 | 1361 |
1465 /** | 1362 /** |
1466 * @param {*} value | 1363 * @param {*} value |
1467 */ | 1364 */ |
1468 function suppressUnused(value) | 1365 function suppressUnused(value) { |
1469 { | |
1470 } | 1366 } |
1471 | 1367 |
1472 /** | 1368 /** |
1473 * @param {function()} callback | 1369 * @param {function()} callback |
1474 * @return {number} | 1370 * @return {number} |
1475 */ | 1371 */ |
1476 self.setImmediate = function(callback) | 1372 self.setImmediate = function(callback) { |
1477 { | 1373 Promise.resolve().then(callback); |
1478 Promise.resolve().then(callback); | 1374 return 0; |
1479 return 0; | 1375 }; |
1480 }; | 1376 |
1481 | 1377 /** |
1482 /** | |
1483 * @param {function(...?)} callback | 1378 * @param {function(...?)} callback |
1484 * @return {!Promise.<T>} | 1379 * @return {!Promise.<T>} |
1485 * @template T | 1380 * @template T |
1486 */ | 1381 */ |
1487 Promise.prototype.spread = function(callback) | 1382 Promise.prototype.spread = function(callback) { |
1488 { | 1383 return this.then(spreadPromise); |
1489 return this.then(spreadPromise); | |
1490 | 1384 |
1491 function spreadPromise(arg) | 1385 function spreadPromise(arg) { |
1492 { | 1386 return callback.apply(null, arg); |
1493 return callback.apply(null, arg); | 1387 } |
1494 } | |
1495 }; | 1388 }; |
1496 | 1389 |
1497 /** | 1390 /** |
1498 * @param {T} defaultValue | 1391 * @param {T} defaultValue |
1499 * @return {!Promise.<T>} | 1392 * @return {!Promise.<T>} |
1500 * @template T | 1393 * @template T |
1501 */ | 1394 */ |
1502 Promise.prototype.catchException = function(defaultValue) { | 1395 Promise.prototype.catchException = function(defaultValue) { |
1503 return this.catch(function(error) { | 1396 return this.catch(function(error) { |
1504 console.error(error); | 1397 console.error(error); |
1505 return defaultValue; | 1398 return defaultValue; |
1506 }); | 1399 }); |
1507 }; | 1400 }; |
1508 | 1401 |
1509 /** | 1402 /** |
1510 * @param {!Map<number, ?>} other | 1403 * @param {!Map<number, ?>} other |
1511 * @param {function(!VALUE,?):boolean} isEqual | 1404 * @param {function(!VALUE,?):boolean} isEqual |
1512 * @return {!{removed: !Array<!VALUE>, added: !Array<?>, equal: !Array<!VALUE>}} | 1405 * @return {!{removed: !Array<!VALUE>, added: !Array<?>, equal: !Array<!VALUE>}} |
1513 * @this {Map<number, VALUE>} | 1406 * @this {Map<number, VALUE>} |
1514 */ | 1407 */ |
1515 Map.prototype.diff = function(other, isEqual) | 1408 Map.prototype.diff = function(other, isEqual) { |
1516 { | 1409 var leftKeys = this.keysArray(); |
1517 var leftKeys = this.keysArray(); | 1410 var rightKeys = other.keysArray(); |
1518 var rightKeys = other.keysArray(); | 1411 leftKeys.sort((a, b) => a - b); |
1519 leftKeys.sort((a, b) => a - b); | 1412 rightKeys.sort((a, b) => a - b); |
1520 rightKeys.sort((a, b) => a - b); | |
1521 | 1413 |
1522 var removed = []; | 1414 var removed = []; |
1523 var added = []; | 1415 var added = []; |
1524 var equal = []; | 1416 var equal = []; |
1525 var leftIndex = 0; | 1417 var leftIndex = 0; |
1526 var rightIndex = 0; | 1418 var rightIndex = 0; |
1527 while (leftIndex < leftKeys.length && rightIndex < rightKeys.length) { | 1419 while (leftIndex < leftKeys.length && rightIndex < rightKeys.length) { |
1528 var leftKey = leftKeys[leftIndex]; | 1420 var leftKey = leftKeys[leftIndex]; |
1529 var rightKey = rightKeys[rightIndex]; | 1421 var rightKey = rightKeys[rightIndex]; |
1530 if (leftKey === rightKey && isEqual(this.get(leftKey), other.get(rightKe
y))) { | 1422 if (leftKey === rightKey && isEqual(this.get(leftKey), other.get(rightKey)))
{ |
1531 equal.push(this.get(leftKey)); | 1423 equal.push(this.get(leftKey)); |
1532 ++leftIndex; | 1424 ++leftIndex; |
1533 ++rightIndex; | 1425 ++rightIndex; |
1534 continue; | 1426 continue; |
1535 } | |
1536 if (leftKey <= rightKey) { | |
1537 removed.push(this.get(leftKey)); | |
1538 ++leftIndex; | |
1539 continue; | |
1540 } | |
1541 added.push(other.get(rightKey)); | |
1542 ++rightIndex; | |
1543 } | 1427 } |
1544 while (leftIndex < leftKeys.length) { | 1428 if (leftKey <= rightKey) { |
1545 var leftKey = leftKeys[leftIndex++]; | 1429 removed.push(this.get(leftKey)); |
1546 removed.push(this.get(leftKey)); | 1430 ++leftIndex; |
| 1431 continue; |
1547 } | 1432 } |
1548 while (rightIndex < rightKeys.length) { | 1433 added.push(other.get(rightKey)); |
1549 var rightKey = rightKeys[rightIndex++]; | 1434 ++rightIndex; |
1550 added.push(other.get(rightKey)); | 1435 } |
1551 } | 1436 while (leftIndex < leftKeys.length) { |
1552 return { | 1437 var leftKey = leftKeys[leftIndex++]; |
1553 added: added, | 1438 removed.push(this.get(leftKey)); |
1554 removed: removed, | 1439 } |
1555 equal: equal | 1440 while (rightIndex < rightKeys.length) { |
1556 }; | 1441 var rightKey = rightKeys[rightIndex++]; |
| 1442 added.push(other.get(rightKey)); |
| 1443 } |
| 1444 return {added: added, removed: removed, equal: equal}; |
1557 }; | 1445 }; |
OLD | NEW |