1 | // Copyright (C) 2009 Google Inc. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | package com.google.caja.ancillary.opt; |
16 | |
17 | import com.google.caja.lexer.CharProducer; |
18 | import com.google.caja.lexer.FilePosition; |
19 | import com.google.caja.lexer.InputSource; |
20 | import com.google.caja.lexer.JsLexer; |
21 | import com.google.caja.lexer.JsTokenQueue; |
22 | import com.google.caja.lexer.ParseException; |
23 | import com.google.caja.parser.js.Block; |
24 | import com.google.caja.parser.js.Expression; |
25 | import com.google.caja.parser.js.Literal; |
26 | import com.google.caja.parser.js.ObjProperty; |
27 | import com.google.caja.parser.js.ObjectConstructor; |
28 | import com.google.caja.parser.js.Parser; |
29 | import com.google.caja.parser.js.Statement; |
30 | import com.google.caja.parser.js.StringLiteral; |
31 | import com.google.caja.parser.js.ValueProperty; |
32 | import com.google.caja.render.Concatenator; |
33 | import com.google.caja.render.JsMinimalPrinter; |
34 | import com.google.caja.reporting.DevNullMessageQueue; |
35 | import com.google.caja.reporting.Message; |
36 | import com.google.caja.reporting.MessageContext; |
37 | import com.google.caja.reporting.MessageQueue; |
38 | import com.google.caja.reporting.RenderContext; |
39 | import com.google.caja.reporting.SimpleMessageQueue; |
40 | import com.google.caja.util.Lists; |
41 | |
42 | import java.io.File; |
43 | import java.io.FileInputStream; |
44 | import java.io.IOException; |
45 | import java.io.InputStreamReader; |
46 | import java.util.List; |
47 | |
48 | /** |
49 | * Optimizes JavaScript code. |
50 | * |
51 | * @author mikesamuel@gmail.com |
52 | */ |
53 | public class JsOptimizer { |
54 | private final List<Statement> compUnits = Lists.newArrayList(); |
55 | private ParseTreeKB optimizer; |
56 | private boolean rename; |
57 | private final MessageQueue mq; |
58 | |
59 | public JsOptimizer(MessageQueue mq) { this.mq = mq; } |
60 | |
61 | /** |
62 | * Register an input for optimization. |
63 | */ |
64 | public JsOptimizer addInput(Statement stmt) { |
65 | compUnits.add(stmt); |
66 | return this; |
67 | } |
68 | |
69 | /** |
70 | * Sets the environment file to use during optimization. |
71 | * The environment file contains facts about the environment in which the |
72 | * optimized output is expected to run, so allows for interpreter-specific |
73 | * optimization. |
74 | */ |
75 | public JsOptimizer setEnvJson(ObjectConstructor envJson) { |
76 | if (optimizer == null) { optimizer = new ParseTreeKB(); } |
77 | List<? extends ObjProperty> props = envJson.children(); |
78 | for (ObjProperty prop : props) { |
79 | // JSON had better not have getters |
80 | ValueProperty vprop = (ValueProperty) prop; |
81 | Expression value = vprop.getValueExpr().fold(false); // fold negative nums |
82 | if (!(value instanceof Literal)) { |
83 | // True for "*useragent*" property inserted by JSKB. |
84 | continue; |
85 | } |
86 | StringLiteral sl = vprop.getPropertyNameNode(); |
87 | String rawExpr = sl.getValue(); |
88 | rawExpr = " " + rawExpr.substring(1, rawExpr.length() - 1) + " "; |
89 | CharProducer valueCp = CharProducer.Factory.fromJsString( |
90 | CharProducer.Factory.fromString(rawExpr, sl.getFilePosition())); |
91 | try { |
92 | Expression expr = jsExpr(valueCp, DevNullMessageQueue.singleton()); |
93 | optimizer.addFact(expr, Fact.is((Literal) value)); |
94 | } catch (ParseException ex) { |
95 | continue; // Triggered for browser specific extensions such as for each |
96 | } |
97 | } |
98 | return this; |
99 | } |
100 | |
101 | /** |
102 | * Sets a flag telling the optimizer whether to rename local variables where |
103 | * doing so would not change semantics on an interpreter that does not allow |
104 | * aliasing of {@code eval}. |
105 | */ |
106 | public JsOptimizer setRename(boolean rename) { |
107 | this.rename = rename; |
108 | return this; |
109 | } |
110 | |
111 | /** |
112 | * Returns an optimized version of the concatenation of the programs |
113 | * registered via {@link #addInput}. |
114 | */ |
115 | public Statement optimize() { |
116 | // TODO(mikesamuel): use the message queue passed to the ctor once Scope no |
117 | // longer complains about cajita.js defining cajita. |
118 | SimpleMessageQueue optMq = new SimpleMessageQueue(); |
119 | Block block = new Block(FilePosition.UNKNOWN, compUnits); |
120 | // Do first since this improves the performance of the ConstVarInliner. |
121 | VarCollector.optimize(block); |
122 | if (optimizer != null) { |
123 | block = optimizer.optimize(block, optMq); |
124 | } |
125 | if (rename) { |
126 | // We pool after the ConstLocalOptimizer invoked by optimizer has run. |
127 | block = ConstantPooler.optimize(block); |
128 | // Now we shorten any long names introduced by the constant pooler. |
129 | block = new LocalVarRenamer(optMq).optimize(block); |
130 | } |
131 | // Finally we rearrange statements and convert conditionals to expressions |
132 | // where it will make things shorter. |
133 | return (Statement) StatementSimplifier.optimize(block, mq); |
134 | } |
135 | |
136 | public static void main(String... args) throws IOException { |
137 | MessageQueue mq = new SimpleMessageQueue(); |
138 | MessageContext mc = new MessageContext(); |
139 | JsOptimizer opt = new JsOptimizer(mq); |
140 | opt.setRename(true); |
141 | opt.setEnvJson(new ObjectConstructor(FilePosition.UNKNOWN)); |
142 | try { |
143 | for (int i = 0, n = args.length; i < n; ++i) { |
144 | String arg = args[i]; |
145 | if ("--norename".equals(arg)) { |
146 | opt.setRename(false); |
147 | } else if (arg.startsWith("--envjson=")) { |
148 | String jsonfile = arg.substring(arg.indexOf('=') + 1); |
149 | opt.setEnvJson((ObjectConstructor) jsExpr(fromFile(jsonfile), mq)); |
150 | } else { |
151 | if ("--".equals(arg)) { ++i; } |
152 | for (;i < n; ++i) { |
153 | CharProducer cp = fromFile(args[i]); |
154 | mc.addInputSource(cp.getCurrentPosition().source()); |
155 | opt.addInput(js(cp, mq)); |
156 | } |
157 | } |
158 | } |
159 | } catch (ParseException ex) { |
160 | ex.toMessageQueue(mq); |
161 | } |
162 | Statement out = opt.optimize(); |
163 | for (Message msg : mq.getMessages()) { |
164 | msg.format(mc, System.err); |
165 | System.err.println(); |
166 | } |
167 | JsMinimalPrinter printer = new JsMinimalPrinter( |
168 | new Concatenator(System.out, null)); |
169 | RenderContext rc = new RenderContext(printer).withRawObjKeys(true); |
170 | if (out instanceof Block) { |
171 | ((Block) out).renderBody(rc); |
172 | } else { |
173 | out.render(rc); |
174 | } |
175 | printer.noMoreTokens(); |
176 | } |
177 | |
178 | private static Block js(CharProducer cp, MessageQueue mq) |
179 | throws ParseException { |
180 | return jsParser(cp, mq).parse(); |
181 | } |
182 | |
183 | private static Expression jsExpr(CharProducer cp, MessageQueue mq) |
184 | throws ParseException { |
185 | Parser p = jsParser(cp, mq); |
186 | Expression e = p.parseExpression(true); |
187 | p.getTokenQueue().expectEmpty(); |
188 | return e; |
189 | } |
190 | |
191 | private static Parser jsParser(CharProducer cp, MessageQueue mq) { |
192 | JsLexer lexer = new JsLexer(cp, false); |
193 | JsTokenQueue tq = new JsTokenQueue(lexer, cp.getCurrentPosition().source()); |
194 | tq.setInputRange(cp.filePositionForOffsets(cp.getOffset(), cp.getLimit())); |
195 | return new Parser(tq, mq); |
196 | } |
197 | |
198 | private static CharProducer fromFile(String path) throws IOException { |
199 | File f = new File(path); |
200 | return CharProducer.Factory.create( |
201 | new InputStreamReader(new FileInputStream(f), "UTF-8"), |
202 | new InputSource(f.toURI())); |
203 | } |
204 | } |