Refactor JsParser interface to be a static method; hide internal state.

Review by: bobv

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4938 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index bbc8d3a..3666044 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -371,7 +371,6 @@
   public String optimizeJavaScript(TreeLogger logger, String program)
       throws UnableToCompleteException {
     logger = logger.branch(TreeLogger.DEBUG, "Attempting to optimize JS", null);
-    JsParser parser = new JsParser();
     Reader r = new StringReader(program);
     JsProgram jsProgram = new JsProgram();
     JsScope topScope = jsProgram.getScope();
@@ -381,8 +380,7 @@
     try {
       SourceInfo sourceInfo = jsProgram.createSourceInfoSynthetic(
           StandardLinkerContext.class, "Linker-derived JS");
-      parser.setSourceInfo(sourceInfo);
-      parser.parseInto(topScope, jsProgram.getGlobalBlock(), r, 1);
+      JsParser.parseInto(sourceInfo, topScope, jsProgram.getGlobalBlock(), r);
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Unable to parse JavaScript", e);
       throw new UnableToCompleteException();
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
index f35fb11..59ffee4 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -961,7 +961,6 @@
   private boolean foundExplicitSourceOrSuperSource;
   private final ObjAttrCvt<Generator> genAttrCvt = new ObjAttrCvt<Generator>(
       Generator.class);
-  private final JsParser jsParser = new JsParser();
   private final JsProgram jsPgm = new JsProgram();
   private final LinkerNameAttrCvt linkerNameAttrCvt = new LinkerNameAttrCvt();
   private final ModuleDefLoader loader;
@@ -1034,9 +1033,8 @@
     List<JsStatement> stmts;
     try {
       // TODO Provide more context here
-      jsParser.setSourceInfo(jsPgm.createSourceInfoSynthetic(
-          ModuleDefSchema.class, "Module.xml"));
-      stmts = jsParser.parse(jsPgm.getScope(), r, startLineNumber);
+      stmts = JsParser.parse(jsPgm.createSourceInfo(startLineNumber,
+          moduleURL.toExternalForm()), jsPgm.getScope(), r);
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Error reading script source", e);
       throw new UnableToCompleteException();
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java b/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
index 0bc1d6f..b514a13 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
@@ -65,7 +65,7 @@
     private final String[] paramNames;
     private final String source;
     private final JsProgram program;
-    
+
     private JsniMethodImpl(String name, String source, String[] paramNames,
         int line, String location, JsProgram program) {
       this.name = name;
@@ -281,9 +281,8 @@
     }
 
     try {
-      JsParser parser = new JsParser();
-      parser.setSourceInfo(program.createSourceInfo(startLine, location));
-      List<JsStatement> stmts = parser.parse(program.getScope(), r, startLine);
+      List<JsStatement> stmts = JsParser.parse(program.createSourceInfo(
+          startLine, location), program.getScope(), r);
 
       return (JsFunction) ((JsExprStmt) stmts.get(0)).getExpression();
     } catch (IOException e) {
diff --git a/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java b/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
index afbeef1..3cc1cd9 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
@@ -16,6 +16,7 @@
 package com.google.gwt.dev.jdt;
 
 import com.google.gwt.dev.jjs.InternalCompilerException;
+import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.js.JsParser;
 import com.google.gwt.dev.js.JsParserException;
 import com.google.gwt.dev.js.ast.JsContext;
@@ -107,7 +108,6 @@
 
   private void findJsniRefsAccurately(MethodDeclaration methodDeclaration,
       String jsniCode) throws InternalCompilerException {
-    JsParser jsParser = new JsParser();
     JsProgram jsProgram = new JsProgram();
 
     String syntheticFnHeader = "function(";
@@ -127,7 +127,8 @@
     StringReader sr = new StringReader(syntheticFnHeader + '\n' + jsniCode);
     try {
       // start at -1 to avoid counting our synthetic header
-    List<JsStatement> result = jsParser.parse(jsProgram.getScope(), sr, -1);
+      List<JsStatement> result = JsParser.parse(SourceInfo.UNKNOWN,
+          jsProgram.getScope(), sr);
       new JsVisitor() {
         public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
           String ident = x.getIdent();
@@ -160,7 +161,7 @@
       String jsniRefString = readJsIdentifier(reader);
       if (jsniRefString == null) {
         // badly formatted identifier; skip to the next @ sign
-          idx++;
+        idx++;
       } else {
         assert (jsniRefString.charAt(0) == '@');
         jsniRefs.add(jsniRefString.substring(1));
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java b/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
index f4381b8..246a0a2 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/SourceInfo.java
@@ -352,4 +352,9 @@
       }
     }
   }
+
+  @Override
+  public String toString() {
+    return origin.toString();
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
index 0b57c9f..4ef81f4 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/SourceOrigin.java
@@ -105,4 +105,9 @@
   public int hashCode() {
     return hash;
   }
+
+  @Override
+  public String toString() {
+    return getFileName() + '(' + getStartLine() + ')';
+  }
 }
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
index 99f9121..b451d7b 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/BuildTypeMap.java
@@ -120,7 +120,6 @@
 
     private int[] currentSeparatorPositions;
 
-    private final JsParser jsParser = new JsParser();
     private final JsProgram jsProgram;
     private JProgram program;
     private List<TypeDeclaration> typeDecls = new ArrayList<TypeDeclaration>();
@@ -728,12 +727,9 @@
         syntheticFnHeader += param.getName();
       }
       syntheticFnHeader += ')';
-      StringReader sr = new StringReader(syntheticFnHeader + '\n' + jsniCode);
+      StringReader sr = new StringReader(syntheticFnHeader + ' ' + jsniCode);
       try {
-        // start at -1 to avoid counting our synthetic header
-        // TODO: get the character position start correct
-        jsParser.setSourceInfo(info);
-        List<JsStatement> result = jsParser.parse(jsProgram.getScope(), sr, -1);
+        List<JsStatement> result = JsParser.parse(info, jsProgram.getScope(), sr);
         JsExprStmt jsExprStmt = (JsExprStmt) result.get(0);
         JsFunction jsFunction = (JsFunction) jsExprStmt.getExpression();
         jsFunction.setFromJava(true);
diff --git a/dev/core/src/com/google/gwt/dev/js/JsParser.java b/dev/core/src/com/google/gwt/dev/js/JsParser.java
index 750f102..674b2b0 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsParser.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsParser.java
@@ -69,7 +69,6 @@
 import java.io.IOException;
 import java.io.Reader;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Stack;
 
@@ -78,14 +77,30 @@
  */
 public class JsParser {
 
+  public static List<JsStatement> parse(SourceInfo rootSourceInfo,
+      JsScope scope, Reader r) throws IOException, JsParserException {
+    return new JsParser(scope.getProgram()).parseImpl(rootSourceInfo, scope, r);
+  }
+
+  public static void parseInto(SourceInfo rootSourceInfo, JsScope scope,
+      JsBlock block, Reader r) throws IOException, JsParserException {
+    List<JsStatement> childStmts = parse(rootSourceInfo, scope, r);
+    List<JsStatement> parentStmts = block.getStatements();
+    parentStmts.addAll(childStmts);
+  }
+
   private JsProgram program;
-  private SourceInfo rootSourceInfo = SourceInfo.UNKNOWN;
+
   private final Stack<JsScope> scopeStack = new Stack<JsScope>();
   private final Stack<SourceInfo> sourceInfoStack = new Stack<SourceInfo>();
 
-  public JsParser() {
+  private JsParser(JsProgram program) {
+    this.program = program;
+  }
+
+  List<JsStatement> parseImpl(SourceInfo rootSourceInfo, JsScope scope, Reader r)
+      throws JsParserException, IOException {
     // Create a custom error handler so that we can throw our own exceptions.
-    //
     Context.enter().setErrorReporter(new ErrorReporter() {
       public void error(String msg, String loc, int ln, String src, int col) {
         throw new UncheckedJsParserException(new JsParserException(msg, ln,
@@ -103,46 +118,26 @@
         // Ignore warnings.
       }
     });
-  }
-
-  public List<JsStatement> parse(JsScope scope, Reader r, int startLine)
-      throws IOException, JsParserException {
     try {
       // Parse using the Rhino parser.
       //
-      TokenStream ts = new TokenStream(r, "", startLine);
+      TokenStream ts = new TokenStream(r, rootSourceInfo.getFileName(),
+          rootSourceInfo.getStartLine());
       Parser parser = new Parser(new IRFactory(ts));
       Node topNode = (Node) parser.parse(ts);
 
       // Map the Rhino AST to ours.
-      //
-      program = scope.getProgram();
       pushScope(scope, rootSourceInfo);
       List<JsStatement> stmts = mapStatements(topNode);
       popScope();
-
       return stmts;
     } catch (UncheckedJsParserException e) {
       throw e.getParserException();
+    } finally {
+      Context.exit();
     }
   }
 
-  public void parseInto(JsScope scope, JsBlock block, Reader r, int startLine)
-      throws IOException, JsParserException {
-    List<JsStatement> childStmts = parse(scope, r, startLine);
-    List<JsStatement> parentStmts = block.getStatements();
-    for (Iterator<JsStatement> iter = childStmts.iterator(); iter.hasNext();) {
-      parentStmts.add(iter.next());
-    }
-  }
-
-  /**
-   * Set the base SourceInfo object to use when creating new JS AST nodes.
-   */
-  public void setSourceInfo(SourceInfo sourceInfo) {
-    rootSourceInfo = sourceInfo;
-  }
-
   private JsParserException createParserException(String msg, Node offender) {
     // TODO: get source info
     offender.getLineno();
@@ -155,8 +150,8 @@
 
   private SourceInfo makeSourceInfo(Node node) {
     SourceInfo parent = sourceInfoStack.peek();
-    SourceInfo toReturn = program.createSourceInfo(node.getLineno()
-        + parent.getStartLine() + 1, parent.getFileName());
+    SourceInfo toReturn = program.createSourceInfo(node.getLineno(),
+        parent.getFileName());
     toReturn.copyMissingCorrelationsFrom(parent);
     return toReturn;
   }
@@ -884,7 +879,7 @@
 
       case TokenStream.NULL:
         return program.getNullLiteral();
-        
+
       case TokenStream.UNDEFINED:
         return program.getUndefinedLiteral();
 
diff --git a/dev/core/src/com/google/gwt/dev/util/xml/ReflectiveParser.java b/dev/core/src/com/google/gwt/dev/util/xml/ReflectiveParser.java
index 81a6ffa..224d1ee 100644
--- a/dev/core/src/com/google/gwt/dev/util/xml/ReflectiveParser.java
+++ b/dev/core/src/com/google/gwt/dev/util/xml/ReflectiveParser.java
@@ -95,6 +95,19 @@
       // Call the handler.
       //
       try {
+        // The line number is at the end of the text; subtract however many
+        // newlines the text contains.
+        for (int i = start, e = start + length, l = e - 1; i < e; ++i) {
+          switch (ch[i]) {
+            case '\r':
+              if (i < l && ch[i + 1] == '\n') {
+                continue;
+              }
+              // Intentional fall-through
+            case '\n':
+              --lineNumber;
+          }
+        }
         final String text = String.valueOf(ch, start, length);
         method.invokeText(lineNumber, text, schemaLevel);
       } catch (UnableToCompleteException e) {
diff --git a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java
index 66ba017..35431e5 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.js;
 
+import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.js.ast.JsVisitor;
@@ -28,8 +29,6 @@
 
 public class JsToStringGenerationVisitorAccuracyTest extends TestCase {
 
-  private JsParser parser = new JsParser();
-
   public void testAdditionPositive() throws Exception {
     // x plus positive 3
     doTest("x + +3");
@@ -135,8 +134,8 @@
   }
 
   private void doTest(String js) throws Exception {
-    List<JsStatement> expected = parser.parse(new JsProgram().getScope(),
-        new StringReader(js), 0);
+    List<JsStatement> expected = JsParser.parse(SourceInfo.UNKNOWN,
+        new JsProgram().getScope(), new StringReader(js));
     List<JsStatement> actual = parse(expected, true);
     ComparingVisitor.exec(expected, actual);
 
@@ -149,7 +148,7 @@
     TextOutput text = new DefaultTextOutput(compact);
     JsVisitor generator = new JsSourceGenerationVisitor(text);
     generator.acceptList(expected);
-    return parser.parse(new JsProgram().getScope(), new StringReader(
-        text.toString()), 0);
+    return JsParser.parse(SourceInfo.UNKNOWN, new JsProgram().getScope(),
+        new StringReader(text.toString()));
   }
 }
diff --git a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java
index edcf1c8..5e01daf 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorConcisenessTest.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.dev.js;
 
+import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.js.ast.JsVisitor;
@@ -28,8 +29,6 @@
 
 public class JsToStringGenerationVisitorConcisenessTest extends TestCase {
 
-  private JsParser parser = new JsParser();
-
   public void testComplexDecrement() throws Exception {
     String output = parse("var x = -(-(-(--y)))");
     assertEquals("var x=- - - --y", output);
@@ -76,8 +75,8 @@
   }
 
   private String parse(String js) throws Exception {
-    List<JsStatement> statements = parser.parse(new JsProgram().getScope(),
-        new StringReader(js), 0);
+    List<JsStatement> statements = JsParser.parse(SourceInfo.UNKNOWN,
+        new JsProgram().getScope(), new StringReader(js));
     TextOutput text = new DefaultTextOutput(true);
     JsVisitor generator = new JsToStringGenerationVisitor(text);
     generator.acceptList(statements);