+
+
+
+
+
+
+
diff --git a/js/ideaAction.patch b/js/ideaAction.patch
new file mode 100644
index 00000000000..4f2eaa8fa34
--- /dev/null
+++ b/js/ideaAction.patch
@@ -0,0 +1,150 @@
+Index: compiler/frontend/src/org/jetbrains/jet/lang/resolve/AnnotationResolver.java
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+--- compiler/frontend/src/org/jetbrains/jet/lang/resolve/AnnotationResolver.java (revision 24a2ec5)
++++ compiler/frontend/src/org/jetbrains/jet/lang/resolve/AnnotationResolver.java (revision 4229e867af6d744d3b384dfc515cbf89caf363ae)
+@@ -173,8 +173,8 @@
+ List result = Lists.newArrayList();
+ for (JetAnnotationEntry annotation : annotations) {
+ AnnotationDescriptor annotationDescriptor = new AnnotationDescriptor();
+- result.add(annotationDescriptor);
+- trace.record(BindingContext.ANNOTATION, annotation, annotationDescriptor);
++ // result.add(annotationDescriptor);
++ // trace.record(BindingContext.ANNOTATION, annotation, annotationDescriptor);
+ }
+ return result;
+ }
+Index: idea/src/META-INF/plugin.xml
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+--- idea/src/META-INF/plugin.xml (revision 24a2ec5)
++++ idea/src/META-INF/plugin.xml (revision 4229e867af6d744d3b384dfc515cbf89caf363ae)
+@@ -1,9 +1,12 @@
+
+ Kotlin
+ Kotlin language support
+- @snapshot@
++ 1.0
+ JetBrains
+
++
++
++
+
+
+ org.jetbrains.jet.plugin.compiler.JetCompilerManager
+@@ -28,16 +31,18 @@
+
+
+
+-
+-
+-
+-
+
+
+
+
++
++
++
++
++
++
+
+
+
+Index: idea/src/org/jetbrains/jet/plugin/actions/TranslateToJsAction.java
+IDEA additional info:
+Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
+<+>UTF-8
+===================================================================
+--- idea/src/org/jetbrains/jet/plugin/actions/TranslateToJsAction.java (revision 4229e867af6d744d3b384dfc515cbf89caf363ae)
++++ idea/src/org/jetbrains/jet/plugin/actions/TranslateToJsAction.java (revision 4229e867af6d744d3b384dfc515cbf89caf363ae)
+@@ -0,0 +1,79 @@
++package org.jetbrains.jet.plugin.actions;
++
++import com.intellij.notification.Notification;
++import com.intellij.notification.NotificationType;
++import com.intellij.notification.Notifications;
++import com.intellij.openapi.actionSystem.AnAction;
++import com.intellij.openapi.actionSystem.AnActionEvent;
++import com.intellij.openapi.actionSystem.LangDataKeys;
++import com.intellij.openapi.actionSystem.PlatformDataKeys;
++import com.intellij.openapi.application.ApplicationManager;
++import com.intellij.openapi.editor.Editor;
++import com.intellij.openapi.project.Project;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.intellij.psi.PsiFile;
++import org.jetbrains.jet.lang.psi.JetFile;
++import org.jetbrains.k2js.facade.K2JSTranslator;
++
++/**
++ * @author Pavel Talanov
++ */
++public final class TranslateToJsAction extends AnAction {
++
++ private static void notifyFailure(Throwable exception) {
++ Notifications.Bus.notify(new Notification("JsTranslator", "Translation failed.",
++ "Exception: " + exception.getMessage(),
++ NotificationType.ERROR));
++ }
++
++ private static void notifySuccess(String outputPath) {
++ Notifications.Bus.notify(new Notification("JsTranslator", "Translation successful.",
++ "Generated file: " + outputPath,
++ NotificationType.INFORMATION));
++ }
++
++ public void actionPerformed(AnActionEvent event) {
++
++ final PsiFile psiFile = event.getData(LangDataKeys.PSI_FILE);
++ if (!(psiFile instanceof JetFile)) {
++ return;
++ }
++
++ Runnable task = new Runnable() {
++ @Override
++ public void run() {
++ try {
++ Project project = psiFile.getProject();
++ String outputPath = getOutputPath(psiFile);
++ K2JSTranslator.translateWithCallToMainAndSaveToFile((JetFile) psiFile,
++ outputPath,
++ project);
++ notifySuccess(outputPath);
++ } catch (Throwable e) {
++ e.printStackTrace();
++ notifyFailure(e);
++ }
++ }
++ };
++ ApplicationManager.getApplication().runWriteAction(task);
++ }
++
++ private static String getOutputPath(PsiFile psiFile) {
++ VirtualFile virtualFile = psiFile.getVirtualFile();
++ assert virtualFile != null : "Internal error: Psi file should correspond to actual virtual file";
++ String originalFilePath = virtualFile.getPath();
++
++ //TODO: make platform independent
++ String pathToDir = originalFilePath.substring(0, originalFilePath.lastIndexOf("/") + 1);
++ String generatedFileName = ((JetFile) psiFile).getNamespaceHeader().getName() + ".js";
++ return pathToDir + generatedFileName;
++ }
++
++
++ @Override
++ public void update(AnActionEvent e) {
++ Editor editor = e.getData(PlatformDataKeys.EDITOR);
++ PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE);
++ e.getPresentation().setEnabled(editor != null && psiFile instanceof JetFile);
++ }
++}
diff --git a/js/js.ast/AUTHORS b/js/js.ast/AUTHORS
new file mode 100644
index 00000000000..fea9b245e9a
--- /dev/null
+++ b/js/js.ast/AUTHORS
@@ -0,0 +1,8 @@
+# Below is a list of people and organizations that have contributed
+# to the Dart project. Names should be added to the list like so:
+#
+# Name/Organization
+
+Google Inc.
+
+Ola Martin Bini
diff --git a/js/js.ast/LICENSE b/js/js.ast/LICENSE
new file mode 100644
index 00000000000..64ed1a49724
--- /dev/null
+++ b/js/js.ast/LICENSE
@@ -0,0 +1,48 @@
+This license applies to all parts of Dart that are not externally
+maintained libraries. The external maintained libraries used by
+Dart are:
+
+JSCRE - in runtime/third_party/jscre
+Ant - in third_party/apache_ant
+Google App Engine for Java SDK - in third_party/appengine-java-sdk
+args4j - in third_party/args4j
+Chrome SDK - in third_party/chromesdk
+Eclipse - in third_party/eclipse
+gsutil = in third_party/gsutil
+Guava - in third_party/guava
+hamcrest - in third_party/hamcrest
+Httplib2 - in third_party/httplib2
+Jetty - in third_party/jetty
+JSON - in third_party/json
+JUnit - in third_party/junit
+Ouath - in third_party/oauth2client
+Python-scss - in third_party/pyscss
+Rhino - in third_party/rhino
+
+The libraries may have their own licenses; we recommend you read them,
+as their terms may differ from the terms below.
+
+Copyright 2011, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/js/js.ast/PATENTS b/js/js.ast/PATENTS
new file mode 100644
index 00000000000..69541968b91
--- /dev/null
+++ b/js/js.ast/PATENTS
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Dart Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation of Dart, where such license applies only to
+those patent claims, both currently owned by Google and acquired in
+the future, licensable by Google that are necessarily infringed by
+this implementation of Dart. This grant does not include claims that
+would be infringed only as a consequence of further modification of
+this implementation. If you or your agent or exclusive licensee
+institute or order or agree to the institution of patent litigation
+against any entity (including a cross-claim or counterclaim in a
+lawsuit) alleging that this implementation of Dart or any code
+incorporated within this implementation of Dart constitutes direct or
+contributory patent infringement, or inducement of patent
+infringement, then any patent rights granted to you under this License
+for this implementation of Dart shall terminate as of the date such
+litigation is filed.
diff --git a/js/js.ast/js.ast.iml b/js/js.ast/js.ast.iml
new file mode 100644
index 00000000000..73bb50aea75
--- /dev/null
+++ b/js/js.ast/js.ast.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/js.ast/js.iml b/js/js.ast/js.iml
new file mode 100644
index 00000000000..1e3480a2bfb
--- /dev/null
+++ b/js/js.ast/js.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/js.ast/src/com/google/dart/compiler/InternalCompilerException.java b/js/js.ast/src/com/google/dart/compiler/InternalCompilerException.java
new file mode 100644
index 00000000000..5db265e920a
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/InternalCompilerException.java
@@ -0,0 +1,15 @@
+// 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.
+
+package com.google.dart.compiler;
+
+/**
+ * Exception thrown when the compiler encounters an unexpected internal error.
+ */
+public class InternalCompilerException extends RuntimeException {
+
+ public InternalCompilerException(String message) {
+ super(message);
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/Source.java b/js/js.ast/src/com/google/dart/compiler/Source.java
new file mode 100644
index 00000000000..13e6e16cbdd
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/Source.java
@@ -0,0 +1,44 @@
+// 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.
+
+package com.google.dart.compiler;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.net.URI;
+
+/**
+ * Abstract interface to a source file.
+ */
+public interface Source {
+
+ /**
+ * Determines whether the given source exists.
+ */
+ boolean exists();
+
+ /**
+ * Returns the last-modified timestamp for this source, using the same units as
+ * {@link java.util.Date#getTime()}.
+ */
+ long getLastModified();
+
+ /**
+ * Gets the name of this source.
+ */
+ String getName();
+
+ /**
+ * Gets a reader for the dart file's source code. The caller is responsible for closing the
+ * returned reader.
+ */
+ Reader getSourceReader() throws IOException;
+
+ /**
+ * Gets the identifier for this source. This is used to uniquely identify the
+ * source, but should not be used to obtain the source content. Use
+ * {@link #getSourceReader()} to obtain the source content.
+ */
+ URI getUri();
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/Cloner.java b/js/js.ast/src/com/google/dart/compiler/backend/js/Cloner.java
new file mode 100644
index 00000000000..fa3447fe168
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/Cloner.java
@@ -0,0 +1,232 @@
+// Copyright 2011, the Dart project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.InternalCompilerException;
+import com.google.dart.compiler.backend.js.ast.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * Implements actual cloning logic. We rely on the JsExpressions to provide
+ * traversal logic. The {@link #stack} field is used to accumulate
+ * already-cloned JsExpression instances. One gotcha that falls out of this is
+ * that argument lists are on the stack in reverse order, so lists should be
+ * constructed via inserts, rather than appends.
+ */
+public class Cloner extends JsVisitor {
+ protected final Stack stack = new Stack();
+ private boolean successful = true;
+
+ /**
+ * @param expression
+ * @return Return a clone of the expression tree
+ */
+ public static JsExpression clone(JsExpression expression) {
+ Cloner c = new Cloner();
+ c.accept(expression);
+ return c.getExpression();
+ }
+
+ @Override
+ public void endVisit(JsArrayAccess x, JsContext ctx) {
+ JsArrayAccess newExpression = new JsArrayAccess();
+ newExpression.setIndexExpr(stack.pop());
+ newExpression.setArrayExpr(stack.pop());
+ stack.push(newExpression);
+ }
+
+ @Override
+ public void endVisit(JsArrayLiteral x, JsContext ctx) {
+ JsArrayLiteral toReturn = new JsArrayLiteral();
+ List expressions = toReturn.getExpressions();
+ int size = x.getExpressions().size();
+ while (size-- > 0) {
+ expressions.add(0, stack.pop());
+ }
+ stack.push(toReturn);
+ }
+
+ @Override
+ public void endVisit(JsBinaryOperation x, JsContext ctx) {
+ JsBinaryOperation toReturn = new JsBinaryOperation(x.getOperator());
+ toReturn.setArg2(stack.pop());
+ toReturn.setArg1(stack.pop());
+ stack.push(toReturn);
+ }
+
+ @Override
+ public void endVisit(JsBooleanLiteral x, JsContext ctx) {
+ stack.push(x);
+ }
+
+ @Override
+ public void endVisit(JsConditional x, JsContext ctx) {
+ JsConditional toReturn = new JsConditional();
+ toReturn.setElseExpression(stack.pop());
+ toReturn.setThenExpression(stack.pop());
+ toReturn.setTestExpression(stack.pop());
+ stack.push(toReturn);
+ }
+
+ /**
+ * The only functions that would get be visited are those being used as
+ * first-class objects.
+ */
+ @Override
+ public void endVisit(JsFunction x, JsContext ctx) {
+ // Set a flag to indicate that we cannot continue, and push a null so
+ // we don't run out of elements on the stack.
+ successful = false;
+ stack.push(null);
+ }
+
+ /**
+ * Cloning the invocation allows us to modify it without damaging other call
+ * sites.
+ */
+ @Override
+ public void endVisit(JsInvocation x, JsContext ctx) {
+ JsInvocation toReturn = new JsInvocation();
+ List params = toReturn.getArguments();
+ int size = x.getArguments().size();
+ while (size-- > 0) {
+ params.add(0, stack.pop());
+ }
+ toReturn.setQualifier(stack.pop());
+ stack.push(toReturn);
+ }
+
+ /**
+ * Do a deep clone of a JsNameRef. Because JsNameRef chains are shared
+ * throughout the AST, you can't just go and change their qualifiers when
+ * re-writing an invocation.
+ */
+ @Override
+ public void endVisit(JsNameRef x, JsContext ctx) {
+ JsNameRef toReturn;
+ JsName name = x.getName();
+ if (name != null) {
+ toReturn = new JsNameRef(name);
+ } else {
+ toReturn = new JsNameRef(x.getIdent());
+ }
+
+ if (x.getQualifier() != null) {
+ toReturn.setQualifier(stack.pop());
+ }
+ stack.push(toReturn);
+ }
+
+ @Override
+ public void endVisit(JsNew x, JsContext ctx) {
+ int size = x.getArguments().size();
+ List arguments = new ArrayList(size);
+ while (size-- > 0) {
+ arguments.add(0, stack.pop());
+ }
+ JsNew toReturn = new JsNew(stack.pop());
+ toReturn.getArguments().addAll(arguments);
+ stack.push(toReturn);
+ }
+
+ @Override
+ public void endVisit(JsNullLiteral x, JsContext ctx) {
+ stack.push(x);
+ }
+
+ @Override
+ public void endVisit(JsNumberLiteral x, JsContext ctx) {
+ stack.push(x);
+ }
+
+ @Override
+ public void endVisit(JsObjectLiteral x, JsContext ctx) {
+ JsObjectLiteral toReturn = new JsObjectLiteral();
+ List inits = toReturn.getPropertyInitializers();
+
+ int size = x.getPropertyInitializers().size();
+ while (size-- > 0) {
+ /*
+ * JsPropertyInitializers are the only non-JsExpression objects that we
+ * care about, so we just go ahead and create the objects in the loop,
+ * rather than expecting it to be on the stack and having to perform
+ * narrowing casts at all stack.pop() invocations.
+ */
+ JsPropertyInitializer newInit = new JsPropertyInitializer();
+ newInit.setValueExpr(stack.pop());
+ newInit.setLabelExpr(stack.pop());
+
+ inits.add(0, newInit);
+ }
+ stack.push(toReturn);
+ }
+
+ @Override
+ public void endVisit(JsPostfixOperation x, JsContext ctx) {
+ JsPostfixOperation toReturn = new JsPostfixOperation(x.getOperator());
+ toReturn.setArg(stack.pop());
+ stack.push(toReturn);
+ }
+
+ @Override
+ public void endVisit(JsPrefixOperation x, JsContext ctx) {
+ JsPrefixOperation toReturn = new JsPrefixOperation(x.getOperator());
+ toReturn.setArg(stack.pop());
+ stack.push(toReturn);
+ }
+
+ @Override
+ public void endVisit(JsRegExp x, JsContext ctx) {
+ stack.push(x);
+ }
+
+ @Override
+ public void endVisit(JsStringLiteral x, JsContext ctx) {
+ stack.push(x);
+ }
+
+ @Override
+ public void endVisit(JsThisRef x, JsContext ctx) {
+ stack.push(new JsThisRef());
+ }
+
+ public JsExpression getExpression() {
+ return (successful && checkStack()) ? stack.peek() : null;
+ }
+
+ private boolean checkStack() {
+ if (stack.size() > 1) {
+ throw new InternalCompilerException("Too many expressions on stack");
+ }
+
+ return stack.size() == 1;
+ }
+}
+
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsConstructExpressionVisitor.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsConstructExpressionVisitor.java
new file mode 100644
index 00000000000..a15af6b44ed
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsConstructExpressionVisitor.java
@@ -0,0 +1,104 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.backend.js.ast.*;
+
+/**
+ * Searches for method invocations in constructor expressions that would not
+ * normally be surrounded by parentheses.
+ */
+public class JsConstructExpressionVisitor extends JsVisitor {
+
+ public static boolean exec(JsExpression expression) {
+ if (JsPrecedenceVisitor.exec(expression) < JsPrecedenceVisitor.PRECEDENCE_NEW) {
+ return true;
+ }
+ JsConstructExpressionVisitor visitor = new JsConstructExpressionVisitor();
+ visitor.accept(expression);
+ return visitor.containsInvocation;
+ }
+
+ private boolean containsInvocation = false;
+
+ private JsConstructExpressionVisitor() {
+ }
+
+ /**
+ * We only look at the array expression since the index has its own scope.
+ */
+ @Override
+ public boolean visit(JsArrayAccess x, JsContext ctx) {
+ accept(x.getArrayExpr());
+ return false;
+ }
+
+ /**
+ * Array literals have their own scoping.
+ */
+ @Override
+ public boolean visit(JsArrayLiteral x, JsContext ctx) {
+ return false;
+ }
+
+ /**
+ * Functions have their own scoping.
+ */
+ @Override
+ public boolean visit(JsFunction x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsInvocation x, JsContext ctx) {
+ containsInvocation = true;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNameRef x, JsContext ctx) {
+ if (!x.isLeaf()) {
+ accept(x.getQualifier());
+ }
+ return false;
+ }
+
+ /**
+ * New constructs bind to the nearest set of parentheses.
+ */
+ @Override
+ public boolean visit(JsNew x, JsContext ctx) {
+ return false;
+ }
+
+ /**
+ * Object literals have their own scope.
+ */
+ @Override
+ public boolean visit(JsObjectLiteral x, JsContext ctx) {
+ return false;
+ }
+
+ /**
+ * We only look at nodes that would not normally be surrounded by parentheses.
+ */
+ @Override
+ protected T doAccept(T node) {
+ // Assign to Object to prevent 'inconvertible types' toJsAst errors due
+ // to http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6548436
+ // reproducible in jdk1.6.0_02.
+ Object o = node;
+ if (o instanceof JsExpression) {
+ JsExpression expression = (JsExpression) o;
+ int precedence = JsPrecedenceVisitor.exec(expression);
+ // Only visit expressions that won't automatically be surrounded by
+ // parentheses
+ if (precedence < JsPrecedenceVisitor.PRECEDENCE_NEW) {
+ return node;
+ }
+ }
+ return super.doAccept(node);
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsFirstExpressionVisitor.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsFirstExpressionVisitor.java
new file mode 100644
index 00000000000..0fdb8a5032b
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsFirstExpressionVisitor.java
@@ -0,0 +1,119 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.backend.js.ast.*;
+
+/**
+ * Determines if an expression statement needs to be surrounded by parentheses.
+ *
+ * The statement or the left-most expression needs to be surrounded by
+ * parentheses if the left-most expression is an object literal or a function
+ * object. Function declarations do not need parentheses.
+ *
+ * For example the following require parentheses:
+ *
+ *
{ key : 'value'}
+ *
{ key : 'value'}.key
+ *
function () {return 1;}()
+ *
function () {return 1;}.prototype
+ *
+ *
+ * The following do not require parentheses:
+ *
+ *
var x = { key : 'value'}
+ *
"string" + { key : 'value'}.key
+ *
function func() {}
+ *
function() {}
+ *
+ */
+public class JsFirstExpressionVisitor extends JsVisitor {
+
+ public static boolean exec(JsExprStmt statement) {
+ JsFirstExpressionVisitor visitor = new JsFirstExpressionVisitor();
+ JsExpression expression = statement.getExpression();
+ // Pure function declarations do not need parentheses
+ if (expression instanceof JsFunction) {
+ return false;
+ }
+ visitor.accept(statement.getExpression());
+ return visitor.needsParentheses;
+ }
+
+ private boolean needsParentheses = false;
+
+ private JsFirstExpressionVisitor() {
+ }
+
+ @Override
+ public boolean visit(JsArrayAccess x, JsContext ctx) {
+ accept(x.getArrayExpr());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsArrayLiteral x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsBinaryOperation x, JsContext ctx) {
+ accept(x.getArg1());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsConditional x, JsContext ctx) {
+ accept(x.getTestExpression());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsFunction x, JsContext ctx) {
+ needsParentheses = true;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsInvocation x, JsContext ctx) {
+ accept(x.getQualifier());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNameRef x, JsContext ctx) {
+ if (!x.isLeaf()) {
+ accept(x.getQualifier());
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNew x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsObjectLiteral x, JsContext ctx) {
+ needsParentheses = true;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsPostfixOperation x, JsContext ctx) {
+ accept(x.getArg());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsPrefixOperation x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsRegExp x, JsContext ctx) {
+ return false;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsNamer.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsNamer.java
new file mode 100644
index 00000000000..0c49ad05750
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsNamer.java
@@ -0,0 +1,23 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.backend.js.ast.JsProgram;
+
+/**
+ * A namer runs through a program and renames the short names of JsNames.
+ * Namers must assign short names that don't clash and that are valid
+ * JS-identifiers. Nested JsScopes must not shadow JsNames from outer
+ * scopes.
+ * If a JsName is marked as non-obfuscatable then it must retain its short
+ * name.
+ */
+public interface JsNamer {
+ /**
+ * GlobalNames the shortNames of all JsNames of the program so that they are valid
+ * JS-identifiers and that there are no clashes and no shadowing.
+ */
+ public void exec(JsProgram program);
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsNormalizer.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsNormalizer.java
new file mode 100644
index 00000000000..04c5530cd42
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsNormalizer.java
@@ -0,0 +1,103 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.backend.js.ast.*;
+
+/**
+ * Fixes any semantic errors introduced by JS AST gen.
+ *
+ *
+ *
Creating clinit calls can put comma expressions as lvalues; the modifying
+ * operation must be moved inside the comma expression to the last argument.
+ *
+ */
+public class JsNormalizer {
+
+ /**
+ * Resolves any unresolved JsNameRefs.
+ */
+ private static class JsNormalizing extends JsModVisitor {
+
+ @Override
+ public void endVisit(JsBinaryOperation x, JsContext ctx) {
+ maybeShuffleModifyingBinary(x, ctx);
+ }
+
+ @Override
+ public void endVisit(JsPostfixOperation x, JsContext ctx) {
+ maybeShuffleModifyingUnary(x, ctx);
+ }
+
+ @Override
+ public void endVisit(JsPrefixOperation x, JsContext ctx) {
+ maybeShuffleModifyingUnary(x, ctx);
+ }
+
+ /**
+ * Due to the way clinits are constructed, you can end up with a comma
+ * operation as the argument to a modifying operation, which is illegal.
+ * Juggle things to put the operator inside of the comma expression.
+ */
+ private void maybeShuffleModifyingBinary(JsBinaryOperation x, JsContext ctx) {
+ JsBinaryOperator myOp = x.getOperator();
+ JsExpression lhs = x.getArg1();
+
+ if (myOp.isAssignment() && (lhs instanceof JsBinaryOperation)) {
+ // Find the rightmost comma operation
+ JsBinaryOperation curLhs = (JsBinaryOperation) lhs;
+ assert (curLhs.getOperator() == JsBinaryOperator.COMMA);
+ while (curLhs.getArg2() instanceof JsBinaryOperation) {
+ curLhs = (JsBinaryOperation) curLhs.getArg2();
+ assert (curLhs.getOperator() == JsBinaryOperator.COMMA);
+ }
+ // curLhs is now the rightmost comma operation; slide our operation in
+ x.setArg1(curLhs.getArg2());
+ curLhs.setArg2(x);
+ // replace myself with the comma expression
+ ctx.replaceMe(lhs);
+ }
+ }
+
+ /**
+ * Due to the way clinits are constructed, you can end up with a comma
+ * operation as the argument to a modifying operation, which is illegal.
+ * Juggle things to put the operator inside of the comma expression.
+ */
+ private void maybeShuffleModifyingUnary(JsUnaryOperation x, JsContext ctx) {
+ JsUnaryOperator myOp = x.getOperator();
+ JsExpression arg = x.getArg();
+ if (myOp.isModifying() && (arg instanceof JsBinaryOperation)) {
+ // Find the rightmost comma operation
+ JsBinaryOperation curArg = (JsBinaryOperation) arg;
+ assert (curArg.getOperator() == JsBinaryOperator.COMMA);
+ while (curArg.getArg2() instanceof JsBinaryOperation) {
+ curArg = (JsBinaryOperation) curArg.getArg2();
+ assert (curArg.getOperator() == JsBinaryOperator.COMMA);
+ }
+ // curArg is now the rightmost comma operation; slide our operation in
+ x.setArg(curArg.getArg2());
+ curArg.setArg2(x);
+ // replace myself with the comma expression
+ ctx.replaceMe(arg);
+ }
+ }
+ }
+
+ public static void exec(JsProgram program) {
+ new JsNormalizer(program).execImpl();
+ }
+
+ private final JsProgram program;
+
+ private JsNormalizer(JsProgram program) {
+ this.program = program;
+ }
+
+ private void execImpl() {
+ JsNormalizing normalizer = new JsNormalizing();
+ normalizer.accept(program);
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsParserException.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsParserException.java
new file mode 100644
index 00000000000..79b53c1d69e
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsParserException.java
@@ -0,0 +1,92 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+/**
+ * Indicates inability to parse JavaScript source.
+ */
+public class JsParserException extends Exception {
+
+ /**
+ * Represents the location of a parser exception.
+ */
+ public static class SourceDetail {
+ private final String fileName;
+ private final int line;
+ private final int lineOffset;
+ private final String lineSource;
+
+ public SourceDetail(int line, String lineSource, int lineOffset, String fileName) {
+ this.line = line;
+ this.lineSource = lineSource;
+ this.lineOffset = lineOffset;
+ this.fileName = fileName;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public int getLine() {
+ return line;
+ }
+
+ public int getLineOffset() {
+ return lineOffset;
+ }
+
+ public String getLineSource() {
+ return lineSource;
+ }
+ }
+
+ private static String createMessageWithDetail(String msg, SourceDetail sourceDetail) {
+ if (sourceDetail == null) {
+ return msg;
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append(sourceDetail.getFileName());
+ sb.append('(');
+ sb.append(sourceDetail.getLine());
+ sb.append(')');
+ sb.append(": ");
+ sb.append(msg);
+ if (sourceDetail.getLineSource() != null) {
+ sb.append("\n> ");
+ sb.append(sourceDetail.getLineSource());
+ sb.append("\n> ");
+ for (int i = 0, n = sourceDetail.getLineOffset(); i < n; ++i) {
+ sb.append('-');
+ }
+ sb.append('^');
+ }
+ return sb.toString();
+ }
+
+ private final SourceDetail sourceDetail;
+
+ public JsParserException(String msg) {
+ this(msg, null);
+ }
+
+ public JsParserException(String msg, int line, String lineSource, int lineOffset, String fileName) {
+ this(msg, new SourceDetail(line, lineSource, lineOffset, fileName));
+ }
+
+ public JsParserException(String msg, SourceDetail sourceDetail) {
+ super(createMessageWithDetail(msg, sourceDetail));
+ this.sourceDetail = sourceDetail;
+ }
+
+ /**
+ * Provides additional source detail in some cases.
+ *
+ * @return additional detail regarding the error, or null if no
+ * additional detail is available
+ */
+ public SourceDetail getSourceDetail() {
+ return sourceDetail;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsPrecedenceVisitor.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsPrecedenceVisitor.java
new file mode 100644
index 00000000000..c3a58b64e22
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsPrecedenceVisitor.java
@@ -0,0 +1,274 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.backend.js.ast.*;
+
+/**
+ * Precedence indices from "JavaScript - The Definitive Guide" 4th Edition (page
+ * 57)
+ *
+ * Precedence 17 is for indivisible primaries that either don't have children,
+ * or provide their own delimiters.
+ *
+ * Precedence 16 is for really important things that have their own AST classes.
+ *
+ * Precedence 15 is for the new construct.
+ *
+ * Precedence 14 is for unary operators.
+ *
+ * Precedences 12 through 4 are for non-assigning binary operators.
+ *
+ * Precedence 3 is for the tertiary conditional.
+ *
+ * Precedence 2 is for assignments.
+ *
+ * Precedence 1 is for comma operations.
+ */
+class JsPrecedenceVisitor extends JsVisitor {
+
+ static final int PRECEDENCE_NEW = 15;
+
+ public static int exec(JsExpression expression) {
+ JsPrecedenceVisitor visitor = new JsPrecedenceVisitor();
+ visitor.accept(expression);
+ if (visitor.answer < 0) {
+ throw new RuntimeException("Precedence must be >= 0!");
+ }
+ return visitor.answer;
+ }
+
+ private int answer = -1;
+
+ private JsPrecedenceVisitor() {
+ }
+
+ @Override
+ public boolean visit(JsArrayAccess x, JsContext ctx) {
+ answer = 16;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsArrayLiteral x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsBinaryOperation x, JsContext ctx) {
+ answer = x.getOperator().getPrecedence();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsBlock x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsBooleanLiteral x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsBreak x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsCase x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsCatch x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsConditional x, JsContext ctx) {
+ answer = 3;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsContinue x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsDebugger x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsDefault x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsDoWhile x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsEmpty x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsExprStmt x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsFor x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsForIn x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsFunction x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsIf x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsInvocation x, JsContext ctx) {
+ answer = 16;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsLabel x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsNameRef x, JsContext ctx) {
+ if (x.isLeaf()) {
+ answer = 17; // primary
+ } else {
+ answer = 16; // property access
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNew x, JsContext ctx) {
+ answer = PRECEDENCE_NEW;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNullLiteral x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNumberLiteral x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsObjectLiteral x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsParameter x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsPostfixOperation x, JsContext ctx) {
+ answer = x.getOperator().getPrecedence();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsPrefixOperation x, JsContext ctx) {
+ answer = x.getOperator().getPrecedence();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsProgram x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsPropertyInitializer x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsRegExp x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsReturn x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsStringLiteral x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsSwitch x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsThisRef x, JsContext ctx) {
+ answer = 17; // primary
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsThrow x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsTry x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsVars.JsVar x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsVars x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+
+ @Override
+ public boolean visit(JsWhile x, JsContext ctx) {
+ throw new RuntimeException("Only expressions have precedence.");
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsPrettyNamer.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsPrettyNamer.java
new file mode 100644
index 00000000000..2c61e01f6b3
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsPrettyNamer.java
@@ -0,0 +1,122 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.backend.js.ast.JsName;
+import com.google.dart.compiler.backend.js.ast.JsProgram;
+import com.google.dart.compiler.backend.js.ast.JsRootScope;
+import com.google.dart.compiler.backend.js.ast.JsScope;
+
+import java.util.*;
+
+/**
+ * A namer that uses short, readable idents to maximize reability.
+ */
+public class JsPrettyNamer implements JsNamer {
+
+ public JsPrettyNamer() {
+ this.program = null;
+ }
+
+ @Override
+ public void exec(JsProgram program) {
+ new JsPrettyNamer(program).execImpl();
+ }
+
+ /**
+ * Communicates to a parent scope all the idents used by all child scopes.
+ */
+ private Set childIdents = null;
+
+ private final JsProgram program;
+
+ /**
+ * A map containing the next integer to try as an identifier suffix for a
+ * given JsScope.
+ */
+ private IdentityHashMap> startIdentForScope =
+ new IdentityHashMap>();
+
+ protected JsPrettyNamer(JsProgram program) {
+ this.program = program;
+ }
+
+ private void execImpl() {
+ visit(program.getRootScope());
+ }
+
+ private boolean isLegal(JsScope scope, Set childIdents, String newIdent) {
+ if (JsReservedIdentifiers.isKeyword(newIdent)) {
+ return false;
+ }
+ if (childIdents.contains(newIdent)) {
+ // one of my children already claimed this ident
+ return false;
+ }
+ /*
+ * Never obfuscate a name into an identifier that conflicts with an existing
+ * unobfuscatable name! It's okay if it conflicts with an existing
+ * obfuscatable name; that name will get obfuscated out of the way.
+ */
+ return (scope.findExistingUnobfuscatableName(newIdent) == null);
+ }
+
+ private void visit(JsScope scope) {
+ HashMap startIdent = startIdentForScope.get(scope);
+ if (startIdent == null) {
+ startIdent = new HashMap();
+ startIdentForScope.put(scope, startIdent);
+ }
+
+ // Save off the childIdents which is currently being computed for my parent.
+ Set myChildIdents = childIdents;
+
+ /*
+ * Visit my children first. Reset childIdents so that my children will get a
+ * clean slate: I do not communicate to my children.
+ */
+ childIdents = new HashSet();
+ List children = scope.getChildren();
+ for (Iterator it = children.iterator(); it.hasNext();) {
+ visit(it.next());
+ }
+
+ JsRootScope rootScope = program.getRootScope();
+ if (scope == rootScope) {
+ return;
+ }
+
+ // Visit all my idents.
+ for (Iterator it = scope.getAllNames(); it.hasNext();) {
+ JsName name = it.next();
+ if (!name.isObfuscatable()) {
+ // Unobfuscatable names become themselves.
+ name.setShortIdent(name.getIdent());
+ continue;
+ }
+
+ String newIdent = name.getShortIdent();
+ if (!isLegal(scope, childIdents, newIdent)) {
+ String checkIdent;
+
+ // Start searching using a suffix hint stored in the scope.
+ // We still do a search in case there is a collision with
+ // a user-provided identifier
+ Integer s = startIdent.get(newIdent);
+ int suffix = (s == null) ? 0 : s.intValue();
+ do {
+ checkIdent = newIdent + "_" + suffix++;
+ } while (!isLegal(scope, childIdents, checkIdent));
+ startIdent.put(newIdent, suffix);
+ name.setShortIdent(checkIdent);
+ } else {
+ // nothing to do; the short name is already good
+ }
+ childIdents.add(name.getShortIdent());
+ }
+ myChildIdents.addAll(childIdents);
+ childIdents = myChildIdents;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsRequiresSemiVisitor.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsRequiresSemiVisitor.java
new file mode 100644
index 00000000000..f4095c5c822
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsRequiresSemiVisitor.java
@@ -0,0 +1,140 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.backend.js.ast.*;
+
+/**
+ * Determines if a statement at the end of a block requires a semicolon.
+ *
+ * For example, the following statements require semicolons:
+ *
+ *
if (cond);
+ *
while (cond);
+ *
+ *
+ * The following do not require semicolons:
+ *
+ *
return 1
+ *
do {} while(true)
+ *
+ */
+public class JsRequiresSemiVisitor extends JsVisitor {
+
+ public static boolean exec(JsStatement lastStatement) {
+ JsRequiresSemiVisitor visitor = new JsRequiresSemiVisitor();
+ visitor.accept(lastStatement);
+ return visitor.needsSemicolon;
+ }
+
+ private boolean needsSemicolon = false;
+
+ private JsRequiresSemiVisitor() {
+ }
+
+ @Override
+ public boolean visit(JsBlock x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsBreak x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsDebugger x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsDoWhile x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsEmpty x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsExprStmt x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsFor x, JsContext ctx) {
+ if (x.getBody() instanceof JsEmpty) {
+ needsSemicolon = true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsForIn x, JsContext ctx) {
+ if (x.getBody() instanceof JsEmpty) {
+ needsSemicolon = true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsIf x, JsContext ctx) {
+ JsStatement thenStmt = x.getThenStmt();
+ JsStatement elseStmt = x.getElseStmt();
+ JsStatement toCheck = thenStmt;
+ if (elseStmt != null) {
+ toCheck = elseStmt;
+ }
+ if (toCheck instanceof JsEmpty) {
+ needsSemicolon = true;
+ } else {
+ // Must recurse to determine last statement (possible if-else chain).
+ accept(toCheck);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsLabel x, JsContext ctx) {
+ if (x.getStmt() instanceof JsEmpty) {
+ needsSemicolon = true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsReturn x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsSwitch x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsThrow x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsTry x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsVars x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsWhile x, JsContext ctx) {
+ if (x.getBody() instanceof JsEmpty) {
+ needsSemicolon = true;
+ }
+ return false;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsReservedIdentifiers.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsReservedIdentifiers.java
new file mode 100644
index 00000000000..56ae5428a3d
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsReservedIdentifiers.java
@@ -0,0 +1,223 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Determines whether or not a particular string is a JavaScript keyword or not.
+ */
+public class JsReservedIdentifiers {
+
+ private static Set javaScriptKeywords;
+ private static Set reservedGlobalSymbols;
+ private static Set reservedPropertySymbols;
+
+ static {
+ javaScriptKeywords = new HashSet();
+ reservedGlobalSymbols = new HashSet();
+ reservedPropertySymbols = new HashSet();
+ initJavaScriptKeywords();
+ initReservedGlobalSymbols();
+ initReservedPropertySymbols();
+ }
+
+ public static boolean isKeyword(String s) {
+ return javaScriptKeywords.contains(s);
+ }
+
+ private static void initJavaScriptKeywords() {
+ String[] keywords = new 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"
+ };
+
+ for (int i = 0; i < keywords.length; i++) {
+ javaScriptKeywords.add(keywords[i]);
+ }
+ }
+
+ /**
+ * @return a set containing all known reserved global identifiers. This set must not be modified.
+ */
+ public static Set getReservedGlobalSymbols() {
+ return reservedGlobalSymbols;
+ }
+
+ /**
+ * Returns true if the string s can not be used as a global identifier. The check includes
+ * JavaScript keywords (as they must not be used either).
+ * @param s
+ * @return true if the given String must not be used as a global identifier.
+ */
+ public static boolean isReservedGlobalSymbol(String s) {
+ return isKeyword(s) || reservedGlobalSymbols.contains(s);
+ }
+
+ private static void initReservedGlobalSymbols() {
+ // Section references are from Ecma-262
+ // (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf)
+ String[] commonBuiltins = new String[] {
+ // 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)
+ // TODO(fredsa) Many, many more functions and member data to add
+ "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, which are needed for linkers
+ // that don't ensure GWT's window != $wnd, document != $doc, etc.
+ // Taken from the Rhino book, pg 715
+ "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",
+
+ // GWT-defined identifiers
+ "$wnd", "$doc", "$entry", "$moduleName", "$moduleBase", "$gwt_version", "$sessionId",
+
+ // Identifiers used by JsStackEmulator; later set to obfuscatable
+ "$stack", "$stackDepth", "$location",
+
+ // TODO: prove why this is necessary or remove it
+ "call"
+ };
+ for (int i = 0; i < commonBuiltins.length; i++) {
+ reservedGlobalSymbols.add(commonBuiltins[i]);
+ }
+ }
+
+ /**
+ * Returns true if the given string can not be used as property symbol. The check excludes
+ * keywords as JavaScript allow keywords as properties.
+ * @param s
+ * @return true if the given string must not be used as a property.
+ */
+ public static boolean isReservedPropertySymbol(String s) {
+ return reservedPropertySymbols.contains(s);
+ }
+
+ private static void initReservedPropertySymbols() {
+ // TODO(floitsch): fill in reserved property symbols.
+ reservedPropertySymbols.add("__PROTO__");
+ reservedPropertySymbols.add("prototype");
+ }
+
+ private JsReservedIdentifiers() {
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsSourceGenerationVisitor.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsSourceGenerationVisitor.java
new file mode 100644
index 00000000000..3a2689f65da
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsSourceGenerationVisitor.java
@@ -0,0 +1,39 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.util.TextOutput;
+import com.google.dart.compiler.backend.js.ast.JsContext;
+import com.google.dart.compiler.backend.js.ast.JsProgramFragment;
+import com.google.dart.compiler.backend.js.ast.JsBlock;
+import com.google.dart.compiler.backend.js.ast.JsProgram;
+
+/**
+ * Generates JavaScript source from an AST.
+ */
+public class JsSourceGenerationVisitor extends JsToStringGenerationVisitor {
+
+ public JsSourceGenerationVisitor(TextOutput out) {
+ super(out);
+ }
+
+ @Override
+ public boolean visit(JsProgram x, JsContext ctx) {
+ // Descend naturally.
+ return true;
+ }
+
+ @Override
+ public boolean visit(JsProgramFragment x, JsContext ctx) {
+ // Descend naturally.
+ return true;
+ }
+
+ @Override
+ public boolean visit(JsBlock x, JsContext ctx) {
+ printJsBlock(x, false, true);
+ return false;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/JsToStringGenerationVisitor.java b/js/js.ast/src/com/google/dart/compiler/backend/js/JsToStringGenerationVisitor.java
new file mode 100644
index 00000000000..a078eca30ac
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/JsToStringGenerationVisitor.java
@@ -0,0 +1,1331 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+import com.google.dart.compiler.backend.js.ast.*;
+import com.google.dart.compiler.util.TextOutput;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+//import com.google.debugging.sourcemap.FilePosition;
+
+/**
+ * Produces text output from a JavaScript AST.
+ */
+public class JsToStringGenerationVisitor extends JsVisitor {
+
+ private static final char[] CHARS_BREAK = "break".toCharArray();
+ private static final char[] CHARS_CASE = "case".toCharArray();
+ private static final char[] CHARS_CATCH = "catch".toCharArray();
+ private static final char[] CHARS_CONTINUE = "continue".toCharArray();
+ private static final char[] CHARS_DEBUGGER = "debugger".toCharArray();
+ private static final char[] CHARS_DEFAULT = "default".toCharArray();
+ private static final char[] CHARS_DO = "do".toCharArray();
+ private static final char[] CHARS_ELSE = "else".toCharArray();
+ private static final char[] CHARS_FALSE = "false".toCharArray();
+ private static final char[] CHARS_FINALLY = "finally".toCharArray();
+ private static final char[] CHARS_FOR = "for".toCharArray();
+ private static final char[] CHARS_FUNCTION = "function".toCharArray();
+ private static final char[] CHARS_IF = "if".toCharArray();
+ private static final char[] CHARS_IN = "in".toCharArray();
+ private static final char[] CHARS_NEW = "new".toCharArray();
+ private static final char[] CHARS_NULL = "null".toCharArray();
+ private static final char[] CHARS_RETURN = "return".toCharArray();
+ private static final char[] CHARS_SWITCH = "switch".toCharArray();
+ private static final char[] CHARS_THIS = "this".toCharArray();
+ private static final char[] CHARS_THROW = "throw".toCharArray();
+ private static final char[] CHARS_TRUE = "true".toCharArray();
+ private static final char[] CHARS_TRY = "try".toCharArray();
+ private static final char[] CHARS_VAR = "var".toCharArray();
+ private static final char[] CHARS_WHILE = "while".toCharArray();
+ private static final char[] HEX_DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ /**
+ * How many lines of code to print inside of a JsBlock when printing terse.
+ */
+ private static final int JSBLOCK_LINES_TO_PRINT = 3;
+
+ /**
+ * A variable name is valid if it contains only letters, numbers, _, $ and
+ * does not begin with a number. There are actually other valid variable
+ * names, such as ones that contain escaped Unicode characters, but we
+ * surround those names with quotes in property initializers to be safe.
+ */
+ private static final Pattern VALID_NAME_PATTERN = Pattern.compile("[a-zA-Z_$][\\w$]*");
+
+ public static String javaScriptString(String value) {
+ return javaScriptString(value, false);
+ }
+
+ /**
+ * Generate JavaScript code that evaluates to the supplied string. Adapted
+ * from
+ * . The difference is that we quote with either " or ' depending on
+ * which one is used less inside the string.
+ */
+ public static String javaScriptString(String value, boolean forceDoubleQuote) {
+ char[] chars = value.toCharArray();
+ final int n = chars.length;
+ int quoteCount = 0;
+ int aposCount = 0;
+ for (int i = 0; i < n; ++i) {
+ switch (chars[i]) {
+ case '"':
+ ++quoteCount;
+ break;
+ case '\'':
+ ++aposCount;
+ break;
+ }
+ }
+
+ StringBuffer result = new StringBuffer(value.length() + 16);
+
+ char quoteChar = (quoteCount < aposCount || forceDoubleQuote) ? '"' : '\'';
+ result.append(quoteChar);
+
+ for (int i = 0; i < n; ++i) {
+ char c = chars[i];
+
+ if (' ' <= c && c <= '~' && c != quoteChar && c != '\\') {
+ // an ordinary print character (like C isprint())
+ result.append(c);
+ continue;
+ }
+
+ int escape = -1;
+ switch (c) {
+ case '\b':
+ escape = 'b';
+ break;
+ case '\f':
+ escape = 'f';
+ break;
+ case '\n':
+ escape = 'n';
+ break;
+ case '\r':
+ escape = 'r';
+ break;
+ case '\t':
+ escape = 't';
+ break;
+ case '"':
+ escape = '"';
+ break; // only reach here if == quoteChar
+ case '\'':
+ escape = '\'';
+ break; // only reach here if == quoteChar
+ case '\\':
+ escape = '\\';
+ break;
+ }
+
+ if (escape >= 0) {
+ // an \escaped sort of character
+ result.append('\\');
+ result.append((char) escape);
+ } else {
+ /*
+ * Emit characters from 0 to 31 that don't have a single character
+ * escape sequence in octal where possible. This saves one or two
+ * characters compared to the hexadecimal format '\xXX'.
+ *
+ * These short octal sequences may only be used at the end of the string
+ * or where the following character is a non-digit. Otherwise, the
+ * following character would be incorrectly interpreted as belonging to
+ * the sequence.
+ */
+ if (c < ' ' && (i == n - 1 || chars[i + 1] < '0' || chars[i + 1] > '9')) {
+ result.append('\\');
+ if (c > 0x7) {
+ result.append((char) ('0' + (0x7 & (c >> 3))));
+ }
+ result.append((char) ('0' + (0x7 & c)));
+ } else {
+ int hexSize;
+ if (c < 256) {
+ // 2-digit hex
+ result.append("\\x");
+ hexSize = 2;
+ } else {
+ // Unicode.
+ result.append("\\u");
+ hexSize = 4;
+ }
+ // append hexadecimal form of ch left-padded with 0
+ for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) {
+ int digit = 0xf & (c >> shift);
+ result.append(HEX_DIGITS[digit]);
+ }
+ }
+ }
+ }
+ result.append(quoteChar);
+ escapeClosingTags(result);
+ String resultString = result.toString();
+ return resultString;
+ }
+
+ /**
+ * Escapes any closing XML tags embedded in str, which could
+ * potentially cause a parse failure in a browser, for example, embedding a
+ * closing <script> tag.
+ *
+ * @param str an unescaped literal; May be null
+ */
+ private static void escapeClosingTags(StringBuffer str) {
+ if (str == null) {
+ return;
+ }
+
+ int index = 0;
+
+ while ((index = str.indexOf("", index)) != -1) {
+ str.insert(index + 1, '\\');
+ }
+ }
+
+ protected boolean needSemi = true;
+
+ /**
+ * "Global" blocks are either the global block of a fragment, or a block
+ * nested directly within some other global block. This definition matters
+ * because the statements designated by statementEnds and statementStarts are
+ * those that appear directly within these global blocks.
+ */
+ private Set globalBlocks = new HashSet();
+ private final TextOutput p;
+ private ArrayList statementEnds = new ArrayList();
+ private ArrayList statementStarts = new ArrayList();
+ private boolean buildMappings;
+ //private List mappings = Lists.newArrayList();
+
+ public JsToStringGenerationVisitor(TextOutput out) {
+ this.p = out;
+ }
+
+ /**
+ * @param generate Whether to translate the source map.
+ */
+ public void generateSourceMap(boolean generate) {
+ this.buildMappings = generate;
+ }
+
+// public void writeSourceMap(Appendable out, String name) throws IOException {
+// GenerateSourceMap generator = new GenerateSourceMap();
+// for (SourceMapping m : mappings) {
+// generator.addMapping(m.getNode(), m.getStart(), m.getEnd());
+// }
+// generator.appendTo(out, name);
+// }
+
+ @Override
+ public void doTraverse(JsVisitable x, JsContext ctx) {
+ //SourceMapping m = null;
+ // TODO(johnlenz): filter out uninteresting node types
+ if (buildMappings) {
+ // m = new SourceMapping((HasSourceInfo) x, new FilePosition(p.getLine(), p.getColumn()));
+ // mappings.add(m);
+ }
+
+ super.doTraverse(x, ctx);
+
+ if (buildMappings) {
+ // m.setEnd(new FilePosition(p.getLine(), p.getColumn()));
+ }
+ }
+
+ @Override
+ public boolean visit(JsArrayAccess x, JsContext ctx) {
+ JsExpression arrayExpr = x.getArrayExpr();
+ _parenPush(x, arrayExpr, false);
+ accept(arrayExpr);
+ _parenPop(x, arrayExpr, false);
+ _lsquare();
+ accept(x.getIndexExpr());
+ _rsquare();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsArrayLiteral x, JsContext ctx) {
+ _lsquare();
+ boolean sep = false;
+ for (Object element : x.getExpressions()) {
+ JsExpression arg = (JsExpression) element;
+ sep = _sepCommaOptSpace(sep);
+ _parenPushIfCommaExpr(arg);
+ accept(arg);
+ _parenPopIfCommaExpr(arg);
+ }
+ _rsquare();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsBinaryOperation x, JsContext ctx) {
+ JsBinaryOperator op = x.getOperator();
+ JsExpression arg1 = x.getArg1();
+ _parenPush(x, arg1, !op.isLeftAssociative());
+ accept(arg1);
+ if (op.isKeyword()) {
+ _parenPopOrSpace(x, arg1, !op.isLeftAssociative());
+ } else {
+ _parenPop(x, arg1, !op.isLeftAssociative());
+ _spaceOpt();
+ }
+ p.print(op.getSymbol());
+ JsExpression arg2 = x.getArg2();
+ if (_spaceCalc(op, arg2)) {
+ _parenPushOrSpace(x, arg2, op.isLeftAssociative());
+ } else {
+ _spaceOpt();
+ _parenPush(x, arg2, op.isLeftAssociative());
+ }
+ accept(arg2);
+ _parenPop(x, arg2, op.isLeftAssociative());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsBlock x, JsContext ctx) {
+ printJsBlock(x, true, true);
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsBooleanLiteral x, JsContext ctx) {
+ if (x.getValue()) {
+ _true();
+ } else {
+ _false();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsBreak x, JsContext ctx) {
+ _break();
+
+ JsNameRef label = x.getLabel();
+ if (label != null) {
+ _space();
+ _nameRef(label);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsCase x, JsContext ctx) {
+ _case();
+ _space();
+ accept(x.getCaseExpr());
+ _colon();
+ _newlineOpt();
+
+ indent();
+ for (Object element : x.getStmts()) {
+ JsStatement stmt = (JsStatement) element;
+ needSemi = true;
+ accept(stmt);
+ if (needSemi) {
+ _semi();
+ }
+ _newlineOpt();
+ }
+ outdent();
+ needSemi = false;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsCatch x, JsContext ctx) {
+ _spaceOpt();
+ _catch();
+ _spaceOpt();
+ _lparen();
+ _nameDef(x.getParameter().getName());
+
+ // Optional catch condition.
+ //
+ JsExpression catchCond = x.getCondition();
+ if (catchCond != null) {
+ _space();
+ _if();
+ _space();
+ accept(catchCond);
+ }
+
+ _rparen();
+ _spaceOpt();
+ accept(x.getBody());
+
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsConditional x, JsContext ctx) {
+ // Associativity: for the then and else branches, it is safe to insert
+ // another
+ // ternary expression, but if the test expression is a ternary, it should
+ // get parentheses around it.
+ {
+ JsExpression testExpression = x.getTestExpression();
+ _parenPush(x, testExpression, true);
+ accept(testExpression);
+ _parenPop(x, testExpression, true);
+ }
+ _questionMark();
+ {
+ JsExpression thenExpression = x.getThenExpression();
+ _parenPush(x, thenExpression, false);
+ accept(thenExpression);
+ _parenPop(x, thenExpression, false);
+ }
+ _colon();
+ {
+ JsExpression elseExpression = x.getElseExpression();
+ _parenPush(x, elseExpression, false);
+ accept(elseExpression);
+ _parenPop(x, elseExpression, false);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsContinue x, JsContext ctx) {
+ _continue();
+
+ JsNameRef label = x.getLabel();
+ if (label != null) {
+ _space();
+ _nameRef(label);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsDebugger x, JsContext ctx) {
+ _debugger();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsDefault x, JsContext ctx) {
+ _default();
+ _colon();
+
+ indent();
+ for (Object element : x.getStmts()) {
+ JsStatement stmt = (JsStatement) element;
+ needSemi = true;
+ accept(stmt);
+ if (needSemi) {
+ _semi();
+ }
+ _newlineOpt();
+ }
+ outdent();
+ needSemi = false;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsDoWhile x, JsContext ctx) {
+ _do();
+ _nestedPush(x.getBody(), true);
+ accept(x.getBody());
+ _nestedPop(x.getBody());
+ if (needSemi) {
+ _semi();
+ _newlineOpt();
+ } else {
+ _spaceOpt();
+ needSemi = true;
+ }
+ _while();
+ _spaceOpt();
+ _lparen();
+ accept(x.getCondition());
+ _rparen();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsEmpty x, JsContext ctx) {
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsExprStmt x, JsContext ctx) {
+ boolean surroundWithParentheses = JsFirstExpressionVisitor.exec(x);
+ if (surroundWithParentheses) {
+ _lparen();
+ }
+ JsExpression expr = x.getExpression();
+ accept(expr);
+ if (surroundWithParentheses) {
+ _rparen();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsFor x, JsContext ctx) {
+ _for();
+ _spaceOpt();
+ _lparen();
+
+ // The init expressions or var decl.
+ //
+ if (x.getInitExpr() != null) {
+ accept(x.getInitExpr());
+ } else if (x.getInitVars() != null) {
+ accept(x.getInitVars());
+ }
+
+ _semi();
+
+ // The loop test.
+ //
+ if (x.getCondition() != null) {
+ _spaceOpt();
+ accept(x.getCondition());
+ }
+
+ _semi();
+
+ // The incr expression.
+ //
+ if (x.getIncrExpr() != null) {
+ _spaceOpt();
+ accept(x.getIncrExpr());
+ }
+
+ _rparen();
+ _nestedPush(x.getBody(), false);
+ accept(x.getBody());
+ _nestedPop(x.getBody());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsForIn x, JsContext ctx) {
+ _for();
+ _spaceOpt();
+ _lparen();
+
+ if (x.getIterVarName() != null) {
+ _var();
+ _space();
+ _nameDef(x.getIterVarName());
+
+ if (x.getIterExpr() != null) {
+ _spaceOpt();
+ _assignment();
+ _spaceOpt();
+ accept(x.getIterExpr());
+ }
+ } else {
+ // Just a name ref.
+ //
+ accept(x.getIterExpr());
+ }
+
+ _space();
+ _in();
+ _space();
+ accept(x.getObjExpr());
+
+ _rparen();
+ _nestedPush(x.getBody(), false);
+ accept(x.getBody());
+ _nestedPop(x.getBody());
+ return false;
+ }
+
+ // function foo(a, b) {
+ // stmts...
+ // }
+ //
+ @Override
+ public boolean visit(JsFunction x, JsContext ctx) {
+ _function();
+
+ // Functions can be anonymous.
+ //
+ if (x.getName() != null) {
+ _space();
+ _nameOf(x);
+ }
+
+ _lparen();
+ boolean sep = false;
+ for (Object element : x.getParameters()) {
+ JsParameter param = (JsParameter) element;
+ sep = _sepCommaOptSpace(sep);
+ accept(param);
+ }
+ _rparen();
+
+ accept(x.getBody());
+ needSemi = true;
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsIf x, JsContext ctx) {
+ _if();
+ _spaceOpt();
+ _lparen();
+ accept(x.getIfExpr());
+ _rparen();
+ JsStatement thenStmt = x.getThenStmt();
+ _nestedPush(thenStmt, false);
+ accept(thenStmt);
+ _nestedPop(thenStmt);
+ JsStatement elseStmt = x.getElseStmt();
+ if (elseStmt != null) {
+ if (needSemi) {
+ _semi();
+ _newlineOpt();
+ } else {
+ _spaceOpt();
+ needSemi = true;
+ }
+ _else();
+ boolean elseIf = elseStmt instanceof JsIf;
+ if (!elseIf) {
+ _nestedPush(elseStmt, true);
+ } else {
+ _space();
+ }
+ accept(elseStmt);
+ if (!elseIf) {
+ _nestedPop(elseStmt);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsInvocation x, JsContext ctx) {
+ JsExpression qualifier = x.getQualifier();
+ _parenPush(x, qualifier, false);
+ accept(qualifier);
+ _parenPop(x, qualifier, false);
+
+ _lparen();
+ boolean sep = false;
+ for (Object element : x.getArguments()) {
+ JsExpression arg = (JsExpression) element;
+ sep = _sepCommaOptSpace(sep);
+ _parenPushIfCommaExpr(arg);
+ accept(arg);
+ _parenPopIfCommaExpr(arg);
+ }
+ _rparen();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsLabel x, JsContext ctx) {
+ _nameOf(x);
+ _colon();
+ _spaceOpt();
+ accept(x.getStmt());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNameRef x, JsContext ctx) {
+ JsExpression q = x.getQualifier();
+ if (q != null) {
+ _parenPush(x, q, false);
+ if (q instanceof JsNumberLiteral) {
+ /**
+ * Fix for Issue #3796. "42.foo" is not allowed, but "(42).foo" is.
+ */
+ _lparen();
+ }
+ accept(q);
+ if (q instanceof JsNumberLiteral) {
+ _rparen();
+ }
+ _parenPop(x, q, false);
+ _dot();
+ }
+ _nameRef(x);
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNew x, JsContext ctx) {
+ _new();
+ _space();
+
+ JsExpression ctorExpr = x.getConstructorExpression();
+ boolean needsParens = JsConstructExpressionVisitor.exec(ctorExpr);
+ if (needsParens) {
+ _lparen();
+ }
+ accept(ctorExpr);
+ if (needsParens) {
+ _rparen();
+ }
+
+ /*
+ * If a constructor call has no arguments, it may simply be replaced with
+ * "new Constructor" with no parentheses.
+ */
+ List args = x.getArguments();
+ if (args.size() > 0) {
+ _lparen();
+ boolean sep = false;
+ for (JsExpression arg : args) {
+ sep = _sepCommaOptSpace(sep);
+ _parenPushIfCommaExpr(arg);
+ accept(arg);
+ _parenPopIfCommaExpr(arg);
+ }
+ _rparen();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNullLiteral x, JsContext ctx) {
+ _null();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsNumberLiteral x, JsContext ctx) {
+ double dvalue = x.getValue();
+ long lvalue = (long) dvalue;
+ if (lvalue == dvalue) {
+ p.print(Long.toString(lvalue));
+ } else {
+ p.print(Double.toString(dvalue));
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsObjectLiteral x, JsContext ctx) {
+ _lbrace();
+ boolean sep = false;
+ for (Object element : x.getPropertyInitializers()) {
+ sep = _sepCommaOptSpace(sep);
+ JsPropertyInitializer propInit = (JsPropertyInitializer) element;
+ printLabel : {
+ JsExpression labelExpr = propInit.getLabelExpr();
+ // labels can be either string, integral, or decimal literals
+ if (labelExpr instanceof JsStringLiteral) {
+ String propName = ((JsStringLiteral) labelExpr).getValue();
+ if (VALID_NAME_PATTERN.matcher(propName).matches()
+ && !JsReservedIdentifiers.isKeyword(propName)) {
+ p.print(propName);
+ break printLabel;
+ }
+ }
+ accept(labelExpr);
+ }
+ _colon();
+ JsExpression valueExpr = propInit.getValueExpr();
+ _parenPushIfCommaExpr(valueExpr);
+ accept(valueExpr);
+ _parenPopIfCommaExpr(valueExpr);
+ }
+ _rbrace();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsParameter x, JsContext ctx) {
+ _nameOf(x);
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsPostfixOperation x, JsContext ctx) {
+ JsUnaryOperator op = x.getOperator();
+ JsExpression arg = x.getArg();
+ // unary operators always associate correctly (I think)
+ _parenPush(x, arg, false);
+ accept(arg);
+ _parenPop(x, arg, false);
+ p.print(op.getSymbol());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsPrefixOperation x, JsContext ctx) {
+ JsUnaryOperator op = x.getOperator();
+ p.print(op.getSymbol());
+ JsExpression arg = x.getArg();
+ if (_spaceCalc(op, arg)) {
+ _space();
+ }
+ // unary operators always associate correctly (I think)
+ _parenPush(x, arg, false);
+ accept(arg);
+ _parenPop(x, arg, false);
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsProgram x, JsContext ctx) {
+ p.print("");
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsProgramFragment x, JsContext ctx) {
+ p.print("");
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsPropertyInitializer x, JsContext ctx) {
+ // Since there are separators, we actually print the property init
+ // in visit(JsObjectLiteral).
+ //
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsRegExp x, JsContext ctx) {
+ _slash();
+ p.print(x.getPattern());
+ _slash();
+ String flags = x.getFlags();
+ if (flags != null) {
+ p.print(flags);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsReturn x, JsContext ctx) {
+ _return();
+ JsExpression expr = x.getExpr();
+ if (expr != null) {
+ _space();
+ accept(expr);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsStringLiteral x, JsContext ctx) {
+ printStringLiteral(x.getValue());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsSwitch x, JsContext ctx) {
+ _switch();
+ _spaceOpt();
+ _lparen();
+ accept(x.getExpr());
+ _rparen();
+ _spaceOpt();
+ _blockOpen();
+ acceptList(x.getCases());
+ _blockClose();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsThisRef x, JsContext ctx) {
+ _this();
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsThrow x, JsContext ctx) {
+ _throw();
+ _space();
+ accept(x.getExpr());
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsTry x, JsContext ctx) {
+ _try();
+ _spaceOpt();
+ accept(x.getTryBlock());
+
+ acceptList(x.getCatches());
+
+ JsBlock finallyBlock = x.getFinallyBlock();
+ if (finallyBlock != null) {
+ _spaceOpt();
+ _finally();
+ _spaceOpt();
+ accept(finallyBlock);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsVars.JsVar x, JsContext ctx) {
+ _nameOf(x);
+ JsExpression initExpr = x.getInitExpr();
+ if (initExpr != null) {
+ _spaceOpt();
+ _assignment();
+ _spaceOpt();
+ _parenPushIfCommaExpr(initExpr);
+ accept(initExpr);
+ _parenPopIfCommaExpr(initExpr);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsVars x, JsContext ctx) {
+ _var();
+ _space();
+ boolean sep = false;
+ for (JsVars.JsVar var : x) {
+ sep = _sepCommaOptSpace(sep);
+ accept(var);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(JsWhile x, JsContext ctx) {
+ _while();
+ _spaceOpt();
+ _lparen();
+ accept(x.getCondition());
+ _rparen();
+ _nestedPush(x.getBody(), false);
+ accept(x.getBody());
+ _nestedPop(x.getBody());
+ return false;
+ }
+
+ // CHECKSTYLE_NAMING_OFF
+ protected void _newline() {
+ p.newline();
+ }
+
+ protected void _newlineOpt() {
+ p.newlineOpt();
+ }
+
+ protected void printJsBlock(JsBlock x, boolean truncate, boolean finalNewline) {
+ boolean needBraces = !x.isGlobalBlock();
+
+ if (needBraces) {
+ // Open braces.
+ //
+ _blockOpen();
+ }
+
+ int count = 0;
+ for (Iterator iter = x.getStatements().iterator(); iter.hasNext(); ++count) {
+ boolean isGlobal = x.isGlobalBlock() || globalBlocks.contains(x);
+
+ if (truncate && count > JSBLOCK_LINES_TO_PRINT) {
+ p.print("[...]");
+ _newlineOpt();
+ break;
+ }
+ JsStatement stmt = iter.next();
+ needSemi = true;
+ boolean shouldRecordPositions = isGlobal && !(stmt instanceof JsBlock);
+ boolean stmtIsGlobalBlock = false;
+ if (isGlobal) {
+ if (stmt instanceof JsBlock) {
+ // A block inside a global block is still considered global
+ stmtIsGlobalBlock = true;
+ globalBlocks.add((JsBlock) stmt);
+ }
+ }
+ if (shouldRecordPositions) {
+ statementStarts.add(p.getPosition());
+ }
+ accept(stmt);
+ if (stmtIsGlobalBlock) {
+ globalBlocks.remove(stmt);
+ }
+ if (needSemi) {
+ /*
+ * Special treatment of function decls: If they are the only item in a
+ * statement (i.e. not part of an assignment operation), just give them
+ * a newline instead of a semi.
+ */
+ boolean functionStmt =
+ stmt instanceof JsExprStmt && ((JsExprStmt) stmt).getExpression() instanceof JsFunction;
+ /*
+ * Special treatment of the last statement in a block: only a few
+ * statements at the end of a block require semicolons.
+ */
+ boolean lastStatement = !iter.hasNext() && needBraces && !JsRequiresSemiVisitor.exec(stmt);
+ if (functionStmt) {
+ if (lastStatement) {
+ _newlineOpt();
+ } else {
+ _newline();
+ }
+ } else {
+ if (lastStatement) {
+ _semiOpt();
+ } else {
+ _semi();
+ }
+ _newlineOpt();
+ }
+ }
+ if (shouldRecordPositions) {
+ assert (statementStarts.size() == statementEnds.size() + 1);
+ statementEnds.add(p.getPosition());
+ }
+ }
+
+ if (needBraces) {
+ // _blockClose() modified
+ p.indentOut();
+ p.print('}');
+ if (finalNewline) {
+ _newlineOpt();
+ }
+ }
+ needSemi = false;
+ }
+
+ private void _assignment() {
+ p.print('=');
+ }
+
+ private void _blockClose() {
+ p.indentOut();
+ p.print('}');
+ _newlineOpt();
+ }
+
+ private void _blockOpen() {
+ p.print('{');
+ p.indentIn();
+ _newlineOpt();
+ }
+
+ private void _break() {
+ p.print(CHARS_BREAK);
+ }
+
+ private void _case() {
+ p.print(CHARS_CASE);
+ }
+
+ private void _catch() {
+ p.print(CHARS_CATCH);
+ }
+
+ private void _colon() {
+ p.print(':');
+ }
+
+ private void _continue() {
+ p.print(CHARS_CONTINUE);
+ }
+
+ private void _debugger() {
+ p.print(CHARS_DEBUGGER);
+ }
+
+ private void _default() {
+ p.print(CHARS_DEFAULT);
+ }
+
+ private void _do() {
+ p.print(CHARS_DO);
+ }
+
+ private void _dot() {
+ p.print('.');
+ }
+
+ private void _else() {
+ p.print(CHARS_ELSE);
+ }
+
+ private void _false() {
+ p.print(CHARS_FALSE);
+ }
+
+ private void _finally() {
+ p.print(CHARS_FINALLY);
+ }
+
+ private void _for() {
+ p.print(CHARS_FOR);
+ }
+
+ private void _function() {
+ p.print(CHARS_FUNCTION);
+ }
+
+ private void _if() {
+ p.print(CHARS_IF);
+ }
+
+ private void _in() {
+ p.print(CHARS_IN);
+ }
+
+ private void _lbrace() {
+ p.print('{');
+ }
+
+ private void _lparen() {
+ p.print('(');
+ }
+
+ private void _lsquare() {
+ p.print('[');
+ }
+
+ private void _nameDef(JsName name) {
+ p.print(name.getShortIdent());
+ }
+
+ private void _nameOf(HasName hasName) {
+ _nameDef(hasName.getName());
+ }
+
+ private void _nameRef(JsNameRef nameRef) {
+ p.print(nameRef.getShortIdent());
+ }
+
+ private boolean _nestedPop(JsStatement statement) {
+ boolean pop = !(statement instanceof JsBlock);
+ if (pop) {
+ p.indentOut();
+ }
+ return pop;
+ }
+
+ private boolean _nestedPush(JsStatement statement, boolean needSpace) {
+ boolean push = !(statement instanceof JsBlock);
+ if (push) {
+ if (needSpace) {
+ _space();
+ }
+ p.indentIn();
+ _newlineOpt();
+ } else {
+ _spaceOpt();
+ }
+ return push;
+ }
+
+ private void _new() {
+ p.print(CHARS_NEW);
+ }
+
+ private void _null() {
+ p.print(CHARS_NULL);
+ }
+
+ private boolean _parenCalc(JsExpression parent, JsExpression child, boolean wrongAssoc) {
+ int parentPrec = JsPrecedenceVisitor.exec(parent);
+ int childPrec = JsPrecedenceVisitor.exec(child);
+ return (parentPrec > childPrec || (parentPrec == childPrec && wrongAssoc));
+ }
+
+ private boolean _parenPop(JsExpression parent, JsExpression child, boolean wrongAssoc) {
+ boolean doPop = _parenCalc(parent, child, wrongAssoc);
+ if (doPop) {
+ _rparen();
+ }
+ return doPop;
+ }
+
+ private boolean _parenPopIfCommaExpr(JsExpression x) {
+ boolean doPop =
+ x instanceof JsBinaryOperation
+ && ((JsBinaryOperation) x).getOperator() == JsBinaryOperator.COMMA;
+ if (doPop) {
+ _rparen();
+ }
+ return doPop;
+ }
+
+ private boolean _parenPopOrSpace(JsExpression parent, JsExpression child, boolean wrongAssoc) {
+ boolean doPop = _parenCalc(parent, child, wrongAssoc);
+ if (doPop) {
+ _rparen();
+ } else {
+ _space();
+ }
+ return doPop;
+ }
+
+ private boolean _parenPush(JsExpression parent, JsExpression child, boolean wrongAssoc) {
+ boolean doPush = _parenCalc(parent, child, wrongAssoc);
+ if (doPush) {
+ _lparen();
+ }
+ return doPush;
+ }
+
+ private boolean _parenPushIfCommaExpr(JsExpression x) {
+ boolean doPush =
+ x instanceof JsBinaryOperation
+ && ((JsBinaryOperation) x).getOperator() == JsBinaryOperator.COMMA;
+ if (doPush) {
+ _lparen();
+ }
+ return doPush;
+ }
+
+ private boolean _parenPushOrSpace(JsExpression parent, JsExpression child, boolean wrongAssoc) {
+ boolean doPush = _parenCalc(parent, child, wrongAssoc);
+ if (doPush) {
+ _lparen();
+ } else {
+ _space();
+ }
+ return doPush;
+ }
+
+ private void _questionMark() {
+ p.print('?');
+ }
+
+ private void _rbrace() {
+ p.print('}');
+ }
+
+ private void _return() {
+ p.print(CHARS_RETURN);
+ }
+
+ private void _rparen() {
+ p.print(')');
+ }
+
+ private void _rsquare() {
+ p.print(']');
+ }
+
+ private void _semi() {
+ p.print(';');
+ }
+
+ private void _semiOpt() {
+ p.printOpt(';');
+ }
+
+ private boolean _sepCommaOptSpace(boolean sep) {
+ if (sep) {
+ p.print(',');
+ _spaceOpt();
+ }
+ return true;
+ }
+
+ private void _slash() {
+ p.print('/');
+ }
+
+ private void _space() {
+ p.print(' ');
+ }
+
+ /**
+ * Decide whether, if op is printed followed by arg,
+ * there needs to be a space between the operator and expression.
+ *
+ * @return true if a space needs to be printed
+ */
+ private boolean _spaceCalc(JsOperator op, JsExpression arg) {
+ if (op.isKeyword()) {
+ return true;
+ }
+ if (arg instanceof JsBinaryOperation) {
+ JsBinaryOperation binary = (JsBinaryOperation) arg;
+ /*
+ * If the binary operation has a higher precedence than op, then it won't
+ * be parenthesized, so check the first argument of the binary operation.
+ */
+ if (binary.getOperator().getPrecedence() > op.getPrecedence()) {
+ return _spaceCalc(op, binary.getArg1());
+ }
+ return false;
+ }
+ if (arg instanceof JsPrefixOperation) {
+ JsOperator op2 = ((JsPrefixOperation) arg).getOperator();
+ return (op == JsBinaryOperator.SUB || op == JsUnaryOperator.NEG)
+ && (op2 == JsUnaryOperator.DEC || op2 == JsUnaryOperator.NEG)
+ || (op == JsBinaryOperator.ADD && op2 == JsUnaryOperator.INC);
+ }
+ if (arg instanceof JsNumberLiteral) {
+ JsNumberLiteral literal = (JsNumberLiteral) arg;
+ return (op == JsBinaryOperator.SUB || op == JsUnaryOperator.NEG) && (literal.getValue() < 0);
+ }
+ return false;
+ }
+
+ private void _spaceOpt() {
+ p.printOpt(' ');
+ }
+
+ private void _switch() {
+ p.print(CHARS_SWITCH);
+ }
+
+ private void _this() {
+ p.print(CHARS_THIS);
+ }
+
+ private void _throw() {
+ p.print(CHARS_THROW);
+ }
+
+ private void _true() {
+ p.print(CHARS_TRUE);
+ }
+
+ private void _try() {
+ p.print(CHARS_TRY);
+ }
+
+ private void _var() {
+ p.print(CHARS_VAR);
+ }
+
+ private void _while() {
+ p.print(CHARS_WHILE);
+ }
+
+ // CHECKSTYLE_NAMING_ON
+
+ private void indent() {
+ p.indentIn();
+ }
+
+ private void outdent() {
+ p.indentOut();
+ }
+
+ private void printStringLiteral(String value) {
+ String resultString = javaScriptString(value);
+ p.print(resultString);
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/UncheckedJsParserException.java b/js/js.ast/src/com/google/dart/compiler/backend/js/UncheckedJsParserException.java
new file mode 100644
index 00000000000..c2db8120537
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/UncheckedJsParserException.java
@@ -0,0 +1,21 @@
+// 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.
+
+package com.google.dart.compiler.backend.js;
+
+/**
+ * An unchecked wrapper exception to interop with Rhino.
+ */
+class UncheckedJsParserException extends RuntimeException {
+
+ private final JsParserException parserException;
+
+ public UncheckedJsParserException(JsParserException parserException) {
+ this.parserException = parserException;
+ }
+
+ public JsParserException getParserException() {
+ return parserException;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/CanBooleanEval.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/CanBooleanEval.java
new file mode 100644
index 00000000000..8f4364d94f2
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/CanBooleanEval.java
@@ -0,0 +1,15 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * An interface that describes the boolean evaluation of an expression.
+ */
+public interface CanBooleanEval {
+
+ boolean isBooleanFalse();
+
+ boolean isBooleanTrue();
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/HasArguments.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/HasArguments.java
new file mode 100644
index 00000000000..5a47c567606
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/HasArguments.java
@@ -0,0 +1,14 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.List;
+
+/**
+ * Implemented by JavaScript objects that accept arguments.
+ */
+public interface HasArguments {
+ List getArguments();
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/HasCondition.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/HasCondition.java
new file mode 100644
index 00000000000..082f4e15eb3
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/HasCondition.java
@@ -0,0 +1,15 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Implemented by JavaScript objects with conditional execution.
+ */
+public interface HasCondition {
+
+ JsExpression getCondition();
+
+ void setCondition(JsExpression condition);
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/HasName.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/HasName.java
new file mode 100644
index 00000000000..8daed3769f4
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/HasName.java
@@ -0,0 +1,12 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Implemented by JavaScript objects that have a name.
+ */
+public interface HasName {
+ JsName getName();
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsArrayAccess.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsArrayAccess.java
new file mode 100644
index 00000000000..46df5108086
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsArrayAccess.java
@@ -0,0 +1,68 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a javascript expression for array access.
+ */
+public final class JsArrayAccess extends JsExpression {
+
+ private JsExpression arrayExpr;
+ private JsExpression indexExpr;
+
+ public JsArrayAccess() {
+ super();
+ }
+
+ public JsArrayAccess(JsExpression arrayExpr, JsExpression indexExpr) {
+ this.arrayExpr = arrayExpr;
+ this.indexExpr = indexExpr;
+ }
+
+ public JsExpression getArrayExpr() {
+ return arrayExpr;
+ }
+
+ public JsExpression getIndexExpr() {
+ return indexExpr;
+ }
+
+ @Override
+ public boolean hasSideEffects() {
+ return arrayExpr.hasSideEffects() || indexExpr.hasSideEffects();
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return false;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ public void setArrayExpr(JsExpression arrayExpr) {
+ this.arrayExpr = arrayExpr;
+ }
+
+ public void setIndexExpr(JsExpression indexExpr) {
+ this.indexExpr = indexExpr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ arrayExpr = v.accept(arrayExpr);
+ indexExpr = v.accept(indexExpr);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.ARRAY_ACCESS;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsArrayLiteral.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsArrayLiteral.java
new file mode 100644
index 00000000000..8cb9f7e6d52
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsArrayLiteral.java
@@ -0,0 +1,67 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a JavaScript expression for array literals.
+ */
+public final class JsArrayLiteral extends JsLiteral {
+
+ private final List exprs = new ArrayList();
+
+ public JsArrayLiteral() {
+ super();
+ }
+
+ public List getExpressions() {
+ return exprs;
+ }
+
+ @Override
+ public boolean hasSideEffects() {
+ for (JsExpression expr : exprs) {
+ if (expr.hasSideEffects()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return false;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ v.acceptWithInsertRemove(exprs);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.ARRAY;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBinaryOperation.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBinaryOperation.java
new file mode 100644
index 00000000000..b09d50c1d05
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBinaryOperation.java
@@ -0,0 +1,107 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript binary operation.
+ */
+public final class JsBinaryOperation extends JsExpression {
+
+ private JsExpression arg1;
+ private JsExpression arg2;
+ private final JsBinaryOperator op;
+
+ public JsBinaryOperation(JsBinaryOperator op) {
+ this(op, null, null);
+ }
+
+ public JsBinaryOperation(JsBinaryOperator op, JsExpression arg1, JsExpression arg2) {
+ this.op = op;
+ this.arg1 = arg1;
+ this.arg2 = arg2;
+ }
+
+ public JsExpression getArg1() {
+ return arg1;
+ }
+
+ public JsExpression getArg2() {
+ return arg2;
+ }
+
+ public JsBinaryOperator getOperator() {
+ return op;
+ }
+
+ @Override
+ public boolean hasSideEffects() {
+ return op.isAssignment() || arg1.hasSideEffects() || arg2.hasSideEffects();
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ // Precarious coding, but none of these can have null results.
+ if (op.getPrecedence() > 5) {
+ return true;
+ }
+ if (op == JsBinaryOperator.OR) {
+ if (arg1 instanceof CanBooleanEval) {
+ if (((CanBooleanEval) arg1).isBooleanTrue()) {
+ assert arg1.isDefinitelyNotNull();
+ return true;
+ }
+ }
+ }
+ // AND and OR can return nulls
+ if (op.isAssignment()) {
+ if (op == JsBinaryOperator.ASG) {
+ return arg2.isDefinitelyNotNull();
+ } else {
+ // All other ASG's are math ops.
+ return true;
+ }
+ }
+
+ if (op == JsBinaryOperator.COMMA) {
+ return arg2.isDefinitelyNotNull();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ if (op == JsBinaryOperator.AND) {
+ return arg1.isDefinitelyNull();
+ }
+ return false;
+ }
+
+ public void setArg1(JsExpression arg1) {
+ this.arg1 = arg1;
+ }
+
+ public void setArg2(JsExpression arg2) {
+ this.arg2 = arg2;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ if (op.isAssignment()) {
+ arg1 = v.acceptLvalue(arg1);
+ } else {
+ arg1 = v.accept(arg1);
+ }
+ arg2 = v.accept(arg2);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.BINARY_OP;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBinaryOperator.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBinaryOperator.java
new file mode 100644
index 00000000000..b54647f3a46
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBinaryOperator.java
@@ -0,0 +1,120 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents the operator in a JavaScript binary operation.
+ */
+public enum JsBinaryOperator implements JsOperator {
+
+ /*
+ * Precedence indices from "JavaScript - The Definitive Guide" 4th Edition
+ * (page 57)
+ *
+ *
+ * Precedence 15 is for really important things that have their own AST
+ * classes.
+ *
+ * Precedence 14 is for unary operators.
+ */
+
+ MUL("*", 13, LEFT | INFIX), DIV("/", 13, LEFT | INFIX), MOD("%", 13, LEFT
+ | INFIX),
+
+ ADD("+", 12, LEFT | INFIX), SUB("-", 12, LEFT | INFIX),
+
+ SHL("<<", 11, LEFT | INFIX), SHR(">>", 11, LEFT | INFIX), SHRU(">>>", 11,
+ LEFT | INFIX),
+
+ LT("<", 10, LEFT | INFIX), LTE("<=", 10, LEFT | INFIX), GT(">", 10, LEFT
+ | INFIX), GTE(">=", 10, LEFT | INFIX), INSTANCEOF("instanceof", 10, LEFT
+ | INFIX), INOP("in", 10, LEFT | INFIX),
+
+ EQ("==", 9, LEFT | INFIX), NEQ("!=", 9, LEFT | INFIX), REF_EQ("===", 9, LEFT
+ | INFIX), REF_NEQ("!==", 9, LEFT | INFIX),
+
+ BIT_AND("&", 8, LEFT | INFIX),
+
+ BIT_XOR("^", 7, LEFT | INFIX),
+
+ BIT_OR("|", 6, LEFT | INFIX),
+
+ AND("&&", 5, LEFT | INFIX),
+
+ OR("||", 4, LEFT | INFIX),
+
+ // Precedence 3 is for the condition operator.
+
+ // These assignment operators are right-associative.
+ ASG("=", 2, INFIX), ASG_ADD("+=", 2, INFIX), ASG_SUB("-=", 2, INFIX), ASG_MUL(
+ "*=", 2, INFIX), ASG_DIV("/=", 2, INFIX), ASG_MOD("%=", 2, INFIX), ASG_SHL(
+ "<<=", 2, INFIX), ASG_SHR(">>=", 2, INFIX), ASG_SHRU(">>>=", 2, INFIX), ASG_BIT_AND(
+ "&=", 2, INFIX), ASG_BIT_OR("|=", 2, INFIX), ASG_BIT_XOR("^=", 2, INFIX),
+
+ COMMA(",", 1, LEFT | INFIX);
+
+ private final int mask;
+ private final int precedence;
+ private final String symbol;
+
+ private JsBinaryOperator(String symbol, int precedence, int mask) {
+ this.symbol = symbol;
+ this.precedence = precedence;
+ this.mask = mask;
+ }
+
+ @Override
+ public int getPrecedence() {
+ return precedence;
+ }
+
+ @Override
+ public String getSymbol() {
+ return symbol;
+ }
+
+ public boolean isAssignment() {
+ /*
+ * Beware, flaky! Maybe I should have added Yet Another Field to
+ * BinaryOperator?
+ */
+ return (getPrecedence() == ASG.getPrecedence());
+ }
+
+ @Override
+ public boolean isKeyword() {
+ return this == INSTANCEOF || this == INOP;
+ }
+
+ @Override
+ public boolean isLeftAssociative() {
+ return (mask & LEFT) != 0;
+ }
+
+ @Override
+ public boolean isPrecedenceLessThan(JsOperator other) {
+ return precedence < other.getPrecedence();
+ }
+
+ @Override
+ public boolean isValidInfix() {
+ return (mask & INFIX) != 0;
+ }
+
+ @Override
+ public boolean isValidPostfix() {
+ return (mask & POSTFIX) != 0;
+ }
+
+ @Override
+ public boolean isValidPrefix() {
+ return (mask & PREFIX) != 0;
+ }
+
+ @Override
+ public String toString() {
+ return symbol;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBlock.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBlock.java
new file mode 100644
index 00000000000..fca607c4eb4
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBlock.java
@@ -0,0 +1,60 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Represents a JavaScript block statement.
+ */
+public class JsBlock extends JsStatement {
+
+ private final LinkedList stmts = new LinkedList();
+
+ public JsBlock() {
+ }
+
+ public JsBlock(JsStatement stmt) {
+ stmts.add(stmt);
+ }
+
+ public List getStatements() {
+ return stmts;
+ }
+
+ public void addStatement(JsStatement statement) {
+ assert statement != null;
+ stmts.add(statement);
+ }
+
+ public boolean isGlobalBlock() {
+ return false;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ v.acceptWithInsertRemove(stmts);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.BLOCK;
+ }
+
+ /*Pavel Talanov*/
+ public void addVarDeclaration(JsVars vars) {
+ stmts.offerFirst(vars);
+ }
+
+ public void setStatements(List statements) {
+ assert this.stmts.isEmpty() : "Already contains statements.";
+ this.stmts.addAll(statements);
+ }
+
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBooleanLiteral.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBooleanLiteral.java
new file mode 100644
index 00000000000..12238ddbb02
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBooleanLiteral.java
@@ -0,0 +1,53 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript literal boolean expression.
+ */
+public final class JsBooleanLiteral extends JsValueLiteral {
+
+ private final boolean value;
+
+ // Should be interned by JsProgram
+ JsBooleanLiteral(boolean value) {
+ this.value = value;
+ }
+
+ public boolean getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return value == false;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return value == true;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ v.visit(this, ctx);
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.BOOLEAN;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBreak.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBreak.java
new file mode 100644
index 00000000000..363f294df78
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsBreak.java
@@ -0,0 +1,46 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents the JavaScript break statement.
+ */
+public final class JsBreak extends JsStatement {
+
+ private final JsNameRef label;
+
+ public JsBreak() {
+ this(null);
+ }
+
+ public JsBreak(JsNameRef label) {
+ super();
+ this.label = label;
+ }
+
+ public JsNameRef getLabel() {
+ return label;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ if (label != null) {
+ v.accept(label);
+ }
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public boolean unconditionalControlBreak() {
+ return true;
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.BREAK;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsCase.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsCase.java
new file mode 100644
index 00000000000..43d2fc41230
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsCase.java
@@ -0,0 +1,39 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents the JavaScript case statement.
+ */
+public final class JsCase extends JsSwitchMember {
+
+ private JsExpression caseExpr;
+
+ public JsCase() {
+ super();
+ }
+
+ public JsExpression getCaseExpr() {
+ return caseExpr;
+ }
+
+ public void setCaseExpr(JsExpression caseExpr) {
+ this.caseExpr = caseExpr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ caseExpr = v.accept(caseExpr);
+ v.acceptWithInsertRemove(stmts);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.CASE;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsCatch.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsCatch.java
new file mode 100644
index 00000000000..f992545b944
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsCatch.java
@@ -0,0 +1,66 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript catch clause.
+ */
+public class JsCatch extends JsNode implements HasCondition {
+
+ protected final JsCatchScope scope;
+ private JsBlock body;
+ private JsExpression condition;
+ private JsParameter param;
+
+ public JsCatch(JsScope parent, String ident) {
+ super();
+ assert (parent != null);
+ scope = new JsCatchScope(parent, ident);
+ param = new JsParameter(scope.findExistingName(ident));
+ }
+
+ public JsBlock getBody() {
+ return body;
+ }
+
+ @Override
+ public JsExpression getCondition() {
+ return condition;
+ }
+
+ public JsParameter getParameter() {
+ return param;
+ }
+
+ public JsScope getScope() {
+ return scope;
+ }
+
+ public void setBody(JsBlock body) {
+ this.body = body;
+ }
+
+ @Override
+ public void setCondition(JsExpression condition) {
+ this.condition = condition;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ param = v.accept(param);
+ if (condition != null) {
+ condition = v.accept(condition);
+ }
+ body = v.accept(body);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.CATCH;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsCatchScope.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsCatchScope.java
new file mode 100644
index 00000000000..5e5a2f2f577
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsCatchScope.java
@@ -0,0 +1,75 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A special scope used only for catch blocks. It only holds a single symbol:
+ * the catch argument's name.
+ */
+public class JsCatchScope extends JsScope {
+
+ private final JsName name;
+
+ public JsCatchScope(JsScope parent, String ident) {
+ super(parent, "Catch scope");
+ this.name = new JsName(this, ident, ident, ident);
+ }
+
+ @Override
+ public JsName declareName(String ident) {
+ // Declare into parent scope!
+ return getParent().declareName(ident);
+ }
+
+ @Override
+ public JsName declareName(String ident, String shortIdent) {
+ // Declare into parent scope!
+ return getParent().declareName(ident, shortIdent);
+ }
+
+ @Override
+ public Iterator getAllNames() {
+ return new Iterator() {
+ private boolean didIterate = false;
+
+ @Override
+ public boolean hasNext() {
+ return !didIterate;
+ }
+
+ @Override
+ public JsName next() {
+ if (didIterate) {
+ throw new NoSuchElementException();
+ }
+ didIterate = true;
+ return name;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ };
+ }
+
+ @Override
+ protected JsName doCreateName(String ident, String shortIdent, String originalName) {
+ throw new UnsupportedOperationException("Cannot create a name in a catch scope");
+ }
+
+ @Override
+ public JsName findExistingNameNoRecurse(String ident) {
+ if (name.getIdent().equals(ident)) {
+ return name;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsConditional.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsConditional.java
new file mode 100644
index 00000000000..b1171bac2c0
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsConditional.java
@@ -0,0 +1,78 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript conditional expression.
+ */
+public final class JsConditional extends JsExpression {
+
+ private JsExpression elseExpr;
+ private JsExpression testExpr;
+ private JsExpression thenExpr;
+
+ public JsConditional() {
+ }
+
+ public JsConditional(JsExpression testExpr, JsExpression thenExpr, JsExpression elseExpr) {
+ this.testExpr = testExpr;
+ this.thenExpr = thenExpr;
+ this.elseExpr = elseExpr;
+ }
+
+ public JsExpression getElseExpression() {
+ return elseExpr;
+ }
+
+ public JsExpression getTestExpression() {
+ return testExpr;
+ }
+
+ public JsExpression getThenExpression() {
+ return thenExpr;
+ }
+
+ @Override
+ public boolean hasSideEffects() {
+ return testExpr.hasSideEffects() || thenExpr.hasSideEffects() || elseExpr.hasSideEffects();
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return thenExpr.isDefinitelyNotNull() && elseExpr.isDefinitelyNotNull();
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return thenExpr.isDefinitelyNull() && elseExpr.isDefinitelyNull();
+ }
+
+ public void setElseExpression(JsExpression elseExpr) {
+ this.elseExpr = elseExpr;
+ }
+
+ public void setTestExpression(JsExpression testExpr) {
+ this.testExpr = testExpr;
+ }
+
+ public void setThenExpression(JsExpression thenExpr) {
+ this.thenExpr = thenExpr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ testExpr = v.accept(testExpr);
+ thenExpr = v.accept(thenExpr);
+ elseExpr = v.accept(elseExpr);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.CONDITIONAL;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsContext.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsContext.java
new file mode 100644
index 00000000000..ddb170052d3
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsContext.java
@@ -0,0 +1,27 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * The context in which a JsNode visitation occurs. This represents the set of
+ * possible operations a JsVisitor subclass can perform on the currently visited
+ * node.
+ */
+public interface JsContext {
+
+ boolean canInsert();
+
+ boolean canRemove();
+
+ void insertAfter(JsVisitable node);
+
+ void insertBefore(JsVisitable node);
+
+ boolean isLvalue();
+
+ void removeMe();
+
+ void replaceMe(JsVisitable node);
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsContinue.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsContinue.java
new file mode 100644
index 00000000000..d761815fe76
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsContinue.java
@@ -0,0 +1,46 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents the JavaScript continue statement.
+ */
+public final class JsContinue extends JsStatement {
+
+ private final JsNameRef label;
+
+ public JsContinue() {
+ this(null);
+ }
+
+ public JsContinue(JsNameRef label) {
+ super();
+ this.label = label;
+ }
+
+ public JsNameRef getLabel() {
+ return label;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ if (label != null) {
+ v.accept(label);
+ }
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public boolean unconditionalControlBreak() {
+ return true;
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.CONTINUE;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsDebugger.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsDebugger.java
new file mode 100644
index 00000000000..34cfae43f48
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsDebugger.java
@@ -0,0 +1,25 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript debugger statement.
+ */
+public class JsDebugger extends JsStatement {
+
+ public JsDebugger() {
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ v.visit(this, ctx);
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.DEBUGGER;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsDefault.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsDefault.java
new file mode 100644
index 00000000000..a76987a8e5d
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsDefault.java
@@ -0,0 +1,28 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents the default option in a JavaScript swtich statement.
+ */
+public final class JsDefault extends JsSwitchMember {
+
+ public JsDefault() {
+ super();
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ v.acceptWithInsertRemove(stmts);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.DEFAULT;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsDoWhile.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsDoWhile.java
new file mode 100644
index 00000000000..1dd4a47e8d4
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsDoWhile.java
@@ -0,0 +1,54 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript do..while statement.
+ */
+public class JsDoWhile extends JsStatement {
+
+ private JsStatement body;
+ private JsExpression condition;
+
+ public JsDoWhile() {
+ super();
+ }
+
+ public JsDoWhile(JsExpression condition, JsStatement body) {
+ super();
+ this.condition = condition;
+ this.body = body;
+ }
+
+ public JsStatement getBody() {
+ return body;
+ }
+
+ public JsExpression getCondition() {
+ return condition;
+ }
+
+ public void setBody(JsStatement body) {
+ this.body = body;
+ }
+
+ public void setCondition(JsExpression condition) {
+ this.condition = condition;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ body = v.accept(body);
+ condition = v.accept(condition);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.DO;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsEmpty.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsEmpty.java
new file mode 100644
index 00000000000..c2ac99e3ac0
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsEmpty.java
@@ -0,0 +1,26 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents an empty statement in JavaScript.
+ */
+public class JsEmpty extends JsStatement {
+
+ // Interned by JsProgram
+ JsEmpty() {
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ v.visit(this, ctx);
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.EMPTY;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsExprStmt.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsExprStmt.java
new file mode 100644
index 00000000000..5b9fc1218c3
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsExprStmt.java
@@ -0,0 +1,36 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript expression statement.
+ */
+public final class JsExprStmt extends JsStatement {
+
+ private JsExpression expr;
+
+ public JsExprStmt(JsExpression expr) {
+ super();
+ this.expr = expr;
+ this.setSourceInfo(expr);
+ }
+
+ public JsExpression getExpression() {
+ return expr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ expr = v.accept(expr);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.EXPR_STMT;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsExpression.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsExpression.java
new file mode 100644
index 00000000000..43545ec98dc
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsExpression.java
@@ -0,0 +1,52 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import com.google.dart.compiler.common.SourceInfo;
+
+/**
+ * An abstract base class for all JavaScript expressions.
+ */
+public abstract class JsExpression extends JsNode {
+
+ protected JsExpression() {
+ }
+
+ /**
+ * Determines whether the expression can cause side effects.
+ */
+ public abstract boolean hasSideEffects();
+
+ /**
+ * True if the target expression is definitely not null.
+ */
+ public abstract boolean isDefinitelyNotNull();
+
+ /**
+ * True if the target expression is definitely null.
+ */
+ public abstract boolean isDefinitelyNull();
+
+ /**
+ * Determines whether or not this expression is a leaf, such as a
+ * {@link JsNameRef}, {@link JsBooleanLiteral}, and so on. Leaf expressions
+ * never need to be parenthesized.
+ */
+ public boolean isLeaf() {
+ // Conservatively say that it isn't a leaf.
+ // Individual subclasses can speak for themselves if they are a leaf.
+ return false;
+ }
+
+ public JsExprStmt makeStmt() {
+ return new JsExprStmt(this);
+ }
+
+ @Override
+ public JsExpression setSourceRef(SourceInfo info) {
+ super.setSourceRef(info);
+ return this;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsFor.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsFor.java
new file mode 100644
index 00000000000..b980d4be1a2
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsFor.java
@@ -0,0 +1,105 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import com.google.dart.compiler.common.SourceInfo;
+
+/**
+ * A for statement. If specified at all, the initializer part is
+ * either a declaration of one or more variables, in which case
+ * {@link #getInitVars()} is used, or an expression, in which case
+ * {@link #getInitExpr()} is used. In the latter case, the comma operator is
+ * often used to create a compound expression.
+ *
+ *
+ * Note that any of the parts of the for loop header can be
+ * null, although the body will never be null.
+ */
+public class JsFor extends JsStatement {
+
+ private JsStatement body;
+ private JsExpression condition;
+ private JsExpression incrExpr;
+ private JsExpression initExpr;
+ private JsVars initVars;
+
+ public JsFor() {
+ super();
+ }
+
+ public JsStatement getBody() {
+ return body;
+ }
+
+ public JsExpression getCondition() {
+ return condition;
+ }
+
+ public JsExpression getIncrExpr() {
+ return incrExpr;
+ }
+
+ public JsExpression getInitExpr() {
+ return initExpr;
+ }
+
+ public JsVars getInitVars() {
+ return initVars;
+ }
+
+ public void setBody(JsStatement body) {
+ this.body = body;
+ }
+
+ public void setCondition(JsExpression condition) {
+ this.condition = condition;
+ }
+
+ public void setIncrExpr(JsExpression incrExpr) {
+ this.incrExpr = incrExpr;
+ }
+
+ public void setInitExpr(JsExpression initExpr) {
+ this.initExpr = initExpr;
+ }
+
+ public void setInitVars(JsVars initVars) {
+ this.initVars = initVars;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ assert (!(initExpr != null && initVars != null));
+
+ if (initExpr != null) {
+ initExpr = v.accept(initExpr);
+ } else if (initVars != null) {
+ initVars = v.accept(initVars);
+ }
+
+ if (condition != null) {
+ condition = v.accept(condition);
+ }
+
+ if (incrExpr != null) {
+ incrExpr = v.accept(incrExpr);
+ }
+ body = v.accept(body);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public JsFor setSourceRef(SourceInfo info) {
+ super.setSourceRef(info);
+ return this;
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.FOR;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsForIn.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsForIn.java
new file mode 100644
index 00000000000..646e349885c
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsForIn.java
@@ -0,0 +1,71 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript for..in statement.
+ */
+public class JsForIn extends JsStatement {
+
+ private JsStatement body;
+ private JsExpression iterExpr;
+ private JsExpression objExpr;
+
+ // Optional: the name of a new iterator variable to introduce
+ private final JsName iterVarName;
+
+ public JsForIn() {
+ this(null);
+ }
+
+ public JsForIn(JsName iterVarName) {
+ this.iterVarName = iterVarName;
+ }
+
+ public JsStatement getBody() {
+ return body;
+ }
+
+ public JsExpression getIterExpr() {
+ return iterExpr;
+ }
+
+ public JsName getIterVarName() {
+ return iterVarName;
+ }
+
+ public JsExpression getObjExpr() {
+ return objExpr;
+ }
+
+ public void setBody(JsStatement body) {
+ this.body = body;
+ }
+
+ public void setIterExpr(JsExpression iterExpr) {
+ this.iterExpr = iterExpr;
+ }
+
+ public void setObjExpr(JsExpression objExpr) {
+ this.objExpr = objExpr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ if (iterExpr != null) {
+ iterExpr = v.acceptLvalue(iterExpr);
+ }
+ objExpr = v.accept(objExpr);
+ body = v.accept(body);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.FOR_IN;
+ }
+}
\ No newline at end of file
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsFunction.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsFunction.java
new file mode 100644
index 00000000000..f2c7d258791
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsFunction.java
@@ -0,0 +1,238 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import com.google.dart.compiler.common.SourceInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a JavaScript function expression.
+ */
+public final class JsFunction extends JsLiteral implements HasName {
+
+ private static void trace(String title, String code) {
+ System.out.println("---------------------------");
+ System.out.println(title + ":");
+ System.out.println("---------------------------");
+ System.out.println(code);
+ }
+
+ protected JsBlock body;
+ protected final List params = new ArrayList();
+ protected final JsScope scope;
+ private boolean artificiallyRescued;
+ private boolean executeOnce;
+ private boolean fromDart;
+ private JsFunction impliedExecute;
+ private JsName name;
+ private boolean trace = false;
+ private boolean traceFirst = true;
+ private boolean hoisted = false;
+ private boolean constructor = false;
+
+ /**
+ * Creates an anonymous function.
+ */
+ public JsFunction(JsScope parent) {
+ this(parent, null, false);
+ }
+
+ /**
+ * Creates a function that is not derived from Dart source.
+ */
+ public JsFunction(JsScope parent, JsName name) {
+ this(parent, name, false);
+ }
+
+ /**
+ * Creates a named function, possibly derived from Dart source.
+ */
+ public JsFunction(JsScope parent, JsName name, boolean fromDart) {
+ assert (parent != null);
+ this.fromDart = fromDart;
+ setName(name);
+ String scopeName = (name == null) ? "" : name.getIdent();
+ scopeName = "function " + scopeName;
+ this.scope = new JsScope(parent, scopeName);
+ }
+
+ public JsBlock getBody() {
+ return body;
+ }
+
+ /**
+ * If true, this indicates that only the first invocation of the function will
+ * have any effects. Subsequent invocations may be considered to be no-op
+ * calls whose return value is ignored.
+ */
+ public boolean getExecuteOnce() {
+ return executeOnce;
+ }
+
+ public JsFunction getImpliedExecute() {
+ return impliedExecute;
+ }
+
+ @Override
+ public JsName getName() {
+ return name;
+ }
+
+ public void setParameters(List params) {
+ this.params.clear();
+ this.params.addAll(params);
+ }
+
+ public List getParameters() {
+ return params;
+ }
+
+ public JsScope getScope() {
+ return scope;
+ }
+
+ @Override
+ public boolean hasSideEffects() {
+ // If there's a name, the name is assigned to.
+ return name != null;
+ }
+
+ public boolean isArtificiallyRescued() {
+ return artificiallyRescued;
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return false;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ public boolean isFromDart() {
+ return fromDart;
+ }
+
+ public void setArtificiallyRescued(boolean rescued) {
+ this.artificiallyRescued = rescued;
+ }
+
+ public void setBody(JsBlock body) {
+ this.body = body;
+ }
+
+ public void setExecuteOnce(boolean executeOnce) {
+ this.executeOnce = executeOnce;
+ }
+
+ public void setFromDart(boolean fromDart) {
+ this.fromDart = fromDart;
+ }
+
+ public void setImpliedExecute(JsFunction impliedExecute) {
+ this.impliedExecute = impliedExecute;
+ }
+
+ public void setName(JsName name) {
+ this.name = name;
+ if (name != null) {
+ if (isFromDart()) {
+ name.setStaticRef(this);
+ }
+ }
+ }
+
+ public void setTrace() {
+ this.trace = true;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ String before = null;
+ if (trace && v instanceof JsModVisitor) {
+ before = this.toSource();
+ if (traceFirst) {
+ traceFirst = false;
+ trace("SCRIPT INITIAL", before);
+ }
+ }
+ if (v.visit(this, ctx)) {
+ v.acceptWithInsertRemove(params);
+ body = v.accept(body);
+ }
+ v.endVisit(this, ctx);
+ if (trace && v instanceof JsModVisitor) {
+ String after = this.toSource();
+ if (!after.equals(before)) {
+ String title = v.getClass().getSimpleName();
+ trace(title, after);
+ }
+ }
+ }
+
+ public void setHoisted() {
+ hoisted = true;
+ }
+
+ /**
+ * Whether the function has been hoisted
+ */
+ public boolean isHoisted() {
+ return hoisted;
+ }
+
+ /**
+ * Rebase the function to a new scope.
+ *
+ * @param newScopeParent The scope to add the function to.
+ */
+ public void rebaseScope(JsScope newScopeParent) {
+ this.scope.rebase(newScopeParent);
+ }
+
+ @Override
+ public JsFunction setSourceRef(SourceInfo info) {
+ super.setSourceRef(info);
+ return this;
+ }
+
+ public boolean isConstructor() {
+ return this.constructor;
+ }
+
+ public boolean setIsConstructor(boolean constructor) {
+ return this.constructor = constructor;
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.FUNCTION;
+ }
+
+ // Pavel Talanov
+ // dummy parameter to distinguish from other constructors
+ private JsFunction(Void dummy, JsScope functionScope) {
+ this.fromDart = false;
+ this.scope = functionScope;
+ }
+
+ public static JsFunction getAnonymousFunctionWithScope(JsScope scope) {
+ return new JsFunction(null, scope);
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsGlobalBlock.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsGlobalBlock.java
new file mode 100644
index 00000000000..9a14004e14c
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsGlobalBlock.java
@@ -0,0 +1,19 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represnts a JavaScript block in the global scope.
+ */
+public class JsGlobalBlock extends JsBlock {
+
+ public JsGlobalBlock() {
+ }
+
+ @Override
+ public boolean isGlobalBlock() {
+ return true;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsIf.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsIf.java
new file mode 100644
index 00000000000..478538235ab
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsIf.java
@@ -0,0 +1,65 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript if statement.
+ */
+public final class JsIf extends JsStatement {
+
+ private JsExpression ifExpr;
+ private JsStatement thenStmt;
+ private JsStatement elseStmt;
+
+ public JsIf() {
+ }
+
+ public JsIf(JsExpression ifExpr, JsStatement thenStmt, JsStatement elseStmt) {
+ this.ifExpr = ifExpr;
+ this.thenStmt = thenStmt;
+ this.elseStmt = elseStmt;
+ }
+
+ public JsStatement getElseStmt() {
+ return elseStmt;
+ }
+
+ public JsExpression getIfExpr() {
+ return ifExpr;
+ }
+
+ public JsStatement getThenStmt() {
+ return thenStmt;
+ }
+
+ public void setElseStmt(JsStatement elseStmt) {
+ this.elseStmt = elseStmt;
+ }
+
+ public void setIfExpr(JsExpression ifExpr) {
+ this.ifExpr = ifExpr;
+ }
+
+ public void setThenStmt(JsStatement thenStmt) {
+ this.thenStmt = thenStmt;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ ifExpr = v.accept(ifExpr);
+ thenStmt = v.accept(thenStmt);
+ if (elseStmt != null) {
+ elseStmt = v.accept(elseStmt);
+ }
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.IF;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsInvocation.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsInvocation.java
new file mode 100644
index 00000000000..777c33730d0
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsInvocation.java
@@ -0,0 +1,73 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a JavaScript invocation.
+ */
+public final class JsInvocation extends JsExpression implements HasArguments {
+
+ private final List args = new ArrayList();
+ private JsExpression qualifier;
+
+ public JsInvocation() {
+ }
+
+ @Override
+ public List getArguments() {
+ return args;
+ }
+
+ public JsExpression getQualifier() {
+ return qualifier;
+ }
+
+ @Override
+ public boolean hasSideEffects() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return false;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ public void setQualifier(JsExpression qualifier) {
+ this.qualifier = qualifier;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ qualifier = v.accept(qualifier);
+ v.acceptList(args);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.INVOKE;
+ }
+
+ // Pavel Talanov
+ public void setArguments(List arguments) {
+ assert this.args.isEmpty() : "Arguments already set.";
+ this.args.addAll(arguments);
+ }
+
+ public void setArguments(JsExpression... arguments) {
+ setArguments(Arrays.asList(arguments));
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsLabel.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsLabel.java
new file mode 100644
index 00000000000..7d026c3a6de
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsLabel.java
@@ -0,0 +1,45 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript label statement.
+ */
+public class JsLabel extends JsStatement implements HasName {
+
+ private final JsName label;
+
+ private JsStatement stmt;
+
+ public JsLabel(JsName label) {
+ this.label = label;
+ }
+
+ @Override
+ public JsName getName() {
+ return label;
+ }
+
+ public JsStatement getStmt() {
+ return stmt;
+ }
+
+ public void setStmt(JsStatement stmt) {
+ this.stmt = stmt;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ stmt = v.accept(stmt);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.LABEL;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsLiteral.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsLiteral.java
new file mode 100644
index 00000000000..c87ae0c8af1
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsLiteral.java
@@ -0,0 +1,14 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript string literal expression.
+ */
+public abstract class JsLiteral extends JsExpression implements CanBooleanEval {
+
+ protected JsLiteral() {
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsModVisitor.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsModVisitor.java
new file mode 100644
index 00000000000..ef6e75ce994
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsModVisitor.java
@@ -0,0 +1,189 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import com.google.dart.compiler.util.Hack;
+
+import java.util.List;
+
+/**
+ * A visitor for iterating through and modifying an AST.
+ */
+public class JsModVisitor extends JsVisitor {
+
+ private class ListContext implements JsContext {
+
+ private List collection;
+ private int index;
+ private boolean removed;
+ private boolean replaced;
+
+ @Override
+ public boolean canInsert() {
+ return true;
+ }
+
+ @Override
+ public boolean canRemove() {
+ return true;
+ }
+
+ @Override
+ public void insertAfter(JsVisitable node) {
+ checkRemoved();
+ collection.add(index + 1, Hack.cast(node));
+ didChange = true;
+ }
+
+ @Override
+ public void insertBefore(JsVisitable node) {
+ checkRemoved();
+ collection.add(index++, Hack.cast(node));
+ didChange = true;
+ }
+
+ @Override
+ public boolean isLvalue() {
+ return false;
+ }
+
+ @Override
+ public void removeMe() {
+ checkState();
+ collection.remove(index--);
+ didChange = removed = true;
+ }
+
+ @Override
+ public void replaceMe(JsVisitable node) {
+ checkState();
+ checkReplacement(collection.get(index), node);
+ collection.set(index, Hack.cast(node));
+ didChange = replaced = true;
+ }
+
+ protected void traverse(List collection) {
+ this.collection = collection;
+ for (index = 0; index < collection.size(); ++index) {
+ removed = replaced = false;
+ doTraverse(collection.get(index), this);
+ }
+ }
+
+ private void checkRemoved() {
+ if (removed) {
+ throw new RuntimeException("Node was already removed");
+ }
+ }
+
+ private void checkState() {
+ checkRemoved();
+ if (replaced) {
+ throw new RuntimeException("Node was already replaced");
+ }
+ }
+ }
+
+ private class LvalueContext extends NodeContext {
+ @Override
+ public boolean isLvalue() {
+ return true;
+ }
+ }
+
+ private class NodeContext implements JsContext {
+ private T node;
+ private boolean replaced;
+
+ @Override
+ public boolean canInsert() {
+ return false;
+ }
+
+ @Override
+ public boolean canRemove() {
+ return false;
+ }
+
+ @Override
+ public void insertAfter(JsVisitable node) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void insertBefore(JsVisitable node) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isLvalue() {
+ return false;
+ }
+
+ @Override
+ public void removeMe() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void replaceMe(JsVisitable node) {
+ if (replaced) {
+ throw new RuntimeException("Node was already replaced");
+ }
+ checkReplacement(this.node, node);
+ this.node = Hack.cast(node);
+ didChange = replaced = true;
+ }
+
+ protected T traverse(T node) {
+ this.node = node;
+ replaced = false;
+ doTraverse(node, this);
+ return this.node;
+ }
+ }
+
+ protected static void checkReplacement(T origNode, T newNode) {
+ if (newNode == null) {
+ throw new RuntimeException("Cannot replace with null");
+ }
+ if (newNode == origNode) {
+ throw new RuntimeException("The replacement is the same as the original");
+ }
+ }
+
+ protected boolean didChange = false;
+
+ @Override
+ public boolean didChange() {
+ return didChange;
+ }
+
+ @Override
+ protected T doAccept(T node) {
+ return new NodeContext().traverse(node);
+ }
+
+ @Override
+ protected void doAcceptList(List collection) {
+ NodeContext ctx = new NodeContext();
+ for (int i = 0, c = collection.size(); i < c; ++i) {
+ ctx.traverse(collection.get(i));
+ if (ctx.replaced) {
+ collection.set(i, ctx.node);
+ }
+ }
+ }
+
+ @Override
+ protected JsExpression doAcceptLvalue(JsExpression expr) {
+ return new LvalueContext().traverse(expr);
+ }
+
+ @Override
+ protected void doAcceptWithInsertRemove(List collection) {
+ new ListContext().traverse(collection);
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsName.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsName.java
new file mode 100644
index 00000000000..ccc1322338a
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsName.java
@@ -0,0 +1,102 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.io.Serializable;
+
+/**
+ * An abstract base class for named JavaScript objects.
+ */
+public class JsName implements Serializable {
+ private final JsScope enclosing;
+ private final String ident;
+ private boolean isObfuscatable;
+ private String shortIdent;
+ private String originalName;
+
+ /**
+ * A back-reference to the JsNode that the JsName refers to.
+ */
+ private JsNode staticRef;
+
+ /**
+ * @param ident the unmangled ident to use for this name
+ */
+JsName(JsScope enclosing, String ident, String shortIdent, String originalName) {
+ this.enclosing = enclosing;
+ this.ident = ident;
+ this.shortIdent = shortIdent;
+ if (originalName != null) {
+ this.originalName = originalName;
+ }
+ this.isObfuscatable = true;
+ }
+
+ public JsScope getEnclosing() {
+ return enclosing;
+ }
+
+ public String getIdent() {
+ return ident;
+ }
+
+ public String getShortIdent() {
+ return shortIdent;
+ }
+
+ public String getOriginalName() {
+ return originalName;
+ }
+
+ public JsNode getStaticRef() {
+ return staticRef;
+ }
+
+ public boolean isObfuscatable() {
+ return isObfuscatable;
+ }
+
+ public JsNameRef makeRef() {
+ return new JsNameRef(this);
+ }
+
+ public void setObfuscatable(boolean isObfuscatable) {
+ this.isObfuscatable = isObfuscatable;
+ }
+
+ public void setShortIdent(String shortIdent) {
+ this.shortIdent = shortIdent;
+ }
+
+ /**
+ * Should never be called except on immutable stuff.
+ */
+ public void setStaticRef(JsNode node) {
+ this.staticRef = node;
+ }
+
+ @Override
+ public String toString() {
+ return ident;
+ }
+
+ @Override
+ public int hashCode() {
+ return ident.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof JsName)) {
+ return false;
+ }
+ JsName other = (JsName) obj;
+ return ident.equals(other.ident) && enclosing == other.enclosing;
+ }
+
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNameRef.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNameRef.java
new file mode 100644
index 00000000000..dc26c8fa61a
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNameRef.java
@@ -0,0 +1,113 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript expression that references a name.
+ */
+public final class JsNameRef extends JsExpression implements CanBooleanEval, HasName {
+
+ private String ident;
+ private JsName name;
+ private JsExpression qualifier;
+
+ public JsNameRef(JsName name) {
+ this.name = name;
+ }
+
+ public JsNameRef(String ident) {
+ this.ident = ident;
+ }
+
+ public String getIdent() {
+ return (name == null) ? ident : name.getIdent();
+ }
+
+ @Override
+ public JsName getName() {
+ return name;
+ }
+
+ public JsExpression getQualifier() {
+ return qualifier;
+ }
+
+ public String getShortIdent() {
+ return (name == null) ? ident : name.getShortIdent();
+ }
+
+ @Override
+ public boolean hasSideEffects() {
+ if (qualifier == null) {
+ return false;
+ }
+ if (!qualifier.isDefinitelyNotNull()) {
+ // Could trigger NPE.
+ return true;
+ }
+ return qualifier.hasSideEffects();
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return isDefinitelyNull();
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return false;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ // TODO: look for single-assignment of stuff from Java?
+ return false;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ if (name != null) {
+ return (name.getEnclosing().getProgram().getUndefinedLiteral().getName() == name);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isLeaf() {
+ if (qualifier == null) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean isResolved() {
+ return name != null;
+ }
+
+ public void resolve(JsName name) {
+ this.name = name;
+ this.ident = null;
+ }
+
+ public void setQualifier(JsExpression qualifier) {
+ this.qualifier = qualifier;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ if (qualifier != null) {
+ qualifier = v.accept(qualifier);
+ }
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.NAME_REF;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNew.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNew.java
new file mode 100644
index 00000000000..68fc366684d
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNew.java
@@ -0,0 +1,66 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents the JavaScript new expression.
+ */
+public final class JsNew extends JsExpression implements HasArguments {
+
+ private final List args = new ArrayList();
+ private JsExpression ctorExpr;
+
+ public JsNew(JsExpression ctorExpr) {
+ this.ctorExpr = ctorExpr;
+ }
+
+ @Override
+ public List getArguments() {
+ return args;
+ }
+
+ public void setArguments(List arguments) {
+ args.clear();
+ args.addAll(arguments);
+ }
+
+ public JsExpression getConstructorExpression() {
+ return ctorExpr;
+ }
+
+ @Override
+ public boolean hasSideEffects() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ // Sadly, in JS it can be!
+ // TODO: analysis could probably determine most instances cannot be null.
+ return false;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ ctorExpr = v.accept(ctorExpr);
+ v.acceptList(args);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.NEW;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNode.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNode.java
new file mode 100644
index 00000000000..0d4b7aaa714
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNode.java
@@ -0,0 +1,50 @@
+// 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.
+package com.google.dart.compiler.backend.js.ast;
+
+import com.google.dart.compiler.backend.js.JsSourceGenerationVisitor;
+import com.google.dart.compiler.backend.js.JsToStringGenerationVisitor;
+import com.google.dart.compiler.common.AbstractNode;
+import com.google.dart.compiler.common.SourceInfo;
+import com.google.dart.compiler.util.DefaultTextOutput;
+
+/**
+ * Base class for all JS AST elements.
+ */
+public abstract class JsNode extends AbstractNode implements JsVisitable {
+
+ protected JsNode() {
+ }
+
+ // Causes source generation to delegate to the one visitor
+ public final String toSource() {
+ DefaultTextOutput out = new DefaultTextOutput(false);
+ JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
+ v.accept(this);
+ return out.toString();
+ }
+
+ // Causes source generation to delegate to the one visitor
+ @Override
+ public final String toString() {
+ DefaultTextOutput out = new DefaultTextOutput(false);
+ JsToStringGenerationVisitor v = new JsToStringGenerationVisitor(out);
+ v.accept(this);
+ return out.toString();
+ }
+
+ @Override
+ public SourceInfo getSourceInfo() {
+ return this;
+ }
+
+ public JsNode setSourceRef(SourceInfo info) {
+ if (info != null) {
+ this.setSourceInfo(info);
+ }
+ return this;
+ }
+
+ public abstract NodeKind getKind();
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNullLiteral.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNullLiteral.java
new file mode 100644
index 00000000000..506b2e3f330
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNullLiteral.java
@@ -0,0 +1,46 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript null literal.
+ */
+public final class JsNullLiteral extends JsValueLiteral {
+
+ // Should only be instantiated in JsProgram
+ JsNullLiteral() {
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return true;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return false;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return false;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return true;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ v.visit(this, ctx);
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.NULL;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNumberLiteral.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNumberLiteral.java
new file mode 100644
index 00000000000..726c6b0ed27
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsNumberLiteral.java
@@ -0,0 +1,53 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Represents a JavaScript literal decimal expression.
+ */
+public final class JsNumberLiteral extends JsValueLiteral {
+
+ private final double value;
+
+ // Should be interned by JsProgram
+ JsNumberLiteral(double value) {
+ this.value = value;
+ }
+
+ public double getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return value == 0.0;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return value != 0.0;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ v.visit(this, ctx);
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.NUMBER;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsObjectLiteral.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsObjectLiteral.java
new file mode 100644
index 00000000000..749ed473c21
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsObjectLiteral.java
@@ -0,0 +1,72 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A JavaScript object literal.
+ */
+public final class JsObjectLiteral extends JsLiteral {
+
+ private final List props;
+
+ public JsObjectLiteral() {
+ props = new ArrayList();
+ }
+
+ public JsObjectLiteral(List props) {
+ assert props != null;
+ this.props = props;
+ }
+
+ public List getPropertyInitializers() {
+ return props;
+ }
+
+ @Override
+ public boolean hasSideEffects() {
+ for (JsPropertyInitializer prop : props) {
+ if (prop.hasSideEffects()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return false;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ v.acceptWithInsertRemove(props);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.OBJECT;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsOperator.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsOperator.java
new file mode 100644
index 00000000000..e4440cbe9ab
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsOperator.java
@@ -0,0 +1,32 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript operator.
+ */
+public interface JsOperator {
+
+ int INFIX = 0x02;
+ int LEFT = 0x01;
+ int POSTFIX = 0x04;
+ int PREFIX = 0x08;
+
+ int getPrecedence();
+
+ String getSymbol();
+
+ boolean isKeyword();
+
+ boolean isLeftAssociative();
+
+ boolean isPrecedenceLessThan(JsOperator other);
+
+ boolean isValidInfix();
+
+ boolean isValidPostfix();
+
+ boolean isValidPrefix();
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsParameter.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsParameter.java
new file mode 100644
index 00000000000..17581ce4084
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsParameter.java
@@ -0,0 +1,34 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript parameter.
+ */
+public final class JsParameter extends JsNode implements HasName {
+
+ private final JsName name;
+
+ public JsParameter(JsName name) {
+ this.name = name;
+ name.setStaticRef(this);
+ }
+
+ @Override
+ public JsName getName() {
+ return name;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ v.visit(this, ctx);
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.PARAMETER;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsPostfixOperation.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsPostfixOperation.java
new file mode 100644
index 00000000000..a33536f6c0d
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsPostfixOperation.java
@@ -0,0 +1,42 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript postfix operation.
+ */
+public final class JsPostfixOperation extends JsUnaryOperation {
+
+ public JsPostfixOperation(JsUnaryOperator op) {
+ this(op, null);
+ }
+
+ public JsPostfixOperation(JsUnaryOperator op, JsExpression arg) {
+ super(op, arg);
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ super.traverse(v, ctx);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.POSTFIX_OP;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsPrefixOperation.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsPrefixOperation.java
new file mode 100644
index 00000000000..8ce12c7bacd
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsPrefixOperation.java
@@ -0,0 +1,69 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript prefix operation.
+ */
+public final class JsPrefixOperation extends JsUnaryOperation implements CanBooleanEval {
+
+ public JsPrefixOperation(JsUnaryOperator op) {
+ this(op, null);
+ }
+
+ public JsPrefixOperation(JsUnaryOperator op, JsExpression arg) {
+ super(op, arg);
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ if (getOperator() == JsUnaryOperator.VOID) {
+ return true;
+ }
+ if (getOperator() == JsUnaryOperator.NOT && getArg() instanceof CanBooleanEval) {
+ CanBooleanEval eval = (CanBooleanEval) getArg();
+ return eval.isBooleanTrue();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ if (getOperator() == JsUnaryOperator.NOT && getArg() instanceof CanBooleanEval) {
+ CanBooleanEval eval = (CanBooleanEval) getArg();
+ return eval.isBooleanFalse();
+ }
+ if (getOperator() == JsUnaryOperator.TYPEOF) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ if (getOperator() == JsUnaryOperator.TYPEOF) {
+ return true;
+ }
+ return getOperator() != JsUnaryOperator.VOID;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return getOperator() == JsUnaryOperator.VOID;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ super.traverse(v, ctx);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.PREFIX_OP;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java
new file mode 100644
index 00000000000..c8e4310ba9b
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsProgram.java
@@ -0,0 +1,174 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A JavaScript program.
+ */
+public final class JsProgram extends JsNode {
+
+ private final JsStatement debuggerStmt;
+ private final JsEmpty emptyStmt;
+ private final JsBooleanLiteral falseLiteral;
+ private JsProgramFragment[] fragments;
+ private final Map indexedFunctions = new HashMap();
+ private final JsNullLiteral nullLiteral;
+ private final Map numberLiteralMap =
+ new HashMap();
+ private final JsScope objectScope;
+ private final JsRootScope rootScope;
+ private final Map stringLiteralMap =
+ new HashMap();
+ private final JsScope topScope;
+ private final JsBooleanLiteral trueLiteral;
+
+ /**
+ * Constructs a JavaScript program object.
+ */
+ public JsProgram(String unitId) {
+ rootScope = new JsRootScope(this);
+ topScope = new JsScope(rootScope, "Global", unitId);
+ objectScope = new JsScope(rootScope, "Object");
+ setFragmentCount(1);
+
+ debuggerStmt = new JsDebugger();
+ emptyStmt = new JsEmpty();
+ falseLiteral = new JsBooleanLiteral(false);
+ nullLiteral = new JsNullLiteral();
+ trueLiteral = new JsBooleanLiteral(true);
+ }
+
+ public JsBooleanLiteral getBooleanLiteral(boolean truth) {
+ if (truth) {
+ return getTrueLiteral();
+ }
+ return getFalseLiteral();
+ }
+
+ /**
+ * Gets the {@link JsStatement} to use whenever parsed source include a
+ * debugger statement.
+ */
+ public JsStatement getDebuggerStmt() {
+ return debuggerStmt;
+ }
+
+ public JsEmpty getEmptyStmt() {
+ return emptyStmt;
+ }
+
+ public JsBooleanLiteral getFalseLiteral() {
+ return falseLiteral;
+ }
+
+ public JsBlock getFragmentBlock(int fragment) {
+ if (fragment < 0 || fragment >= fragments.length) {
+ throw new IllegalArgumentException("Invalid fragment: " + fragment);
+ }
+ return fragments[fragment].getGlobalBlock();
+ }
+
+ public int getFragmentCount() {
+ return this.fragments.length;
+ }
+
+ /**
+ * Gets the one and only global block.
+ */
+ public JsBlock getGlobalBlock() {
+ return getFragmentBlock(0);
+ }
+
+ public JsFunction getIndexedFunction(String name) {
+ return indexedFunctions.get(name);
+ }
+
+ public JsNullLiteral getNullLiteral() {
+ return nullLiteral;
+ }
+
+ public JsNumberLiteral getNumberLiteral(double value) {
+ JsNumberLiteral lit = numberLiteralMap.get(value);
+ if (lit == null) {
+ lit = new JsNumberLiteral(value);
+ numberLiteralMap.put(value, lit);
+ }
+
+ return lit;
+ }
+
+ public JsScope getObjectScope() {
+ return objectScope;
+ }
+
+ /**
+ * Gets the quasi-mythical root scope. This is not the same as the top scope;
+ * all unresolvable identifiers wind up here, because they are considered
+ * external to the program.
+ */
+ public JsRootScope getRootScope() {
+ return rootScope;
+ }
+
+ /**
+ * Gets the top level scope. This is the scope of all the statements in the
+ * main program.
+ */
+ public JsScope getScope() {
+ return topScope;
+ }
+
+ /**
+ * Creates or retrieves a JsStringLiteral from an interned object pool.
+ */
+ public JsStringLiteral getStringLiteral(String value) {
+ JsStringLiteral lit = stringLiteralMap.get(value);
+ if (lit == null) {
+ lit = new JsStringLiteral(value);
+ stringLiteralMap.put(value, lit);
+ }
+ return lit;
+ }
+
+ public JsBooleanLiteral getTrueLiteral() {
+ return trueLiteral;
+ }
+
+ public JsNameRef getUndefinedLiteral() {
+ return new JsNameRef("$Dart$Null");
+ }
+
+ public void setFragmentCount(int fragments) {
+ this.fragments = new JsProgramFragment[fragments];
+ for (int i = 0; i < fragments; i++) {
+ this.fragments[i] = new JsProgramFragment();
+ }
+ }
+
+ public void setIndexedFunctions(Map indexedFunctions) {
+ this.indexedFunctions.clear();
+ this.indexedFunctions.putAll(indexedFunctions);
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ for (JsProgramFragment fragment : fragments) {
+ v.accept(fragment);
+ }
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.PROGRAM;
+ }
+
+
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsProgramFragment.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsProgramFragment.java
new file mode 100644
index 00000000000..3c6859e2150
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsProgramFragment.java
@@ -0,0 +1,34 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * One independently loadable fragment of a {@link JsProgram}.
+ */
+public class JsProgramFragment extends JsNode {
+
+ private final JsGlobalBlock globalBlock;
+
+ public JsProgramFragment() {
+ this.globalBlock = new JsGlobalBlock();
+ }
+
+ public JsBlock getGlobalBlock() {
+ return globalBlock;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ v.accept(globalBlock);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.PROGRAM_FRAGMENT;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsPropertyInitializer.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsPropertyInitializer.java
new file mode 100644
index 00000000000..d68d8d01af4
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsPropertyInitializer.java
@@ -0,0 +1,56 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Used in object literals to specify property values by name.
+ */
+public class JsPropertyInitializer extends JsNode {
+
+ private JsExpression labelExpr;
+ private JsExpression valueExpr;
+
+ public JsPropertyInitializer() {
+ }
+
+ public JsPropertyInitializer(JsExpression labelExpr, JsExpression valueExpr) {
+ this.labelExpr = labelExpr;
+ this.valueExpr = valueExpr;
+ }
+
+ public JsExpression getLabelExpr() {
+ return labelExpr;
+ }
+
+ public JsExpression getValueExpr() {
+ return valueExpr;
+ }
+
+ public boolean hasSideEffects() {
+ return labelExpr.hasSideEffects() || valueExpr.hasSideEffects();
+ }
+
+ public void setLabelExpr(JsExpression labelExpr) {
+ this.labelExpr = labelExpr;
+ }
+
+ public void setValueExpr(JsExpression valueExpr) {
+ this.valueExpr = valueExpr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ labelExpr = v.accept(labelExpr);
+ valueExpr = v.accept(valueExpr);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.PROPERTY_INIT;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsRegExp.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsRegExp.java
new file mode 100644
index 00000000000..158c7a5e092
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsRegExp.java
@@ -0,0 +1,64 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript regular expression.
+ */
+public final class JsRegExp extends JsValueLiteral {
+
+ private String flags;
+ private String pattern;
+
+ public JsRegExp() {
+ }
+
+ public String getFlags() {
+ return flags;
+ }
+
+ public String getPattern() {
+ return pattern;
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return false;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ public void setFlags(String suffix) {
+ this.flags = suffix;
+ }
+
+ public void setPattern(String re) {
+ this.pattern = re;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ v.visit(this, ctx);
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.REGEXP;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsReturn.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsReturn.java
new file mode 100644
index 00000000000..16d90e4b353
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsReturn.java
@@ -0,0 +1,48 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript return statement.
+ */
+public final class JsReturn extends JsStatement {
+
+ private JsExpression expr;
+
+ public JsReturn() {
+ }
+
+ public JsReturn(JsExpression expr) {
+ this.expr = expr;
+ }
+
+ public JsExpression getExpr() {
+ return expr;
+ }
+
+ public void setExpr(JsExpression expr) {
+ this.expr = expr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ if (expr != null) {
+ expr = v.accept(expr);
+ }
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public boolean unconditionalControlBreak() {
+ return true;
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.RETURN;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsRootScope.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsRootScope.java
new file mode 100644
index 00000000000..33b71f26222
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsRootScope.java
@@ -0,0 +1,50 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import com.google.dart.compiler.backend.js.JsReservedIdentifiers;
+
+/**
+ * The root scope is the parent of every scope. All identifiers in this scope
+ * are not obfuscatable. This scope is prefilled with reserved global
+ * JavaScript symbols.
+ */
+public final class JsRootScope extends JsScope {
+
+ private final JsProgram program;
+
+ public JsRootScope(JsProgram program) {
+ super("Root");
+ this.program = program;
+ }
+
+ @Override
+ public JsProgram getProgram() {
+ return program;
+ }
+
+ @Override
+ protected JsName doCreateName(String ident, String shortIdent, String originalName) {
+ JsName name = super.doCreateName(ident, shortIdent, originalName);
+ name.setObfuscatable(false);
+ return name;
+ }
+
+ @Override
+ public JsName findExistingNameNoRecurse(String ident) {
+ JsName name = super.findExistingNameNoRecurse(ident);
+ if (name == null) {
+ if (JsReservedIdentifiers.getReservedGlobalSymbols().contains(ident)) {
+ /*
+ * Lazily add JsNames for reserved identifiers. Since a JsName for a reserved global symbol
+ * must report a legitimate enclosing scope, we can't simply have a shared set of symbol
+ * names.
+ */
+ name = doCreateName(ident, ident, ident);
+ }
+ }
+ return name;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsScope.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsScope.java
new file mode 100644
index 00000000000..0a4053960b8
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsScope.java
@@ -0,0 +1,308 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import com.google.dart.compiler.util.Lists;
+import com.google.dart.compiler.util.Maps;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A scope is a factory for creating and allocating
+ * {@link JsName}s. A JavaScript AST is
+ * built in terms of abstract name objects without worrying about obfuscation,
+ * keyword/identifier blacklisting, and so on.
+ *
+ *
+ *
+ * Scopes are associated with
+ * {@link JsFunction}s, but the two are
+ * not equivalent. Functions have scopes, but a scope does not
+ * necessarily have an associated Function. Examples of this include the
+ * {@link JsRootScope} and synthetic
+ * scopes that might be created by a client.
+ *
+ *
+ *
+ * Scopes can have parents to provide constraints when allocating actual
+ * identifiers for names. Specifically, names in child scopes are chosen such
+ * that they do not conflict with names in their parent scopes. The ultimate
+ * parent is usually the global scope (see
+ * {@link JsProgram#getRootScope()}),
+ * but parentless scopes are useful for managing names that are always accessed
+ * with a qualifier and could therefore never be confused with the global scope
+ * hierarchy.
+ */
+public class JsScope implements Serializable {
+
+ private List children = Collections.emptyList();
+ private final String description;
+ private Map names = Collections.emptyMap();
+ private JsScope parent;
+ protected int tempIndex = 0;
+ private final String scopeId;
+
+ /*
+ * Create a scope with parent.
+ */
+ public JsScope(JsScope parent, String description) {
+ this(parent, description, null);
+ }
+
+ /**
+ * Create a scope with parent.
+ */
+ public JsScope(JsScope parent, String description, String scopeId) {
+ assert (parent != null);
+ this.scopeId = scopeId;
+ this.description = description;
+ this.parent = parent;
+ parent.children = Lists.add(parent.children, this);
+ }
+
+ /**
+ * Rebase the function to a new scope.
+ *
+ * @param newParent The scope to add the function to.
+ */
+ public void rebase(JsScope newParent) {
+ detachFromParent();
+ parent = newParent;
+ parent.children = Lists.add(parent.children, this);
+ }
+
+ /**
+ * Rebase the function's children to a new scope.
+ *
+ * @param newParent
+ */
+ public void rebaseChildScopes(JsScope newParent) {
+ if (newParent == this) {
+ return;
+ }
+ parent.children = Lists.addAll(parent.children, children);
+ for (JsScope child : children) {
+ child.parent = newParent;
+ }
+ children = Collections.emptyList();
+ }
+
+ /**
+ * Subclasses can detach and become parentless.
+ */
+ protected void detachFromParent() {
+ JsScope oldParent = parent;
+
+ oldParent.children = Lists.remove(
+ parent.children, oldParent.children.indexOf(this));
+
+ parent = null;
+ }
+
+ /**
+ * Subclasses can be parentless.
+ */
+ protected JsScope(String description) {
+ this.description = description;
+ this.parent = null;
+ this.scopeId = null;
+ }
+
+ /**
+ * Gets a name object associated with the specified ident in this scope,
+ * creating it if necessary.
+ * If the JsName does not exist yet, a new JsName is created. The ident,
+ * short name, and original name of the newly created JsName are equal to
+ * the given ident.
+ *
+ * @param ident An identifier that is unique within this scope.
+ */
+ public JsName declareName(String ident) {
+ JsName name = findExistingNameNoRecurse(ident);
+ if (name != null) {
+ return name;
+ }
+ return doCreateName(ident, ident, ident);
+ }
+
+ /**
+ * Creates a new variable with an unique ident in this scope.
+ * The generated JsName is guaranteed to have an identifier (but not short
+ * name) that does not clash with any existing variables in the scope.
+ * Future declarations of variables might however clash with the temporary
+ * (unless they use this function).
+ */
+ public JsName declareFreshName(String shortName) {
+ String ident = shortName;
+ int counter = 0;
+ while (findExistingNameNoRecurse(ident) != null) {
+ ident = shortName + "_" + counter++;
+ }
+ return doCreateName(ident, shortName, shortName);
+ }
+
+ String getNextTempName() {
+ // TODO(ngeoffray): Decide on a convention for temporary variables
+ // introduced by the compiler.
+ return "tmp$" + (scopeId != null ? scopeId + "$" : "") + tempIndex++;
+ }
+
+ /**
+ * Creates a temporary variable with an unique name in this scope.
+ * The generated temporary is guaranteed to have an identifier (but not short
+ * name) that does not clash with any existing variables in the scope.
+ * Future declarations of variables might however clash with the temporary.
+ */
+ public JsName declareTemporary() {
+ return declareFreshName(getNextTempName());
+ }
+
+ /**
+ * Gets a name object associated with the specified ident in this scope,
+ * creating it if necessary.
+ * If the JsName does not exist yet, a new JsName is created with the given
+ * ident, short name and original name.
+ *
+ * @param ident An identifier that is unique within this scope.
+ * @param shortIdent A "pretty" name that does not have to be unique.
+ * @throws IllegalArgumentException if ident already exists in this scope but
+ * the requested short name does not match the existing short name.
+ */
+ public JsName declareName(String ident, String shortIdent) {
+ return declareName(ident, shortIdent, ident);
+ }
+
+ /**
+ * Gets a name object associated with the specified ident in this scope,
+ * creating it if necessary.
+ * If the JsName does not exist yet, a new JsName is created. The original
+ * name stored in the JsName is equal to the (unmangled) specified originalName.
+ *
+ * @param ident An identifier that is unique within this scope.
+ * @param shortIdent A "pretty" name that does not have to be unique.
+ * @param originalName The original name in the source.
+ * @throws IllegalArgumentException if ident already exists in this scope but
+ * the requested short name does not match the existing short name,
+ * or the original name does not match the existing original name.
+ */
+ public JsName declareName(String ident, String shortIdent, String originalName) {
+ JsName name = findExistingNameNoRecurse(ident);
+ if (name != null) {
+ if (!name.getShortIdent().equals(shortIdent)
+ || !nullableEquals(name.getOriginalName(), originalName)) {
+ throw new IllegalArgumentException("Requested short name " + shortIdent
+ + " conflicts with preexisting short name " + name.getShortIdent() + " for identifier "
+ + ident);
+ }
+ return name;
+ }
+ return doCreateName(ident, shortIdent, originalName);
+ }
+
+ boolean nullableEquals(String s1, String s2) {
+ return (s1 == null) ? (s2 == null) : s1.equals(s2);
+ }
+
+ /**
+ * Attempts to find the name object for the specified ident, searching in this
+ * scope, and if not found, in the parent scopes.
+ *
+ * @return null if the identifier has no associated name
+ */
+ public final JsName findExistingName(String ident) {
+ JsName name = findExistingNameNoRecurse(ident);
+ if (name == null && parent != null) {
+ return parent.findExistingName(ident);
+ }
+ return name;
+ }
+
+ /**
+ * Attempts to find an unobfuscatable name object for the specified ident,
+ * searching in this scope, and if not found, in the parent scopes.
+ *
+ * @return null if the identifier has no associated name
+ */
+ public final JsName findExistingUnobfuscatableName(String ident) {
+ JsName name = findExistingNameNoRecurse(ident);
+ if (name != null && name.isObfuscatable()) {
+ name = null;
+ }
+ if (name == null && parent != null) {
+ return parent.findExistingUnobfuscatableName(ident);
+ }
+ return name;
+ }
+
+ /**
+ * Returns an iterator for all the names defined by this scope.
+ */
+ public Iterator getAllNames() {
+ return names.values().iterator();
+ }
+
+ /**
+ * Returns a list of this scope's child scopes.
+ */
+ public final List getChildren() {
+ return children;
+ }
+
+ /**
+ * Returns the parent scope of this scope, or null if this is the
+ * root scope.
+ */
+ public final JsScope getParent() {
+ return parent;
+ }
+
+ /**
+ * Returns the associated program.
+ */
+ public JsProgram getProgram() {
+ assert (parent != null) : "Subclasses must override getProgram() if they do not set a parent";
+ return parent.getProgram();
+ }
+
+ @Override
+ public final String toString() {
+ if (parent != null) {
+ return description + "->" + parent;
+ } else {
+ return description;
+ }
+ }
+
+ /**
+ * Creates a new name in this scope.
+ */
+ protected JsName doCreateName(String ident, String shortIdent, String originalName) {
+ JsName name = new JsName(this, ident, shortIdent, originalName);
+ names = Maps.putOrdered(names, ident, name);
+ return name;
+ }
+
+ /**
+ * Attempts to find the name object for the specified ident, searching in this
+ * scope only.
+ *
+ * @return null if the identifier has no associated name
+ */
+ public JsName findExistingNameNoRecurse(String ident) {
+ return names.get(ident);
+ }
+
+ /**
+ *
+ * Checks whether this scope owns this name.
+ */
+ public boolean ownsName(JsName name) {
+ return names.containsValue(name);
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsStatement.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsStatement.java
new file mode 100644
index 00000000000..82d37ca1185
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsStatement.java
@@ -0,0 +1,22 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Abstract base class for JavaScript statement objects.
+ */
+public abstract class JsStatement extends JsNode {
+
+ protected JsStatement() {
+ }
+
+ /**
+ * Returns true if this statement definitely causes an abrupt change in flow
+ * control.
+ */
+ public boolean unconditionalControlBreak() {
+ return false;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsStringLiteral.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsStringLiteral.java
new file mode 100644
index 00000000000..77e60c9b89a
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsStringLiteral.java
@@ -0,0 +1,53 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript string literal expression.
+ */
+public final class JsStringLiteral extends JsValueLiteral {
+
+ private final String value;
+
+ // These only get created by JsProgram so that they can be interned.
+ JsStringLiteral(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return value.length() == 0;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return value.length() != 0;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ v.visit(this, ctx);
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.STRING;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsSwitch.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsSwitch.java
new file mode 100644
index 00000000000..5a49624af42
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsSwitch.java
@@ -0,0 +1,47 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A JavaScript switch statement.
+ */
+public class JsSwitch extends JsStatement {
+
+ private final List cases = new ArrayList();
+ private JsExpression expr;
+
+ public JsSwitch() {
+ super();
+ }
+
+ public List getCases() {
+ return cases;
+ }
+
+ public JsExpression getExpr() {
+ return expr;
+ }
+
+ public void setExpr(JsExpression expr) {
+ this.expr = expr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ expr = v.accept(expr);
+ v.acceptWithInsertRemove(cases);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.SWITCH;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsSwitchMember.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsSwitchMember.java
new file mode 100644
index 00000000000..0a748f60078
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsSwitchMember.java
@@ -0,0 +1,24 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A member/case in a JavaScript switch object.
+ */
+public abstract class JsSwitchMember extends JsNode {
+
+ protected final List stmts = new ArrayList();
+
+ protected JsSwitchMember() {
+ super();
+ }
+
+ public List getStmts() {
+ return stmts;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsThisRef.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsThisRef.java
new file mode 100644
index 00000000000..9adb6459488
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsThisRef.java
@@ -0,0 +1,51 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript this reference.
+ */
+public final class JsThisRef extends JsValueLiteral {
+
+ public JsThisRef() {
+ super();
+ }
+
+ @Override
+ public boolean isBooleanFalse() {
+ return false;
+ }
+
+ @Override
+ public boolean isBooleanTrue() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNotNull() {
+ /*
+ * You'd think that you could get a null this via function.call/apply, but
+ * in fact you can't: they just make this be the window object instead. So
+ * it really can't ever be null.
+ */
+ return true;
+ }
+
+ @Override
+ public boolean isDefinitelyNull() {
+ return false;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ v.visit(this, ctx);
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.THIS;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsThrow.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsThrow.java
new file mode 100644
index 00000000000..4616418fb6b
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsThrow.java
@@ -0,0 +1,48 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript throw statement.
+ */
+public class JsThrow extends JsStatement {
+
+ private JsExpression expr;
+
+ public JsThrow() {
+ super();
+ }
+
+ public JsThrow(JsExpression expr) {
+ super();
+ this.expr = expr;
+ }
+
+ public JsExpression getExpr() {
+ return expr;
+ }
+
+ public void setExpr(JsExpression expr) {
+ this.expr = expr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ expr = v.accept(expr);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public boolean unconditionalControlBreak() {
+ return true;
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.THROW;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsTry.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsTry.java
new file mode 100644
index 00000000000..61357bcd406
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsTry.java
@@ -0,0 +1,59 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A JavaScript try statement.
+ */
+public class JsTry extends JsStatement {
+
+ private final List catches = new ArrayList();
+ private JsBlock finallyBlock;
+ private JsBlock tryBlock;
+
+ public JsTry() {
+ super();
+ }
+
+ public List getCatches() {
+ return catches;
+ }
+
+ public JsBlock getFinallyBlock() {
+ return finallyBlock;
+ }
+
+ public JsBlock getTryBlock() {
+ return tryBlock;
+ }
+
+ public void setFinallyBlock(JsBlock block) {
+ this.finallyBlock = block;
+ }
+
+ public void setTryBlock(JsBlock block) {
+ tryBlock = block;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ tryBlock = v.accept(tryBlock);
+ v.acceptWithInsertRemove(catches);
+ if (finallyBlock != null) {
+ finallyBlock = v.accept(finallyBlock);
+ }
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.TRY;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsUnaryOperation.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsUnaryOperation.java
new file mode 100644
index 00000000000..00f103f35c9
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsUnaryOperation.java
@@ -0,0 +1,53 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript prefix or postfix operation.
+ */
+public abstract class JsUnaryOperation extends JsExpression {
+
+ private JsExpression arg;
+
+ private final JsUnaryOperator op;
+
+ public JsUnaryOperation(JsUnaryOperator op) {
+ this(op, null);
+ }
+
+ public JsUnaryOperation(JsUnaryOperator op, JsExpression arg) {
+ super();
+ this.op = op;
+ this.arg = arg;
+ }
+
+ public JsExpression getArg() {
+ return arg;
+ }
+
+ public JsUnaryOperator getOperator() {
+ return op;
+ }
+
+ @Override
+ public final boolean hasSideEffects() {
+ return op.isModifying() || arg.hasSideEffects();
+ }
+
+ public void setArg(JsExpression arg) {
+ this.arg = arg;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (op.isModifying()) {
+ // The delete operator is practically like an assignment of undefined, so
+ // for practical purposes we're treating it as an lvalue.
+ arg = v.acceptLvalue(arg);
+ } else {
+ arg = v.accept(arg);
+ }
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsUnaryOperator.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsUnaryOperator.java
new file mode 100644
index 00000000000..2b854d8313e
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsUnaryOperator.java
@@ -0,0 +1,78 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript unary operator.
+ */
+public enum JsUnaryOperator implements JsOperator {
+
+ /*
+ * Precedence indices from "JavaScript - The Definitive Guide" 4th Edition
+ * (page 57)
+ */
+ BIT_NOT("~", 14, PREFIX), DEC("--", 14, POSTFIX | PREFIX), DELETE("delete", 14, PREFIX), INC(
+ "++", 14, POSTFIX | PREFIX), NEG("-", 14, PREFIX), POS("+", 14, PREFIX),
+ NOT("!", 14, PREFIX), TYPEOF("typeof", 14, PREFIX), VOID("void", 14, PREFIX);
+
+ private final int mask;
+ private final int precedence;
+ private final String symbol;
+
+ private JsUnaryOperator(String symbol, int precedence, int mask) {
+ this.symbol = symbol;
+ this.precedence = precedence;
+ this.mask = mask;
+ }
+
+ @Override
+ public int getPrecedence() {
+ return precedence;
+ }
+
+ @Override
+ public String getSymbol() {
+ return symbol;
+ }
+
+ @Override
+ public boolean isKeyword() {
+ return this == DELETE || this == TYPEOF || this == VOID;
+ }
+
+ @Override
+ public boolean isLeftAssociative() {
+ return (mask & LEFT) != 0;
+ }
+
+ public boolean isModifying() {
+ return this == DEC || this == INC || this == DELETE;
+ }
+
+ @Override
+ public boolean isPrecedenceLessThan(JsOperator other) {
+ return precedence < other.getPrecedence();
+ }
+
+ @Override
+ public boolean isValidInfix() {
+ return (mask & INFIX) != 0;
+ }
+
+ @Override
+ public boolean isValidPostfix() {
+ return (mask & POSTFIX) != 0;
+ }
+
+ @Override
+ public boolean isValidPrefix() {
+ return (mask & PREFIX) != 0;
+ }
+
+ @Override
+ public String toString() {
+ return symbol;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsValueLiteral.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsValueLiteral.java
new file mode 100644
index 00000000000..f23ab5dbce4
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsValueLiteral.java
@@ -0,0 +1,24 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript string literal expression.
+ */
+public abstract class JsValueLiteral extends JsLiteral {
+
+ protected JsValueLiteral() {
+ }
+
+ @Override
+ public final boolean hasSideEffects() {
+ return false;
+ }
+
+ @Override
+ public final boolean isLeaf() {
+ return true;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsVars.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsVars.java
new file mode 100644
index 00000000000..aaf629259aa
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsVars.java
@@ -0,0 +1,109 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import com.google.dart.compiler.common.SourceInfo;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A JavaScript var statement.
+ */
+public class JsVars extends JsStatement implements Iterable {
+
+ /**
+ * A var declared using the JavaScript var statement.
+ */
+ public static class JsVar extends JsNode implements HasName {
+
+ private final JsName name;
+ private JsExpression initExpr;
+
+ public JsVar(JsName name) {
+ this.name = name;
+ }
+
+ public JsVar(JsName name, JsExpression initExpr) {
+ this.name = name;
+ this.initExpr = initExpr;
+ }
+
+ public JsExpression getInitExpr() {
+ return initExpr;
+ }
+
+ @Override
+ public JsName getName() {
+ return name;
+ }
+
+ public void setInitExpr(JsExpression initExpr) {
+ this.initExpr = initExpr;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ if (initExpr != null) {
+ initExpr = v.accept(initExpr);
+ }
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public JsVar setSourceRef(SourceInfo info) {
+ super.setSourceRef(info);
+ return this;
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.VAR;
+ }
+ }
+
+ private final List vars = new ArrayList();
+
+ public JsVars() {
+ }
+
+ public void add(JsVar var) {
+ vars.add(var);
+ }
+
+ public int getNumVars() {
+ return vars.size();
+ }
+
+ public void insert(JsVar var) {
+ vars.add(var);
+ }
+
+ public boolean isEmpty() {
+ return vars.isEmpty();
+ }
+
+ // Iterator returns JsVar objects
+ @Override
+ public Iterator iterator() {
+ return vars.iterator();
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ v.acceptWithInsertRemove(vars);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.VARS;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsVisitable.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsVisitable.java
new file mode 100644
index 00000000000..626648af8ae
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsVisitable.java
@@ -0,0 +1,19 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * Abstracts the idea that a class can be traversed.
+ */
+public interface JsVisitable {
+
+ /**
+ * Causes this object to have the visitor visit itself and its children.
+ *
+ * @param visitor the visitor that should traverse this node
+ * @param ctx the context of an existing traversal
+ */
+ void traverse(JsVisitor visitor, JsContext ctx);
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsVisitor.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsVisitor.java
new file mode 100644
index 00000000000..5b9cd983d08
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsVisitor.java
@@ -0,0 +1,430 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+import java.util.List;
+
+/**
+ * Implemented by nodes that will visit child nodes.
+ */
+@SuppressWarnings("unused")
+public class JsVisitor {
+
+ protected static final JsContext LVALUE_CONTEXT = new JsContext() {
+
+ @Override
+ public boolean canInsert() {
+ return false;
+ }
+
+ @Override
+ public boolean canRemove() {
+ return false;
+ }
+
+ @Override
+ public void insertAfter(JsVisitable node) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void insertBefore(JsVisitable node) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isLvalue() {
+ return true;
+ }
+
+ @Override
+ public void removeMe() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void replaceMe(JsVisitable node) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ protected static final JsContext UNMODIFIABLE_CONTEXT = new JsContext() {
+
+ @Override
+ public boolean canInsert() {
+ return false;
+ }
+
+ @Override
+ public boolean canRemove() {
+ return false;
+ }
+
+ @Override
+ public void insertAfter(JsVisitable node) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void insertBefore(JsVisitable node) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isLvalue() {
+ return false;
+ }
+
+ @Override
+ public void removeMe() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void replaceMe(JsVisitable node) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ public final T accept(T node) {
+ return this.doAccept(node);
+ }
+
+ public final void acceptList(List collection) {
+ doAcceptList(collection);
+ }
+
+ public JsExpression acceptLvalue(JsExpression expr) {
+ return doAcceptLvalue(expr);
+ }
+
+ public final void acceptWithInsertRemove(List collection) {
+ doAcceptWithInsertRemove(collection);
+ }
+
+ public boolean didChange() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void endVisit(JsArrayAccess x, JsContext ctx) {
+ }
+
+ public void endVisit(JsArrayLiteral x, JsContext ctx) {
+ }
+
+ public void endVisit(JsBinaryOperation x, JsContext ctx) {
+ }
+
+ public void endVisit(JsBlock x, JsContext ctx) {
+ }
+
+ public void endVisit(JsBooleanLiteral x, JsContext ctx) {
+ }
+
+ public void endVisit(JsBreak x, JsContext ctx) {
+ }
+
+ public void endVisit(JsCase x, JsContext ctx) {
+ }
+
+ public void endVisit(JsCatch x, JsContext ctx) {
+ }
+
+ public void endVisit(JsConditional x, JsContext ctx) {
+ }
+
+ public void endVisit(JsContinue x, JsContext ctx) {
+ }
+
+ public void endVisit(JsDebugger x, JsContext ctx) {
+ }
+
+ public void endVisit(JsDefault x, JsContext ctx) {
+ }
+
+ public void endVisit(JsDoWhile x, JsContext ctx) {
+ }
+
+ public void endVisit(JsEmpty x, JsContext ctx) {
+ }
+
+ public void endVisit(JsExprStmt x, JsContext ctx) {
+ }
+
+ public void endVisit(JsFor x, JsContext ctx) {
+ }
+
+ public void endVisit(JsForIn x, JsContext ctx) {
+ }
+
+ public void endVisit(JsFunction x, JsContext ctx) {
+ }
+
+ public void endVisit(JsIf x, JsContext ctx) {
+ }
+
+ public void endVisit(JsInvocation x, JsContext ctx) {
+ }
+
+ public void endVisit(JsLabel x, JsContext ctx) {
+ }
+
+ public void endVisit(JsNameRef x, JsContext ctx) {
+ }
+
+ public void endVisit(JsNew x, JsContext ctx) {
+ }
+
+ public void endVisit(JsNullLiteral x, JsContext ctx) {
+ }
+
+ public void endVisit(JsNumberLiteral x, JsContext ctx) {
+ }
+
+ public void endVisit(JsObjectLiteral x, JsContext ctx) {
+ }
+
+ public void endVisit(JsParameter x, JsContext ctx) {
+ }
+
+ public void endVisit(JsPostfixOperation x, JsContext ctx) {
+ }
+
+ public void endVisit(JsPrefixOperation x, JsContext ctx) {
+ }
+
+ public void endVisit(JsProgram x, JsContext ctx) {
+ }
+
+ public void endVisit(JsProgramFragment x, JsContext ctx) {
+ }
+
+ public void endVisit(JsPropertyInitializer x, JsContext ctx) {
+ }
+
+ public void endVisit(JsRegExp x, JsContext ctx) {
+ }
+
+ public void endVisit(JsReturn x, JsContext ctx) {
+ }
+
+ public void endVisit(JsStringLiteral x, JsContext ctx) {
+ }
+
+ public void endVisit(JsSwitch x, JsContext ctx) {
+ }
+
+ public void endVisit(JsThisRef x, JsContext ctx) {
+ }
+
+ public void endVisit(JsThrow x, JsContext ctx) {
+ }
+
+ public void endVisit(JsTry x, JsContext ctx) {
+ }
+
+ public void endVisit(JsVars.JsVar x, JsContext ctx) {
+ }
+
+ public void endVisit(JsVars x, JsContext ctx) {
+ }
+
+ public void endVisit(JsWhile x, JsContext ctx) {
+ }
+
+ public boolean visit(JsArrayAccess x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsArrayLiteral x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsBinaryOperation x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsBlock x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsBooleanLiteral x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsBreak x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsCase x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsCatch x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsConditional x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsContinue x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsDebugger x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsDefault x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsDoWhile x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsEmpty x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsExprStmt x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsFor x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsForIn x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsFunction x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsIf x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsInvocation x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsLabel x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsNameRef x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsNew x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsNullLiteral x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsNumberLiteral x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsObjectLiteral x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsParameter x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsPostfixOperation x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsPrefixOperation x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsProgram x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsProgramFragment x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsPropertyInitializer x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsRegExp x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsReturn x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsStringLiteral x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsSwitch x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsThisRef x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsThrow x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsTry x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsVars.JsVar x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsVars x, JsContext ctx) {
+ return true;
+ }
+
+ public boolean visit(JsWhile x, JsContext ctx) {
+ return true;
+ }
+
+ protected T doAccept(T node) {
+ doTraverse(node, UNMODIFIABLE_CONTEXT);
+ return node;
+ }
+
+ protected void doAcceptList(List collection) {
+ for (T node : collection) {
+ doTraverse(node, UNMODIFIABLE_CONTEXT);
+ }
+ }
+
+ protected JsExpression doAcceptLvalue(JsExpression expr) {
+ doTraverse(expr, LVALUE_CONTEXT);
+ return expr;
+ }
+
+ protected void doAcceptWithInsertRemove(List collection) {
+ for (T node : collection) {
+ doTraverse(node, UNMODIFIABLE_CONTEXT);
+ }
+ }
+
+ protected void doTraverse(JsVisitable node, JsContext ctx) {
+ node.traverse(this, ctx);
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsWhile.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsWhile.java
new file mode 100644
index 00000000000..c40203bc1f1
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/JsWhile.java
@@ -0,0 +1,52 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+/**
+ * A JavaScript while statement.
+ */
+public class JsWhile extends JsStatement {
+
+ private JsStatement body;
+ private JsExpression condition;
+
+ public JsWhile() {
+ }
+
+ public JsWhile(JsExpression condition, JsStatement body) {
+ this.condition = condition;
+ this.body = body;
+ }
+
+ public JsStatement getBody() {
+ return body;
+ }
+
+ public JsExpression getCondition() {
+ return condition;
+ }
+
+ public void setBody(JsStatement body) {
+ this.body = body;
+ }
+
+ public void setCondition(JsExpression condition) {
+ this.condition = condition;
+ }
+
+ @Override
+ public void traverse(JsVisitor v, JsContext ctx) {
+ if (v.visit(this, ctx)) {
+ condition = v.accept(condition);
+ body = v.accept(body);
+ }
+ v.endVisit(this, ctx);
+ }
+
+ @Override
+ public NodeKind getKind() {
+ return NodeKind.WHILE;
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/backend/js/ast/NodeKind.java b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/NodeKind.java
new file mode 100644
index 00000000000..614e4bb3bd9
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/backend/js/ast/NodeKind.java
@@ -0,0 +1,50 @@
+// 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.
+
+package com.google.dart.compiler.backend.js.ast;
+
+public enum NodeKind {
+ ARRAY_ACCESS,
+ ARRAY,
+ BINARY_OP,
+ BLOCK,
+ BOOLEAN,
+ BREAK,
+ CASE,
+ CATCH,
+ CONDITIONAL,
+ CONTINUE,
+ DEBUGGER,
+ DEFAULT,
+ DO,
+ EMPTY,
+ EXPR_STMT,
+ FOR,
+ FOR_IN,
+ FUNCTION,
+ IF,
+ INVOKE,
+ LABEL,
+ NAME_REF,
+ NEW,
+ NULL,
+ NUMBER,
+ OBJECT,
+ PARAMETER,
+ POSTFIX_OP,
+ PREFIX_OP,
+ PROGRAM,
+ PROGRAM_FRAGMENT,
+ PROPERTY_INIT,
+ REGEXP,
+ RETURN,
+ STRING,
+ SWITCH,
+ THIS,
+ THROW,
+ TRY,
+ VARS,
+ VAR,
+ WHILE
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/common/AbstractNode.java b/js/js.ast/src/com/google/dart/compiler/common/AbstractNode.java
new file mode 100644
index 00000000000..5b70197dd13
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/common/AbstractNode.java
@@ -0,0 +1,81 @@
+// 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.
+
+package com.google.dart.compiler.common;
+
+import com.google.dart.compiler.Source;
+
+/**
+ * Abstract base class for nodes that carry source information.
+ */
+public class AbstractNode implements SourceInfo, HasSourceInfo {
+
+ // TODO(johnlenz): All this source location data is wasteful.
+ // Move it into a com.google.dart.compiler.backend.common object, that can be shared between the ASTs
+ // or something.
+ protected Source source = null;
+ protected int sourceLine = -1;
+ protected int sourceColumn = -1;
+ protected int sourceStart = -1;
+ protected int sourceLength = -1;
+
+ @Override
+ public Source getSource() {
+ return source;
+ }
+
+ @Override
+ public int getSourceLine() {
+ return sourceLine;
+ }
+
+ @Override
+ public int getSourceColumn() {
+ return sourceColumn;
+ }
+
+ @Override
+ public int getSourceStart() {
+ return sourceStart;
+ }
+
+ @Override
+ public int getSourceLength() {
+ return sourceLength;
+ }
+
+ @Override
+ public SourceInfo getSourceInfo() {
+ return this;
+ }
+
+ @Override
+ public void setSourceInfo(SourceInfo info) {
+ source = info.getSource();
+ sourceStart = info.getSourceStart();
+ sourceLength = info.getSourceLength();
+ sourceLine = info.getSourceLine();
+ sourceColumn = info.getSourceColumn();
+ }
+
+ @Override
+ public final void setSourceLocation(
+ Source source, int line, int column, int startPosition, int length) {
+ assert (startPosition != -1 && length >= 0
+ || startPosition == -1 && length == 0);
+ this.source = source;
+ this.sourceLine = line;
+ this.sourceColumn = column;
+ this.sourceStart = startPosition;
+ this.sourceLength = length;
+ }
+
+ public final void setSourceRange(int startPosition, int length) {
+ assert (startPosition != -1 && length >= 0
+ || startPosition == -1 && length == 0);
+ this.sourceStart = startPosition;
+ this.sourceLength = length;
+ }
+
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/common/HasSourceInfo.java b/js/js.ast/src/com/google/dart/compiler/common/HasSourceInfo.java
new file mode 100644
index 00000000000..bda3da94996
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/common/HasSourceInfo.java
@@ -0,0 +1,55 @@
+// 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.
+
+package com.google.dart.compiler.common;
+
+import com.google.dart.compiler.Source;
+
+/**
+ * Abstract view of a class that has source info.
+ */
+public interface HasSourceInfo {
+
+ /**
+ * Return the source info associated with this object.
+ */
+ SourceInfo getSourceInfo();
+
+ /**
+ * Set the source info associated with this object. May only be called once.
+ * @param info
+ */
+ void setSourceInfo(SourceInfo info);
+
+ /**
+ * Sets the source range of the original source file where the source fragment
+ * corresponding to this node was found.
+ *
+ *
+ * Each node in the subtree (other than the contrived nodes) carries source
+ * range(s) information relating back to positions in the given source (the
+ * given source itself is not remembered with the AST). The source range
+ * usually begins at the first character of the first token corresponding to
+ * the node; leading whitespace and comments are not included. The
+ * source range usually extends through the last character of the last token
+ * corresponding to the node; trailing whitespace and comments are not
+ * included. There are a handful of exceptions (including the various body
+ * declarations). Source ranges nest properly: the source range for a child is
+ * always within the source range of its parent, and the source ranges of
+ * sibling nodes never overlap.
+ *
+ * @param source the associated source
+ * @param line the 1-based line index, or -1, if no source
+ * location is available
+ * @param column the 1-based column index, or -1, if no source
+ * location is available
+ * @param startPosition a 0-based character index, or -1, if no
+ * source location is available
+ * @param length a (possibly 0) length, or -1, if no source
+ * location is available
+ * @see SourceInfo#getSourceStart()
+ * @see SourceInfo#getSourceLength()
+ */
+ void setSourceLocation(Source source, int line, int column, int startPosition, int length);
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/common/Name.java b/js/js.ast/src/com/google/dart/compiler/common/Name.java
new file mode 100644
index 00000000000..4697b491d96
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/common/Name.java
@@ -0,0 +1,121 @@
+// 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.
+
+package com.google.dart.compiler.common;
+
+import java.io.*;
+import java.nio.charset.Charset;
+
+/**
+ * Instead of Strings, we use GlobalNames to abstract the underlying representation.
+ *
+ *
+ *
GlobalNames are globally unique and use identity for equality.
+ *
GlobalNames are interned, so all references to the same name use the same
+ * bytes.
+ *
+ * TODO(scottb): use byte[] instead of char[] when the parser is rewritten
+ * to parse byte[].
+ */
+public final class Name implements Serializable {
+
+ /**
+ * The encoding this class uses when converting between chars and bytes.
+ */
+ public static final Charset CHARSET = Charset.forName("UTF-8");
+
+ private static final NameFactory factory = new NameFactory();
+
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * Return the Name corresponding to the data. An internal reference to
+ * data is kept for efficiency, do NOT mutate data after calling
+ * this method.
+ */
+ public static Name of(char[] data) {
+ return factory.of(data);
+ }
+
+ /**
+ * Return the Name corresponding to the data. An internal copy of the data is
+ * made.
+ */
+ public static Name of(char[] data, int offset, int length) {
+ return factory.of(data, offset, length);
+ }
+
+ static int computeHashCode(char[] data, int offset, int length) {
+ // Effective Java Item 9.
+ int result = 89;
+ for (int i = offset, end = offset + length; i < end; ++i) {
+ result *= 31;
+ result += data[i];
+ }
+ return result;
+ }
+
+ final char[] data;
+
+ private final transient int hashCode;
+
+ Name(char[] data, int hashCode) {
+ this.data = data;
+ this.hashCode = hashCode;
+ }
+
+ /**
+ * Always compares based on identity.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ /**
+ * Returns the hashCode of the underlying data.
+ */
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ /**
+ * Constructs a String to represent the internal data.
+ */
+ @Override
+ public String toString() {
+ return String.valueOf(data);
+ }
+
+ /**
+ * Write my data into a {@link java.io.OutputStream} using the encoding specified in
+ * {@link #CHARSET}.
+ */
+ public void writeBytesTo(OutputStream out) throws IOException {
+ // TODO(scottb): avoid allocating the String.
+ out.write(new String(data).getBytes(CHARSET));
+ }
+
+ /**
+ * Write my character data into a {@link java.io.PrintStream}.
+ */
+ public void writeCharsTo(PrintStream out) {
+ out.print(data);
+ }
+
+ /**
+ * Write my character data into a {@link java.io.Writer}.
+ */
+ public void writeCharsTo(Writer writer) throws IOException {
+ writer.write(data);
+ }
+
+ /**
+ * Replace with the canonical instance.
+ */
+ private Object readResolve() {
+ return Name.of(data);
+ }
+}
diff --git a/js/js.ast/src/com/google/dart/compiler/common/NameFactory.java b/js/js.ast/src/com/google/dart/compiler/common/NameFactory.java
new file mode 100644
index 00000000000..d305f290bf9
--- /dev/null
+++ b/js/js.ast/src/com/google/dart/compiler/common/NameFactory.java
@@ -0,0 +1,226 @@
+// 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.
+
+package com.google.dart.compiler.common;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.MapMaker;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Manages the life cycle, uniqueness, and object identity invariants of
+ * {@link Name}.
+ */
+final class NameFactory {
+ /*
+ * TODO: does this actually save memory in practice? For each interned Name,
+ * we use:
+ *
+ * 1) The data array (n bytes).
+ *
+ * 2) The Name object (2 fields)
+ *
+ * 3) The RealKey (1 field + 4 fields from Reference).
+ *
+ * 4) ConcurrentMap.Entry (4 fields)
+ *
+ * Of course, a Map is really much heavier than we need, all we really need is
+ * a Set where you can retrieve an object already in the Set. A bare linear
+ * probe hash table of RealKey would let us get rid of the Entry object.
+ */
+
+ /**
+ * The whole point of this class is to cheaply create a light-weight key that
+ * doesn't need to make its own copy of the data. This object is constructed
+ * with equality semantics to {com.google.dart.compiler.backend.common.NameFactory.RealKey}, for doing cheap map lookups.
+ */
+ private static final class FakeKey {
+ private final char[] data;
+ private final int hashCode;
+ private final int length;
+ private final int offset;
+
+ public FakeKey(char[] data, int hashCode) {
+ this(data, 0, data.length, hashCode);
+ }
+
+ public FakeKey(char[] data, int offset, int length, int hashCode) {
+ this.data = data;
+ this.offset = offset;
+ this.length = length;
+ this.hashCode = hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ /*
+ * NOTE: this ONLY WORKS for comparisons to RealKey. But that's all we
+ * need since we only store real keys in the map.
+ */
+ Name name = ((RealKey) obj).get();
+ if (name == null) {
+ return false;
+ }
+ return equalsName(name);
+ }
+
+ public boolean equalsName(Name name) {
+ if (this.length != name.data.length) {
+ return false;
+ }
+ int itMine = this.offset;
+ int itTheirs = 0;
+ int endMine = this.offset + this.length;
+ while (itMine < endMine) {
+ if (this.data[itMine++] != name.data[itTheirs++]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ return "FakeKey(" + String.valueOf(data, offset, length) + ")";
+ }
+ }
+
+ /**
+ * Adapted from {@link com.google.common.collect.Interners#newWeakInterner()}.
+ */
+ private static final class RealKey extends WeakReference {
+ /**
+ * Must store the hashCode locally so we can be removed from
+ * {com.google.dart.compiler.backend.common.NameFactory#map} after our referent is cleared.
+ */
+ private final int hashCode;
+
+ public RealKey(Name name, ReferenceQueue queue) {
+ super(name, queue);
+ hashCode = name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof FakeKey) {
+ Name referent = get();
+ if (referent == null) {
+ return false;
+ }
+ return ((FakeKey) obj).equalsName(referent);
+ }
+ if (obj instanceof RealKey) {
+ Name referent = get();
+ if (referent == null) {
+ return false;
+ }
+ Name otherReferent = ((RealKey) obj).get();
+ if (otherReferent == null) {
+ return false;
+ }
+ return Arrays.equals(referent.data, otherReferent.data);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ Name referent = get();
+ return "RealKey(" + referent + ")";
+ }
+ }
+
+ private final ConcurrentMap