Add a JsVisitor to restructure block-like structures to ensure compatibility with limits on the number of statements in a block in IE7.
Resolves issue 1440.

Patch by: bobv
Review by: scottb


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1916 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 1812878..1def354 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -57,6 +57,7 @@
 import com.google.gwt.dev.js.JsSourceGenerationVisitor;
 import com.google.gwt.dev.js.JsStringInterner;
 import com.google.gwt.dev.js.JsSymbolResolver;
+import com.google.gwt.dev.js.JsIEBlockSizeVisitor;
 import com.google.gwt.dev.js.JsUnusedFunctionRemover;
 import com.google.gwt.dev.js.JsVerboseNamer;
 import com.google.gwt.dev.js.ast.JsProgram;
@@ -422,6 +423,10 @@
           throw new InternalCompilerException("Unknown output mode");
       }
 
+      // (12) Work around an IE7 bug
+      // http://code.google.com/p/google-web-toolkit/issues/detail?id=1440
+      JsIEBlockSizeVisitor.exec(jsProgram);
+
       DefaultTextOutput out = new DefaultTextOutput(
           options.getOutput().shouldMinimize());
       JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
diff --git a/dev/core/src/com/google/gwt/dev/js/JsIEBlockSizeVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsIEBlockSizeVisitor.java
new file mode 100644
index 0000000..56d6d85
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsIEBlockSizeVisitor.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.js;
+
+import com.google.gwt.dev.js.ast.JsBlock;
+import com.google.gwt.dev.js.ast.JsCase;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsDefault;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsSwitchMember;
+import com.google.gwt.dev.js.ast.JsVisitor;
+
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Some versions of IE7 have a limit on the number of statements that can appear
+ * within a JsBlock. This visitor will restructure blocks and other block-like
+ * structures with too many statements in order to reduce the total number of
+ * statements that appear within any given block to fewer than
+ * {@value #MAX_BLOCK_SIZE} statements by creating nested blocks:
+ * 
+ * <pre>
+ * {
+ *   { statements }
+ *   { statements }
+ *   remainder of statements
+ * }
+ * </pre>
+ * 
+ * This change is purely structural, it will not affect code flow.
+ */
+public class JsIEBlockSizeVisitor {
+
+  /**
+   * Visits every block-like structure.
+   */
+  private static class BlockVisitor extends JsVisitor {
+
+    @Override
+    public void endVisit(JsBlock x, JsContext<JsStatement> ctx) {
+      restructure(x.getStatements());
+    }
+
+    @Override
+    public void endVisit(JsCase x, JsContext<JsSwitchMember> ctx) {
+      restructure(x.getStmts());
+    }
+
+    @Override
+    public void endVisit(JsDefault x, JsContext<JsSwitchMember> ctx) {
+      restructure(x.getStmts());
+    }
+
+    /**
+     * Perform the restructuring on a list of statements. Blocks are created as
+     * necessary to prevent any given block from exceeding the maximum size.
+     */
+    private void restructure(List<JsStatement> statements) {
+      // This outer loop will collapse the newly-created block into super-blocks
+      while (statements.size() > MAX_BLOCK_SIZE) {
+        ListIterator<JsStatement> i = statements.listIterator();
+        List<JsStatement> statementsInNewBlock = null;
+
+        // This loop represents a single fold over the list of statements
+        while (statements.size() > MAX_BLOCK_SIZE && i.hasNext()) {
+          JsStatement current = i.next();
+
+          if (statementsInNewBlock == null) {
+            // Replace the current statement with a new block
+            JsBlock newBlock = new JsBlock();
+            statementsInNewBlock = newBlock.getStatements();
+            i.set(newBlock);
+          } else {
+            /*
+             * There's an open replacement block, remove the statement from its
+             * current block.
+             */
+            i.remove();
+          }
+
+          // Move the statement into the new block
+          statementsInNewBlock.add(current);
+
+          /*
+           * If we hit the cap on a new block, discard the reference to create a
+           * new block for the next statement that we see.
+           */
+          if (statementsInNewBlock.size() == MAX_BLOCK_SIZE) {
+            statementsInNewBlock = null;
+          }
+        }
+      }
+      assert statements.size() <= MAX_BLOCK_SIZE;
+    }
+  }
+
+  // Use this value instead to test the effects of the visitor
+  // private static final int MAX_BLOCK_SIZE = 1 << 5;
+
+  private static final int MAX_BLOCK_SIZE = 1 << 15 - 1;
+
+  /**
+   * Entry point.
+   */
+  public static void exec(JsProgram program) {
+    (new BlockVisitor()).accept(program);
+  }
+}