| 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 |