001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.js;
017:
018: import com.google.gwt.dev.js.ast.JsBinaryOperation;
019: import com.google.gwt.dev.js.ast.JsContext;
020: import com.google.gwt.dev.js.ast.JsExpression;
021: import com.google.gwt.dev.js.ast.JsModVisitor;
022: import com.google.gwt.dev.js.ast.JsName;
023: import com.google.gwt.dev.js.ast.JsPostfixOperation;
024: import com.google.gwt.dev.js.ast.JsPrefixOperation;
025: import com.google.gwt.dev.js.ast.JsProgram;
026: import com.google.gwt.dev.js.ast.JsPropertyInitializer;
027: import com.google.gwt.dev.js.ast.JsScope;
028: import com.google.gwt.dev.js.ast.JsStringLiteral;
029: import com.google.gwt.dev.js.ast.JsVars;
030: import com.google.gwt.dev.js.ast.JsVars.JsVar;
031:
032: import java.util.Comparator;
033: import java.util.Map;
034: import java.util.TreeMap;
035:
036: /**
037: * Interns all String literals in a JsProgram. Each unique String will be
038: * assigned to a variable in the program's main block and instances of a
039: * JsStringLiteral replaced with a JsNameRef. This optimization is complete in a
040: * single pass, although it may be performed multiple times without duplicating
041: * the intern pool.
042: */
043: public class JsStringInterner {
044:
045: /**
046: * Replaces JsStringLiterals with JsNameRefs, creating new JsName allocations
047: * on the fly.
048: */
049: private static class StringVisitor extends JsModVisitor {
050: /**
051: * Records the scope in which the interned identifiers should be unique.
052: */
053: final JsScope scope;
054:
055: /**
056: * This is a TreeMap to ensure consistent iteration order, based on the
057: * lexicographical ordering of the string constant.
058: */
059: final Map<JsStringLiteral, JsName> toCreate = new TreeMap<JsStringLiteral, JsName>(
060: new Comparator<JsStringLiteral>() {
061: public int compare(JsStringLiteral o1,
062: JsStringLiteral o2) {
063: return o1.getValue().compareTo(o2.getValue());
064: }
065: });
066:
067: /**
068: * A counter used for assigning ids to Strings. Even though it's unlikely
069: * that someone would actually have two billion strings in their
070: * application, it doesn't hurt to think ahead.
071: */
072: long lastId = 0;
073:
074: /**
075: * Constructor.
076: *
077: * @param scope specifies the scope in which the interned strings should be
078: * created.
079: */
080: public StringVisitor(JsScope scope) {
081: this .scope = scope;
082: }
083:
084: /**
085: * Prevents 'fixing' an otherwise illegal operation.
086: */
087: @Override
088: public boolean visit(JsBinaryOperation x,
089: JsContext<JsExpression> ctx) {
090: return !x.getOperator().isAssignment()
091: || !(x.getArg1() instanceof JsStringLiteral);
092: }
093:
094: /**
095: * Prevents 'fixing' an otherwise illegal operation.
096: */
097: @Override
098: public boolean visit(JsPostfixOperation x,
099: JsContext<JsExpression> ctx) {
100: return !(x.getArg() instanceof JsStringLiteral);
101: }
102:
103: /**
104: * Prevents 'fixing' an otherwise illegal operation.
105: */
106: @Override
107: public boolean visit(JsPrefixOperation x,
108: JsContext<JsExpression> ctx) {
109: return !(x.getArg() instanceof JsStringLiteral);
110: }
111:
112: /**
113: * We ignore property initializer labels in object literals, but do process
114: * the expression. This is because the LHS is always treated as a string,
115: * and never evaluated as an expression.
116: */
117: @Override
118: public boolean visit(JsPropertyInitializer x,
119: JsContext<JsPropertyInitializer> ctx) {
120: x.setValueExpr(accept(x.getValueExpr()));
121: return false;
122: }
123:
124: /**
125: * Replace JsStringLiteral instances with JsNameRefs.
126: */
127: @Override
128: public boolean visit(JsStringLiteral x,
129: JsContext<JsExpression> ctx) {
130: JsName name = toCreate.get(x);
131: if (name == null) {
132: String ident = PREFIX + lastId++;
133: name = scope.declareName(ident);
134: toCreate.put(x, name);
135: }
136:
137: ctx.replaceMe(name.makeRef());
138:
139: return false;
140: }
141:
142: /**
143: * This prevents duplicating the intern pool by not traversing JsVar
144: * declarations that look like they were created by the interner.
145: */
146: @Override
147: public boolean visit(JsVar x, JsContext<JsVar> ctx) {
148: return !(x.getName().getIdent().startsWith(PREFIX));
149: }
150: }
151:
152: public static final String PREFIX = "$intern_";
153:
154: public static boolean exec(JsProgram program) {
155: StringVisitor v = new StringVisitor(program.getScope());
156: v.accept(program);
157:
158: if (v.toCreate.size() > 0) {
159: // Create the pool of variable names.
160: JsVars vars = new JsVars();
161: for (Map.Entry<JsStringLiteral, JsName> entry : v.toCreate
162: .entrySet()) {
163: JsVar var = new JsVar(entry.getValue());
164: var.setInitExpr(entry.getKey());
165: vars.add(var);
166: }
167: program.getGlobalBlock().getStatements().add(0, vars);
168: }
169:
170: return v.didChange();
171: }
172:
173: /**
174: * Utility class.
175: */
176: private JsStringInterner() {
177: }
178: }
|