add more code generation tuning

- allow adapting case statements
- add an annotation to substitute a Java method body with plain raw
TypeScript
This commit is contained in:
Renaud Pawlak 2017-01-25 05:22:10 +01:00
parent 6be33221dc
commit 5b1cf57841
8 changed files with 220 additions and 70 deletions

View File

@ -0,0 +1,46 @@
/*
* JSweet - http://www.jsweet.org
* Copyright (C) 2015 CINCHEO SAS <renaud.pawlak@cincheo.fr>
*
* 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 jsweet.lang;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation allows the programmer to substitute a method body
* implementation by a TypeScript implementation.
*
* <p>
* The annotation's value contains TypeScript which is generated as is by the
* JSweet transpiler. The code will be checked by the TypeScript transpiler.
*
* @author Renaud Pawlak
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface TypeScriptBody {
/**
* The code that will be generated by the transpiler in place of the
* annotated method body.
*/
java.lang.String value();
}

View File

@ -237,6 +237,7 @@ public abstract class JSweetConfig {
public static final String ANNOTATION_ROOT = JSweetConfig.LANG_PACKAGE + ".Root";
public static final String ANNOTATION_NAME = JSweetConfig.LANG_PACKAGE + ".Name";
public static final String ANNOTATION_DECORATOR = JSweetConfig.LANG_PACKAGE + ".Decorator";
public static final String ANNOTATION_TYPE_SCRIPT_BODY = JSweetConfig.LANG_PACKAGE + ".TypeScriptBody";
public static final String ANNOTATION_FUNCTIONAL_INTERFACE = FunctionalInterface.class.getName();
/**

View File

@ -285,6 +285,10 @@ public class RemoveJavaDependenciesAdapter<C extends JSweetContext> extends Java
printMacroName(targetMethodName);
print(invocation.args.head).print(".indexOf(").printArgList(invocation.args.tail).print(")");
return true;
case "sort":
printMacroName(targetMethodName);
print(invocation.args.head).print(".sort(").printArgList(invocation.args.tail).print(")");
return true;
}
break;
case "java.util.Arrays":

View File

@ -1690,28 +1690,52 @@ public class Java2TypeScriptTranslator<C extends JSweetContext> extends Abstract
endIndent().println().printIndent().print("}");
} else {
print(" ").print("{").println().startIndent();
enter(methodDecl.getBody());
if (!methodDecl.getBody().stats.isEmpty()
&& methodDecl.getBody().stats.head.toString().startsWith("super(")) {
printBlockStatement(methodDecl.getBody().stats.head);
if (parent != null) {
printInstanceInitialization(parent, methodDecl.sym);
}
printBlockStatements(methodDecl.getBody().stats.tail);
if (context.hasAnnotationType(methodDecl.sym, JSweetConfig.ANNOTATION_TYPE_SCRIPT_BODY)) {
String replacedBody = (String) context.getAnnotationValue(methodDecl.sym,
JSweetConfig.ANNOTATION_TYPE_SCRIPT_BODY, null);
printIndent().print(replacedBody).println();
} else {
if (parent != null) {
printInstanceInitialization(parent, methodDecl.sym);
enter(methodDecl.getBody());
if (!methodDecl.getBody().stats.isEmpty()
&& methodDecl.getBody().stats.head.toString().startsWith("super(")) {
printBlockStatement(methodDecl.getBody().stats.head);
if (parent != null) {
printInstanceInitialization(parent, methodDecl.sym);
}
printBlockStatements(methodDecl.getBody().stats.tail);
} else {
if (parent != null) {
printInstanceInitialization(parent, methodDecl.sym);
}
printBlockStatements(methodDecl.getBody().stats);
}
printBlockStatements(methodDecl.getBody().stats);
exit();
}
endIndent().printIndent().print("}");
exit();
}
}
}
}
private void printInstanceInitialization(JCClassDecl clazz, MethodSymbol method) {
protected void printVariableInitialization(JCClassDecl clazz, JCVariableDecl var) {
if (getScope().innerClassNotStatic && !Util.isConstantOrNullField(var)) {
String name = var.getName().toString();
if (context.getFieldNameMapping(var.sym) != null) {
name = context.getFieldNameMapping(var.sym);
}
printIndent().print("this.").print(name).print(" = ").print(var.init).print(";").println();
} else if (var.init == null && Util.isCoreType(var.type)) {
String name = var.getName().toString();
if (context.getFieldNameMapping(var.sym) != null) {
name = context.getFieldNameMapping(var.sym);
}
printIndent().print("this.").print(name).print(" = ").print(Util.getTypeInitialValue(var.type)).print(";")
.println();
}
}
protected void printInstanceInitialization(JCClassDecl clazz, MethodSymbol method) {
if (getContext().options.isInterfaceTracking() && method == null || method.isConstructor()) {
getScope().hasDeclaredConstructor = true;
if (getScope().innerClassNotStatic) {
@ -1721,20 +1745,7 @@ public class Java2TypeScriptTranslator<C extends JSweetContext> extends Abstract
for (JCTree member : clazz.defs) {
if (member instanceof JCVariableDecl) {
JCVariableDecl var = (JCVariableDecl) member;
if (getScope().innerClassNotStatic && !Util.isConstantOrNullField(var)) {
String name = var.getName().toString();
if (context.getFieldNameMapping(var.sym) != null) {
name = context.getFieldNameMapping(var.sym);
}
printIndent().print("this.").print(name).print(" = ").print(var.init).print(";").println();
} else if (var.init == null && Util.isCoreType(var.type)) {
String name = var.getName().toString();
if (context.getFieldNameMapping(var.sym) != null) {
name = context.getFieldNameMapping(var.sym);
}
printIndent().print("this.").print(name).print(" = ").print(Util.getTypeInitialValue(var.type))
.print(";").println();
}
printVariableInitialization(clazz, var);
} else if (member instanceof JCBlock) {
JCBlock block = (JCBlock) member;
if (!block.isStatic()) {
@ -1798,37 +1809,43 @@ public class Java2TypeScriptTranslator<C extends JSweetContext> extends Abstract
}
}
enter(method.getBody());
com.sun.tools.javac.util.List<JCStatement> stats = skipFirst ? method.getBody().stats.tail
: method.getBody().stats;
if (!stats.isEmpty() && stats.head.toString().startsWith("super(")) {
printBlockStatement(stats.head);
printFieldInitializations();
if (!initialized) {
printInstanceInitialization(getParent(JCClassDecl.class), method.sym);
}
if (!stats.tail.isEmpty()) {
printIndent().print("((").print(") => {").startIndent().println();
printBlockStatements(stats.tail);
endIndent().printIndent().print("})(").print(");").println();
}
if (context.hasAnnotationType(method.sym, JSweetConfig.ANNOTATION_TYPE_SCRIPT_BODY)) {
String replacedBody = (String) context.getAnnotationValue(method.sym,
JSweetConfig.ANNOTATION_TYPE_SCRIPT_BODY, null);
printIndent().print(replacedBody).println();
} else {
if (!initialized) {
printInstanceInitialization(getParent(JCClassDecl.class), method.sym);
}
if (!stats.isEmpty() || !method.sym.isConstructor()) {
printIndent();
}
if (!method.sym.isConstructor()) {
print("return <any>");
}
if (!stats.isEmpty() || !method.sym.isConstructor()) {
print("((").print(") => {").startIndent().println();
printBlockStatements(stats);
endIndent().printIndent().print("})(").print(");").println();
enter(method.getBody());
com.sun.tools.javac.util.List<JCStatement> stats = skipFirst ? method.getBody().stats.tail
: method.getBody().stats;
if (!stats.isEmpty() && stats.head.toString().startsWith("super(")) {
printBlockStatement(stats.head);
printFieldInitializations();
if (!initialized) {
printInstanceInitialization(getParent(JCClassDecl.class), method.sym);
}
if (!stats.tail.isEmpty()) {
printIndent().print("((").print(") => {").startIndent().println();
printBlockStatements(stats.tail);
endIndent().printIndent().print("})(").print(");").println();
}
} else {
if (!initialized) {
printInstanceInitialization(getParent(JCClassDecl.class), method.sym);
}
if (!stats.isEmpty() || !method.sym.isConstructor()) {
printIndent();
}
if (!method.sym.isConstructor()) {
print("return <any>");
}
if (!stats.isEmpty() || !method.sym.isConstructor()) {
print("((").print(") => {").startIndent().println();
printBlockStatements(stats);
endIndent().printIndent().print("})(").print(");").println();
}
}
exit();
}
exit();
} else {
String returnValue = Util.getTypeInitialValue(method.sym.getReturnType());
if (returnValue != null) {
@ -3431,18 +3448,23 @@ public class Java2TypeScriptTranslator<C extends JSweetContext> extends Abstract
printIndent().print("}");
}
protected void printCaseStatementPattern(JCExpression pattern) {
}
@Override
public void visitCase(JCCase caseStatement) {
if (caseStatement.pat != null) {
print("case ");
if (caseStatement.pat.type.isPrimitive()
|| String.class.getName().equals(caseStatement.pat.type.toString())) {
print(caseStatement.pat);
} else {
if (context.useModules) {
print(caseStatement.pat.type.tsym.getSimpleName() + "." + caseStatement.pat);
if (!getAdapter().substituteCaseStatementPattern(caseStatement, caseStatement.pat)) {
if (caseStatement.pat.type.isPrimitive()
|| String.class.getName().equals(caseStatement.pat.type.toString())) {
print(caseStatement.pat);
} else {
print(getRootRelativeName(caseStatement.pat.type.tsym) + "." + caseStatement.pat);
if (context.useModules) {
print(caseStatement.pat.type.tsym.getSimpleName() + "." + caseStatement.pat);
} else {
print(getRootRelativeName(caseStatement.pat.type.tsym) + "." + caseStatement.pat);
}
}
}
} else {

View File

@ -36,6 +36,7 @@ import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCArrayAccess;
import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCCase;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop;
@ -487,4 +488,12 @@ public abstract class AbstractPrinterAdapter<C extends JSweetContext> {
public boolean substituteInstanceof(String exprStr, JCTree expr, Type type) {
return false;
}
/**
* Substitutes if necessary the pattern of a case statement.
*/
public boolean substituteCaseStatementPattern(JCCase caseStatement, JCExpression pattern) {
return false;
}
}

