Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1064)

Unified Diff: sdk/lib/_internal/compiler/implementation/js_backend/namer.dart

Issue 694353007: Move dart2js from sdk/lib/_internal/compiler to pkg/compiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: sdk/lib/_internal/compiler/implementation/js_backend/namer.dart
diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart b/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart
deleted file mode 100644
index 4eff34a459a1a27cfae6151df1b119eab7803b15..0000000000000000000000000000000000000000
--- a/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart
+++ /dev/null
@@ -1,1396 +0,0 @@
-// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-part of js_backend;
-
-/**
- * Assigns JavaScript identifiers to Dart variables, class-names and members.
- */
-class Namer implements ClosureNamer {
-
- static const javaScriptKeywords = const <String>[
- // These are current keywords.
- "break", "delete", "function", "return", "typeof", "case", "do", "if",
- "switch", "var", "catch", "else", "in", "this", "void", "continue",
- "false", "instanceof", "throw", "while", "debugger", "finally", "new",
- "true", "with", "default", "for", "null", "try",
-
- // These are future keywords.
- "abstract", "double", "goto", "native", "static", "boolean", "enum",
- "implements", "package", "super", "byte", "export", "import", "private",
- "synchronized", "char", "extends", "int", "protected", "throws",
- "class", "final", "interface", "public", "transient", "const", "float",
- "long", "short", "volatile"
- ];
-
- static const reservedPropertySymbols =
- const <String>["__proto__", "prototype", "constructor", "call"];
-
- // Symbols that we might be using in our JS snippets.
- static const reservedGlobalSymbols = const <String>[
- // Section references are from Ecma-262
- // (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf)
-
- // 15.1.1 Value Properties of the Global Object
- "NaN", "Infinity", "undefined",
-
- // 15.1.2 Function Properties of the Global Object
- "eval", "parseInt", "parseFloat", "isNaN", "isFinite",
-
- // 15.1.3 URI Handling Function Properties
- "decodeURI", "decodeURIComponent",
- "encodeURI",
- "encodeURIComponent",
-
- // 15.1.4 Constructor Properties of the Global Object
- "Object", "Function", "Array", "String", "Boolean", "Number", "Date",
- "RegExp", "Error", "EvalError", "RangeError", "ReferenceError",
- "SyntaxError", "TypeError", "URIError",
-
- // 15.1.5 Other Properties of the Global Object
- "Math",
-
- // 10.1.6 Activation Object
- "arguments",
-
- // B.2 Additional Properties (non-normative)
- "escape", "unescape",
-
- // Window props (https://developer.mozilla.org/en/DOM/window)
- "applicationCache", "closed", "Components", "content", "controllers",
- "crypto", "defaultStatus", "dialogArguments", "directories",
- "document", "frameElement", "frames", "fullScreen", "globalStorage",
- "history", "innerHeight", "innerWidth", "length",
- "location", "locationbar", "localStorage", "menubar",
- "mozInnerScreenX", "mozInnerScreenY", "mozScreenPixelsPerCssPixel",
- "name", "navigator", "opener", "outerHeight", "outerWidth",
- "pageXOffset", "pageYOffset", "parent", "personalbar", "pkcs11",
- "returnValue", "screen", "scrollbars", "scrollMaxX", "scrollMaxY",
- "self", "sessionStorage", "sidebar", "status", "statusbar", "toolbar",
- "top", "window",
-
- // Window methods (https://developer.mozilla.org/en/DOM/window)
- "alert", "addEventListener", "atob", "back", "blur", "btoa",
- "captureEvents", "clearInterval", "clearTimeout", "close", "confirm",
- "disableExternalCapture", "dispatchEvent", "dump",
- "enableExternalCapture", "escape", "find", "focus", "forward",
- "GeckoActiveXObject", "getAttention", "getAttentionWithCycleCount",
- "getComputedStyle", "getSelection", "home", "maximize", "minimize",
- "moveBy", "moveTo", "open", "openDialog", "postMessage", "print",
- "prompt", "QueryInterface", "releaseEvents", "removeEventListener",
- "resizeBy", "resizeTo", "restore", "routeEvent", "scroll", "scrollBy",
- "scrollByLines", "scrollByPages", "scrollTo", "setInterval",
- "setResizeable", "setTimeout", "showModalDialog", "sizeToContent",
- "stop", "uuescape", "updateCommands", "XPCNativeWrapper",
- "XPCSafeJSOjbectWrapper",
-
- // Mozilla Window event handlers, same cite
- "onabort", "onbeforeunload", "onchange", "onclick", "onclose",
- "oncontextmenu", "ondragdrop", "onerror", "onfocus", "onhashchange",
- "onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown",
- "onmousemove", "onmouseout", "onmouseover", "onmouseup",
- "onmozorientation", "onpaint", "onreset", "onresize", "onscroll",
- "onselect", "onsubmit", "onunload",
-
- // Safari Web Content Guide
- // http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/SafariWebContent.pdf
- // WebKit Window member data, from WebKit DOM Reference
- // (http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/WebKitDOMRef/DOMWindow_idl/Classes/DOMWindow/index.html)
- "ontouchcancel", "ontouchend", "ontouchmove", "ontouchstart",
- "ongesturestart", "ongesturechange", "ongestureend",
-
- // extra window methods
- "uneval",
-
- // keywords https://developer.mozilla.org/en/New_in_JavaScript_1.7,
- // https://developer.mozilla.org/en/New_in_JavaScript_1.8.1
- "getPrototypeOf", "let", "yield",
-
- // "future reserved words"
- "abstract", "int", "short", "boolean", "interface", "static", "byte",
- "long", "char", "final", "native", "synchronized", "float", "package",
- "throws", "goto", "private", "transient", "implements", "protected",
- "volatile", "double", "public",
-
- // IE methods
- // (http://msdn.microsoft.com/en-us/library/ms535873(VS.85).aspx#)
- "attachEvent", "clientInformation", "clipboardData", "createPopup",
- "dialogHeight", "dialogLeft", "dialogTop", "dialogWidth",
- "onafterprint", "onbeforedeactivate", "onbeforeprint",
- "oncontrolselect", "ondeactivate", "onhelp", "onresizeend",
-
- // Common browser-defined identifiers not defined in ECMAScript
- "event", "external", "Debug", "Enumerator", "Global", "Image",
- "ActiveXObject", "VBArray", "Components",
-
- // Functions commonly defined on Object
- "toString", "getClass", "constructor", "prototype", "valueOf",
-
- // Client-side JavaScript identifiers
- "Anchor", "Applet", "Attr", "Canvas", "CanvasGradient",
- "CanvasPattern", "CanvasRenderingContext2D", "CDATASection",
- "CharacterData", "Comment", "CSS2Properties", "CSSRule",
- "CSSStyleSheet", "Document", "DocumentFragment", "DocumentType",
- "DOMException", "DOMImplementation", "DOMParser", "Element", "Event",
- "ExternalInterface", "FlashPlayer", "Form", "Frame", "History",
- "HTMLCollection", "HTMLDocument", "HTMLElement", "IFrame", "Image",
- "Input", "JSObject", "KeyEvent", "Link", "Location", "MimeType",
- "MouseEvent", "Navigator", "Node", "NodeList", "Option", "Plugin",
- "ProcessingInstruction", "Range", "RangeException", "Screen", "Select",
- "Table", "TableCell", "TableRow", "TableSelection", "Text", "TextArea",
- "UIEvent", "Window", "XMLHttpRequest", "XMLSerializer",
- "XPathException", "XPathResult", "XSLTProcessor",
-
- // These keywords trigger the loading of the java-plugin. For the
- // next-generation plugin, this results in starting a new Java process.
- "java", "Packages", "netscape", "sun", "JavaObject", "JavaClass",
- "JavaArray", "JavaMember",
- ];
-
- static const reservedGlobalObjectNames = const <String>[
- "A",
- "B",
- "C", // Global object for *C*onstants.
- "D",
- "E",
- "F",
- "G",
- "H", // Global object for internal (*H*elper) libraries.
- // I is used for used for the Isolate function.
- "J", // Global object for the interceptor library.
- "K",
- "L",
- "M",
- "N",
- "O",
- "P", // Global object for other *P*latform libraries.
- "Q",
- "R",
- "S",
- "T",
- "U",
- "V",
- "W", // Global object for *W*eb libraries (dart:html).
- "X",
- "Y",
- "Z",
- ];
-
- static const reservedGlobalHelperFunctions = const <String>[
- "init",
- "Isolate",
- ];
-
- static final userGlobalObjects = new List.from(reservedGlobalObjectNames)
- ..remove('C')
- ..remove('H')
- ..remove('J')
- ..remove('P')
- ..remove('W');
-
- Set<String> _jsReserved = null;
- /// Names that cannot be used by members, top level and static
- /// methods.
- Set<String> get jsReserved {
- if (_jsReserved == null) {
- _jsReserved = new Set<String>();
- _jsReserved.addAll(javaScriptKeywords);
- _jsReserved.addAll(reservedPropertySymbols);
- }
- return _jsReserved;
- }
-
- Set<String> _jsVariableReserved = null;
- /// Names that cannot be used by local variables and parameters.
- Set<String> get jsVariableReserved {
- if (_jsVariableReserved == null) {
- _jsVariableReserved = new Set<String>();
- _jsVariableReserved.addAll(javaScriptKeywords);
- _jsVariableReserved.addAll(reservedPropertySymbols);
- _jsVariableReserved.addAll(reservedGlobalSymbols);
- _jsVariableReserved.addAll(reservedGlobalObjectNames);
- // 26 letters in the alphabet, 25 not counting I.
- assert(reservedGlobalObjectNames.length == 25);
- _jsVariableReserved.addAll(reservedGlobalHelperFunctions);
- }
- return _jsVariableReserved;
- }
-
- final String currentIsolate = r'$';
- final String getterPrefix = r'get$';
- final String setterPrefix = r'set$';
- final String metadataField = '@';
- final String callPrefix = 'call';
- final String callCatchAllName = r'call$catchAll';
- final String reflectableField = r'$reflectable';
- final String defaultValuesField = r'$defaultValues';
- final String methodsWithOptionalArgumentsField =
- r'$methodsWithOptionalArguments';
-
- final String classDescriptorProperty = r'^';
-
- // Name of property in a class description for the native dispatch metadata.
- final String nativeSpecProperty = '%';
-
- static final RegExp IDENTIFIER = new RegExp(r'^[A-Za-z_$][A-Za-z0-9_$]*$');
- static final RegExp NON_IDENTIFIER_CHAR = new RegExp(r'[^A-Za-z_0-9$]');
-
- /**
- * Map from top-level or static elements to their unique identifiers provided
- * by [getName].
- *
- * Invariant: Keys must be declaration elements.
- */
- final Compiler compiler;
- final Map<Element, String> globals;
- final Map<String, LibraryElement> shortPrivateNameOwners;
-
- final Set<String> usedGlobalNames;
- final Set<String> usedInstanceNames;
- final Map<String, String> globalNameMap;
- final Map<String, String> suggestedGlobalNames;
- final Map<String, String> instanceNameMap;
- final Map<String, String> suggestedInstanceNames;
-
- final Map<String, String> operatorNameMap;
- final Map<String, int> popularNameCounters;
-
- final Map<ConstantValue, String> constantNames;
- final Map<ConstantValue, String> constantLongNames;
- ConstantCanonicalHasher constantHasher;
-
- // All alphanumeric characters.
- static const String _alphaNumeric =
- 'abcdefghijklmnopqrstuvwxyzABZDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
-
- Namer(Compiler compiler)
- : compiler = compiler,
- globals = new Map<Element, String>(),
- shortPrivateNameOwners = new Map<String, LibraryElement>(),
- usedGlobalNames = new Set<String>(),
- usedInstanceNames = new Set<String>(),
- instanceNameMap = new Map<String, String>(),
- operatorNameMap = new Map<String, String>(),
- globalNameMap = new Map<String, String>(),
- suggestedGlobalNames = new Map<String, String>(),
- suggestedInstanceNames = new Map<String, String>(),
- popularNameCounters = new Map<String, int>(),
- constantNames = new Map<ConstantValue, String>(),
- constantLongNames = new Map<ConstantValue, String>(),
- constantHasher = new ConstantCanonicalHasher(compiler),
- functionTypeNamer = new FunctionTypeNamer(compiler);
-
- JavaScriptBackend get backend => compiler.backend;
-
- String get isolateName => 'Isolate';
- String get isolatePropertiesName => r'$isolateProperties';
- /**
- * Some closures must contain their name. The name is stored in
- * [STATIC_CLOSURE_NAME_NAME].
- */
- String get STATIC_CLOSURE_NAME_NAME => r'$name';
- String get closureInvocationSelectorName => Compiler.CALL_OPERATOR_NAME;
- bool get shouldMinify => false;
-
- String getNameForJsGetName(Node node, String name) {
- switch (name) {
- case 'GETTER_PREFIX': return getterPrefix;
- case 'SETTER_PREFIX': return setterPrefix;
- case 'CALL_PREFIX': return callPrefix;
- case 'CALL_CATCH_ALL': return callCatchAllName;
- case 'REFLECTABLE': return reflectableField;
- case 'CLASS_DESCRIPTOR_PROPERTY': return classDescriptorProperty;
- default:
- compiler.reportError(
- node, MessageKind.GENERIC,
- {'text': 'Error: Namer has no name for "$name".'});
- return 'BROKEN';
- }
- }
-
- String constantName(ConstantValue constant) {
- // In the current implementation it doesn't make sense to give names to
- // function constants since the function-implementation itself serves as
- // constant and can be accessed directly.
- assert(!constant.isFunction);
- String result = constantNames[constant];
- if (result == null) {
- String longName = constantLongName(constant);
- result = getFreshName(longName, usedGlobalNames, suggestedGlobalNames,
- ensureSafe: true);
- constantNames[constant] = result;
- }
- return result;
- }
-
- // The long name is unminified and may have collisions.
- String constantLongName(ConstantValue constant) {
- String longName = constantLongNames[constant];
- if (longName == null) {
- longName = new ConstantNamingVisitor(compiler, constantHasher)
- .getName(constant);
- constantLongNames[constant] = longName;
- }
- return longName;
- }
-
- String breakLabelName(LabelDefinition label) {
- return '\$${label.labelName}\$${label.target.nestingLevel}';
- }
-
- String implicitBreakLabelName(JumpTarget target) {
- return '\$${target.nestingLevel}';
- }
-
- // We sometimes handle continue targets differently from break targets,
- // so we have special continue-only labels.
- String continueLabelName(LabelDefinition label) {
- return 'c\$${label.labelName}\$${label.target.nestingLevel}';
- }
-
- String implicitContinueLabelName(JumpTarget target) {
- return 'c\$${target.nestingLevel}';
- }
-
- /**
- * If the [name] is not private returns [:name:]. Otherwise
- * mangles the [name] so that each library has a unique name.
- */
- String privateName(LibraryElement library, String name) {
- // Public names are easy.
- String nameString = name;
- if (!isPrivateName(name)) return nameString;
-
- // The first library asking for a short private name wins.
- LibraryElement owner =
- shortPrivateNameOwners.putIfAbsent(nameString, () => library);
-
- if (owner == library && !nameString.contains('\$')) {
- // Since the name doesn't contain $ it doesn't clash with any
- // of the private names that have the library name as the prefix.
- return nameString;
- } else {
- // Make sure to return a private name that starts with _ so it
- // cannot clash with any public names.
- String libraryName = getNameOfLibrary(library);
- return '_$libraryName\$$nameString';
- }
- }
-
- String instanceMethodName(FunctionElement element) {
- // TODO(ahe): Could this be: return invocationName(new
- // Selector.fromElement(element))?
- String elementName = element.name;
- String name = operatorNameToIdentifier(elementName);
- if (name != elementName) return getMappedOperatorName(name);
-
- LibraryElement library = element.library;
- if (element.isGenerativeConstructorBody) {
- name = Elements.reconstructConstructorNameSourceString(element);
- }
- FunctionSignature signature = element.functionSignature;
- // We don't mangle the closure invoking function name because it
- // is generated by string concatenation in applyFunction from
- // js_helper.dart. To keep code size down, we potentially shorten
- // the prefix though.
- String methodName;
- if (name == closureInvocationSelectorName) {
- methodName = '$callPrefix\$${signature.parameterCount}';
- } else {
- methodName = '${privateName(library, name)}\$${signature.parameterCount}';
- }
- if (signature.optionalParametersAreNamed &&
- !signature.optionalParameters.isEmpty) {
- StringBuffer buffer = new StringBuffer();
- signature.orderedOptionalParameters.forEach((Element element) {
- buffer.write('\$${safeName(element.name)}');
- });
- methodName = '$methodName$buffer';
- }
- if (name == closureInvocationSelectorName) return methodName;
- return getMappedInstanceName(methodName);
- }
-
- String publicInstanceMethodNameByArity(String name, int arity) {
- String newName = operatorNameToIdentifier(name);
- if (newName != name) return getMappedOperatorName(newName);
- assert(!isPrivateName(name));
- // We don't mangle the closure invoking function name because it
- // is generated by string concatenation in applyFunction from
- // js_helper.dart. To keep code size down, we potentially shorten
- // the prefix though.
- if (name == closureInvocationSelectorName) return '$callPrefix\$$arity';
-
- return getMappedInstanceName('$name\$$arity');
- }
-
- String invocationName(Selector selector) {
- if (selector.isGetter) {
- String proposedName = privateName(selector.library, selector.name);
- return '$getterPrefix${getMappedInstanceName(proposedName)}';
- } else if (selector.isSetter) {
- String proposedName = privateName(selector.library, selector.name);
- return '$setterPrefix${getMappedInstanceName(proposedName)}';
- } else {
- String name = selector.name;
- if (selector.kind == SelectorKind.OPERATOR
- || selector.kind == SelectorKind.INDEX) {
- name = operatorNameToIdentifier(name);
- assert(name != selector.name);
- return getMappedOperatorName(name);
- }
- assert(name == operatorNameToIdentifier(name));
- StringBuffer buffer = new StringBuffer();
- for (String argumentName in selector.getOrderedNamedArguments()) {
- buffer.write('\$${safeName(argumentName)}');
- }
- String suffix = '\$${selector.argumentCount}$buffer';
- // We don't mangle the closure invoking function name because it
- // is generated by string concatenation in applyFunction from
- // js_helper.dart. We potentially shorten the prefix though.
- if (selector.isClosureCall) {
- return "$callPrefix$suffix";
- } else {
- String proposedName = privateName(selector.library, name);
- return getMappedInstanceName('$proposedName$suffix');
- }
- }
- }
-
- /**
- * Returns the internal name used for an invocation mirror of this selector.
- */
- String invocationMirrorInternalName(Selector selector)
- => invocationName(selector);
-
- /**
- * Returns name of accessor (root to getter and setter) for a static or
- * instance field.
- */
- String fieldAccessorName(Element element) {
- return element.isInstanceMember
- ? instanceFieldAccessorName(element)
- : getNameOfField(element);
- }
-
- /**
- * Returns name of the JavaScript property used to store a static or instance
- * field.
- */
- String fieldPropertyName(Element element) {
- return element.isInstanceMember
- ? instanceFieldPropertyName(element)
- : getNameOfField(element);
- }
-
- /**
- * Returns name of accessor (root to getter and setter) for an instance field.
- */
- String instanceFieldAccessorName(Element element) {
- String proposedName = privateName(element.library, element.name);
- return getMappedInstanceName(proposedName);
- }
-
- String readTypeVariableName(TypeVariableElement element) {
- return '\$tv_${instanceFieldAccessorName(element)}';
- }
-
- /**
- * Returns name of the JavaScript property used to store an instance field.
- */
- String instanceFieldPropertyName(Element element) {
- if (element.hasFixedBackendName) {
- return element.fixedBackendName;
- }
- // If a class is used anywhere as a mixin, we must make the name unique so
- // that it does not accidentally shadow. Also, the mixin name must be
- // constant over all mixins.
- ClassWorld classWorld = compiler.world;
- if (classWorld.isUsedAsMixin(element.enclosingClass) ||
- shadowingAnotherField(element)) {
- // Construct a new name for the element based on the library and class it
- // is in. The name here is not important, we just need to make sure it is
- // unique. If we are minifying, we actually construct the name from the
- // minified version of the class name, but the result is minified once
- // again, so that is not visible in the end result.
- String libraryName = getNameOfLibrary(element.library);
- String className = getNameOfClass(element.enclosingClass);
- String instanceName = privateName(element.library, element.name);
- return getMappedInstanceName('$libraryName\$$className\$$instanceName');
- }
-
- String proposedName = privateName(element.library, element.name);
- return getMappedInstanceName(proposedName);
- }
-
-
- bool shadowingAnotherField(Element element) {
- return element.enclosingClass.hasFieldShadowedBy(element);
- }
-
- String setterName(Element element) {
- // We dynamically create setters from the field-name. The setter name must
- // therefore be derived from the instance field-name.
- LibraryElement library = element.library;
- String name = getMappedInstanceName(privateName(library, element.name));
- return '$setterPrefix$name';
- }
-
- String setterNameFromAccessorName(String name) {
- // We dynamically create setters from the field-name. The setter name must
- // therefore be derived from the instance field-name.
- return '$setterPrefix$name';
- }
-
- String getterNameFromAccessorName(String name) {
- // We dynamically create getters from the field-name. The getter name must
- // therefore be derived from the instance field-name.
- return '$getterPrefix$name';
- }
-
- String getterName(Element element) {
- // We dynamically create getters from the field-name. The getter name must
- // therefore be derived from the instance field-name.
- LibraryElement library = element.library;
- String name = getMappedInstanceName(privateName(library, element.name));
- return '$getterPrefix$name';
- }
-
- String getMappedGlobalName(String proposedName, {bool ensureSafe: true}) {
- var newName = globalNameMap[proposedName];
- if (newName == null) {
- newName = getFreshName(proposedName, usedGlobalNames,
- suggestedGlobalNames, ensureSafe: ensureSafe);
- globalNameMap[proposedName] = newName;
- }
- return newName;
- }
-
- String getMappedInstanceName(String proposedName) {
- var newName = instanceNameMap[proposedName];
- if (newName == null) {
- newName = getFreshName(proposedName, usedInstanceNames,
- suggestedInstanceNames, ensureSafe: true);
- instanceNameMap[proposedName] = newName;
- }
- return newName;
- }
-
- String getMappedOperatorName(String proposedName) {
- var newName = operatorNameMap[proposedName];
- if (newName == null) {
- newName = getFreshName(proposedName, usedInstanceNames,
- suggestedInstanceNames, ensureSafe: false);
- operatorNameMap[proposedName] = newName;
- }
- return newName;
- }
-
- String getFreshName(String proposedName,
- Set<String> usedNames,
- Map<String, String> suggestedNames,
- {bool ensureSafe: true}) {
- var candidate;
- if (ensureSafe) {
- proposedName = safeName(proposedName);
- }
- assert(!jsReserved.contains(proposedName));
- if (!usedNames.contains(proposedName)) {
- candidate = proposedName;
- } else {
- var counter = popularNameCounters[proposedName];
- var i = counter == null ? 0 : counter;
- while (usedNames.contains("$proposedName$i")) {
- i++;
- }
- popularNameCounters[proposedName] = i + 1;
- candidate = "$proposedName$i";
- }
- usedNames.add(candidate);
- return candidate;
- }
-
- String getClosureVariableName(String name, int id) {
- return "${name}_$id";
- }
-
- /**
- * Returns a preferred JS-id for the given top-level or static element.
- * The returned id is guaranteed to be a valid JS-id.
- */
- String _computeGuess(Element element) {
- assert(!element.isInstanceMember);
- String name;
- if (element.isGenerativeConstructor) {
- name = "${element.enclosingClass.name}\$"
- "${element.name}";
- } else if (element.isFactoryConstructor) {
- // TODO(johnniwinther): Change factory name encoding as to not include
- // the class-name twice.
- String className = element.enclosingClass.name;
- name = '${className}_${Elements.reconstructConstructorName(element)}';
- } else if (Elements.isStaticOrTopLevel(element)) {
- if (element.isClassMember) {
- ClassElement enclosingClass = element.enclosingClass;
- name = "${enclosingClass.name}_"
- "${element.name}";
- } else {
- name = element.name.replaceAll('+', '_');
- }
- } else if (element.isLibrary) {
- LibraryElement library = element;
- name = library.getLibraryOrScriptName();
- if (name.contains('.')) {
- // For libraries that have a library tag, we use the last part
- // of the fully qualified name as their base name. For all other
- // libraries, we use the first part of their filename.
- name = library.hasLibraryName()
- ? name.substring(name.lastIndexOf('.') + 1)
- : name.substring(0, name.indexOf('.'));
- }
- // The filename based name can contain all kinds of nasty characters. Make
- // sure it is an identifier.
- if (!IDENTIFIER.hasMatch(name)) {
- name = name.replaceAllMapped(NON_IDENTIFIER_CHAR,
- (match) => match[0].codeUnitAt(0).toRadixString(16));
- if (!IDENTIFIER.hasMatch(name)) { // e.g. starts with digit.
- name = 'lib_$name';
- }
- }
- } else {
- name = element.name;
- }
- return name;
- }
-
- String getInterceptorSuffix(Iterable<ClassElement> classes) {
- String abbreviate(ClassElement cls) {
- if (cls == compiler.objectClass) return "o";
- if (cls == backend.jsStringClass) return "s";
- if (cls == backend.jsArrayClass) return "a";
- if (cls == backend.jsDoubleClass) return "d";
- if (cls == backend.jsIntClass) return "i";
- if (cls == backend.jsNumberClass) return "n";
- if (cls == backend.jsNullClass) return "u";
- if (cls == backend.jsBoolClass) return "b";
- if (cls == backend.jsInterceptorClass) return "I";
- return cls.name;
- }
- List<String> names = classes
- .where((cls) => !Elements.isNativeOrExtendsNative(cls))
- .map(abbreviate)
- .toList();
- // There is one dispatch mechanism for all native classes.
- if (classes.any((cls) => Elements.isNativeOrExtendsNative(cls))) {
- names.add("x");
- }
- // Sort the names of the classes after abbreviating them to ensure
- // the suffix is stable and predictable for the suggested names.
- names.sort();
- return names.join();
- }
-
- String getInterceptorName(Element element, Iterable<ClassElement> classes) {
- if (classes.contains(backend.jsInterceptorClass)) {
- // If the base Interceptor class is in the set of intercepted classes, we
- // need to go through the generic getInterceptorMethod, since any subclass
- // of the base Interceptor could match.
- return getNameOfInstanceMember(element);
- }
- String suffix = getInterceptorSuffix(classes);
- return getMappedGlobalName("${element.name}\$$suffix");
- }
-
- String getOneShotInterceptorName(Selector selector,
- Iterable<ClassElement> classes) {
- // The one-shot name is a global name derived from the invocation name. To
- // avoid instability we would like the names to be unique and not clash with
- // other global names.
-
- String root = invocationName(selector); // Is already safe.
-
- if (classes.contains(backend.jsInterceptorClass)) {
- // If the base Interceptor class is in the set of intercepted classes,
- // this is the most general specialization which uses the generic
- // getInterceptor method. To keep the name short, we add '$' only to
- // distinguish from global getters or setters; operators and methods can't
- // clash.
- // TODO(sra): Find a way to get the simple name when Object is not in the
- // set of classes for most general variant, e.g. "$lt$n" could be "$lt".
- if (selector.isGetter || selector.isSetter) root = '$root\$';
- return getMappedGlobalName(root, ensureSafe: false);
- } else {
- String suffix = getInterceptorSuffix(classes);
- return getMappedGlobalName("$root\$$suffix", ensureSafe: false);
- }
- }
-
- /// Returns the runtime name for [element]. The result is not safe as an id.
- String getRuntimeTypeName(Element element) {
- if (element == null) return 'dynamic';
- return getNameForRti(element);
- }
-
- /**
- * Returns a preferred JS-id for the given element. The returned id is
- * guaranteed to be a valid JS-id. Globals and static fields are furthermore
- * guaranteed to be unique.
- *
- * For accessing statics consider calling [elementAccess] instead.
- */
- // TODO(ahe): This is an internal method to the Namer (and its subclasses)
- // and should not be call from outside.
- String getNameX(Element element) {
- if (element.isInstanceMember) {
- if (element.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY
- || element.kind == ElementKind.FUNCTION) {
- return instanceMethodName(element);
- } else if (element.kind == ElementKind.GETTER) {
- return getterName(element);
- } else if (element.kind == ElementKind.SETTER) {
- return setterName(element);
- } else if (element.kind == ElementKind.FIELD) {
- compiler.internalError(element,
- 'Use instanceFieldPropertyName or instanceFieldAccessorName.');
- return null;
- } else {
- compiler.internalError(element,
- 'getName for bad kind: ${element.kind}.');
- return null;
- }
- } else {
- // Use declaration element to ensure invariant on [globals].
- element = element.declaration;
- // Dealing with a top-level or static element.
- String cached = globals[element];
- if (cached != null) return cached;
-
- String guess = _computeGuess(element);
- ElementKind kind = element.kind;
- if (kind == ElementKind.VARIABLE ||
- kind == ElementKind.PARAMETER) {
- // The name is not guaranteed to be unique.
- return safeName(guess);
- }
- if (kind == ElementKind.GENERATIVE_CONSTRUCTOR ||
- kind == ElementKind.FUNCTION ||
- kind == ElementKind.CLASS ||
- kind == ElementKind.FIELD ||
- kind == ElementKind.GETTER ||
- kind == ElementKind.SETTER ||
- kind == ElementKind.TYPEDEF ||
- kind == ElementKind.LIBRARY) {
- bool fixedName = false;
- if (Elements.isInstanceField(element)) {
- fixedName = element.hasFixedBackendName;
- }
- String result = fixedName
- ? guess
- : getFreshName(guess, usedGlobalNames, suggestedGlobalNames,
- ensureSafe: true);
- globals[element] = result;
- return result;
- }
- compiler.internalError(element,
- 'getName for unknown kind: ${element.kind}.');
- return null;
- }
- }
-
- String getNameForRti(Element element) => getNameX(element);
-
- String getNameOfLibrary(LibraryElement library) => getNameX(library);
-
- String getNameOfClass(ClassElement cls) => getNameX(cls);
-
- String getNameOfField(VariableElement field) => getNameX(field);
-
- // TODO(ahe): Remove this method. Use get getNameOfMember instead.
- String getNameOfInstanceMember(Element member) => getNameX(member);
-
- String getNameOfMember(Element member) => getNameX(member);
-
- String getNameOfGlobalField(VariableElement field) => getNameX(field);
-
- /// Returns true if [element] is stored on current isolate ('$'). We intend
- /// to store only mutable static state in [currentIsolate], constants are
- /// stored in 'C', and functions, accessors, classes, etc. are stored in one
- /// of the other objects in [reservedGlobalObjectNames].
- bool isPropertyOfCurrentIsolate(Element element) {
- // TODO(ahe): Make sure this method's documentation is always true and
- // remove the word "intend".
- return
- // TODO(ahe): Re-write these tests to be positive (so it only returns
- // true for static/top-level mutable fields). Right now, a number of
- // other elements, such as bound closures also live in [currentIsolate].
- !element.isAccessor &&
- !element.isClass &&
- !element.isTypedef &&
- !element.isConstructor &&
- !element.isFunction &&
- !element.isLibrary;
- }
-
- /// Returns [currentIsolate] or one of [reservedGlobalObjectNames].
- String globalObjectFor(Element element) {
- if (isPropertyOfCurrentIsolate(element)) return currentIsolate;
- LibraryElement library = element.library;
- if (library == backend.interceptorsLibrary) return 'J';
- if (library.isInternalLibrary) return 'H';
- if (library.isPlatformLibrary) {
- if ('${library.canonicalUri}' == 'dart:html') return 'W';
- return 'P';
- }
- return userGlobalObjects[
- library.getLibraryOrScriptName().hashCode % userGlobalObjects.length];
- }
-
- jsAst.PropertyAccess elementAccess(Element element) {
- String name = getNameX(element);
- return new jsAst.PropertyAccess.field(
- new jsAst.VariableUse(globalObjectFor(element)),
- name);
- }
-
- String getLazyInitializerName(Element element) {
- assert(Elements.isStaticOrTopLevelField(element));
- return getMappedGlobalName("$getterPrefix${getNameX(element)}");
- }
-
- String getStaticClosureName(Element element) {
- assert(Elements.isStaticOrTopLevelFunction(element));
- return getMappedGlobalName("${getNameX(element)}\$closure");
- }
-
- jsAst.Expression isolateLazyInitializerAccess(Element element) {
- return js('#.#',
- [globalObjectFor(element), getLazyInitializerName(element)]);
- }
-
- jsAst.Expression isolateStaticClosureAccess(Element element) {
- return js('#.#()',
- [globalObjectFor(element), getStaticClosureName(element)]);
- }
-
- // This name is used as part of the name of a TypeConstant
- String uniqueNameForTypeConstantElement(Element element) {
- // TODO(sra): If we replace the period with an identifier character,
- // TypeConstants will have better names in unminified code.
- return "${globalObjectFor(element)}.${getNameX(element)}";
- }
-
- String globalObjectForConstant(ConstantValue constant) => 'C';
-
- String operatorIsPrefix() => r'$is';
-
- String operatorAsPrefix() => r'$as';
-
- String operatorSignature() => r'$signature';
-
- String typedefTag() => r'typedef';
-
- String functionTypeTag() => r'func';
-
- String functionTypeVoidReturnTag() => r'void';
-
- String functionTypeReturnTypeTag() => r'ret';
-
- String functionTypeRequiredParametersTag() => r'args';
-
- String functionTypeOptionalParametersTag() => r'opt';
-
- String functionTypeNamedParametersTag() => r'named';
-
- Map<FunctionType,String> functionTypeNameMap =
- new Map<FunctionType,String>();
- final FunctionTypeNamer functionTypeNamer;
-
- String getFunctionTypeName(FunctionType functionType) {
- return functionTypeNameMap.putIfAbsent(functionType, () {
- String proposedName = functionTypeNamer.computeName(functionType);
- String freshName = getFreshName(proposedName, usedInstanceNames,
- suggestedInstanceNames, ensureSafe: true);
- return freshName;
- });
- }
-
- String operatorIsType(DartType type) {
- if (type.isFunctionType) {
- // TODO(erikcorry): Reduce from $isx to ix when we are minifying.
- return '${operatorIsPrefix()}_${getFunctionTypeName(type)}';
- }
- return operatorIs(type.element);
- }
-
- String operatorIs(Element element) {
- // TODO(erikcorry): Reduce from $isx to ix when we are minifying.
- return '${operatorIsPrefix()}${getRuntimeTypeName(element)}';
- }
-
- /*
- * Returns a name that does not clash with reserved JS keywords,
- * and also ensures it won't clash with other identifiers.
- */
- String _safeName(String name, Set<String> reserved) {
- if (reserved.contains(name) || name.startsWith(r'$')) {
- name = '\$$name';
- }
- assert(!reserved.contains(name));
- return name;
- }
-
- String substitutionName(Element element) {
- // TODO(ahe): Creating a string here is unfortunate. It is slow (due to
- // string concatenation in the implementation), and may prevent
- // segmentation of '$'.
- return '${operatorAsPrefix()}${getNameForRti(element)}';
- }
-
- String safeName(String name) => _safeName(name, jsReserved);
- String safeVariableName(String name) => _safeName(name, jsVariableReserved);
-
- String operatorNameToIdentifier(String name) {
- if (name == null) return null;
- if (name == '==') {
- return r'$eq';
- } else if (name == '~') {
- return r'$not';
- } else if (name == '[]') {
- return r'$index';
- } else if (name == '[]=') {
- return r'$indexSet';
- } else if (name == '*') {
- return r'$mul';
- } else if (name == '/') {
- return r'$div';
- } else if (name == '%') {
- return r'$mod';
- } else if (name == '~/') {
- return r'$tdiv';
- } else if (name == '+') {
- return r'$add';
- } else if (name == '<<') {
- return r'$shl';
- } else if (name == '>>') {
- return r'$shr';
- } else if (name == '>=') {
- return r'$ge';
- } else if (name == '>') {
- return r'$gt';
- } else if (name == '<=') {
- return r'$le';
- } else if (name == '<') {
- return r'$lt';
- } else if (name == '&') {
- return r'$and';
- } else if (name == '^') {
- return r'$xor';
- } else if (name == '|') {
- return r'$or';
- } else if (name == '-') {
- return r'$sub';
- } else if (name == 'unary-') {
- return r'$negate';
- } else {
- return name;
- }
- }
-
- void forgetElement(Element element) {
- String globalName = globals[element];
- invariant(element, globalName != null, message: 'No global name.');
- usedGlobalNames.remove(globalName);
- globals.remove(element);
- }
-}
-
-/**
- * Generator of names for [ConstantValue] values.
- *
- * The names are stable under perturbations of the source. The name is either a
- * short sequence of words, if this can be found from the constant, or a type
- * followed by a hash tag.
- *
- * List_imX // A List, with hash tag.
- * C_Sentinel // const Sentinel(), "C_" added to avoid clash
- * // with class name.
- * JSInt_methods // an interceptor.
- * Duration_16000 // const Duration(milliseconds: 16)
- * EventKeyProvider_keyup // const EventKeyProvider('keyup')
- *
- */
-class ConstantNamingVisitor implements ConstantValueVisitor {
-
- static final RegExp IDENTIFIER = new RegExp(r'^[A-Za-z_$][A-Za-z0-9_$]*$');
- static const MAX_FRAGMENTS = 5;
- static const MAX_EXTRA_LENGTH = 30;
- static const DEFAULT_TAG_LENGTH = 3;
-
- final Compiler compiler;
- final ConstantCanonicalHasher hasher;
-
- String root = null; // First word, usually a type name.
- bool failed = false; // Failed to generate something pretty.
- List<String> fragments = <String>[];
- int length = 0;
-
- ConstantNamingVisitor(this.compiler, this.hasher);
-
- String getName(ConstantValue constant) {
- _visit(constant);
- if (root == null) return 'CONSTANT';
- if (failed) return '${root}_${getHashTag(constant, DEFAULT_TAG_LENGTH)}';
- if (fragments.length == 1) return 'C_${root}';
- return fragments.join('_');
- }
-
- String getHashTag(ConstantValue constant, int width) =>
- hashWord(hasher.getHash(constant), width);
-
- String hashWord(int hash, int length) {
- hash &= 0x1fffffff;
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < length; i++) {
- int digit = hash % 62;
- sb.write('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
- [digit]);
- hash ~/= 62;
- if (hash == 0) break;
- }
- return sb.toString();
- }
-
- void addRoot(String fragment) {
- if (root == null && fragments.isEmpty) {
- root = fragment;
- }
- add(fragment);
- }
-
- void add(String fragment) {
- assert(fragment.length > 0);
- fragments.add(fragment);
- length += fragment.length;
- if (fragments.length > MAX_FRAGMENTS) failed = true;
- if (root != null && length > root.length + 1 + MAX_EXTRA_LENGTH) {
- failed = true;
- }
- }
-
- void addIdentifier(String fragment) {
- if (fragment.length <= MAX_EXTRA_LENGTH && IDENTIFIER.hasMatch(fragment)) {
- add(fragment);
- } else {
- failed = true;
- }
- }
-
- _visit(ConstantValue constant) {
- return constant.accept(this);
- }
-
- visitFunction(FunctionConstantValue constant) {
- add(constant.element.name);
- }
-
- visitNull(NullConstantValue constant) {
- add('null');
- }
-
- visitInt(IntConstantValue constant) {
- // No `addRoot` since IntConstants are always inlined.
- if (constant.primitiveValue < 0) {
- add('m${-constant.primitiveValue}');
- } else {
- add('${constant.primitiveValue}');
- }
- }
-
- visitDouble(DoubleConstantValue constant) {
- failed = true;
- }
-
- visitTrue(TrueConstantValue constant) {
- add('true');
- }
-
- visitFalse(FalseConstantValue constant) {
- add('false');
- }
-
- visitString(StringConstantValue constant) {
- // No `addRoot` since string constants are always inlined.
- addIdentifier(constant.primitiveValue.slowToString());
- }
-
- visitList(ListConstantValue constant) {
- // TODO(9476): Incorporate type parameters into name.
- addRoot('List');
- int length = constant.length;
- if (constant.length == 0) {
- add('empty');
- } else if (length >= MAX_FRAGMENTS) {
- failed = true;
- } else {
- for (int i = 0; i < length; i++) {
- _visit(constant.entries[i]);
- if (failed) break;
- }
- }
- }
-
- visitMap(JavaScriptMapConstant constant) {
- // TODO(9476): Incorporate type parameters into name.
- addRoot('Map');
- if (constant.length == 0) {
- add('empty');
- } else {
- // Using some bits from the keys hash tag groups the names Maps with the
- // same structure.
- add(getHashTag(constant.keyList, 2) + getHashTag(constant, 3));
- }
- }
-
- visitConstructed(ConstructedConstantValue constant) {
- addRoot(constant.type.element.name);
- for (int i = 0; i < constant.fields.length; i++) {
- _visit(constant.fields[i]);
- if (failed) return;
- }
- }
-
- visitType(TypeConstantValue constant) {
- addRoot('Type');
- DartType type = constant.representedType;
- JavaScriptBackend backend = compiler.backend;
- String name = backend.rti.getTypeRepresentationForTypeConstant(type);
- addIdentifier(name);
- }
-
- visitInterceptor(InterceptorConstantValue constant) {
- addRoot(constant.dispatchedType.element.name);
- add('methods');
- }
-
- visitDummy(DummyConstantValue constant) {
- add('dummy_receiver');
- }
-
- visitDeferred(DeferredConstantValue constant) {
- addRoot('Deferred');
- }
-}
-
-/**
- * Generates canonical hash values for [ConstantValue]s.
- *
- * Unfortunately, [Constant.hashCode] is not stable under minor perturbations,
- * so it can't be used for generating names. This hasher keeps consistency
- * between runs by basing hash values of the names of elements, rather than
- * their hashCodes.
- */
-class ConstantCanonicalHasher implements ConstantValueVisitor<int> {
-
- static const _MASK = 0x1fffffff;
- static const _UINT32_LIMIT = 4 * 1024 * 1024 * 1024;
-
-
- final Compiler compiler;
- final Map<ConstantValue, int> hashes = new Map<ConstantValue, int>();
-
- ConstantCanonicalHasher(this.compiler);
-
- int getHash(ConstantValue constant) => _visit(constant);
-
- int _visit(ConstantValue constant) {
- int hash = hashes[constant];
- if (hash == null) {
- hash = _finish(constant.accept(this));
- hashes[constant] = hash;
- }
- return hash;
- }
-
- int visitNull(NullConstantValue constant) => 1;
- int visitTrue(TrueConstantValue constant) => 2;
- int visitFalse(FalseConstantValue constant) => 3;
-
- int visitFunction(FunctionConstantValue constant) {
- return _hashString(1, constant.element.name);
- }
-
- int visitInt(IntConstantValue constant) => _hashInt(constant.primitiveValue);
-
- int visitDouble(DoubleConstantValue constant) {
- return _hashDouble(constant.primitiveValue);
- }
-
- int visitString(StringConstantValue constant) {
- return _hashString(2, constant.primitiveValue.slowToString());
- }
-
- int visitList(ListConstantValue constant) {
- return _hashList(constant.length, constant.entries);
- }
-
- int visitMap(MapConstantValue constant) {
- int hash = _hashList(constant.length, constant.keys);
- return _hashList(hash, constant.values);
- }
-
- int visitConstructed(ConstructedConstantValue constant) {
- int hash = _hashString(3, constant.type.element.name);
- for (int i = 0; i < constant.fields.length; i++) {
- hash = _combine(hash, _visit(constant.fields[i]));
- }
- return hash;
- }
-
- int visitType(TypeConstantValue constant) {
- DartType type = constant.representedType;
- JavaScriptBackend backend = compiler.backend;
- String name = backend.rti.getTypeRepresentationForTypeConstant(type);
- return _hashString(4, name);
- }
-
- visitInterceptor(InterceptorConstantValue constant) {
- String typeName = constant.dispatchedType.element.name;
- return _hashString(5, typeName);
- }
-
- visitDummy(DummyConstantValue constant) {
- compiler.internalError(NO_LOCATION_SPANNABLE,
- 'DummyReceiverConstant should never be named and never be subconstant');
- }
-
- visitDeferred(DeferredConstantValue constant) {
- int hash = constant.prefix.hashCode;
- return _combine(hash, constant.referenced.accept(this));
- }
-
- int _hashString(int hash, String s) {
- int length = s.length;
- hash = _combine(hash, length);
- // Increasing stride is O(log N) on large strings which are unlikely to have
- // many collisions.
- for (int i = 0; i < length; i += 1 + (i >> 2)) {
- hash = _combine(hash, s.codeUnitAt(i));
- }
- return hash;
- }
-
- int _hashList(int hash, List<ConstantValue> constants) {
- for (ConstantValue constant in constants) {
- hash = _combine(hash, _visit(constant));
- }
- return hash;
- }
-
- static int _hashInt(int value) {
- if (value.abs() < _UINT32_LIMIT) return _MASK & value;
- return _hashDouble(value.toDouble());
- }
-
- static int _hashDouble(double value) {
- double magnitude = value.abs();
- int sign = value < 0 ? 1 : 0;
- if (magnitude < _UINT32_LIMIT) { // 2^32
- int intValue = value.toInt();
- // Integer valued doubles in 32-bit range hash to the same values as ints.
- int hash = _hashInt(intValue);
- if (value == intValue) return hash;
- hash = _combine(hash, sign);
- int fraction = ((magnitude - intValue.abs()) * (_MASK + 1)).toInt();
- hash = _combine(hash, fraction);
- return hash;
- } else if (value.isInfinite) {
- return _combine(6, sign);
- } else if (value.isNaN) {
- return 7;
- } else {
- int hash = 0;
- while (magnitude >= _UINT32_LIMIT) {
- magnitude = magnitude / _UINT32_LIMIT;
- hash++;
- }
- hash = _combine(hash, sign);
- return _combine(hash, _hashDouble(magnitude));
- }
- }
-
- /**
- * [_combine] and [_finish] are parts of the [Jenkins hash function][1],
- * modified by using masking to keep values in SMI range.
- *
- * [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function
- */
- static int _combine(int hash, int value) {
- hash = _MASK & (hash + value);
- hash = _MASK & (hash + (((_MASK >> 10) & hash) << 10));
- hash = hash ^ (hash >> 6);
- return hash;
- }
-
- static int _finish(int hash) {
- hash = _MASK & (hash + (((_MASK >> 3) & hash) << 3));
- hash = hash & (hash >> 11);
- return _MASK & (hash + (((_MASK >> 15) & hash) << 15));
- }
-}
-
-class FunctionTypeNamer extends DartTypeVisitor {
- final Compiler compiler;
- StringBuffer sb;
-
- FunctionTypeNamer(this.compiler);
-
- JavaScriptBackend get backend => compiler.backend;
-
- String computeName(DartType type) {
- sb = new StringBuffer();
- visit(type);
- return sb.toString();
- }
-
- visit(DartType type) {
- type.accept(this, null);
- }
-
- visitType(DartType type, _) {
- sb.write(type.name);
- }
-
- visitFunctionType(FunctionType type, _) {
- if (backend.rti.isSimpleFunctionType(type)) {
- sb.write('args${type.parameterTypes.length}');
- return;
- }
- visit(type.returnType);
- sb.write('_');
- for (DartType parameter in type.parameterTypes) {
- sb.write('_');
- visit(parameter);
- }
- bool first = false;
- for (DartType parameter in type.optionalParameterTypes) {
- if (!first) {
- sb.write('_');
- }
- sb.write('_');
- visit(parameter);
- first = true;
- }
- if (!type.namedParameterTypes.isEmpty) {
- first = false;
- for (DartType parameter in type.namedParameterTypes) {
- if (!first) {
- sb.write('_');
- }
- sb.write('_');
- visit(parameter);
- first = true;
- }
- }
- }
-}

Powered by Google App Engine
This is Rietveld 408576698