Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of dart2js.js_emitter; | 5 part of dart2js.js_emitter; |
| 6 | 6 |
| 7 class NsmEmitter extends CodeEmitterHelper { | 7 class NsmEmitter extends CodeEmitterHelper { |
| 8 final List<Selector> trivialNsmHandlers = <Selector>[]; | 8 final List<Selector> trivialNsmHandlers = <Selector>[]; |
| 9 | 9 |
| 10 /// If this is true then we can generate the noSuchMethod handlers at startup | 10 /// If this is true then we can generate the noSuchMethod handlers at startup |
| 11 /// time, instead of them being emitted as part of the Object class. | 11 /// time, instead of them being emitted as part of the Object class. |
| 12 bool get generateTrivialNsmHandlers => true; | 12 bool get generateTrivialNsmHandlers => true; |
| 13 | 13 |
| 14 // If we need fewer than this many noSuchMethod handlers we can save space by | 14 // If we need fewer than this many noSuchMethod handlers we can save space by |
| 15 // just emitting them in JS, rather than emitting the JS needed to generate | 15 // just emitting them in JS, rather than emitting the JS needed to generate |
| 16 // them at run time. | 16 // them at run time. |
| 17 static const VERY_FEW_NO_SUCH_METHOD_HANDLERS = 10; | 17 static const VERY_FEW_NO_SUCH_METHOD_HANDLERS = 10; |
| 18 | 18 |
| 19 static const MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING = 4; | 19 static const MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING = 4; |
| 20 | 20 |
| 21 void emitNoSuchMethodHandlers(AddPropertyFunction addProperty) { | 21 void emitNoSuchMethodHandlers(AddPropertyFunction addProperty) { |
| 22 | 22 |
| 23 ClassStubGenerator generator = | 23 ClassStubGenerator generator = |
| 24 new ClassStubGenerator(compiler, namer, backend); | 24 new ClassStubGenerator(compiler, namer, backend); |
| 25 | 25 |
| 26 // Keep track of the JavaScript names we've already added so we | 26 // Keep track of the JavaScript names we've already added so we |
| 27 // do not introduce duplicates (bad for code size). | 27 // do not introduce duplicates (bad for code size). |
| 28 Map<String, Selector> addedJsNames | 28 Map<jsAst.Name, Selector> addedJsNames |
| 29 = generator.computeSelectorsForNsmHandlers(); | 29 = generator.computeSelectorsForNsmHandlers(); |
| 30 | 30 |
| 31 // Set flag used by generateMethod helper below. If we have very few | 31 // Set flag used by generateMethod helper below. If we have very few |
| 32 // handlers we use addProperty for them all, rather than try to generate | 32 // handlers we use addProperty for them all, rather than try to generate |
| 33 // them at runtime. | 33 // them at runtime. |
| 34 bool haveVeryFewNoSuchMemberHandlers = | 34 bool haveVeryFewNoSuchMemberHandlers = |
| 35 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); | 35 (addedJsNames.length < VERY_FEW_NO_SUCH_METHOD_HANDLERS); |
| 36 for (String jsName in addedJsNames.keys.toList()..sort()) { | 36 List<jsAst.Name> names = addedJsNames.keys.toList() |
| 37 ..sort(); | |
| 38 for (jsAst.Name jsName in names) { | |
| 37 Selector selector = addedJsNames[jsName]; | 39 Selector selector = addedJsNames[jsName]; |
| 38 String reflectionName = emitter.getReflectionName(selector, jsName); | 40 String reflectionName = emitter.getReflectionName(selector, jsName); |
| 39 | 41 |
| 40 if (reflectionName != null) { | 42 if (reflectionName != null) { |
| 41 emitter.mangledFieldNames[jsName] = reflectionName; | 43 emitter.mangledFieldNames[jsName] = reflectionName; |
| 42 } | 44 } |
| 43 | 45 |
| 44 List<jsAst.Expression> argNames = | 46 List<jsAst.Expression> argNames = |
| 45 selector.callStructure.getOrderedNamedArguments().map((String name) => | 47 selector.callStructure.getOrderedNamedArguments().map((String name) => |
| 46 js.string(name)).toList(); | 48 js.string(name)).toList(); |
| 47 int type = selector.invocationMirrorKind; | 49 int type = selector.invocationMirrorKind; |
| 48 if (!haveVeryFewNoSuchMemberHandlers && | 50 if (!haveVeryFewNoSuchMemberHandlers && |
| 49 isTrivialNsmHandler(type, argNames, selector, jsName) && | 51 isTrivialNsmHandler(type, argNames, selector, jsName) && |
| 50 reflectionName == null) { | 52 reflectionName == null) { |
| 51 trivialNsmHandlers.add(selector); | 53 trivialNsmHandlers.add(selector); |
| 52 } else { | 54 } else { |
| 53 StubMethod method = | 55 StubMethod method = |
| 54 generator.generateStubForNoSuchMethod(jsName, selector); | 56 generator.generateStubForNoSuchMethod(jsName, selector); |
| 55 addProperty(method.name, method.code); | 57 addProperty(method.name, method.code); |
| 56 if (reflectionName != null) { | 58 if (reflectionName != null) { |
| 57 bool accessible = | 59 bool accessible = |
| 58 compiler.world.allFunctions.filter(selector, null).any( | 60 compiler.world.allFunctions.filter(selector, null).any( |
| 59 (Element e) => backend.isAccessibleByReflection(e)); | 61 (Element e) => backend.isAccessibleByReflection(e)); |
| 60 addProperty('+$reflectionName', js(accessible ? '2' : '0')); | 62 addProperty(namer.asName('+$reflectionName'), |
| 63 js(accessible ? '2' : '0')); | |
| 61 } | 64 } |
| 62 } | 65 } |
| 63 } | 66 } |
| 64 } | 67 } |
| 65 | 68 |
| 66 // Identify the noSuchMethod handlers that are so simple that we can | 69 // Identify the noSuchMethod handlers that are so simple that we can |
| 67 // generate them programatically. | 70 // generate them programatically. |
| 68 bool isTrivialNsmHandler( | 71 bool isTrivialNsmHandler( |
| 69 int type, List argNames, Selector selector, String internalName) { | 72 int type, List argNames, Selector selector, jsAst.Name internalName) { |
| 70 if (!generateTrivialNsmHandlers) return false; | 73 if (!generateTrivialNsmHandlers) return false; |
| 71 // Check for interceptor calling convention. | |
| 72 if (backend.isInterceptedName(selector.name)) { | |
| 73 // We can handle the calling convention used by intercepted names in the | |
| 74 // diff encoding, but we don't use that for non-minified code. | |
| 75 if (!compiler.enableMinification) return false; | |
| 76 String shortName = namer.invocationMirrorInternalName(selector); | |
| 77 if (shortName.length > MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) { | |
| 78 return false; | |
| 79 } | |
| 80 } | |
| 81 // Check for named arguments. | 74 // Check for named arguments. |
| 82 if (argNames.length != 0) return false; | 75 if (argNames.length != 0) return false; |
| 83 // Check for unexpected name (this doesn't really happen). | 76 // Check for unexpected name (this doesn't really happen). |
| 84 if (internalName.startsWith(namer.getterPrefix[0])) return type == 1; | 77 if (internalName is GetterName) return type == 1; |
| 85 if (internalName.startsWith(namer.setterPrefix[0])) return type == 2; | 78 if (internalName is SetterName) return type == 2; |
| 86 return type == 0; | 79 return type == 0; |
| 87 } | 80 } |
| 88 | 81 |
| 89 /** | 82 /** |
| 90 * Adds (at runtime) the handlers to the Object class which catch calls to | 83 * Adds (at runtime) the handlers to the Object class which catch calls to |
| 91 * methods that the object does not have. The handlers create an invocation | 84 * methods that the object does not have. The handlers create an invocation |
| 92 * mirror object. | 85 * mirror object. |
| 93 * | 86 * |
| 94 * The current version only gives you the minified name when minifying (when | 87 * The current version only gives you the minified name when minifying (when |
| 95 * not minifying this method is not called). | 88 * not minifying this method is not called). |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 121 * The reason we don't encode long minified names with this method is that | 114 * The reason we don't encode long minified names with this method is that |
| 122 * decoding the base 88 numbers would overflow JavaScript's puny integers. | 115 * decoding the base 88 numbers would overflow JavaScript's puny integers. |
| 123 * | 116 * |
| 124 * There are some selectors that have a special calling convention (because | 117 * There are some selectors that have a special calling convention (because |
| 125 * they are called with the receiver as the first argument). They need a | 118 * they are called with the receiver as the first argument). They need a |
| 126 * slightly different noSuchMethod handler, so we handle these first. | 119 * slightly different noSuchMethod handler, so we handle these first. |
| 127 */ | 120 */ |
| 128 List<jsAst.Statement> buildTrivialNsmHandlers() { | 121 List<jsAst.Statement> buildTrivialNsmHandlers() { |
| 129 List<jsAst.Statement> statements = <jsAst.Statement>[]; | 122 List<jsAst.Statement> statements = <jsAst.Statement>[]; |
| 130 if (trivialNsmHandlers.length == 0) return statements; | 123 if (trivialNsmHandlers.length == 0) return statements; |
| 131 // Sort by calling convention, JS name length and by JS name. | |
| 132 trivialNsmHandlers.sort((a, b) { | |
| 133 bool aIsIntercepted = backend.isInterceptedName(a.name); | |
| 134 bool bIsIntercepted = backend.isInterceptedName(b.name); | |
| 135 if (aIsIntercepted != bIsIntercepted) return aIsIntercepted ? -1 : 1; | |
| 136 String aName = namer.invocationMirrorInternalName(a); | |
| 137 String bName = namer.invocationMirrorInternalName(b); | |
| 138 if (aName.length != bName.length) return aName.length - bName.length; | |
| 139 return aName.compareTo(bName); | |
| 140 }); | |
| 141 | 124 |
| 142 // Find out how many selectors there are with the special calling | 125 // Find out how many selectors there are with the special calling |
| 143 // convention. | 126 // convention. |
| 144 int firstNormalSelector = trivialNsmHandlers.length; | 127 bool hasSpecialCallingConvention(Selector selector) { |
| 145 for (int i = 0; i < trivialNsmHandlers.length; i++) { | 128 return backend.isInterceptedName(selector.name); |
| 146 if (!backend.isInterceptedName(trivialNsmHandlers[i].name)) { | |
| 147 firstNormalSelector = i; | |
| 148 break; | |
| 149 } | |
| 150 } | 129 } |
| 130 Iterable<Selector> specialSelectors = trivialNsmHandlers.where( | |
| 131 (Selector s) => backend.isInterceptedName(s.name)); | |
| 132 Iterable<Selector> ordinarySelectors = trivialNsmHandlers.where( | |
| 133 (Selector s) => !backend.isInterceptedName(s.name)); | |
| 151 | 134 |
| 152 // Get the short names (JS names, perhaps minified). | 135 // Get the short names (JS names, perhaps minified). |
| 153 Iterable<String> shorts = trivialNsmHandlers.map((selector) => | 136 List<jsAst.Name> specialShorts = |
| 154 namer.invocationMirrorInternalName(selector)); | 137 specialSelectors.map(namer.invocationMirrorInternalName); |
| 155 var diffEncoding = new StringBuffer(); | 138 List<jsAst.Name> ordinaryShorts = |
| 156 | 139 ordinarySelectors.map(namer.invocationMirrorInternalName); |
| 157 // Treat string as a number in base 88 with digits in ASCII order from # to | |
| 158 // z. The short name sorting is based on length, and uses ASCII order for | |
| 159 // equal length strings so this means that names are ascending. The hash | |
| 160 // character, #, is never given as input, but we need it because it's the | |
| 161 // implicit leading zero (otherwise we could not code names with leading | |
| 162 // dollar signs). | |
| 163 int fromBase88(String x) { | |
| 164 int answer = 0; | |
| 165 for (int i = 0; i < x.length; i++) { | |
| 166 int c = x.codeUnitAt(i); | |
| 167 // No support for Unicode minified identifiers in JS. | |
| 168 assert(c >= $$ && c <= $z); | |
| 169 answer *= 88; | |
| 170 answer += c - $HASH; | |
| 171 } | |
| 172 return answer; | |
| 173 } | |
| 174 | |
| 175 // Big endian encoding, A = 0, B = 1... | |
| 176 // A lower case letter terminates the number. | |
| 177 String toBase26(int x) { | |
| 178 int c = x; | |
| 179 var encodingChars = <int>[]; | |
| 180 encodingChars.add($a + (c % 26)); | |
| 181 while (true) { | |
| 182 c ~/= 26; | |
| 183 if (c == 0) break; | |
| 184 encodingChars.add($A + (c % 26)); | |
| 185 } | |
| 186 return new String.fromCharCodes(encodingChars.reversed.toList()); | |
| 187 } | |
| 188 | 140 |
| 189 bool minify = compiler.enableMinification; | 141 bool minify = compiler.enableMinification; |
| 190 bool useDiffEncoding = minify && shorts.length > 30; | 142 bool useDiffEncoding = minify && trivialNsmHandlers.length > 30; |
| 191 | 143 |
| 192 int previous = 0; | 144 jsAst.Expression diffEncoding = new _DiffEncodedListOfNames( |
| 193 int nameCounter = 0; | 145 [specialShorts, ordinaryShorts], |
| 194 for (String short in shorts) { | 146 useDiffEncoding); |
| 195 // Emit period that resets the diff base to zero when we switch to normal | |
| 196 // calling convention (this avoids the need to code negative diffs). | |
| 197 if (useDiffEncoding && nameCounter == firstNormalSelector) { | |
| 198 diffEncoding.write("."); | |
| 199 previous = 0; | |
| 200 } | |
| 201 if (short.length <= MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING && | |
| 202 useDiffEncoding) { | |
| 203 int base63 = fromBase88(short); | |
| 204 int diff = base63 - previous; | |
| 205 previous = base63; | |
| 206 String base26Diff = toBase26(diff); | |
| 207 diffEncoding.write(base26Diff); | |
| 208 } else { | |
| 209 if (useDiffEncoding || diffEncoding.length != 0) { | |
| 210 diffEncoding.write(","); | |
| 211 } | |
| 212 diffEncoding.write(short); | |
| 213 } | |
| 214 nameCounter++; | |
| 215 } | |
| 216 | 147 |
| 217 // Startup code that loops over the method names and puts handlers on the | 148 // Startup code that loops over the method names and puts handlers on the |
| 218 // Object class to catch noSuchMethod invocations. | 149 // Object class to catch noSuchMethod invocations. |
| 219 ClassElement objectClass = compiler.objectClass; | 150 ClassElement objectClass = compiler.objectClass; |
| 220 jsAst.Expression createInvocationMirror = backend.emitter | 151 jsAst.Expression createInvocationMirror = backend.emitter |
| 221 .staticFunctionAccess(backend.getCreateInvocationMirror()); | 152 .staticFunctionAccess(backend.getCreateInvocationMirror()); |
| 222 if (useDiffEncoding) { | 153 if (useDiffEncoding) { |
| 223 statements.add(js.statement('''{ | 154 statements.add(js.statement('''{ |
| 224 var objectClassObject = processedClasses.collected[#objectClass], | 155 var objectClassObject = processedClasses.collected[#objectClass], |
| 225 shortNames = #diffEncoding.split(","), | 156 nameSequences = #diffEncoding.split("."), |
| 226 nameNumber = 0, | 157 shortNames = []; |
| 227 diffEncodedString = shortNames[0], | 158 for (var j = 0; j < nameSequences.length; ++j) { |
| 228 calculatedShortNames = [0, 1]; // 0, 1 are args for splice. | 159 var sequence = nameSequences[j].split(","), |
| 229 // If we are loading a deferred library the object class will not be i n | 160 nameNumber = 0, |
| 230 // the collectedClasses so objectClassObject is undefined, and we skip | 161 diffEncodedString = sequence[0]; |
|
sra1
2015/06/23 20:54:16
Move under 'if (sequence.length)'.
If sequence can
herhut
2015/06/24 07:55:07
Done.
| |
| 231 // setting up the names. | 162 // If we are loading a deferred library the object class will not be |
| 232 | 163 // in the collectedClasses so objectClassObject is undefined, and we |
| 233 if (objectClassObject) { | 164 // skip setting up the names. |
| 234 if (objectClassObject instanceof Array) | 165 // Likewise, if the current sequence is empty, we don't process it. |
| 235 objectClassObject = objectClassObject[1]; | 166 if (objectClassObject && sequence.length) { |
| 236 for (var i = 0; i < diffEncodedString.length; i++) { | 167 if (objectClassObject instanceof Array) |
| 237 var codes = [], | 168 objectClassObject = objectClassObject[1]; |
|
sra1
2015/06/23 20:54:16
This should probably be hoisted out of the j loop.
herhut
2015/06/24 07:55:07
Done.
| |
| 238 diff = 0, | 169 for (var i = 0; i < diffEncodedString.length; i++) { |
| 239 digit = diffEncodedString.charCodeAt(i); | 170 var codes = [], |
| 240 if (digit == ${$PERIOD}) { | 171 diff = 0, |
| 241 nameNumber = 0; | 172 digit = diffEncodedString.charCodeAt(i); |
| 242 digit = diffEncodedString.charCodeAt(++i); | 173 for (; digit <= ${$Z};) { |
| 174 diff *= 26; | |
| 175 diff += (digit - ${$A}); | |
| 176 digit = diffEncodedString.charCodeAt(++i); | |
| 177 } | |
| 178 diff *= 26; | |
| 179 diff += (digit - ${$a}); | |
| 180 nameNumber += diff; | |
| 181 for (var remaining = nameNumber; | |
| 182 remaining > 0; | |
| 183 remaining = (remaining / 88) | 0) { | |
| 184 codes.unshift(${$HASH} + remaining % 88); | |
|
sra1
2015/06/23 20:54:16
I'm curious of you noticed this showing up in star
herhut
2015/06/24 07:55:07
No, I was not even aware this existed before worki
| |
| 185 } | |
| 186 shortNames.push( | |
| 187 String.fromCharCode.apply(String, codes)); | |
| 243 } | 188 } |
| 244 for (; digit <= ${$Z};) { | 189 Array.prototype.push.apply(shortNames, sequence.shift()); |
| 245 diff *= 26; | |
| 246 diff += (digit - ${$A}); | |
| 247 digit = diffEncodedString.charCodeAt(++i); | |
| 248 } | |
| 249 diff *= 26; | |
| 250 diff += (digit - ${$a}); | |
| 251 nameNumber += diff; | |
| 252 for (var remaining = nameNumber; | |
| 253 remaining > 0; | |
| 254 remaining = (remaining / 88) | 0) { | |
| 255 codes.unshift(${$HASH} + remaining % 88); | |
| 256 } | |
| 257 calculatedShortNames.push( | |
| 258 String.fromCharCode.apply(String, codes)); | |
| 259 } | 190 } |
| 260 shortNames.splice.apply(shortNames, calculatedShortNames); | |
| 261 } | 191 } |
| 262 }''', {'objectClass': js.string(namer.className(objectClass)), | 192 }''', {'objectClass': js.quoteName(namer.className(objectClass)), |
| 263 'diffEncoding': js.string('$diffEncoding')})); | 193 'diffEncoding': diffEncoding})); |
| 264 } else { | 194 } else { |
| 265 // No useDiffEncoding version. | 195 // No useDiffEncoding version. |
| 266 Iterable<String> longs = trivialNsmHandlers.map((selector) => | 196 Iterable<String> longs = trivialNsmHandlers.map((selector) => |
| 267 selector.invocationMirrorMemberName); | 197 selector.invocationMirrorMemberName); |
| 268 statements.add(js.statement( | 198 statements.add(js.statement( |
| 269 'var objectClassObject = processedClasses.collected[#objectClass],' | 199 'var objectClassObject = processedClasses.collected[#objectClass],' |
| 270 ' shortNames = #diffEncoding.split(",")', | 200 ' shortNames = #diffEncoding.split(",")', |
| 271 {'objectClass': js.string(namer.className(objectClass)), | 201 {'objectClass': js.quoteName(namer.className(objectClass)), |
| 272 'diffEncoding': js.string('$diffEncoding')})); | 202 'diffEncoding': diffEncoding})); |
| 273 if (!minify) { | 203 if (!minify) { |
| 274 statements.add(js.statement('var longNames = #longs.split(",")', | 204 statements.add(js.statement('var longNames = #longs.split(",")', |
| 275 {'longs': js.string(longs.join(','))})); | 205 {'longs': js.string(longs.join(','))})); |
| 276 } | 206 } |
| 277 statements.add(js.statement( | 207 statements.add(js.statement( |
| 278 'if (objectClassObject instanceof Array)' | 208 'if (objectClassObject instanceof Array)' |
| 279 ' objectClassObject = objectClassObject[1];')); | 209 ' objectClassObject = objectClassObject[1];')); |
| 280 } | 210 } |
| 281 | 211 |
| 282 List<jsAst.Expression> sliceOffsetArguments = | 212 List<jsAst.Expression> sliceOffsetArguments = |
| 283 firstNormalSelector == 0 | 213 specialSelectors.isEmpty |
| 284 ? [] | 214 ? [] |
| 285 : (firstNormalSelector == shorts.length | 215 : (ordinarySelectors.isEmpty |
| 286 ? [js.number(1)] | 216 ? [js.number(1)] |
| 287 : [js('(j < #) ? 1 : 0', js.number(firstNormalSelector))]); | 217 : [js('(j < #) ? 1 : 0', js.number(specialSelectors.length))]); |
|
sra1
2015/06/23 20:54:16
I fixed a bug in this code, sorry that you will ha
herhut
2015/06/24 07:55:07
Acknowledged.
| |
| 288 | 218 |
| 289 var sliceOffsetParams = sliceOffsetArguments.isEmpty ? [] : ['sliceOffset']; | 219 var sliceOffsetParams = sliceOffsetArguments.isEmpty ? [] : ['sliceOffset']; |
| 290 | 220 |
| 291 statements.add(js.statement(''' | 221 statements.add(js.statement(''' |
| 292 // If we are loading a deferred library the object class will not be in | 222 // If we are loading a deferred library the object class will not be in |
| 293 // the collectedClasses so objectClassObject is undefined, and we skip | 223 // the collectedClasses so objectClassObject is undefined, and we skip |
| 294 // setting up the names. | 224 // setting up the names. |
| 295 if (objectClassObject) { | 225 if (objectClassObject) { |
| 296 for (var j = 0; j < shortNames.length; j++) { | 226 for (var j = 0; j < shortNames.length; j++) { |
| 297 var type = 0; | 227 var type = 0; |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 317 }''', { | 247 }''', { |
| 318 'sliceOffsetParams': sliceOffsetParams, | 248 'sliceOffsetParams': sliceOffsetParams, |
| 319 'noSuchMethodName': namer.noSuchMethodName, | 249 'noSuchMethodName': namer.noSuchMethodName, |
| 320 'createInvocationMirror': createInvocationMirror, | 250 'createInvocationMirror': createInvocationMirror, |
| 321 'names': minify ? 'shortNames' : 'longNames', | 251 'names': minify ? 'shortNames' : 'longNames', |
| 322 'sliceOffsetArguments': sliceOffsetArguments})); | 252 'sliceOffsetArguments': sliceOffsetArguments})); |
| 323 | 253 |
| 324 return statements; | 254 return statements; |
| 325 } | 255 } |
| 326 } | 256 } |
| 257 | |
| 258 /// When pretty printed, this node computes a diff-encoded string for the list | |
| 259 /// of given names. | |
| 260 /// | |
| 261 /// See [buildTrivialNsmHandlers]. | |
| 262 class _DiffEncodedListOfNames extends jsAst.DeferredString | |
| 263 implements AstContainer { | |
| 264 String _cachedValue; | |
| 265 jsAst.ArrayInitializer ast; | |
| 266 bool useDiffEncoding; | |
| 267 | |
| 268 _DiffEncodedListOfNames(Iterable<Iterable<jsAst.Name>> names, | |
| 269 this.useDiffEncoding) { | |
| 270 // Store the names in ArrayInitializer nodes to make them discoverable | |
| 271 // by traversals of the ast. | |
| 272 ast = new jsAst.ArrayInitializer( | |
| 273 names.map((Iterable i) => new jsAst.ArrayInitializer(i.toList())) | |
| 274 .toList()); | |
| 275 } | |
| 276 | |
| 277 void _computeDiffEncodingForList(Iterable<jsAst.Name> names, | |
| 278 StringBuffer diffEncoding) { | |
| 279 // Treat string as a number in base 88 with digits in ASCII order from # to | |
| 280 // z. The short name sorting is based on length, and uses ASCII order for | |
| 281 // equal length strings so this means that names are ascending. The hash | |
| 282 // character, #, is never given as input, but we need it because it's the | |
| 283 // implicit leading zero (otherwise we could not code names with leading | |
| 284 // dollar signs). | |
| 285 int fromBase88(String x) { | |
| 286 int answer = 0; | |
| 287 for (int i = 0; i < x.length; i++) { | |
| 288 int c = x.codeUnitAt(i); | |
| 289 // No support for Unicode minified identifiers in JS. | |
| 290 assert(c >= $$ && c <= $z); | |
| 291 answer *= 88; | |
| 292 answer += c - $HASH; | |
| 293 } | |
| 294 return answer; | |
| 295 } | |
| 296 | |
| 297 // Big endian encoding, A = 0, B = 1... | |
| 298 // A lower case letter terminates the number. | |
| 299 String toBase26(int x) { | |
| 300 int c = x; | |
| 301 var encodingChars = <int>[]; | |
| 302 encodingChars.add($a + (c % 26)); | |
| 303 while (true) { | |
| 304 c ~/= 26; | |
| 305 if (c == 0) break; | |
| 306 encodingChars.add($A + (c % 26)); | |
| 307 } | |
| 308 return new String.fromCharCodes(encodingChars.reversed.toList()); | |
| 309 } | |
| 310 | |
| 311 // Sort by length, then lexicographic. | |
| 312 int compare(String a, String b) { | |
| 313 if (a.length != b.length) return a.length - b.length; | |
| 314 return a.compareTo(b); | |
| 315 } | |
| 316 | |
| 317 List<String> shorts = | |
| 318 names.map((jsAst.Name name) => name.name) | |
| 319 .toList() | |
| 320 ..sort(compare); | |
| 321 | |
| 322 int previous = 0; | |
| 323 for (String short in shorts) { | |
| 324 if (useDiffEncoding && | |
| 325 short.length <= NsmEmitter.MAX_MINIFIED_LENGTH_FOR_DIFF_ENCODING) { | |
| 326 int base63 = fromBase88(short); | |
| 327 int diff = base63 - previous; | |
| 328 previous = base63; | |
| 329 String base26Diff = toBase26(diff); | |
| 330 diffEncoding.write(base26Diff); | |
| 331 } else { | |
| 332 if (useDiffEncoding || diffEncoding.length != 0) { | |
| 333 diffEncoding.write(','); | |
| 334 } | |
| 335 diffEncoding.write(short); | |
| 336 } | |
| 337 } | |
| 338 } | |
| 339 | |
| 340 String _computeDiffEncoding() { | |
| 341 StringBuffer buffer = new StringBuffer(); | |
| 342 for (jsAst.ArrayInitializer list in ast.elements) { | |
| 343 if (buffer.isNotEmpty) { | |
| 344 if (useDiffEncoding) { | |
| 345 // Emit period that resets the diff base to zero when we switch to | |
| 346 // normal calling convention (this avoids the need to code negative | |
| 347 // diffs). | |
| 348 buffer.write("."); | |
| 349 } else { | |
| 350 // Write a separator for the next sequence. | |
| 351 buffer.write(","); | |
| 352 } | |
| 353 } | |
| 354 List<jsAst.Name> names = list.elements; | |
| 355 _computeDiffEncodingForList(names, buffer); | |
| 356 } | |
| 357 return buffer.toString(); | |
| 358 } | |
| 359 | |
| 360 String get value { | |
| 361 if (_cachedValue == null) { | |
| 362 _cachedValue = _computeDiffEncoding(); | |
| 363 } | |
| 364 | |
| 365 return _cachedValue; | |
| 366 } | |
| 367 } | |
| OLD | NEW |