| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 package com.google.dart.compiler.backend.js.analysis; | |
| 6 | |
| 7 import com.google.common.io.CharStreams; | |
| 8 import com.google.common.io.Closeables; | |
| 9 import com.google.dart.compiler.DartCompilerContext; | |
| 10 import com.google.dart.compiler.LibrarySource; | |
| 11 | |
| 12 import org.mozilla.javascript.EvaluatorException; | |
| 13 import org.mozilla.javascript.Parser; | |
| 14 import org.mozilla.javascript.ast.AstNode; | |
| 15 import org.mozilla.javascript.ast.AstRoot; | |
| 16 import org.mozilla.javascript.ast.NodeVisitor; | |
| 17 | |
| 18 import java.io.IOException; | |
| 19 import java.io.Reader; | |
| 20 import java.io.Writer; | |
| 21 import java.util.ArrayList; | |
| 22 import java.util.HashMap; | |
| 23 import java.util.LinkedHashSet; | |
| 24 import java.util.List; | |
| 25 import java.util.Map; | |
| 26 import java.util.Set; | |
| 27 | |
| 28 /** | |
| 29 * A JavaScript tree shaker that is specialized for the output produced by | |
| 30 * dartc. | |
| 31 */ | |
| 32 public class TreeShaker { | |
| 33 private static class VisitorIOException extends RuntimeException { | |
| 34 public VisitorIOException(IOException e) { | |
| 35 super(e); | |
| 36 } | |
| 37 | |
| 38 @Override | |
| 39 public IOException getCause() { | |
| 40 return (IOException) super.getCause(); | |
| 41 } | |
| 42 } | |
| 43 | |
| 44 private static final class OutputFileWriter implements NodeVisitor { | |
| 45 private final Set<AstNode> nodesToEmit; | |
| 46 private final Writer outputFile; | |
| 47 private final Reader inputFile; | |
| 48 private long lastReadPosition = 0; | |
| 49 private long outputSize = 0; | |
| 50 | |
| 51 private OutputFileWriter(Set<AstNode> nodesToEmit, Writer outputFile, Reader
inputFile) { | |
| 52 this.nodesToEmit = nodesToEmit; | |
| 53 this.outputFile = outputFile; | |
| 54 this.inputFile = inputFile; | |
| 55 } | |
| 56 | |
| 57 @Override | |
| 58 public boolean visit(AstNode node) { | |
| 59 if (node.getAstRoot() == node) { | |
| 60 return true; | |
| 61 } | |
| 62 | |
| 63 try { | |
| 64 if (nodesToEmit.contains(node)) { | |
| 65 int nodePosition = node.getAbsolutePosition(); | |
| 66 inputFile.skip(nodePosition - lastReadPosition); | |
| 67 | |
| 68 char[] buffer = new char[node.getLength()]; | |
| 69 int charsRead = inputFile.read(buffer); | |
| 70 assert (charsRead == buffer.length); | |
| 71 outputFile.write(buffer); | |
| 72 outputFile.write("\n"); | |
| 73 outputSize += charsRead + 1; | |
| 74 lastReadPosition = nodePosition + node.getLength(); | |
| 75 } | |
| 76 } catch (IOException e) { | |
| 77 throw new VisitorIOException(e); | |
| 78 } | |
| 79 | |
| 80 return false; | |
| 81 } | |
| 82 | |
| 83 public long getOutputSize() { | |
| 84 return outputSize; | |
| 85 } | |
| 86 } | |
| 87 | |
| 88 private static final boolean DEBUG = false; | |
| 89 | |
| 90 /** | |
| 91 * Returns the set of {@link AstNode}s that should be emitted into the final | |
| 92 * JS code. | |
| 93 */ | |
| 94 private static Set<AstNode> computeNodesToEmit(AstRoot root) { | |
| 95 List<AstNode> globals = new ArrayList<AstNode>(); | |
| 96 Map<String, List<JavascriptElement>> namesToElements = | |
| 97 new HashMap<String, List<JavascriptElement>>(); | |
| 98 TopLevelElementIndexer declVisitor = new TopLevelElementIndexer(namesToEleme
nts, globals); | |
| 99 root.visit(declVisitor); | |
| 100 | |
| 101 if (DEBUG) { | |
| 102 TopLevelElementIndexer.printNamesToElements(namesToElements); | |
| 103 TopLevelElementIndexer.printGlobals(globals); | |
| 104 } | |
| 105 | |
| 106 List<AstNode> worklist = new ArrayList<AstNode>(); | |
| 107 for (AstNode global : globals) { | |
| 108 worklist.add(global); | |
| 109 } | |
| 110 | |
| 111 worklist.addAll(declVisitor.getEntryPoints()); | |
| 112 DependencyComputer dependencyComputer = new DependencyComputer(namesToElemen
ts); | |
| 113 final Set<AstNode> nodesProcessed = new LinkedHashSet<AstNode>(); | |
| 114 while (!worklist.isEmpty()) { | |
| 115 AstNode node = worklist.remove(worklist.size() - 1); | |
| 116 if (!nodesProcessed.add(node)) { | |
| 117 continue; | |
| 118 } | |
| 119 | |
| 120 if (DEBUG) { | |
| 121 try { | |
| 122 System.out.println(node.toSource()); | |
| 123 System.out.println("Dependencies:"); | |
| 124 } catch (Exception e) { | |
| 125 // Ignore exceptions thrown by rhino's toSource method... | |
| 126 } | |
| 127 } | |
| 128 | |
| 129 List<JavascriptElement> dependencies = dependencyComputer.computeDependenc
ies(node); | |
| 130 for (JavascriptElement dependency : dependencies) { | |
| 131 if (dependency.isNative() || nodesProcessed.contains(dependency.getNode(
))) { | |
| 132 // Skip natives since they don't have a node in the AST | |
| 133 continue; | |
| 134 } | |
| 135 | |
| 136 if (DEBUG) { | |
| 137 System.out.println("\t" + dependency.getQualifiedName()); | |
| 138 } | |
| 139 | |
| 140 worklist.add(dependency.getNode()); | |
| 141 } | |
| 142 } | |
| 143 return nodesProcessed; | |
| 144 } | |
| 145 | |
| 146 /** | |
| 147 * Reduce the input JS file by following the conservative "call graph" and | |
| 148 * pruning dead code. | |
| 149 */ | |
| 150 public static long reduce(LibrarySource app, DartCompilerContext context, | |
| 151 String completeArtifactName, Writer outputFile) throws IOException { | |
| 152 Reader inputFile = context.getArtifactReader(app, "", completeArtifactName); | |
| 153 // Mark beyond the expected length so we can reset back to zero | |
| 154 AstRoot root = null; | |
| 155 boolean failed = true; | |
| 156 try { | |
| 157 Parser parser = new Parser(); | |
| 158 root = parser.parse(inputFile, "", 1); | |
| 159 failed = false; | |
| 160 } catch (EvaluatorException e) { | |
| 161 /* | |
| 162 * This can happen if we generate bad JS code. For example, the negative | |
| 163 * tests may cause invalid control flow constructs to be generated. In | |
| 164 * this case we will swallow the exception and simply copy the input file | |
| 165 * to the output file. | |
| 166 */ | |
| 167 Closeables.close(inputFile, failed); | |
| 168 inputFile = context.getArtifactReader(app, "", completeArtifactName); | |
| 169 return CharStreams.copy(inputFile, outputFile); | |
| 170 } finally { | |
| 171 Closeables.close(inputFile, failed); | |
| 172 } | |
| 173 | |
| 174 final Set<AstNode> nodesProcessed = computeNodesToEmit(root); | |
| 175 | |
| 176 // Need to get a new reader since we don't cache the stream | |
| 177 failed = true; | |
| 178 inputFile = context.getArtifactReader(app, "", completeArtifactName); | |
| 179 OutputFileWriter outputFileWriter = | |
| 180 new OutputFileWriter(nodesProcessed, outputFile, inputFile); | |
| 181 try { | |
| 182 root.visit(outputFileWriter); | |
| 183 failed = false; | |
| 184 return outputFileWriter.getOutputSize(); | |
| 185 } catch (VisitorIOException e) { | |
| 186 throw e.getCause(); | |
| 187 } finally { | |
| 188 Closeables.close(inputFile, failed); | |
| 189 } | |
| 190 } | |
| 191 } | |
| OLD | NEW |