| /* |
| * Copyright 2014 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.dev.js; |
| |
| import com.google.gwt.dev.jjs.SourceOrigin; |
| import com.google.gwt.dev.js.ast.JsArrayLiteral; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsLiteral; |
| import com.google.gwt.dev.js.ast.JsModVisitor; |
| import com.google.gwt.dev.js.ast.JsName; |
| import com.google.gwt.dev.js.ast.JsObjectLiteral; |
| import com.google.gwt.dev.js.ast.JsProgram; |
| import com.google.gwt.dev.js.ast.JsStatement; |
| import com.google.gwt.dev.js.ast.JsVisitor; |
| import com.google.gwt.dev.util.DefaultTextOutput; |
| import com.google.gwt.dev.util.TextOutput; |
| |
| import junit.framework.TestCase; |
| |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Verifies that {@link JsNamespaceChooser} can put globals into namespaces. |
| */ |
| public class JsLiteralInternerTest extends TestCase { |
| |
| private static final String ONES = "1111111111111"; |
| private static final String TWOS = "2222222222222"; |
| private static final String THREES = "3333333333333"; |
| |
| private static final String AS_STRING = "'aaaaaaaaaaaaa'"; |
| private static final String BS_STRING = "'bbbbbbbbbbbbb'"; |
| private static final String CS_STRING = "'ccccccccccccc'"; |
| |
| private static final String AS_REGEX = "/aaaaaaaaaaaaa/"; |
| private static final String BS_REGEX = "/bbbbbbbbbbbbb/"; |
| private static final String CS_REGEX = "/ccccccccccccc/"; |
| |
| private static final String BIG = "'bigbigbigbigbigbigbigbigbigbigbigbigbigbig'"; |
| private static final String ALSO_BIG = "'alsobigbigbigbigbigbigbigbigbigbigbigbigbigbig'"; |
| |
| private static final String SMALL_BUT_INTERNABLE = "'aaa'"; |
| |
| public void testSimpleIntern() throws Exception { |
| // Numbers |
| checkTranslation( |
| String.format("var x = %1$s, y = %1$s;", ONES), |
| String.format("var $intern_0=%1$s;var x=$intern_0,y=$intern_0;", ONES)); |
| |
| // Objects |
| checkTranslation( |
| String.format("var x={a:%1$s,b:%2$s}, y={a:%1$s,b:%2$s}, z={a:%1$s,b:%3$s};", |
| ONES, TWOS, THREES), |
| String.format("var $intern_0={'a':%1$s,'b':%2$s};var x=$intern_0,y=$intern_0,z={'a':%1$s,'b':%3$s};", |
| ONES, TWOS, THREES)); |
| |
| checkTranslation( |
| String.format("var x={a:%1$s,b:%2$s}, y={a:%1$s,b:%2$s}, z={a:%1$s,b:%3$s};", |
| ONES, TWOS, THREES), |
| String.format("var $intern_0=%1$s,$intern_1=%2$s;var x={'a':$intern_0,'b':$intern_1}," + |
| "y={'a':$intern_0,'b':$intern_1},z={'a':$intern_0,'b':%3$s};", ONES, TWOS, THREES), false); |
| |
| // Strings |
| checkTranslation( |
| String.format("var x = %1$s, y = %1$s + %2$s, z = %3$s;", AS_STRING, BS_STRING, CS_STRING), |
| String.format("var $intern_0=%1$s;var x=$intern_0,y=$intern_0+%2$s,z=%3$s;", AS_STRING, |
| BS_STRING, CS_STRING)); |
| |
| // Regexes are not internalizable. |
| checkTranslation( |
| String.format("var x = %1$s, y = %1$s + %2$s, z = %3$s;", AS_REGEX, BS_REGEX, CS_REGEX), |
| String.format("var x=%1$s,y=%1$s+%2$s,z=%3$s;", AS_REGEX, BS_REGEX, CS_REGEX)); |
| |
| // Arrays |
| checkTranslation( |
| String.format("var x = [%1$s,%2$s], y = [%1$s, %2$s], z = [%1$s,%3$s];", |
| ONES, TWOS, THREES), |
| String.format("var $intern_0=[%1$s,%2$s];var x=$intern_0,y=$intern_0,z=[%1$s,%3$s];", |
| ONES, TWOS, THREES)); |
| |
| checkTranslation(String.format("var x = [%1$s,%2$s], y = [%1$s, %2$s], z = [%1$s, %3$s];", |
| ONES, TWOS, THREES), |
| String.format("var $intern_0=%1$s,$intern_1=%2$s;var x=[$intern_0,$intern_1]," + |
| "y=[$intern_0,$intern_1],z=[$intern_0,%3$s];", ONES, TWOS, THREES), false); |
| } |
| |
| public void testDoNotInternSmallNumbers() throws Exception { |
| checkTranslation("var x = 2, y = 2;", "var x=2,y=2;"); |
| } |
| |
| public void testDoNotInternSingleOccurrence() throws Exception { |
| checkTranslation( |
| String.format("var x = %1$s, y = %2$s;", BIG, ALSO_BIG), |
| String.format("var x=%1$s,y=%2$s;", BIG, ALSO_BIG)); |
| } |
| |
| public void testDoNotInternEmptyObjectsOrArrayLiterals() throws Exception { |
| checkTranslation("var x = {}, y = {};", "var x={},y={};"); |
| checkTranslation("var x = [], y = [];", "var x=[],y=[];"); |
| } |
| |
| public void testInternInLhs() throws Exception { |
| checkTranslation( |
| String.format("var a = %1$s;a[%1$s]++;", AS_STRING), |
| String.format("var $intern_0=%1$s;var a=$intern_0;a[$intern_0]++;", AS_STRING)); |
| checkTranslation( |
| String.format("var a = %1$s;++a[%1$s];", AS_STRING), |
| String.format("var $intern_0=%1$s;var a=$intern_0;++a[$intern_0];", AS_STRING)); |
| } |
| |
| public void testDoNotInternIllegalLhs() throws Exception { |
| checkTranslation(String.format("var a = %1$s;%1$s++;", AS_STRING), |
| String.format("var a=%1$s;%1$s++;", AS_STRING)); |
| checkTranslation(String.format("var a = %1$s;++%1$s;", AS_STRING), |
| String.format("var a=%1$s;++%1$s;", AS_STRING)); |
| // TODO(rluble): Should also check for literal on lhs of a binary op, but it seems that |
| // the JsParser already checks validity so it must be arrived at by a transformation. |
| } |
| |
| public void testProfitability() throws Exception { |
| // Non profitable |
| checkTranslation( |
| String.format("var x = %1$s, y = %1$s, z = %1$s;", SMALL_BUT_INTERNABLE), |
| String.format("var x=%1$s,y=%1$s,z=%1$s;", SMALL_BUT_INTERNABLE)); |
| |
| // Becomes profitable as the number of occurrences increases |
| checkTranslation( |
| String.format("var x = %1$s, y = %1$s, z = %1$s, z = %1$s, z = %1$s, z = %1$s, z = %1$s, " + |
| "z = %1$s;", SMALL_BUT_INTERNABLE), |
| String.format("var $intern_0=%1$s;var x=$intern_0,y=$intern_0,z=$intern_0,z=$intern_0," + |
| "z=$intern_0,z=$intern_0,z=$intern_0,z=$intern_0;", SMALL_BUT_INTERNABLE)); |
| |
| // With a big string also becomes profitable. |
| checkTranslation( |
| String.format("var x = %1$s, y = %1$s, z = %1$s;", BIG), |
| String.format("var $intern_0=%1$s;var x=$intern_0,y=$intern_0,z=$intern_0;", BIG)); |
| } |
| |
| private void checkTranslation(String source, String expectedJs) |
| throws IOException, JsParserException { |
| checkTranslation(source, expectedJs, true); |
| } |
| |
| private void checkTranslation(String source, String expectedJs, |
| boolean internObjectAndArrayLiterals) |
| throws IOException, JsParserException { |
| JsProgram program = parseJs(source); |
| // Mark all object literals as internable |
| if (internObjectAndArrayLiterals) { |
| new JsModVisitor() { |
| @Override |
| public void endVisit(JsObjectLiteral x, JsContext ctx) { |
| x.setInternable(); |
| } |
| |
| @Override |
| public void endVisit(JsArrayLiteral x, JsContext ctx) { |
| x.setInternable(); |
| } |
| }.accept(program); |
| } |
| exec(program); |
| String actual = serializeJs(program); |
| assertEquals(expectedJs, actual); |
| } |
| |
| private Map<JsName, JsLiteral> exec(JsProgram program) { |
| // Prerequisite: resolve name references. |
| JsSymbolResolver.exec(program); |
| return JsLiteralInterner.exec(null, program, JsLiteralInterner.INTERN_ALL); |
| } |
| |
| private static JsProgram parseJs(String js) throws IOException, JsParserException { |
| JsProgram program = new JsProgram(); |
| List<JsStatement> statements = JsParser.parse(SourceOrigin.UNKNOWN, program.getScope(), |
| new StringReader(js)); |
| program.getGlobalBlock().getStatements().addAll(statements); |
| return program; |
| } |
| |
| private static String serializeJs(JsProgram program1) { |
| TextOutput text = new DefaultTextOutput(true); |
| JsVisitor generator = new JsSourceGenerationVisitor(text); |
| generator.accept(program1); |
| return text.toString(); |
| } |
| } |