GitHub PullRequestReviewCommentEvent at 2014-08-11 18:15:20
Repository: traceur-compiler
Actor: rodyhaddad
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/google/traceur-compiler/pulls/comments/16069961","id":16069961,"diff_hunk":"@@ -196,587 +189,643 @@ export class BlockBindingTransformer extends ParseTreeTransformer {\n    * Pops scope, tracks proper matching of push_/pop_ operations.\n    * @param {Scope} scope\n    */\n-  pop_(scope) {\n+  popScope(scope) {\n     if (this.scope_ != scope) {\n       throw new Error('BlockBindingTransformer scope mismatch');\n     }\n-\n     this.scope_ = scope.parent;\n+    this.blockRenames_ = this.scope_ && this.scope_.blockBindingRenames || [];\n   }\n \n-  // The transform methods override from the base.\n+  revisitTreeForScopes(tree) {\n+    this.scopeBuilder_.scope = this.scope_;\n+    this.scopeBuilder_.visitAny(tree);\n+    this.scopeBuilder_.scope = null;\n+  }\n \n-  /**\n-   * Transforms block scope, rewriting all block-scoped variables/functions.\n-   * @param {Block} tree\n-   * @return {ParseTree}\n-   */\n-  transformBlock(tree) {\n-    // Push new scope.\n-    var scope = this.push_(this.createBlockScope_());\n-\n-    // Transform the block contents\n-    var statements = this.transformList(tree.statements);\n-\n-    if (scope.blockVariables != null) {\n-      // rewrite into catch construct\n-      tree = createBlock([\n-        this.rewriteAsCatch_(scope.blockVariables, createBlock(statements))\n-      ]);\n-    } else if (statements != tree.statements) {\n-      tree = createBlock(statements);\n-    }\n+  // this is a start and end point of this transformer\n+  transformFunctionBody(tree) {\n+    if (tree === this.rootTree_ || !this.rootTree_) {\n+      tree = super(tree);\n+      if (this.prependStatement_.length || this.blockRenames_.length) {\n+        var statements = prependStatements(tree.statements, ...this.prependStatement_);\n+        tree = new FunctionBody(tree.location, statements);\n+        tree = this.flushRenames(tree);\n+      }\n+    } else {\n+      var functionTransform = new BlockBindingTransformer(this.idGenerator_,\n+          this.reporter_, tree, this.scopeBuilder_, this.scope_);\n+      var functionBodyTree = functionTransform.transformAny(tree);\n \n-    this.pop_(scope);\n+      if (functionBodyTree === tree) {\n+        return tree;\n+      }\n+      tree = new FunctionBody(tree.location, functionBodyTree.statements);\n+    }\n     return tree;\n   }\n \n-  /**\n-   * Declares block-scoped variables. Does so by wrapping a block in\n-   * a try .. catch for each block scoped variable in the set.\n-   *\n-   * 'let x = 1;' turns into:\n-   *\n-   * try {\n-   *   throw undefined;\n-   * } catch (x) {\n-   *   x = 1;   // let x = 1\n-   *   ...\n-   *   }\n-   * }\n-   *\n-   * @param {Object} blockVariables\n-   * @param {ParseTree} statements\n-   * @return {ParseTree}\n-   */\n-  rewriteAsCatch_(blockVariables, statement) {\n-    // Build the try .. catch structure from within.\n-    // try {\n-    //   throw undefined;\n-    // } catch (<block scoped variable>) {\n-    //   <block>\n-    // }\n-    for (var variable in blockVariables) {\n-      statement =\n-          createTryStatement(\n-              createBlock([                      // try\n-                createThrowStatement(createUndefinedExpression())\n-              ]),\n-              createCatch(                  // catch\n-                  createBindingIdentifier(variable),\n-                  createBlock([statement])),\n-              null);                       // finally\n-    }\n+  // this is a start and end point of this transformer\n+  transformScript(tree) {\n+    if (tree === this.rootTree_ || !this.rootTree_) {\n+      tree = super(tree);\n+      if (this.prependStatement_.length || this.blockRenames_.length) {\n+        tree.scriptItemList = prependStatements(tree.scriptItemList, ...this.prependStatement_);\n+        tree = this.flushRenames(tree);\n+        return new Script(tree.location, tree.scriptItemList, tree.moduleName);\n+      }\n+      return tree;\n+    } else {\n+      var functionTransform = new BlockBindingTransformer(this.idGenerator_,\n+          this.reporter_, tree, this.scopeBuilder_);\n+      var newTree = functionTransform.transformAny(tree);\n+\n+      if (newTree === tree) {\n+        return tree;\n+      }\n \n-    return statement;\n+      return new Script(tree.location, newTree.scriptItemList, tree.moduleName);\n+    }\n   }\n \n-  /** Class declarations should have been transformed away. */\n-  /**\n-   * @param {ClassDeclaration} tree\n-   * @return {ParseTree}\n-   */\n-  transformClassDeclaration(tree) {\n-    throw new Error('ClassDeclaration should be transformed away.');\n+  // even if the actual transformations are in the transformVariableDeclarationList_\n+  // the Statement itself might become a AnonBlock\n+  transformVariableStatement(tree) {\n+    var declarations = this.transformVariableDeclarationList_(tree.declarations);\n+    if (declarations.type === ANON_BLOCK) {\n+      return declarations;\n+    }\n+\n+    if (declarations === tree.declarations) {\n+      return tree;\n+    }\n+    return new VariableStatement(tree.location, declarations);\n   }\n \n-  /**\n-   * Transforms for .. in statement.\n-   */\n-  /**\n-   * @param {ForInStatement} tree\n-   * @return {ParseTree}\n-   */\n-  transformForInStatement(tree) {\n-    // Save it here because tree may change in the variable rewrite\n-    var treeBody = tree.body;\n+  transformVariableDeclarationList_(tree) {\n+    if (tree.declarationType === LET || tree.declarationType === CONST) {\n+      // just switch it to VAR\n+      if (this.scope_.isVarScope) {\n+        return new VariableDeclarationList(null, VAR,\n+          tree.declarations.map((declaration) => {\n+            return new VariableDeclaration(declaration.location,\n+              createBindingIdentifier(this.getVariableName_(declaration)), null, this.transformAny(declaration.initializer));\n+          })\n+        );\n+      }\n \n-    var initializer;\n-    if (tree.initializer != null &&\n-        tree.initializer.type == VARIABLE_DECLARATION_LIST) {\n+      // hoist variable declarations and assign them a value at the current location\n+      var variablesToDeclare = [];\n+      var assignments = [];\n+      tree.declarations.forEach((variableDeclaration) => {\n+        var variableName = this.getVariableName_(variableDeclaration);\n+        var uniqueName = variableName + this.idGenerator_.generateUniqueIdentifier();\n \n-      // for (var/let/const x [ = ...] in ...)\n-      var variables = tree.initializer;\n+        this.blockRenames_.push(new Rename(variableName, uniqueName));\n+        variableName = uniqueName;\n \n-      // Only one declaration allowed.\n-      if (variables.declarations.length != 1) {\n-        throw new Error('for .. in has != 1 variables');\n-      }\n+        var lvalue = createIdentifierExpression(variableName);\n+        var initializer = super.transformAny(variableDeclaration.initializer);\n \n-      var variable = variables.declarations[0];\n-      var variableName = this.getVariableName_(variable);\n-\n-      switch (variables.declarationType) {\n-        case LET:\n-        case CONST: {\n-          // initializer is illegal in for (const/let x in ...)\n-          // this should have been caught in the parser.\n-          if (variable.initializer != null) {\n-            throw new Error(\n-                'const/let in for-in may not have an initializer');\n-          }\n-\n-          // Build the result\n-          // for (var $x in ...) {\n-          //   let x = $x;\n-          //   ...\n-          // }\n-          // TODO: Use temp allocator.\n-          initializer = createVariableDeclarationList(\n-              VAR, `$${variableName}`, null);\n-\n-          // Add the let statement into the block and rewrite it next.\n-          // It is easier than creating the catch block manually etc.\n-          treeBody = this.prependToBlock_(\n-              createVariableStatement(\n-                  LET,\n-                  variableName,\n-                  createIdentifierExpression(`$${variableName}`)),\n-              treeBody);\n-          break;\n+        variablesToDeclare.push(variableName);\n+\n+        if (initializer) {\n+          assignments.push(createAssignmentStatement(lvalue, initializer));\n         }\n+      });\n+\n+      this.prependStatement_.push(\n+        new VariableStatement(null,\n+          new VariableDeclarationList(null, VAR,\n+            variablesToDeclare.map((variableName) => {\n+              return new VariableDeclaration(null,\n+                  createBindingIdentifier(variableName), null, null);\n+            })\n+          )));\n+      return new AnonBlock(null, assignments);\n+    }\n+    return this.transformAny(tree);\n+  }\n \n-        case VAR:\n-          // No special work for var\n-          initializer = this.transformVariables_(variables);\n-          break;\n+  transformBlock(tree) {\n+    var scope = this.pushScope(tree);\n+    tree = super(tree);\n+    tree = this.flushRenames(tree);\n+    this.popScope(scope);\n+    return tree;\n+  }\n \n-        default:\n-          throw new Error('Unreachable.');\n-      }\n-    } else {\n-      initializer = this.transformAny(tree.initializer);\n+  transformCatch(tree) {\n+    var scope = this.pushScope(tree);\n+    var binding = this.transformAny(tree.binding);\n+    // following ScopeVisitor, the catchBody block doesn't have a scope so we can't transform in this class\n+    var statements = this.transformList(tree.catchBody.statements);\n+    if (binding !== tree.binding || statements !== tree.catchBody.statements) {\n+      tree = new Catch(tree.location, binding, new Block(tree.catchBody.location, statements));\n     }\n+    tree = this.flushRenames(tree);\n+    this.popScope(scope);\n+    return tree;\n+  }\n+\n+  transformFunctionForScope_(func, tree) {\n+    var scope = this.pushScope(tree);\n+    tree = func();\n+    tree = this.flushRenames(tree);\n+    this.popScope(scope);\n+    return tree;\n+  }\n \n-    var result = tree;\n-    var collection = this.transformAny(tree.collection);\n-    var body = this.transformAny(treeBody);\n+  transformGetAccessor(tree) {\n+    return this.transformFunctionForScope_(() => super(tree), tree);\n+  }\n \n-    if (initializer != tree.initializer ||\n-        collection != tree.collection ||\n-        body != tree.body) {\n-      result = createForInStatement(initializer, collection, body);\n-    }\n+  transformSetAccessor(tree) {\n+    return this.transformFunctionForScope_(() => super(tree), tree);\n+  }\n \n-    return result;\n+  transformFunctionExpression(tree) {\n+    return this.transformFunctionForScope_(() => super(tree), tree);\n   }\n \n-  /**\n-   * TODO: Use non-scoped blocks (statement comma) when available.\n-   * @param {ParseTree} statement\n-   * @param {ParseTree} body\n-   * @return {Block}\n-   */\n-  prependToBlock_(statement, body) {\n-    if (body.type == BLOCK) {\n-      return createBlock([statement, ... body.statements]);\n+  transformFunctionDeclaration(tree) {\n+    // Named function in a block scope is only scoped to the block.\n+    // Hoist the function name and assign it in the current location\n+    if (!this.scope_.isVarScope) {\n+      var fnName = tree.name.getStringValue();\n+      var uniqueName = fnName + this.idGenerator_.generateUniqueIdentifier();\n+      this.blockRenames_.push(new Rename(fnName, uniqueName));\n+      fnName = uniqueName;\n+\n+      // f = function( ... ) { ... }\n+      tree = createExpressionStatement(\n+          createAssignmentExpression(\n+              createIdentifierExpression(fnName),\n+              new FunctionExpression(tree.location, null, tree.functionKind,\n+                  tree.parameterList, tree.typeAnnotation,\n+                  tree.annotations, tree.body)));\n+\n+\n+      this.prependStatement_.push(\n+        new VariableStatement(null,\n+          new VariableDeclarationList(null, VAR,\n+            [\n+              new VariableDeclaration(null,\n+                createBindingIdentifier(fnName), null, null)\n+            ]\n+          )));\n+\n+      this.revisitTreeForScopes(tree);\n+    }\n+\n+    if (tree.type === FUNCTION_DECLARATION) {\n+      return this.transformFunctionForScope_(() => super(tree), tree);\n     } else {\n-      return createBlock([statement, body]);\n+      return this.transformAny(tree);\n     }\n   }\n \n   /**\n-   * Transforms the for ( ... ; ... ; ... ) { ... } statement.\n-   * @param {ForStatement} tree\n-   * @return {ParseTree}\n+   * @param func a function that continues the transform of the loop\n+   * @param tree the loop tree\n+   * @param loopFactory a function that recreates the loop with a provided initializer,\n+   *    a set of renames for the loop headers, and an optional loop body\n+   * @returns {ParseTree}\n    */\n-  transformForStatement(tree) {\n-    var initializer;\n-    if (tree.initializer != null &&\n-        tree.initializer.type == VARIABLE_DECLARATION_LIST) {\n-\n-      // for (var/let/const ... ; ; ) { ... }\n-      var variables = tree.initializer;\n-\n-      switch (variables.declarationType) {\n-        case LET:\n-        case CONST:\n-          // let/const are rewritten differently so the code below doesn't apply\n-          return this.transformForLet_(tree, variables);\n-\n-        case VAR:\n-          // No special work for var.\n-          initializer = this.transformVariables_(variables);\n-          break;\n-\n-        default:\n-          throw new Error('Reached unreachable.');\n-      }\n-    } else {\n-      // The non-var case: for (x = ...; ; ) { ... }\n-      initializer = this.transformAny(tree.initializer);\n+  transformLoop_(func, tree, loopFactory) {\n+    var scope, initializerIsBlockBinding;\n+    if (tree.initializer && tree.initializer.type === VARIABLE_DECLARATION_LIST\n+      && tree.initializer.declarationType !== VAR) {\n+      initializerIsBlockBinding = true;\n     }\n \n-    // Finish transforming the body.\n-    var condition = this.transformAny(tree.condition);\n-    var increment = this.transformAny(tree.increment);\n-    var body = this.transformAny(tree.body);\n-\n-    var result = tree;\n-\n-    if (initializer != tree.initializer ||\n-        condition != tree.condition ||\n-        increment != tree.increment ||\n-        body != tree.body) {\n-      // Create new for statement.\n-      result = createForStatement(initializer, condition, increment, body);\n+    if (initializerIsBlockBinding) {\n+      scope = this.pushScope(tree);\n     }\n \n-    return result;\n-  }\n-\n-  /*\n-   * Transforms the for (let ...; ...; ...) { ... } statement. There are few\n-   * steps to this:\n-   *\n-   * 1. Hoist the declaration out of the for loop (keep as let for further\n-   *    rewrite)\n-   * 2. Rename the hoisted declared variables\n-   * 3. Wrap the for loop body in a try..finally block\n-   * 4. Before the try block, copy all variables into new block scoped\n-   *    variables (using original names)\n-   * 5. In the finally, write-back the to the hoisted variables\n-   *\n-   * For example:\n-   *\n-   * for (let x = 1, y = x + 2; x + y < 10, x ++, y ++) {\n-   *  if (condition) {\n-   *    continue;\n-   *  }\n-   * }\n-   *\n-   * translates into:\n-   *\n-   * {\n-   *   let $x = 1, $y = $x + 2;     // initializer dependencies\n-   *   for ( ; $x + $y < 10; $x++, $y++) {\n-   *     let x = $x, y = $y;\n-   *     try {\n-   *       // for loop body\n-   *       if (condition) {\n-   *         continue;\n-   *       }\n-   *\n-   *     } finally {\n-   *       $x = x;    // write-backs into the hoisted variables\n-   *       $y = y;\n-   *     }\n-   *   }\n-   * }\n-   * @param {ForStatement} tree\n-   * @param {VariableDeclarationList} variables\n-   * @return {ParseTree}\n-   */\n-  transformForLet_(tree, variables) {\n+    // We only create an \"iife\" if the loop has block bindings and functions\n+    // that use those block binded variables\n+    var finder = new FindBlockBindingInLoop(tree, this.scopeBuilder_);\n+    if (!finder.found) {\n+      // just switch it to var\n+      if (initializerIsBlockBinding) {\n+        var renames = [];\n+        var initializer = new VariableDeclarationList(null, VAR, tree.initializer.declarations.map((declaration) => {\n+          var variableName = this.getVariableName_(declaration);\n+          var uniqueName = variableName + this.idGenerator_.generateUniqueIdentifier();\n+\n+          renames.push(new Rename(variableName, uniqueName));\n+          return new VariableDeclaration(null, createBindingIdentifier(uniqueName), null, declaration.initializer);\n+        }));\n+        initializer = renameAll(renames, initializer);\n+\n+        tree = loopFactory(initializer, renames, renameAll(renames, tree.body));\n+        this.scopeBuilder_.scope = this.scope_;\n+        this.scopeBuilder_.visitAny(tree);\n+        this.scopeBuilder_.scope = null;\n+        tree = func(tree);\n+      } else {\n+        return func();\n+      }\n+    } else {\n+      var iifeParameterList = [];\n+      var iifeArgumentList = [];\n+      var renames = [];\n+      var initializer = null;\n+      // switch to var and rename variables, holding them in potential iife argument/parameter list\n+      if (tree.initializer && tree.initializer.type === VARIABLE_DECLARATION_LIST) {\n+        initializer = new VariableDeclarationList(null, VAR, tree.initializer.declarations.map((declaration) => {\n+          var variableName = this.getVariableName_(declaration);\n+          var uniqueName = variableName + this.idGenerator_.generateUniqueIdentifier();\n+\n+\n+          iifeArgumentList.push(createIdentifierExpression(uniqueName));\n+          iifeParameterList.push(new FormalParameter(null,\n+            new BindingElement(null, createBindingIdentifier(variableName), null), null, []));\n+\n+          renames.push(new Rename(variableName, uniqueName));\n+          return new VariableDeclaration(null, createBindingIdentifier(uniqueName), null, declaration.initializer);\n+        }));\n+\n+        initializer = renameAll(renames, initializer);\n+      }\n \n-    // Accumulator for 'let x = $x;'\n-    var copyFwd = [];\n+      // the loop might already have a label, let's keep it with us\n+      var loopLabel = this.labelledLoops_.get(tree);\n \n-    // Accumulator for '$x = x' copybacks\n-    var copyBak = [];\n+      var iifeInfo = IIFEExtractAbruptCompletions.createIIFE(\n+        this.idGenerator_, tree.body, iifeParameterList, iifeArgumentList,\n+          () => loopLabel = loopLabel || this.idGenerator_.generateUniqueIdentifier()\n+        );\n \n-    // Accumulator for the hoisted declaration: let $x = 1, ...;\n-    var hoisted = [];\n+      tree = loopFactory(initializer, renames, iifeInfo.loopBody);\n \n-    var renames = [];\n+      if (loopLabel) {\n+        tree = new LabelledStatement(tree.location, createIdentifierToken(loopLabel), tree);\n+      }\n \n-    variables.declarations.forEach((variable) => {\n-      var variableName = this.getVariableName_(variable);\n-      var hoistedName = `$${variableName}`;\n+      tree = new AnonBlock(tree.location, [iifeInfo.variableStatements, tree]);\n \n-      // perform renames in the initializer\n-      var initializer = renameAll(renames, variable.initializer);\n+      this.revisitTreeForScopes(tree);\n+      tree = this.transformAny(tree);\n+    }\n \n-      // hoisted declaration: let $x = 1\n-      hoisted.push(createVariableDeclaration(hoistedName, initializer));\n+    if (initializerIsBlockBinding) {\n+      tree = this.flushRenames(tree);\n+      this.popScope(scope);\n+    }\n+    return tree;\n+  }\n \n-      // copy forward: let x = $x;\n-      copyFwd.push(\n-          createVariableDeclaration(\n-              variableName,\n-              createIdentifierExpression(hoistedName)));\n+  transformForInStatement(tree) {\n+    return this.transformLoop_((t=tree) => super(t), tree, (initializer, renames = [], body = tree.body) => {\n+      return new ForInStatement(tree.location, initializer, renameAll(renames, tree.collection), body)\n+    });\n+  }\n \n-      // copy back: $x = x;\n-      copyBak.push(\n-          createExpressionStatement(\n-              createAssignmentExpression(\n-                  createIdentifierExpression(hoistedName),\n-                  createIdentifierExpression(variableName))));\n+  transformForStatement(tree) {\n+    return this.transformLoop_((t=tree) => super(t), tree, (initializer, renames = [], body = tree.body) => {\n+      return new ForStatement(tree.location, initializer, renameAll(renames, tree.condition), renameAll(renames, tree.increment), body)\n+    });\n+  }\n \n-      // Remember rename for the subsequent initializers\n-      renames.push(new Rename(variableName, hoistedName));\n+  transformWhileStatement(tree) {\n+    return this.transformLoop_((t=tree) => super(t), tree, (initializer, renames = [], body = tree.body) => {\n+      return new WhileStatement(tree.location, renameAll(renames, tree.condition), body)\n     });\n+  }\n \n-    // 'tree.condition' with renamed variables\n-    var condition = renameAll(renames, tree.condition);\n-    // 'tree.increment' with renamed variables\n-    var increment = renameAll(renames, tree.increment);\n-\n-    // package it all up\n-    var transformedForLoop = createBlock([\n-      // hoisted declaration\n-      createVariableStatement(\n-          createVariableDeclarationList(\n-              LET, hoisted)),\n-      // for loop\n-      createForStatement(\n-          null,\n-          condition,\n-          increment,\n-          // body\n-          createBlock([\n-            createVariableStatement(\n-                // let x = $x;\n-                createVariableDeclarationList(LET, copyFwd)),\n-            // try { ... } finally { copyBak }\n-            createTryStatement(\n-                // try - the original for loop body\n-                toBlock(tree.body),\n-                // catch (none)\n-                null,\n-                // finally - the writebacks\n-                createFinally(createBlock(copyBak)))\n-          ]))\n-      ]);\n-\n-    // Now transform the rewritten for loop! This is safe to do because the\n-    return this.transformAny(transformedForLoop);\n+  transformDoWhileStatement(tree) {\n+    return this.transformLoop_((t=tree) => super(t), tree, (initializer, renames = [], body = tree.body) => {\n+      return new DoWhileStatement(tree.location, body, renameAll(renames, tree.condition))\n+    });\n   }\n \n-  /**\n-   * Transforms a function declaration statement. Function name in the block\n-   * scope is scoped to the block only, so the same rewrite applies.\n-   *\n-   * @param {FunctionDeclaration} tree\n-   * @return {ParseTree}\n-   */\n-  transformFunctionDeclaration(tree) {\n-    var body = this.transformFunctionBody(tree.body);\n-    var parameterList = this.transformAny(tree.parameterList);\n+  // We want to keep track of loops with labels.\n+  // If transforming them doesn't result in a statement (AnonBlock),\n+  // then remove the label\n+  transformLabelledStatement(tree) {\n+    if (tree.statement.isIterationStatement()) {\n+      this.labelledLoops_.set(tree.statement, tree.name.value);\n+      var statement = this.transformAny(tree.statement);\n+      if (!statement.isStatement()) {\n+        return statement;\n+      }\n+      if (statement === tree.statement) {\n+        return tree;\n+      }\n+      return new LabelledStatement(tree.location, tree.name, statement);\n+    } else {\n+      return super(tree);\n+    }\n+  }\n+}\n \n-    if (this.scope_.type === ScopeType.BLOCK) {\n-      // Named function in a block scope is only scoped to the block.\n-      // Add function name into variable hash to later 'declare' the\n-      // block scoped variable for it.\n-      this.scope_.addBlockScopedVariable(tree.name.identifierToken.value);\n+/**\n+ * Givens a list of statements, this extracts all the needed `return`s, `break`s and `continue`s (abrupt completions statements).\n+ * It returns an object containing\n+ * - variableStatement: Might contain aliases to `this` and `arguments`, and also a function\n+ * - loopBody: Might contain a call to the function defined above, and also a switch statement for the abrupt completions\n+ * - needsSurroundingLabel: A boolean indicating whether the surrounding loop requires a label (used for breaks and continues)\n+ */\n+class IIFEExtractAbruptCompletions extends ParseTreeTransformer {\n+\n+  constructor(idGenerator, requestSurroundingLabel) {\n+    this.idGenerator_ = idGenerator;\n+    this.inLoop_ = 0;\n+    this.inBreakble_ = 0;\n+    this.variableDeclarations_ = [];\n+    this.extractedStatements_ = [];\n+    this.requestSurroundingLabel_ = requestSurroundingLabel;\n+    this.surroudingLoopLabel_ = null;\n+    this.labelledStatements_ = {};\n+  }\n \n-      // f = function( ... ) { ... }\n-      return createExpressionStatement(\n-          createAssignmentExpression(\n-              createIdentifierExpression(tree.name.identifierToken),\n-              new FunctionExpression(tree.location, null, tree.functionKind,\n-                                     parameterList, tree.typeAnnotation,\n-                                     tree.annotations, body)));\n-    }\n+  createIIFE(body, paramList, argsList) {\n+    body = this.transformAny(body);\n+\n+    body = alphaRenameThisAndArguments(this, body);\n+    var tmpFnName = this.idGenerator_.generateUniqueIdentifier();\n+    // function (...) { ... }\n+    var functionExpression = new FunctionExpression(null, null, null,\n+      new FormalParameterList(null, paramList), null, [], new FunctionBody(null, body.statements || [body]));\n+    // var $tmpFn = ${functionExpression}\n+    this.variableDeclarations_.push(new VariableDeclaration(null,\n+      createBindingIdentifier(tmpFnName), null, functionExpression));\n+    // $tmpFn(...)\n+    var functionCall = new CallExpression(null, new IdentifierExpression(null, tmpFnName), new ArgumentList(null, argsList));\n+\n+\n+    var loopBody = null;\n+    if (this.extractedStatements_.length || this.hasReturns) {\n+      var tmpVarName = createIdentifierExpression(this.idGenerator_.generateUniqueIdentifier());\n+      // hoist declaration\n+      this.variableDeclarations_.push(new VariableDeclaration(null,\n+        createBindingIdentifier(tmpVarName), null, null));\n+\n+      var maybeReturn;\n+      if (this.hasReturns) {\n+        maybeReturn = parseStatement `if (${tmpVarName} && ${tmpVarName}.hasOwnProperty(\"v\")) return ${tmpVarName}.v;`;\n+      }\n \n-    if (body === tree.body &&\n-        parameterList === tree.parameterList) {\n-      return tree;\n-    }\n+      if (this.extractedStatements_.length) {\n+        // handle each extractedStatement as a case clause\n+        var caseClauses = this.extractedStatements_.map(function (statement, index) {\n+          return new CaseClause(null, new LiteralExpression(null, index), [statement]);\n+        });\n \n-    return new FunctionDeclaration(tree.location, tree.name, tree.functionKind,\n-                                   parameterList, tree.typeAnnotation,\n-                                   tree.annotations, body);\n-  }\n+        // default clause is the return statement, if it's needed\n+        if (maybeReturn) {\n+          caseClauses.push(new DefaultClause(null, [maybeReturn]));\n+        }\n \n-  /**\n-   * Transforms the whole program.\n-   * @param {Script} tree\n-   * @return {ParseTree}\n-   */\n-  transformScript(tree) {\n-    // Push new scope\n-    var scope = this.push_(this.createScriptScope_());\n+        // $tmpVar = $tmpFn(...); switch($tmpVar) {...}\n+        loopBody = new Block(null, [\n+          new ExpressionStatement(null, createAssignmentExpression(tmpVarName, functionCall)),\n+          new SwitchStatement(null, tmpVarName, caseClauses)\n+        ]);\n+      } else {\n+        // $tmpVar = $tmpFn(...); ${maybeReturn}\n+        loopBody = new Block(null, [\n+          new ExpressionStatement(null, createAssignmentExpression(tmpVarName, functionCall)),\n+          maybeReturn\n+        ]);\n+      }\n+    } else {\n+      // $tmpFn(...)\n+      loopBody = new Block(null, [new ExpressionStatement(null, functionCall)]);\n+    }\n \n-    var result = super.transformScript(tree);\n \n-    this.pop_(scope);\n-    return result;\n+    return {\n+      variableStatements: new VariableStatement(null, new VariableDeclarationList(null, VAR, this.variableDeclarations_)),\n+      loopBody: loopBody\n+    };\n   }\n \n-  /**\n-   * Variable declarations are detected earlier and handled elsewhere.\n-   * @param {VariableDeclaration} tree\n-   * @return {ParseTree}\n-   */\n-  transformVariableDeclaration(tree) {\n-    throw new Error('Should never see variable declaration tree.');\n+  requestSurroundingLabel_() {\n+    return this.surroudingLoopLabel_ || (this.surroudingLoopLabel_ = this.requestSurroundingLabel_())\n   }\n \n-  /**\n-   * Variable declarations are detected earlier and handled elsewhere.\n-   * @param {VariableDeclarationList} tree\n-   * @return {ParseTree}\n-   */\n-  transformVariableDeclarationList(tree) {\n-    throw new Error('Should never see variable declaration list.');\n+  // alphaRenameThisAndArguments\n+  addTempVarForArguments() {\n+    var tmpVarName = this.idGenerator_.generateUniqueIdentifier();\n+    this.variableDeclarations_.push(new VariableDeclaration(null,\n+      createBindingIdentifier(tmpVarName), null, new ThisExpression(null)));\n+    return tmpVarName;\n+  }\n+  // alphaRenameThisAndArguments\n+  addTempVarForThis() {\n+    var tmpVarName = this.idGenerator_.generateUniqueIdentifier();\n+    this.variableDeclarations_.push(new VariableDeclaration(null,\n+      createBindingIdentifier(tmpVarName), null, new IdentifierExpression(null, ARGUMENTS)));\n+    return tmpVarName;\n   }\n \n-  /**\n-   * Transforms the variable statement. Inside a block const and let\n-   * are transformed into block-scoped variables.\n-   * Outside of the block, const and let becomes regular variables.\n-   * @param {VariableStatement} tree\n-   * @return {ParseTree}\n-   */\n-  transformVariableStatement(tree) {\n-    if (this.scope_.type == ScopeType.BLOCK) {\n-      // let/const have block scoped meaning only in block scope.\n-      switch (tree.declarations.declarationType) {\n-        case CONST:\n-        case LET:\n-          return this.transformBlockVariables_(tree.declarations);\n-\n-        default:\n-          break;\n-      }\n+  transformAny(tree) {\n+    if (tree) {\n+      if (tree.isBreakableStatement()) this.inBreakble_++;\n+      if (tree.isIterationStatement()) this.inLoop_++;\n+      tree = super(tree);\n+      if (tree.isBreakableStatement()) this.inBreakble_--;\n+      if (tree.isIterationStatement()) this.inLoop_--;\n+      return tree;\n     }\n+    return tree; // null !== undefined\n+  }\n \n-    // Default handling.\n-    var variables = this.transformVariables_(tree.declarations);\n+  transformReturnStatement(tree) {\n+    this.hasReturns = true;\n+    return new ReturnStatement(tree.location, createPropertyDescriptor({\n+      v: tree.expression","path":"src/codegeneration/BlockBindingTransformer.js","position":1077,"original_position":1077,"commit_id":"63099285b503117ad363965ef7ea6cd57321ba25","original_commit_id":"63099285b503117ad363965ef7ea6cd57321ba25","user":{"login":"rodyhaddad","id":1331190,"avatar_url":"https://avatars.githubusercontent.com/u/1331190?v=2","gravatar_id":"bed0bccea858f1e04e64ba57c8e96d10","url":"https://api.github.com/users/rodyhaddad","html_url":"https://github.com/rodyhaddad","followers_url":"https://api.github.com/users/rodyhaddad/followers","following_url":"https://api.github.com/users/rodyhaddad/following{/other_user}","gists_url":"https://api.github.com/users/rodyhaddad/gists{/gist_id}","starred_url":"https://api.github.com/users/rodyhaddad/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rodyhaddad/subscriptions","organizations_url":"https://api.github.com/users/rodyhaddad/orgs","repos_url":"https://api.github.com/users/rodyhaddad/repos","events_url":"https://api.github.com/users/rodyhaddad/events{/privacy}","received_events_url":"https://api.github.com/users/rodyhaddad/received_events","type":"User","site_admin":false},"body":"Hah, good catch!\r\n\r\nDone","created_at":"2014-08-11T18:15:20Z","updated_at":"2014-08-11T18:15:20Z","html_url":"https://github.com/google/traceur-compiler/pull/1243#discussion_r16069961","pull_request_url":"https://api.github.com/repos/google/traceur-compiler/pulls/1243","_links":{"self":{"href":"https://api.github.com/repos/google/traceur-compiler/pulls/comments/16069961"},"html":{"href":"https://github.com/google/traceur-compiler/pull/1243#discussion_r16069961"},"pull_request":{"href":"https://api.github.com/repos/google/traceur-compiler/pulls/1243"}}},"pull_request":{"url":"https://api.github.com/repos/google/traceur-compiler/pulls/1243","id":19389718,"html_url":"https://github.com/google/traceur-compiler/pull/1243","diff_url":"https://github.com/google/traceur-compiler/pull/1243.diff","patch_url":"https://github.com/google/traceur-compiler/pull/1243.patch","issue_url":"https://api.github.com/repos/google/traceur-compiler/issues/1243","number":1243,"state":"open","title":"Rewrite BlockBindingTransfomer for better performance","user":{"login":"rodyhaddad","id":1331190,"avatar_url":"https://avatars.githubusercontent.com/u/1331190?v=2","gravatar_id":"bed0bccea858f1e04e64ba57c8e96d10","url":"https://api.github.com/users/rodyhaddad","html_url":"https://github.com/rodyhaddad","followers_url":"https://api.github.com/users/rodyhaddad/followers","following_url":"https://api.github.com/users/rodyhaddad/following{/other_user}","gists_url":"https://api.github.com/users/rodyhaddad/gists{/gist_id}","starred_url":"https://api.github.com/users/rodyhaddad/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rodyhaddad/subscriptions","organizations_url":"https://api.github.com/users/rodyhaddad/orgs","repos_url":"https://api.github.com/users/rodyhaddad/repos","events_url":"https://api.github.com/users/rodyhaddad/events{/privacy}","received_events_url":"https://api.github.com/users/rodyhaddad/received_events","type":"User","site_admin":false},"body":"This is my proposition to optimize the BlockBindingTransfomer\r\n\r\nThe general idea behind it is:\r\n\r\n* No longer use try-catch\r\n\r\n* In variable declarations, transform any `let/const` to `var` and rename the variable to avoid name collisions.\r\n\r\n* In loops, check if it contains any block bindings AND function declarations that use those block binded variables. \r\nIf that's the case, then extract the loopBody to a function (~iife), and transform the final loopBody to invoke that function. \r\nThe loopBody could have had `break`s, `continue`s and `return`s in it. These are handled by `if` statements and `switch` cases in the resulting transformed loopBody. Labels are accounted for (see tests)\r\n* If the loop doesn't contain block bindings that are used in function declarations, then everything is transformed to `var` and renamed to avoid name collisions.\r\n\r\nThe PR isn't perfect and does have some rough edges. Please do comment on them and bash anything wrong that you see. It's my first contribution to traceur :-)\r\nIt does pass all the tests.\r\n\r\nIt's a significant change.\r\nI'm free most days if anyone wants to pair up for quicker review","created_at":"2014-08-06T08:05:21Z","updated_at":"2014-08-11T18:15:20Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a94a41f6285f807929b5b22b8ce5bfe5e2a5e5ab","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/google/traceur-compiler/pulls/1243/commits","review_comments_url":"https://api.github.com/repos/google/traceur-compiler/pulls/1243/comments","review_comment_url":"https://api.github.com/repos/google/traceur-compiler/pulls/comments/{number}","comments_url":"https://api.github.com/repos/google/traceur-compiler/issues/1243/comments","statuses_url":"https://api.github.com/repos/google/traceur-compiler/statuses/63099285b503117ad363965ef7ea6cd57321ba25","head":{"label":"rodyhaddad:chore/better-block-binding","ref":"chore/better-block-binding","sha":"63099285b503117ad363965ef7ea6cd57321ba25","user":{"login":"rodyhaddad","id":1331190,"avatar_url":"https://avatars.githubusercontent.com/u/1331190?v=2","gravatar_id":"bed0bccea858f1e04e64ba57c8e96d10","url":"https://api.github.com/users/rodyhaddad","html_url":"https://github.com/rodyhaddad","followers_url":"https://api.github.com/users/rodyhaddad/followers","following_url":"https://api.github.com/users/rodyhaddad/following{/other_user}","gists_url":"https://api.github.com/users/rodyhaddad/gists{/gist_id}","starred_url":"https://api.github.com/users/rodyhaddad/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rodyhaddad/subscriptions","organizations_url":"https://api.github.com/users/rodyhaddad/orgs","repos_url":"https://api.github.com/users/rodyhaddad/repos","events_url":"https://api.github.com/users/rodyhaddad/events{/privacy}","received_events_url":"https://api.github.com/users/rodyhaddad/received_events","type":"User","site_admin":false},"repo":{"id":21828143,"name":"traceur-compiler","full_name":"rodyhaddad/traceur-compiler","owner":{"login":"rodyhaddad","id":1331190,"avatar_url":"https://avatars.githubusercontent.com/u/1331190?v=2","gravatar_id":"bed0bccea858f1e04e64ba57c8e96d10","url":"https://api.github.com/users/rodyhaddad","html_url":"https://github.com/rodyhaddad","followers_url":"https://api.github.com/users/rodyhaddad/followers","following_url":"https://api.github.com/users/rodyhaddad/following{/other_user}","gists_url":"https://api.github.com/users/rodyhaddad/gists{/gist_id}","starred_url":"https://api.github.com/users/rodyhaddad/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rodyhaddad/subscriptions","organizations_url":"https://api.github.com/users/rodyhaddad/orgs","repos_url":"https://api.github.com/users/rodyhaddad/repos","events_url":"https://api.github.com/users/rodyhaddad/events{/privacy}","received_events_url":"https://api.github.com/users/rodyhaddad/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/rodyhaddad/traceur-compiler","description":"Traceur is a JavaScript.next-to-JavaScript-of-today compiler","fork":true,"url":"https://api.github.com/repos/rodyhaddad/traceur-compiler","forks_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/forks","keys_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/teams","hooks_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/hooks","issue_events_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/issues/events{/number}","events_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/events","assignees_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/assignees{/user}","branches_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/branches{/branch}","tags_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/tags","blobs_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/git/refs{/sha}","trees_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/statuses/{sha}","languages_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/languages","stargazers_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/stargazers","contributors_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/contributors","subscribers_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/subscribers","subscription_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/subscription","commits_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/commits{/sha}","git_commits_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/git/commits{/sha}","comments_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/comments{/number}","issue_comment_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/issues/comments/{number}","contents_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/contents/{+path}","compare_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/merges","archive_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/downloads","issues_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/issues{/number}","pulls_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/pulls{/number}","milestones_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/milestones{/number}","notifications_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/labels{/name}","releases_url":"https://api.github.com/repos/rodyhaddad/traceur-compiler/releases{/id}","created_at":"2014-07-14T17:11:15Z","updated_at":"2014-07-21T19:47:55Z","pushed_at":"2014-08-07T21:46:06Z","git_url":"git://github.com/rodyhaddad/traceur-compiler.git","ssh_url":"git@github.com:rodyhaddad/traceur-compiler.git","clone_url":"https://github.com/rodyhaddad/traceur-compiler.git","svn_url":"https://github.com/rodyhaddad/traceur-compiler","homepage":"","size":19758,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"google:master","ref":"master","sha":"102ce4153e5774141ca3ff306bbe60168c840af1","user":{"login":"google","id":1342004,"avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=2","gravatar_id":"24ba30616d2a20673f54c2aee36d159e","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"repo":{"id":9060347,"name":"traceur-compiler","full_name":"google/traceur-compiler","owner":{"login":"google","id":1342004,"avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=2","gravatar_id":"24ba30616d2a20673f54c2aee36d159e","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/google/traceur-compiler","description":"Traceur is a JavaScript.next-to-JavaScript-of-today compiler","fork":false,"url":"https://api.github.com/repos/google/traceur-compiler","forks_url":"https://api.github.com/repos/google/traceur-compiler/forks","keys_url":"https://api.github.com/repos/google/traceur-compiler/keys{/key_id}","collaborators_url":"https://api.github.com/repos/google/traceur-compiler/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/google/traceur-compiler/teams","hooks_url":"https://api.github.com/repos/google/traceur-compiler/hooks","issue_events_url":"https://api.github.com/repos/google/traceur-compiler/issues/events{/number}","events_url":"https://api.github.com/repos/google/traceur-compiler/events","assignees_url":"https://api.github.com/repos/google/traceur-compiler/assignees{/user}","branches_url":"https://api.github.com/repos/google/traceur-compiler/branches{/branch}","tags_url":"https://api.github.com/repos/google/traceur-compiler/tags","blobs_url":"https://api.github.com/repos/google/traceur-compiler/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/google/traceur-compiler/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/google/traceur-compiler/git/refs{/sha}","trees_url":"https://api.github.com/repos/google/traceur-compiler/git/trees{/sha}","statuses_url":"https://api.github.com/repos/google/traceur-compiler/statuses/{sha}","languages_url":"https://api.github.com/repos/google/traceur-compiler/languages","stargazers_url":"https://api.github.com/repos/google/traceur-compiler/stargazers","contributors_url":"https://api.github.com/repos/google/traceur-compiler/contributors","subscribers_url":"https://api.github.com/repos/google/traceur-compiler/subscribers","subscription_url":"https://api.github.com/repos/google/traceur-compiler/subscription","commits_url":"https://api.github.com/repos/google/traceur-compiler/commits{/sha}","git_commits_url":"https://api.github.com/repos/google/traceur-compiler/git/commits{/sha}","comments_url":"https://api.github.com/repos/google/traceur-compiler/comments{/number}","issue_comment_url":"https://api.github.com/repos/google/traceur-compiler/issues/comments/{number}","contents_url":"https://api.github.com/repos/google/traceur-compiler/contents/{+path}","compare_url":"https://api.github.com/repos/google/traceur-compiler/compare/{base}...{head}","merges_url":"https://api.github.com/repos/google/traceur-compiler/merges","archive_url":"https://api.github.com/repos/google/traceur-compiler/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/google/traceur-compiler/downloads","issues_url":"https://api.github.com/repos/google/traceur-compiler/issues{/number}","pulls_url":"https://api.github.com/repos/google/traceur-compiler/pulls{/number}","milestones_url":"https://api.github.com/repos/google/traceur-compiler/milestones{/number}","notifications_url":"https://api.github.com/repos/google/traceur-compiler/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/google/traceur-compiler/labels{/name}","releases_url":"https://api.github.com/repos/google/traceur-compiler/releases{/id}","created_at":"2013-03-27T18:05:40Z","updated_at":"2014-08-11T15:03:48Z","pushed_at":"2014-08-11T16:55:45Z","git_url":"git://github.com/google/traceur-compiler.git","ssh_url":"git@github.com:google/traceur-compiler.git","clone_url":"https://github.com/google/traceur-compiler.git","svn_url":"https://github.com/google/traceur-compiler","homepage":"","size":38103,"stargazers_count":2768,"watchers_count":2768,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"forks_count":179,"mirror_url":null,"open_issues_count":173,"forks":179,"open_issues":173,"watchers":2768,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/google/traceur-compiler/pulls/1243"},"html":{"href":"https://github.com/google/traceur-compiler/pull/1243"},"issue":{"href":"https://api.github.com/repos/google/traceur-compiler/issues/1243"},"comments":{"href":"https://api.github.com/repos/google/traceur-compiler/issues/1243/comments"},"review_comments":{"href":"https://api.github.com/repos/google/traceur-compiler/pulls/1243/comments"},"review_comment":{"href":"https://api.github.com/repos/google/traceur-compiler/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/google/traceur-compiler/pulls/1243/commits"},"statuses":{"href":"https://api.github.com/repos/google/traceur-compiler/statuses/63099285b503117ad363965ef7ea6cd57321ba25"}}}}