View File

@ -21,8 +21,11 @@ import static org.jsweet.transpiler.JSweetProblem.GLOBAL_INDEXER_GET;
import static org.jsweet.transpiler.JSweetProblem.GLOBAL_INDEXER_SET;
import static org.junit.Assert.assertEquals;
import org.jsweet.transpiler.JSweetContext;
import org.jsweet.transpiler.JSweetFactory;
import org.jsweet.transpiler.JSweetProblem;
import org.jsweet.transpiler.ModuleKind;
import org.jsweet.transpiler.typescript.Java2TypeScriptAdapter;
import org.junit.Assert;
import org.junit.Test;
@ -53,6 +56,7 @@ import source.structural.NoWildcardsInImports;
import source.structural.ObjectTypes;
import source.structural.StaticMembersInInterfaces;
import source.structural.TwoClassesInSameFile;
import source.structural.TypeScriptBodyAnnotation;
import source.structural.WrongConstructsInInterfaces;
import source.structural.WrongThisAccessOnStatic;
import source.structural.globalclasses.Globals;
@ -81,7 +85,8 @@ public class StructuralTests extends AbstractTest {
@Test
public void testVariableMethodNameClashes() {
transpile(logHandler -> {
logHandler.assertReportedProblems(JSweetProblem.HIDDEN_INVOCATION, JSweetProblem.HIDDEN_INVOCATION, JSweetProblem.HIDDEN_INVOCATION);
logHandler.assertReportedProblems(JSweetProblem.HIDDEN_INVOCATION, JSweetProblem.HIDDEN_INVOCATION,
JSweetProblem.HIDDEN_INVOCATION);
}, getSourceFile(NameClashesWithMethodInvocations.class));
}
@ -188,7 +193,7 @@ public class StructuralTests extends AbstractTest {
assertEquals("There should be no errors", 0, logHandler.reportedProblems.size());
}, getSourceFile(InterfaceInheritance.class));
}
@Test
public void testInstanceofForInterfaces() {
eval((logHandler, r) -> {
@ -232,7 +237,8 @@ public class StructuralTests extends AbstractTest {
// Assert.assertEquals("invoked1_2", r.get("Static"));
Assert.assertEquals("invoked1_2", r.get("Ok"));
Assert.assertEquals("invoked1_2", r.get("test2"));
}, getSourceFile(Globals.class), getSourceFile(source.structural.globalclasses.e.Globals.class), getSourceFile(GlobalFunctionAccessFromMain.class));
}, getSourceFile(Globals.class), getSourceFile(source.structural.globalclasses.e.Globals.class),
getSourceFile(GlobalFunctionAccessFromMain.class));
}
@Test
@ -257,7 +263,8 @@ public class StructuralTests extends AbstractTest {
Assert.assertEquals("A method was not executed as expected", true, r.get("m2"));
Assert.assertEquals("A method was not executed as expected", true, r.get("sm1"));
Assert.assertEquals("A method was not executed as expected", true, r.get("sm2"));
}, getSourceFile(AutoImportClassesInSamePackageUsed.class), getSourceFile(AutoImportClassesInSamePackage.class));
}, getSourceFile(AutoImportClassesInSamePackageUsed.class),
getSourceFile(AutoImportClassesInSamePackage.class));
}
@Test
@ -271,8 +278,9 @@ public class StructuralTests extends AbstractTest {
@Test
public void testWrongGlobals() {
transpile(logHandler -> {
logHandler.assertReportedProblems(JSweetProblem.GLOBALS_CLASS_CANNOT_HAVE_SUPERCLASS, JSweetProblem.GLOBAL_CONSTRUCTOR_DEF,
JSweetProblem.GLOBALS_CAN_ONLY_HAVE_STATIC_MEMBERS, JSweetProblem.GLOBALS_CAN_ONLY_HAVE_STATIC_MEMBERS);
logHandler.assertReportedProblems(JSweetProblem.GLOBALS_CLASS_CANNOT_HAVE_SUPERCLASS,
JSweetProblem.GLOBAL_CONSTRUCTOR_DEF, JSweetProblem.GLOBALS_CAN_ONLY_HAVE_STATIC_MEMBERS,
JSweetProblem.GLOBALS_CAN_ONLY_HAVE_STATIC_MEMBERS);
}, getSourceFile(source.structural.wrongglobals.Globals.class));
}
@ -286,7 +294,8 @@ public class StructuralTests extends AbstractTest {
@Test
public void testWrongThisAccessOnStatic() {
transpile(ModuleKind.none, logHandler -> {
logHandler.assertReportedProblems(JSweetProblem.CANNOT_ACCESS_STATIC_MEMBER_ON_THIS, JSweetProblem.CANNOT_ACCESS_STATIC_MEMBER_ON_THIS);
logHandler.assertReportedProblems(JSweetProblem.CANNOT_ACCESS_STATIC_MEMBER_ON_THIS,
JSweetProblem.CANNOT_ACCESS_STATIC_MEMBER_ON_THIS);
}, getSourceFile(WrongThisAccessOnStatic.class));
}
@ -304,13 +313,36 @@ public class StructuralTests extends AbstractTest {
}, getSourceFile(JSNI.class));
}
@Test
public void testTypeScriptBody() {
createTranspiler(new JSweetFactory<JSweetContext>() {
@Override
public Java2TypeScriptAdapter<JSweetContext> createAdapter(JSweetContext context) {
return new Java2TypeScriptAdapter<JSweetContext>(context) {
{
context.addAnnotation("@TypeScriptBody('return (this.i + 2)')",
"source.structural.TypeScriptBodyAnnotation.m2()");
}
};
}
});
eval(ModuleKind.none, (logHandler, r) -> {
logHandler.assertReportedProblems();
assertEquals(2, (int) r.get("test1"));
assertEquals(3, (int) r.get("test2"));
assertEquals(1, (int) r.get("test3"));
}, getSourceFile(TypeScriptBodyAnnotation.class));
createTranspiler(new JSweetFactory<JSweetContext>());
}
@Test
public void testDefaultMethods() {
// TODO: make it work with modules
eval(ModuleKind.none, (logHandler, r) -> {
logHandler.assertReportedProblems();
assertEquals("m,m1,m2-overriden", r.get("trace"));
}, getSourceFile(ClassWithStaticMethod.class), getSourceFile(DefaultMethods.class), getSourceFile(DefaultMethodsConsumer.class));
}, getSourceFile(ClassWithStaticMethod.class), getSourceFile(DefaultMethods.class),
getSourceFile(DefaultMethodsConsumer.class));
}
@Test

View File

@ -174,6 +174,12 @@ public class Collections implements Cloneable, Serializable {
trace.push("" + (java.util.Arrays.deepEquals(points1, points2)));
List<String> l3 = Arrays.asList(a3);
java.util.Collections.sort(l3);
java.util.Collections.sort(l3, c);
$export("trace", trace.join(","));
}

View File

@ -0,0 +1,30 @@
package source.structural;
import static jsweet.util.Globals.$export;
import jsweet.lang.TypeScriptBody;
public class TypeScriptBodyAnnotation {
public static void main(String[] args) {
$export("test1", new TypeScriptBodyAnnotation().m1());
$export("test2", new TypeScriptBodyAnnotation().m2());
$export("test3", new TypeScriptBodyAnnotation().m3());
}
int i = 1;
@TypeScriptBody("return this.i + 1;")
public int m1() {
return i;
}
public int m2() {
return i;
}
public int m3() {
return i;
}
}