| 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 // TODO(ahe): Share these with js_helper.dart. | 7 // TODO(ahe): Share these with js_helper.dart. |
| 8 const FUNCTION_INDEX = 0; | 8 const FUNCTION_INDEX = 0; |
| 9 const NAME_INDEX = 1; | 9 const NAME_INDEX = 1; |
| 10 const CALL_NAME_INDEX = 2; | 10 const CALL_NAME_INDEX = 2; |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 48 emitter.generateEmbeddedGlobalAccess(embeddedNames.MANGLED_GLOBAL_NAMES); | 48 emitter.generateEmbeddedGlobalAccess(embeddedNames.MANGLED_GLOBAL_NAMES); |
| 49 jsAst.Expression mangledNamesAccess = | 49 jsAst.Expression mangledNamesAccess = |
| 50 emitter.generateEmbeddedGlobalAccess(embeddedNames.MANGLED_NAMES); | 50 emitter.generateEmbeddedGlobalAccess(embeddedNames.MANGLED_NAMES); |
| 51 jsAst.Expression librariesAccess = | 51 jsAst.Expression librariesAccess = |
| 52 emitter.generateEmbeddedGlobalAccess(embeddedNames.LIBRARIES); | 52 emitter.generateEmbeddedGlobalAccess(embeddedNames.LIBRARIES); |
| 53 jsAst.Expression metadataAccess = | 53 jsAst.Expression metadataAccess = |
| 54 emitter.generateEmbeddedGlobalAccess(embeddedNames.METADATA); | 54 emitter.generateEmbeddedGlobalAccess(embeddedNames.METADATA); |
| 55 | 55 |
| 56 jsAst.Statement processClassData = js.statement('''{ | 56 jsAst.Statement processClassData = js.statement('''{ |
| 57 function processClassData(cls, descriptor, processedClasses) { | 57 function processClassData(cls, descriptor, processedClasses) { |
| 58 var newDesc = {}; | 58 descriptor = convertToSlowObject(descriptor); // Use a slow object. |
| 59 var previousProperty; | 59 var previousProperty; |
| 60 for (var property in descriptor) { | 60 var properties = Object.keys(descriptor); |
| 61 if (!hasOwnProperty.call(descriptor, property)) continue; | 61 for (var i = 0; i < properties.length; i++) { |
| 62 var firstChar = property.substring(0, 1); | 62 var property = properties[i]; |
| 63 var firstChar = property.charCodeAt(0); |
| 63 if (property === "static") { | 64 if (property === "static") { |
| 64 processStatics(#embeddedStatics[cls] = descriptor[property], | 65 processStatics(#embeddedStatics[cls] = descriptor.static, |
| 65 processedClasses); | 66 processedClasses); |
| 66 } else if (firstChar === "+") { | 67 } else if (firstChar === 43) { // 43 is "+". |
| 67 mangledNames[previousProperty] = property.substring(1); | 68 mangledNames[previousProperty] = property.substring(1); |
| 68 var flag = descriptor[property]; | 69 var flag = descriptor[property]; |
| 69 if (flag > 0) | 70 if (flag > 0) |
| 70 descriptor[previousProperty].$reflectableField = flag; | 71 descriptor[previousProperty].$reflectableField = flag; |
| 71 } else if (firstChar === "*") { | 72 } else if (firstChar === 42) { // 42 is "*" |
| 72 newDesc[previousProperty].$defaultValuesField = descriptor[property]; | 73 descriptor[previousProperty].$defaultValuesField = descriptor[property]; |
| 73 var optionalMethods = newDesc.$methodsWithOptionalArgumentsField; | 74 var optionalMethods = descriptor.$methodsWithOptionalArgumentsField; |
| 74 if (!optionalMethods) { | 75 if (!optionalMethods) { |
| 75 newDesc.$methodsWithOptionalArgumentsField = optionalMethods={} | 76 descriptor.$methodsWithOptionalArgumentsField = optionalMethods={} |
| 76 } | 77 } |
| 77 optionalMethods[property] = previousProperty; | 78 optionalMethods[property] = previousProperty; |
| 78 } else { | 79 } else { |
| 79 var elem = descriptor[property]; | 80 var elem = descriptor[property]; |
| 80 if (property !== "${namer.classDescriptorProperty}" && | 81 if (property !== "${namer.classDescriptorProperty}" && |
| 81 elem != null && | 82 elem != null && |
| 82 elem.constructor === Array && | 83 elem.constructor === Array && |
| 83 property !== "<>") { | 84 property !== "<>") { |
| 84 addStubs(newDesc, elem, property, false, descriptor, []); | 85 addStubs(descriptor, elem, property, false, []); |
| 85 } else { | 86 } else { |
| 86 newDesc[previousProperty = property] = elem; | 87 previousProperty = property; |
| 87 } | 88 } |
| 88 } | 89 } |
| 89 } | 90 } |
| 90 | 91 |
| 91 /* The 'fields' are either a constructor function or a | 92 /* The 'fields' are either a constructor function or a |
| 92 * string encoding fields, constructor and superclass. Gets the | 93 * string encoding fields, constructor and superclass. Gets the |
| 93 * superclass and fields in the format | 94 * superclass and fields in the format |
| 94 * 'Super;field1,field2' | 95 * 'Super;field1,field2' |
| 95 * from the CLASS_DESCRIPTOR_PROPERTY property on the descriptor. | 96 * from the CLASS_DESCRIPTOR_PROPERTY property on the descriptor. |
| 96 */ | 97 */ |
| 97 var classData = newDesc["${namer.classDescriptorProperty}"], | 98 var classData = descriptor["${namer.classDescriptorProperty}"], |
| 98 split, supr, fields = classData; | 99 split, supr, fields = classData; |
| 99 | 100 |
| 100 if (#hasRetainedMetadata) | 101 if (#hasRetainedMetadata) |
| 101 if (typeof classData == "object" && | 102 if (typeof classData == "object" && |
| 102 classData instanceof Array) { | 103 classData instanceof Array) { |
| 103 classData = fields = classData[0]; | 104 classData = fields = classData[0]; |
| 104 } | 105 } |
| 105 // ${ClassBuilder.fieldEncodingDescription}. | 106 // ${ClassBuilder.fieldEncodingDescription}. |
| 106 var s = fields.split(";"); | 107 var s = fields.split(";"); |
| 107 fields = s[1] == "" ? [] : s[1].split(","); | 108 fields = s[1] == "" ? [] : s[1].split(","); |
| 108 supr = s[0]; | 109 supr = s[0]; |
| 109 // ${ClassBuilder.functionTypeEncodingDescription}. | 110 // ${ClassBuilder.functionTypeEncodingDescription}. |
| 110 split = supr.split(":"); | 111 split = supr.split(":"); |
| 111 if (split.length == 2) { | 112 if (split.length == 2) { |
| 112 supr = split[0]; | 113 supr = split[0]; |
| 113 var functionSignature = split[1]; | 114 var functionSignature = split[1]; |
| 114 if (functionSignature) | 115 if (functionSignature) |
| 115 newDesc.${namer.operatorSignature} = function(s) { | 116 descriptor.${namer.operatorSignature} = function(s) { |
| 116 return function() { | 117 return function() { |
| 117 return #metadata[s]; | 118 return #metadata[s]; |
| 118 }; | 119 }; |
| 119 }(functionSignature); | 120 }(functionSignature); |
| 120 } | 121 } |
| 121 | 122 |
| 122 if (supr) processedClasses.pending[cls] = supr; | 123 if (supr) processedClasses.pending[cls] = supr; |
| 123 if (#notInCspMode) { | 124 if (#notInCspMode) { |
| 124 processedClasses.combinedConstructorFunction += defineClass(cls, fields); | 125 processedClasses.combinedConstructorFunction += defineClass(cls, fields); |
| 125 processedClasses.constructorsList.push(cls); | 126 processedClasses.constructorsList.push(cls); |
| 126 } | 127 } |
| 127 processedClasses.collected[cls] = [globalObject, newDesc]; | 128 processedClasses.collected[cls] = [globalObject, descriptor]; |
| 128 classes.push(cls); | 129 classes.push(cls); |
| 129 } | 130 } |
| 130 }''', {'embeddedStatics': staticsAccess, | 131 }''', {'embeddedStatics': staticsAccess, |
| 131 'hasRetainedMetadata': backend.hasRetainedMetadata, | 132 'hasRetainedMetadata': backend.hasRetainedMetadata, |
| 132 'metadata': metadataAccess, | 133 'metadata': metadataAccess, |
| 133 'notInCspMode': !compiler.useContentSecurityPolicy}); | 134 'notInCspMode': !compiler.useContentSecurityPolicy}); |
| 134 | 135 |
| 135 // TODO(zarah): Remove empty else branches in output when if(#hole) is false. | 136 // TODO(zarah): Remove empty else branches in output when if(#hole) is false. |
| 136 jsAst.Statement processStatics = js.statement(''' | 137 jsAst.Statement processStatics = js.statement(''' |
| 137 function processStatics(descriptor, processedClasses) { | 138 function processStatics(descriptor, processedClasses) { |
| 138 for (var property in descriptor) { | 139 var properties = Object.keys(descriptor); |
| 139 if (!hasOwnProperty.call(descriptor, property)) continue; | 140 for (var i = 0; i < properties.length; i++) { |
| 141 var property = properties[i]; |
| 140 if (property === "${namer.classDescriptorProperty}") continue; | 142 if (property === "${namer.classDescriptorProperty}") continue; |
| 141 var element = descriptor[property]; | 143 var element = descriptor[property]; |
| 142 var firstChar = property.substring(0, 1); | 144 var firstChar = property.charCodeAt(0); |
| 143 var previousProperty; | 145 var previousProperty; |
| 144 if (firstChar === "+") { | 146 if (firstChar === 43) { // 43 is "+". |
| 145 mangledGlobalNames[previousProperty] = property.substring(1); | 147 mangledGlobalNames[previousProperty] = property.substring(1); |
| 146 var flag = descriptor[property]; | 148 var flag = descriptor[property]; |
| 147 if (flag > 0) | 149 if (flag > 0) |
| 148 descriptor[previousProperty].$reflectableField = flag; | 150 descriptor[previousProperty].$reflectableField = flag; |
| 149 if (element && element.length) | 151 if (element && element.length) |
| 150 #typeInformation[previousProperty] = element; | 152 #typeInformation[previousProperty] = element; |
| 151 } else if (firstChar === "*") { | 153 } else if (firstChar === 42) { // 42 is "*" |
| 152 globalObject[previousProperty].$defaultValuesField = element; | 154 globalObject[previousProperty].$defaultValuesField = element; |
| 153 var optionalMethods = descriptor.$methodsWithOptionalArgumentsField; | 155 var optionalMethods = descriptor.$methodsWithOptionalArgumentsField; |
| 154 if (!optionalMethods) { | 156 if (!optionalMethods) { |
| 155 descriptor.$methodsWithOptionalArgumentsField = optionalMethods = {} | 157 descriptor.$methodsWithOptionalArgumentsField = optionalMethods = {} |
| 156 } | 158 } |
| 157 optionalMethods[property] = previousProperty; | 159 optionalMethods[property] = previousProperty; |
| 158 } else if (typeof element === "function") { | 160 } else if (typeof element === "function") { |
| 159 globalObject[previousProperty = property] = element; | 161 globalObject[previousProperty = property] = element; |
| 160 functions.push(property); | 162 functions.push(property); |
| 161 #globalFunctions[property] = element; | 163 #globalFunctions[property] = element; |
| 162 } else if (element.constructor === Array) { | 164 } else if (element.constructor === Array) { |
| 163 if (#needsStructuredMemberInfo) { | 165 if (#needsStructuredMemberInfo) { |
| 164 addStubs(globalObject, element, property, | 166 addStubs(globalObject, element, property, true, functions); |
| 165 true, descriptor, functions); | |
| 166 } | 167 } |
| 167 } else { | 168 } else { |
| 168 // We will not enter this case if no classes are defined. | 169 // We will not enter this case if no classes are defined. |
| 169 if (#hasClasses) { | 170 if (#hasClasses) { |
| 170 previousProperty = property; | 171 previousProperty = property; |
| 171 processClassData(property, element, processedClasses); | 172 processClassData(property, element, processedClasses); |
| 172 } | 173 } |
| 173 } | 174 } |
| 174 } | 175 } |
| 175 } | 176 } |
| 176 ''', {'typeInformation': typeInformationAccess, | 177 ''', {'typeInformation': typeInformationAccess, |
| 177 'globalFunctions': globalFunctionsAccess, | 178 'globalFunctions': globalFunctionsAccess, |
| 178 'hasClasses': oldEmitter.needsClassSupport, | 179 'hasClasses': oldEmitter.needsClassSupport, |
| 179 'needsStructuredMemberInfo': oldEmitter.needsStructuredMemberInfo}); | 180 'needsStructuredMemberInfo': oldEmitter.needsStructuredMemberInfo}); |
| 180 | 181 |
| 181 | 182 |
| 182 /** | 183 /** |
| 183 * See [dart2js.js_emitter.ContainerBuilder.addMemberMethod] for format of | 184 * See [dart2js.js_emitter.ContainerBuilder.addMemberMethod] for format of |
| 184 * [array]. | 185 * [array]. |
| 185 */ | 186 */ |
| 186 jsAst.Statement addStubs = js.statement(''' | 187 jsAst.Statement addStubs = js.statement(''' |
| 187 function addStubs(descriptor, array, name, isStatic, | 188 function addStubs(descriptor, array, name, isStatic, functions) { |
| 188 originalDescriptor, functions) { | |
| 189 var index = $FUNCTION_INDEX, alias = array[index], f; | 189 var index = $FUNCTION_INDEX, alias = array[index], f; |
| 190 if (typeof alias == "string") { | 190 if (typeof alias == "string") { |
| 191 f = array[++index]; | 191 f = array[++index]; |
| 192 } else { | 192 } else { |
| 193 f = alias; | 193 f = alias; |
| 194 alias = name; | 194 alias = name; |
| 195 } | 195 } |
| 196 var funcs = [originalDescriptor[name] = descriptor[name] = | 196 var funcs = [descriptor[name] = descriptor[alias] = f]; |
| 197 descriptor[alias] = f]; | |
| 198 f.\$stubName = name; | 197 f.\$stubName = name; |
| 199 functions.push(name); | 198 functions.push(name); |
| 200 for (; index < array.length; index += 2) { | 199 for (; index < array.length; index += 2) { |
| 201 f = array[index + 1]; | 200 f = array[index + 1]; |
| 202 if (typeof f != "function") break; | 201 if (typeof f != "function") break; |
| 203 f.\$stubName = ${readString("array", "index + 2")}; | 202 f.\$stubName = ${readString("array", "index + 2")}; |
| 204 funcs.push(f); | 203 funcs.push(f); |
| 205 if (f.\$stubName) { | 204 if (f.\$stubName) { |
| 206 originalDescriptor[f.\$stubName] = descriptor[f.\$stubName] = f; | 205 descriptor[f.\$stubName] = f; |
| 207 functions.push(f.\$stubName); | 206 functions.push(f.\$stubName); |
| 208 } | 207 } |
| 209 } | 208 } |
| 209 index++; |
| 210 for (var i = 0; i < funcs.length; index++, i++) { | 210 for (var i = 0; i < funcs.length; index++, i++) { |
| 211 funcs[i].\$callName = ${readString("array", "index + 1")}; | 211 funcs[i].\$callName = ${readString("array", "index")}; |
| 212 } | 212 } |
| 213 var getterStubName = ${readString("array", "++index")}; | 213 var getterStubName = ${readString("array", "index")}; |
| 214 array = array.slice(++index); | 214 array = array.slice(++index); |
| 215 var requiredParameterInfo = ${readInt("array", "0")}; | 215 var requiredParameterInfo = ${readInt("array", "0")}; |
| 216 var requiredParameterCount = requiredParameterInfo >> 1; | 216 var requiredParameterCount = requiredParameterInfo >> 1; |
| 217 var isAccessor = (requiredParameterInfo & 1) === 1; | 217 var isAccessor = (requiredParameterInfo & 1) === 1; |
| 218 var isSetter = requiredParameterInfo === 3; | 218 var isSetter = requiredParameterInfo === 3; |
| 219 var isGetter = requiredParameterInfo === 1; | 219 var isGetter = requiredParameterInfo === 1; |
| 220 var optionalParameterInfo = ${readInt("array", "1")}; | 220 var optionalParameterInfo = ${readInt("array", "1")}; |
| 221 var optionalParameterCount = optionalParameterInfo >> 1; | 221 var optionalParameterCount = optionalParameterInfo >> 1; |
| 222 var optionalParametersAreNamed = (optionalParameterInfo & 1) === 1; | 222 var optionalParametersAreNamed = (optionalParameterInfo & 1) === 1; |
| 223 var isIntercepted = | 223 var isIntercepted = |
| 224 requiredParameterCount + optionalParameterCount != funcs[0].length; | 224 requiredParameterCount + optionalParameterCount != funcs[0].length; |
| 225 var functionTypeIndex = ${readFunctionType("array", "2")}; | 225 var functionTypeIndex = ${readFunctionType("array", "2")}; |
| 226 var unmangledNameIndex = $unmangledNameIndex; | 226 var unmangledNameIndex = $unmangledNameIndex; |
| 227 | 227 |
| 228 if (getterStubName) { | 228 if (getterStubName) { |
| 229 f = tearOff(funcs, array, isStatic, name, isIntercepted); | 229 f = tearOff(funcs, array, isStatic, name, isIntercepted); |
| 230 descriptor[name].\$getter = f; | 230 descriptor[name].\$getter = f; |
| 231 f.\$getterStub = true; | 231 f.\$getterStub = true; |
| 232 // Used to create an isolate using spawnFunction. | 232 // Used to create an isolate using spawnFunction. |
| 233 if (isStatic) #globalFunctions[name] = f; | 233 if (isStatic) #globalFunctions[name] = f; |
| 234 originalDescriptor[getterStubName] = descriptor[getterStubName] = f; | 234 descriptor[getterStubName] = f; |
| 235 funcs.push(f); | 235 funcs.push(f); |
| 236 if (getterStubName) functions.push(getterStubName); | 236 if (getterStubName) functions.push(getterStubName); |
| 237 f.\$stubName = getterStubName; | 237 f.\$stubName = getterStubName; |
| 238 f.\$callName = null; | 238 f.\$callName = null; |
| 239 // Update the interceptedNames map (which only exists if `invokeOn` was | 239 // Update the interceptedNames map (which only exists if `invokeOn` was |
| 240 // enabled). | 240 // enabled). |
| 241 if (#enabledInvokeOn) | 241 if (#enabledInvokeOn) |
| 242 if (isIntercepted) #interceptedNames[getterStubName] = 1; | 242 if (isIntercepted) #interceptedNames[getterStubName] = 1; |
| 243 } | 243 } |
| 244 | 244 |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 290 if (!#statics) #statics = map(); | 290 if (!#statics) #statics = map(); |
| 291 if (!#typeInformation) #typeInformation = map(); | 291 if (!#typeInformation) #typeInformation = map(); |
| 292 if (!#globalFunctions) #globalFunctions = map(); | 292 if (!#globalFunctions) #globalFunctions = map(); |
| 293 if (#enabledInvokeOn) | 293 if (#enabledInvokeOn) |
| 294 if (!#interceptedNames) #interceptedNames = #interceptedNamesSet; | 294 if (!#interceptedNames) #interceptedNames = #interceptedNamesSet; |
| 295 var libraries = #libraries; | 295 var libraries = #libraries; |
| 296 var mangledNames = #mangledNames; | 296 var mangledNames = #mangledNames; |
| 297 var mangledGlobalNames = #mangledGlobalNames; | 297 var mangledGlobalNames = #mangledGlobalNames; |
| 298 var hasOwnProperty = Object.prototype.hasOwnProperty; | 298 var hasOwnProperty = Object.prototype.hasOwnProperty; |
| 299 var length = reflectionData.length; | 299 var length = reflectionData.length; |
| 300 var processedClasses = Object.create(null); | 300 var processedClasses = map(); |
| 301 processedClasses.collected = Object.create(null); | 301 processedClasses.collected = map(); |
| 302 processedClasses.pending = Object.create(null); | 302 processedClasses.pending = map(); |
| 303 if (#notInCspMode) { | 303 if (#notInCspMode) { |
| 304 processedClasses.constructorsList = []; | 304 processedClasses.constructorsList = []; |
| 305 // For every class processed [processedClasses.combinedConstructorFunction] | 305 // For every class processed [processedClasses.combinedConstructorFunction] |
| 306 // will be updated with the corresponding constructor function. | 306 // will be updated with the corresponding constructor function. |
| 307 processedClasses.combinedConstructorFunction = | 307 processedClasses.combinedConstructorFunction = |
| 308 "function \$reflectable(fn){fn.$reflectableField=1;return fn};\\n"+ | 308 "function \$reflectable(fn){fn.$reflectableField=1;return fn};\\n"+ |
| 309 "var \$desc;\\n"; | 309 "var \$desc;\\n"; |
| 310 } | 310 } |
| 311 for (var i = 0; i < length; i++) { | 311 for (var i = 0; i < length; i++) { |
| 312 var data = reflectionData[i]; | 312 var data = reflectionData[i]; |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 400 constructor["${namer.metadataField}"] = desc; | 400 constructor["${namer.metadataField}"] = desc; |
| 401 allClasses[cls] = constructor; | 401 allClasses[cls] = constructor; |
| 402 globalObject[cls] = constructor; | 402 globalObject[cls] = constructor; |
| 403 } | 403 } |
| 404 constructors = null; | 404 constructors = null; |
| 405 | 405 |
| 406 #finishClassFunction; | 406 #finishClassFunction; |
| 407 | 407 |
| 408 #trivialNsmHandlers; | 408 #trivialNsmHandlers; |
| 409 | 409 |
| 410 for (var cls in processedClasses.pending) finishClass(cls); | 410 var properties = Object.keys(processedClasses.pending); |
| 411 for (var i = 0; i < properties.length; i++) finishClass(properties[i]); |
| 411 } | 412 } |
| 412 }''', {'allClasses': allClassesAccess, | 413 }''', {'allClasses': allClassesAccess, |
| 413 'debugFastObjects': DEBUG_FAST_OBJECTS, | 414 'debugFastObjects': DEBUG_FAST_OBJECTS, |
| 414 'isTreeShakingDisabled': backend.isTreeShakingDisabled, | 415 'isTreeShakingDisabled': backend.isTreeShakingDisabled, |
| 415 'finishClassFunction': oldEmitter.buildFinishClass(hasNativeClasses), | 416 'finishClassFunction': oldEmitter.buildFinishClass(hasNativeClasses), |
| 416 'trivialNsmHandlers': oldEmitter.buildTrivialNsmHandlers(), | 417 'trivialNsmHandlers': oldEmitter.buildTrivialNsmHandlers(), |
| 417 'inCspMode': compiler.useContentSecurityPolicy, | 418 'inCspMode': compiler.useContentSecurityPolicy, |
| 418 'notInCspMode': !compiler.useContentSecurityPolicy}); | 419 'notInCspMode': !compiler.useContentSecurityPolicy}); |
| 419 | 420 |
| 420 List<jsAst.Statement> incrementalSupport = <jsAst.Statement>[]; | 421 List<jsAst.Statement> incrementalSupport = <jsAst.Statement>[]; |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 481 (function() { | 482 (function() { |
| 482 var result = $array[$index]; | 483 var result = $array[$index]; |
| 483 if ($check) { | 484 if ($check) { |
| 484 throw new Error( | 485 throw new Error( |
| 485 name + ": expected value of type \'$type\' at index " + ($index) + | 486 name + ": expected value of type \'$type\' at index " + ($index) + |
| 486 " but got " + (typeof result)); | 487 " but got " + (typeof result)); |
| 487 } | 488 } |
| 488 return result; | 489 return result; |
| 489 })()'''; | 490 })()'''; |
| 490 } | 491 } |
| OLD | NEW |