OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 | 7 |
8 class OldEmitter implements Emitter { | 8 class OldEmitter implements Emitter { |
9 final Compiler compiler; | 9 final Compiler compiler; |
10 final CodeEmitterTask task; | 10 final CodeEmitterTask task; |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
67 | 67 |
68 CodeBuffer get mainBuffer { | 68 CodeBuffer get mainBuffer { |
69 return getBuffer(compiler.deferredLoadTask.mainOutputUnit); | 69 return getBuffer(compiler.deferredLoadTask.mainOutputUnit); |
70 } | 70 } |
71 | 71 |
72 /** | 72 /** |
73 * List of expressions and statements that will be included in the | 73 * List of expressions and statements that will be included in the |
74 * precompiled function. | 74 * precompiled function. |
75 * | 75 * |
76 * To save space, dart2js normally generates constructors and accessors | 76 * To save space, dart2js normally generates constructors and accessors |
77 * dynamically. This doesn't work in CSP mode, and may impact startup time | 77 * dynamically. This doesn't work in CSP mode, so dart2js emits them directly |
78 * negatively. So dart2js will emit these functions to a separate file that | 78 * when in CSP mode. |
79 * can be optionally included to support CSP mode or for faster startup. | |
80 */ | 79 */ |
81 Map<OutputUnit, List<jsAst.Node>> _cspPrecompiledFunctions = | 80 Map<OutputUnit, List<jsAst.Node>> _cspPrecompiledFunctions = |
82 new Map<OutputUnit, List<jsAst.Node>>(); | 81 new Map<OutputUnit, List<jsAst.Node>>(); |
83 | 82 |
84 Map<OutputUnit, List<jsAst.Expression>> _cspPrecompiledConstructorNames = | 83 Map<OutputUnit, List<jsAst.Expression>> _cspPrecompiledConstructorNames = |
85 new Map<OutputUnit, List<jsAst.Expression>>(); | 84 new Map<OutputUnit, List<jsAst.Expression>>(); |
86 | 85 |
87 /** | 86 /** |
88 * Accumulate properties for classes and libraries, describing their | 87 * Accumulate properties for classes and libraries, describing their |
89 * static/top-level members. | 88 * static/top-level members. |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
159 String get finishIsolateConstructorName | 158 String get finishIsolateConstructorName |
160 => '${namer.isolateName}.\$finishIsolateConstructor'; | 159 => '${namer.isolateName}.\$finishIsolateConstructor'; |
161 String get isolatePropertiesName | 160 String get isolatePropertiesName |
162 => '${namer.isolateName}.${namer.isolatePropertiesName}'; | 161 => '${namer.isolateName}.${namer.isolatePropertiesName}'; |
163 String get lazyInitializerName | 162 String get lazyInitializerName |
164 => '${namer.isolateName}.\$lazy'; | 163 => '${namer.isolateName}.\$lazy'; |
165 String get initName => 'init'; | 164 String get initName => 'init'; |
166 String get makeConstListProperty | 165 String get makeConstListProperty |
167 => namer.getMappedInstanceName('makeConstantList'); | 166 => namer.getMappedInstanceName('makeConstantList'); |
168 | 167 |
| 168 /// The name of the property that contains all field names. |
| 169 /// |
| 170 /// This property is added to constructors when isolate support is enabled. |
| 171 static const String FIELD_NAMES_PROPERTY_NAME = r"$__fields__"; |
| 172 |
169 /// For deferred loading we communicate the initializers via this global var. | 173 /// For deferred loading we communicate the initializers via this global var. |
170 final String deferredInitializers = r"$dart_deferred_initializers"; | 174 final String deferredInitializers = r"$dart_deferred_initializers"; |
171 | 175 |
172 /// All the global state can be passed around with this variable. | 176 /// All the global state can be passed around with this variable. |
173 String get globalsHolder => namer.getMappedGlobalName("globalsHolder"); | 177 String get globalsHolder => namer.getMappedGlobalName("globalsHolder"); |
174 | 178 |
175 jsAst.Expression generateEmbeddedGlobalAccess(String global) { | 179 jsAst.Expression generateEmbeddedGlobalAccess(String global) { |
176 return js(generateEmbeddedGlobalAccessString(global)); | 180 return js(generateEmbeddedGlobalAccessString(global)); |
177 } | 181 } |
178 | 182 |
179 String generateEmbeddedGlobalAccessString(String global) { | 183 String generateEmbeddedGlobalAccessString(String global) { |
180 // TODO(floitsch): don't use 'init' as global embedder storage. | 184 // TODO(floitsch): don't use 'init' as global embedder storage. |
181 return '$initName.$global'; | 185 return '$initName.$global'; |
182 } | 186 } |
183 | 187 |
184 jsAst.Expression isolateLazyInitializerAccess(Element element) { | 188 jsAst.Expression isolateLazyInitializerAccess(Element element) { |
185 return jsAst.js('#.#', [namer.globalObjectFor(element), | 189 return jsAst.js('#.#', [namer.globalObjectFor(element), |
186 namer.getLazyInitializerName(element)]); | 190 namer.getLazyInitializerName(element)]); |
187 } | 191 } |
188 | 192 |
189 jsAst.Expression isolateStaticClosureAccess(Element element) { | 193 jsAst.Expression isolateStaticClosureAccess(Element element) { |
190 return jsAst.js('#.#()', | 194 return jsAst.js('#.#()', |
191 [namer.globalObjectFor(element), namer.getStaticClosureName(element)]); | 195 [namer.globalObjectFor(element), namer.getStaticClosureName(element)]); |
192 } | 196 } |
193 | 197 |
194 jsAst.PropertyAccess globalPropertyAccess(Element element) { | 198 jsAst.PropertyAccess globalPropertyAccess(Element element) { |
195 String name = namer.getNameX(element); | 199 String name = namer.getNameX(element); |
196 jsAst.PropertyAccess pa = new jsAst.PropertyAccess.field( | 200 jsAst.PropertyAccess pa = new jsAst.PropertyAccess.field( |
197 new jsAst.VariableUse(namer.globalObjectFor(element)), | 201 new jsAst.VariableUse(namer.globalObjectFor(element)), |
198 name); | 202 name); |
199 return pa; | 203 return pa; |
200 } | 204 } |
201 | 205 |
202 jsAst.PropertyAccess staticFieldAccess(Element element) { | 206 jsAst.PropertyAccess staticFieldAccess(Element element) { |
203 return globalPropertyAccess(element); | 207 return globalPropertyAccess(element); |
204 } | 208 } |
205 | 209 |
206 jsAst.PropertyAccess staticFunctionAccess(Element element) { | 210 jsAst.PropertyAccess staticFunctionAccess(Element element) { |
207 return globalPropertyAccess(element); | 211 return globalPropertyAccess(element); |
208 } | 212 } |
209 | 213 |
210 jsAst.PropertyAccess classAccess(Element element) { | 214 jsAst.PropertyAccess classAccess(Element element) { |
211 return globalPropertyAccess(element); | 215 return globalPropertyAccess(element); |
212 } | 216 } |
213 | 217 |
214 jsAst.PropertyAccess typedefAccess(Element element) { | 218 jsAst.PropertyAccess typedefAccess(Element element) { |
215 return globalPropertyAccess(element); | 219 return globalPropertyAccess(element); |
216 } | 220 } |
217 | 221 |
218 jsAst.FunctionDeclaration get generateAccessorFunction { | 222 jsAst.FunctionDeclaration get generateAccessorFunction { |
219 const RANGE1_SIZE = RANGE1_LAST - RANGE1_FIRST + 1; | 223 const RANGE1_SIZE = RANGE1_LAST - RANGE1_FIRST + 1; |
220 const RANGE2_SIZE = RANGE2_LAST - RANGE2_FIRST + 1; | 224 const RANGE2_SIZE = RANGE2_LAST - RANGE2_FIRST + 1; |
221 const RANGE1_ADJUST = - (FIRST_FIELD_CODE - RANGE1_FIRST); | 225 const RANGE1_ADJUST = - (FIRST_FIELD_CODE - RANGE1_FIRST); |
222 const RANGE2_ADJUST = - (FIRST_FIELD_CODE + RANGE1_SIZE - RANGE2_FIRST); | 226 const RANGE2_ADJUST = - (FIRST_FIELD_CODE + RANGE1_SIZE - RANGE2_FIRST); |
223 const RANGE3_ADJUST = | 227 const RANGE3_ADJUST = |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
281 accessors.push(property + "\$reflectable(" + fn + ");\\n"); | 285 accessors.push(property + "\$reflectable(" + fn + ");\\n"); |
282 else | 286 else |
283 accessors.push(property + fn + ";\\n"); | 287 accessors.push(property + fn + ";\\n"); |
284 } | 288 } |
285 } | 289 } |
286 | 290 |
287 return field; | 291 return field; |
288 }'''); | 292 }'''); |
289 } | 293 } |
290 | 294 |
291 List get defineClassFunction { | 295 List<jsAst.Node> get defineClassFunction { |
292 // First the class name, then the field names in an array and the members | 296 // First the class name, then the field names in an array and the members |
293 // (inside an Object literal). | 297 // (inside an Object literal). |
294 // The caller can also pass in the constructor as a function if needed. | 298 // The caller can also pass in the constructor as a function if needed. |
295 // | 299 // |
296 // Example: | 300 // Example: |
297 // defineClass("A", ["x", "y"], { | 301 // defineClass("A", ["x", "y"], { |
298 // foo$1: function(y) { | 302 // foo$1: function(y) { |
299 // print(this.x + y); | 303 // print(this.x + y); |
300 // }, | 304 // }, |
301 // bar$2: function(t, v) { | 305 // bar$2: function(t, v) { |
302 // this.x = t - v; | 306 // this.x = t - v; |
303 // }, | 307 // }, |
304 // }); | 308 // }); |
305 | 309 |
306 var defineClass = js('''function(name, fields) { | 310 bool hasIsolateSupport = compiler.hasIsolateSupport; |
307 var accessors = []; | 311 String fieldNamesProperty = FIELD_NAMES_PROPERTY_NAME; |
308 | 312 |
309 var str = "function " + name + "("; | 313 jsAst.Expression defineClass = js(''' |
310 var body = ""; | 314 function(name, fields) { |
| 315 var accessors = []; |
| 316 |
| 317 var str = "function " + name + "("; |
| 318 var body = ""; |
| 319 if (#hasIsolateSupport) { var fieldNames = ""; } |
| 320 |
| 321 for (var i = 0; i < fields.length; i++) { |
| 322 if(i != 0) str += ", "; |
| 323 |
| 324 var field = generateAccessor(fields[i], accessors, name); |
| 325 if (#hasIsolateSupport) { fieldNames += "'" + field + "',"; } |
| 326 var parameter = "parameter_" + field; |
| 327 str += parameter; |
| 328 body += ("this." + field + " = " + parameter + ";\\n"); |
| 329 } |
| 330 str += ") {\\n" + body + "}\\n"; |
| 331 str += name + ".builtin\$cls=\\"" + name + "\\";\\n"; |
| 332 str += "\$desc=\$collectedClasses." + name + ";\\n"; |
| 333 str += "if(\$desc instanceof Array) \$desc = \$desc[1];\\n"; |
| 334 str += name + ".prototype = \$desc;\\n"; |
| 335 if (typeof defineClass.name != "string") { |
| 336 str += name + ".name=\\"" + name + "\\";\\n"; |
| 337 } |
| 338 if (#hasIsolateSupport) { |
| 339 str += name + ".$fieldNamesProperty=[" + fieldNames + "];\\n"; |
| 340 } |
| 341 str += accessors.join(""); |
| 342 |
| 343 return str; |
| 344 }''', { 'hasIsolateSupport': hasIsolateSupport }); |
311 | 345 |
312 for (var i = 0; i < fields.length; i++) { | |
313 if(i != 0) str += ", "; | |
314 | |
315 var field = generateAccessor(fields[i], accessors, name); | |
316 var parameter = "parameter_" + field; | |
317 str += parameter; | |
318 body += ("this." + field + " = " + parameter + ";\\n"); | |
319 } | |
320 str += ") {\\n" + body + "}\\n"; | |
321 str += name + ".builtin\$cls=\\"" + name + "\\";\\n"; | |
322 str += "\$desc=\$collectedClasses." + name + ";\\n"; | |
323 str += "if(\$desc instanceof Array) \$desc = \$desc[1];\\n"; | |
324 str += name + ".prototype = \$desc;\\n"; | |
325 if (typeof defineClass.name != "string") { | |
326 str += name + ".name=\\"" + name + "\\";\\n"; | |
327 } | |
328 str += accessors.join(""); | |
329 | |
330 return str; | |
331 }'''); | |
332 // Declare a function called "generateAccessor". This is used in | 346 // Declare a function called "generateAccessor". This is used in |
333 // defineClassFunction (it's a local declaration in init()). | 347 // defineClassFunction (it's a local declaration in init()). |
334 List<jsAst.Node> saveDefineClass = []; | 348 List result = <jsAst.Node>[ |
335 if (compiler.hasIncrementalSupport) { | |
336 saveDefineClass.add( | |
337 js(r'self.$dart_unsafe_eval.defineClass = defineClass')); | |
338 } | |
339 return [ | |
340 generateAccessorFunction, | 349 generateAccessorFunction, |
341 js('$generateAccessorHolder = generateAccessor'), | 350 js('$generateAccessorHolder = generateAccessor'), |
342 new jsAst.FunctionDeclaration( | 351 new jsAst.FunctionDeclaration( |
343 new jsAst.VariableDeclaration('defineClass'), defineClass) ] | 352 new jsAst.VariableDeclaration('defineClass'), defineClass) ]; |
344 ..addAll(saveDefineClass); | 353 |
| 354 if (compiler.hasIncrementalSupport) { |
| 355 result.add( |
| 356 js(r'self.$dart_unsafe_eval.defineClass = defineClass')); |
| 357 } |
| 358 |
| 359 if (hasIsolateSupport) { |
| 360 jsAst.Expression classIdExtractorAccess = |
| 361 generateEmbeddedGlobalAccess(embeddedNames.CLASS_ID_EXTRACTOR); |
| 362 var classIdExtractorAssignment = |
| 363 js('# = function(o) { return o.constructor.name; }', |
| 364 classIdExtractorAccess); |
| 365 |
| 366 jsAst.Expression classFieldsExtractorAccess = |
| 367 generateEmbeddedGlobalAccess(embeddedNames.CLASS_FIELDS_EXTRACTOR); |
| 368 var classFieldsExtractorAssignment = js(''' |
| 369 # = function(o) { |
| 370 var fieldNames = o.constructor.$fieldNamesProperty; |
| 371 if (!fieldNames) return []; // TODO(floitsch): do something else here. |
| 372 var result = []; |
| 373 result.length = fieldNames.length; |
| 374 for (var i = 0; i < fieldNames.length; i++) { |
| 375 result[i] = o[fieldNames[i]]; |
| 376 } |
| 377 return result; |
| 378 }''', classFieldsExtractorAccess); |
| 379 |
| 380 jsAst.Expression instanceFromClassIdAccess = |
| 381 generateEmbeddedGlobalAccess(embeddedNames.INSTANCE_FROM_CLASS_ID); |
| 382 jsAst.Expression allClassesAccess = |
| 383 generateEmbeddedGlobalAccess(embeddedNames.ALL_CLASSES); |
| 384 var instanceFromClassIdAssignment = |
| 385 js('# = function(name) { return new #[name](); }', |
| 386 [instanceFromClassIdAccess, allClassesAccess]); |
| 387 |
| 388 jsAst.Expression initializeEmptyInstanceAccess = |
| 389 generateEmbeddedGlobalAccess(embeddedNames.INITIALIZE_EMPTY_INSTANCE); |
| 390 var initializeEmptyInstanceAssignment = js(''' |
| 391 # = function(name, o, fields) { |
| 392 #[name].apply(o, fields); |
| 393 return o; |
| 394 }''', [ initializeEmptyInstanceAccess, allClassesAccess ]); |
| 395 |
| 396 result.addAll([classIdExtractorAssignment, |
| 397 classFieldsExtractorAssignment, |
| 398 instanceFromClassIdAssignment, |
| 399 initializeEmptyInstanceAssignment]); |
| 400 } |
| 401 |
| 402 return result; |
345 } | 403 } |
346 | 404 |
347 /** Needs defineClass to be defined. */ | 405 /** Needs defineClass to be defined. */ |
348 jsAst.Expression buildInheritFrom() { | 406 jsAst.Expression buildInheritFrom() { |
349 jsAst.Expression result = js(r''' | 407 jsAst.Expression result = js(r''' |
350 function() { | 408 function() { |
351 function tmp() {} | 409 function tmp() {} |
352 var hasOwnProperty = Object.prototype.hasOwnProperty; | 410 var hasOwnProperty = Object.prototype.hasOwnProperty; |
353 return function (constructor, superConstructor) { | 411 return function (constructor, superConstructor) { |
354 tmp.prototype = superConstructor.prototype; | 412 tmp.prototype = superConstructor.prototype; |
(...skipping 948 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1303 ..write(',$_') | 1361 ..write(',$_') |
1304 ..write(jsAst.prettyPrint(initializers, | 1362 ..write(jsAst.prettyPrint(initializers, |
1305 compiler, | 1363 compiler, |
1306 monitor: compiler.dumpInfoTask)) | 1364 monitor: compiler.dumpInfoTask)) |
1307 ..write(library == compiler.mainApp ? ',${n}1' : "") | 1365 ..write(library == compiler.mainApp ? ',${n}1' : "") |
1308 ..write('],$n'); | 1366 ..write('],$n'); |
1309 } | 1367 } |
1310 | 1368 |
1311 void emitPrecompiledConstructor(OutputUnit outputUnit, | 1369 void emitPrecompiledConstructor(OutputUnit outputUnit, |
1312 String constructorName, | 1370 String constructorName, |
1313 jsAst.Expression constructorAst) { | 1371 jsAst.Expression constructorAst, |
| 1372 List<String> fields) { |
1314 cspPrecompiledFunctionFor(outputUnit).add( | 1373 cspPrecompiledFunctionFor(outputUnit).add( |
1315 new jsAst.FunctionDeclaration( | 1374 new jsAst.FunctionDeclaration( |
1316 new jsAst.VariableDeclaration(constructorName), constructorAst)); | 1375 new jsAst.VariableDeclaration(constructorName), constructorAst)); |
1317 cspPrecompiledFunctionFor(outputUnit).add( | 1376 |
1318 js.statement(r'''{ | 1377 String fieldNamesProperty = FIELD_NAMES_PROPERTY_NAME; |
1319 #.builtin$cls = #; | 1378 bool hasIsolateSupport = compiler.hasIsolateSupport; |
1320 if (!"name" in #) | 1379 jsAst.Node fieldNamesArray = |
1321 #.name = #; | 1380 hasIsolateSupport ? js.stringArray(fields) : new jsAst.LiteralNull(); |
1322 $desc=$collectedClasses.#; | 1381 |
| 1382 cspPrecompiledFunctionFor(outputUnit).add(js.statement(r''' |
| 1383 { |
| 1384 #constructorName.builtin$cls = #constructorNameString; |
| 1385 if (!"name" in #constructorName) |
| 1386 #constructorName.name = #constructorNameString; |
| 1387 $desc = $collectedClasses.#constructorName; |
1323 if ($desc instanceof Array) $desc = $desc[1]; | 1388 if ($desc instanceof Array) $desc = $desc[1]; |
1324 #.prototype = $desc; | 1389 #constructorName.prototype = $desc; |
| 1390 ''' /* next string is not a raw string */ ''' |
| 1391 if (#hasIsolateSupport) { |
| 1392 #constructorName.$fieldNamesProperty = #fieldNamesArray; |
| 1393 } |
1325 }''', | 1394 }''', |
1326 [ constructorName, js.string(constructorName), | 1395 {"constructorName": constructorName, |
1327 constructorName, | 1396 "constructorNameString": js.string(constructorName), |
1328 constructorName, js.string(constructorName), | 1397 "hasIsolateSupport": hasIsolateSupport, |
1329 constructorName, | 1398 "fieldNamesArray": fieldNamesArray})); |
1330 constructorName | |
1331 ])); | |
1332 | 1399 |
1333 cspPrecompiledConstructorNamesFor(outputUnit).add(js('#', constructorName)); | 1400 cspPrecompiledConstructorNamesFor(outputUnit).add(js('#', constructorName)); |
1334 } | 1401 } |
1335 | 1402 |
1336 /// Extracts the output name of the compiler's outputUri. | 1403 /// Extracts the output name of the compiler's outputUri. |
1337 String deferredPartFileName(OutputUnit outputUnit, | 1404 String deferredPartFileName(OutputUnit outputUnit, |
1338 {bool addExtension: true}) { | 1405 {bool addExtension: true}) { |
1339 String outPath = compiler.outputUri != null | 1406 String outPath = compiler.outputUri != null |
1340 ? compiler.outputUri.path | 1407 ? compiler.outputUri.path |
1341 : "out"; | 1408 : "out"; |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1384 js.string(typeReference)); | 1451 js.string(typeReference)); |
1385 jsAst.Node declaration = new jsAst.ObjectInitializer([descriptor]); | 1452 jsAst.Node declaration = new jsAst.ObjectInitializer([descriptor]); |
1386 String mangledName = namer.getNameX(typedef); | 1453 String mangledName = namer.getNameX(typedef); |
1387 String reflectionName = getReflectionName(typedef, mangledName); | 1454 String reflectionName = getReflectionName(typedef, mangledName); |
1388 getElementDescriptor(library) | 1455 getElementDescriptor(library) |
1389 ..addProperty(mangledName, declaration) | 1456 ..addProperty(mangledName, declaration) |
1390 ..addProperty("+$reflectionName", js.string('')); | 1457 ..addProperty("+$reflectionName", js.string('')); |
1391 // Also emit a trivial constructor for CSP mode. | 1458 // Also emit a trivial constructor for CSP mode. |
1392 String constructorName = mangledName; | 1459 String constructorName = mangledName; |
1393 jsAst.Expression constructorAst = js('function() {}'); | 1460 jsAst.Expression constructorAst = js('function() {}'); |
| 1461 List<String> fieldNames = []; |
1394 emitPrecompiledConstructor(mainOutputUnit, | 1462 emitPrecompiledConstructor(mainOutputUnit, |
1395 constructorName, | 1463 constructorName, |
1396 constructorAst); | 1464 constructorAst, |
| 1465 fieldNames); |
1397 } | 1466 } |
1398 } | 1467 } |
1399 | 1468 |
1400 void emitMangledNames() { | 1469 void emitMangledNames() { |
1401 if (!mangledFieldNames.isEmpty) { | 1470 if (!mangledFieldNames.isEmpty) { |
1402 var keys = mangledFieldNames.keys.toList(); | 1471 var keys = mangledFieldNames.keys.toList(); |
1403 keys.sort(); | 1472 keys.sort(); |
1404 var properties = []; | 1473 var properties = []; |
1405 for (String key in keys) { | 1474 for (String key in keys) { |
1406 var value = js.string('${mangledFieldNames[key]}'); | 1475 var value = js.string('${mangledFieldNames[key]}'); |
(...skipping 696 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2103 for (Element element in compiler.enqueuer.codegen.newlyEnqueuedElements) { | 2172 for (Element element in compiler.enqueuer.codegen.newlyEnqueuedElements) { |
2104 if (element.isInstanceMember) { | 2173 if (element.isInstanceMember) { |
2105 cachedClassBuilders.remove(element.enclosingClass); | 2174 cachedClassBuilders.remove(element.enclosingClass); |
2106 | 2175 |
2107 nativeEmitter.cachedBuilders.remove(element.enclosingClass); | 2176 nativeEmitter.cachedBuilders.remove(element.enclosingClass); |
2108 | 2177 |
2109 } | 2178 } |
2110 } | 2179 } |
2111 } | 2180 } |
2112 } | 2181 } |
OLD | NEW |