From 83946e81028ee1407be09a78e7fbb92ff210de5d Mon Sep 17 00:00:00 2001 From: Sam Cao Date: Thu, 28 Nov 2024 12:25:55 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A6=84=20refactor:=20Resign=20ast=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../caoccao/javet/buddy/ts2java/Ts2Java.java | 2 +- .../caoccao/javet/buddy/ts2java/Ts2JavaX.java | 96 +++++++++++++ .../buddy/ts2java/ast/BaseTs2JavaAst.java | 40 ++++++ .../ts2java/ast/clazz/Ts2JavaAstFunction.java | 44 ++++-- .../ast/expr/lit/Ts2JavaAstNumber.java | 98 ++++++++++++++ .../ts2java/ast/interfaces/ITs2JavaAst.java | 3 + .../interfaces/ITs2JavaAstJsxAttrValue.java | 24 ++++ .../ast/interfaces/ITs2JavaAstKey.java | 24 ++++ .../ast/interfaces/ITs2JavaAstLit.java | 25 ++++ .../ast/interfaces/ITs2JavaAstPropName.java | 25 ++++ .../ast/interfaces/ITs2JavaAstTsLit.java | 24 ++++ .../ts2java/ast/memo/Ts2JavaMemoFunction.java | 104 ++++++++++++++ .../ts2java/ast/stmt/Ts2JavaAstBlockStmt.java | 11 ++ .../ast/stmt/Ts2JavaAstReturnStmt.java | 25 +++- .../ts2java/compiler/JavaLocalVariable.java | 2 +- .../caoccao/javet/buddy/ts2java/TsClassX.java | 127 ++++++++++++++++++ .../ts2java/ast/TestBasicOperations.java | 9 ++ 17 files changed, 672 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/caoccao/javet/buddy/ts2java/Ts2JavaX.java create mode 100644 src/main/java/com/caoccao/javet/buddy/ts2java/ast/expr/lit/Ts2JavaAstNumber.java create mode 100644 src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstJsxAttrValue.java create mode 100644 src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstKey.java create mode 100644 src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstLit.java create mode 100644 src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstPropName.java create mode 100644 src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstTsLit.java create mode 100644 src/test/java/com/caoccao/javet/buddy/ts2java/TsClassX.java diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/Ts2Java.java b/src/main/java/com/caoccao/javet/buddy/ts2java/Ts2Java.java index 4ddde1a..6ad85f3 100644 --- a/src/main/java/com/caoccao/javet/buddy/ts2java/Ts2Java.java +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/Ts2Java.java @@ -68,7 +68,7 @@ public void transpile() throws Swc4jCoreException { Swc4jParseOutput output = swc4j.parse(getTsCode(), swc4jParseOptions); Swc4jAstModule module = (Swc4jAstModule) output.getProgram(); if (module == null) { - throw new Ts2JavaException("The TypeScript code must be a script, not a module."); + throw new Ts2JavaException("The TypeScript code must be a module, not a script."); } if (module.getBody().isEmpty()) { throw new Ts2JavaException("The TypeScript code must contain at least one statement."); diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/Ts2JavaX.java b/src/main/java/com/caoccao/javet/buddy/ts2java/Ts2JavaX.java new file mode 100644 index 0000000..a192804 --- /dev/null +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/Ts2JavaX.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024. caoccao.com Sam Cao + * + * 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.caoccao.javet.buddy.ts2java; + +import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemoDynamicType; +import com.caoccao.javet.buddy.ts2java.ast.stmt.Ts2JavaAstClassDecl; +import com.caoccao.javet.buddy.ts2java.exceptions.Ts2JavaException; +import com.caoccao.javet.swc4j.Swc4j; +import com.caoccao.javet.swc4j.ast.program.Swc4jAstModule; +import com.caoccao.javet.swc4j.ast.stmt.Swc4jAstClassDecl; +import com.caoccao.javet.swc4j.enums.Swc4jMediaType; +import com.caoccao.javet.swc4j.enums.Swc4jParseMode; +import com.caoccao.javet.swc4j.exceptions.Swc4jCoreException; +import com.caoccao.javet.swc4j.options.Swc4jParseOptions; +import com.caoccao.javet.swc4j.outputs.Swc4jParseOutput; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class Ts2JavaX { + protected final static Swc4j swc4j = new Swc4j(); + protected final static Swc4jParseOptions swc4jParseOptions = new Swc4jParseOptions() + .setMediaType(Swc4jMediaType.TypeScript) + .setParseMode(Swc4jParseMode.Module) + .setCaptureAst(true); + protected final String packageName; + protected final String tsCode; + protected List> classes; + + public Ts2JavaX(String packageName, String tsCode) { + classes = new ArrayList<>(); + this.packageName = packageName; + this.tsCode = Objects.requireNonNull(tsCode); + } + + public List> getClasses() { + return classes; + } + + public String getPackageName() { + return packageName; + } + + public String getTsCode() { + return tsCode; + } + + public void transpile() throws Swc4jCoreException { + classes.clear(); + Swc4jParseOutput output = swc4j.parse(getTsCode(), swc4jParseOptions); + Swc4jAstModule module = (Swc4jAstModule) output.getProgram(); + if (module == null) { + throw new Ts2JavaException("The TypeScript code must be a module, not a script."); + } + if (module.getBody().isEmpty()) { + throw new Ts2JavaException("The TypeScript code must contain at least one statement."); + } + List classDecls = module.getBody().stream() + .filter(ast -> ast instanceof Swc4jAstClassDecl) + .map(ast -> (Swc4jAstClassDecl) ast) + .collect(Collectors.toList()); + if (classDecls.isEmpty()) { + throw new Ts2JavaException("There must be at least one class declaration in the TypeScript code."); + } + for (Swc4jAstClassDecl classDecl : classDecls) { + DynamicType.Builder builder = new ByteBuddy() + .subclass(Object.class, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR); + Ts2JavaMemoDynamicType memo = new Ts2JavaMemoDynamicType(builder); + Ts2JavaAstClassDecl ast = new Ts2JavaAstClassDecl(null, classDecl, memo, getPackageName()); + ast.compile(); + builder = memo.getBuilder(); + try (DynamicType.Unloaded unloadedType = builder.make()) { + classes.add(unloadedType.load(getClass().getClassLoader()).getLoaded()); + } + } + } +} diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/BaseTs2JavaAst.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/BaseTs2JavaAst.java index b515dce..20d5415 100644 --- a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/BaseTs2JavaAst.java +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/BaseTs2JavaAst.java @@ -18,7 +18,11 @@ import com.caoccao.javet.buddy.ts2java.ast.interfaces.ITs2JavaAst; import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemo; +import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemoFunction; import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAst; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.jar.asm.Label; +import net.bytebuddy.jar.asm.MethodVisitor; import java.util.Objects; @@ -27,11 +31,29 @@ public abstract class BaseTs2JavaAst parent; + protected TypeDescription type; public BaseTs2JavaAst(ITs2JavaAst parent, AST ast, Memo memo) { this.ast = Objects.requireNonNull(ast); this.memo = Objects.requireNonNull(memo); this.parent = parent; + type = TypeDescription.ForLoadedType.of(void.class); + } + + protected static Size mergeSize(Size... sizes) { + int sizeImpact = 0; + int maximalSize = 0; + for (Size size : sizes) { + sizeImpact += size.getSizeImpact(); + maximalSize = Math.max(maximalSize, size.getMaximalSize()); + } + return new Size(sizeImpact, maximalSize); + } + + protected static Size mergeSize(Size leftSize, Size rightSize) { + return new Size( + leftSize.getSizeImpact() + rightSize.getSizeImpact(), + Math.max(leftSize.getMaximalSize(), rightSize.getMaximalSize())); } @Override @@ -47,4 +69,22 @@ public Memo getMemo() { public ITs2JavaAst getParent() { return parent; } + + @Override + public TypeDescription getType() { + return type; + } + + protected void visitLineNumber(MethodVisitor methodVisitor) { + if (memo instanceof Ts2JavaMemoFunction) { + Ts2JavaMemoFunction memoFunction = (Ts2JavaMemoFunction) memo; + final int lineNumber = ast.getSpan().getLine(); + if (memoFunction.getLineNumber() < lineNumber) { + Label label = new Label(); + methodVisitor.visitLabel(label); + methodVisitor.visitLineNumber(lineNumber, label); + memoFunction.setLineNumber(lineNumber); + } + } + } } diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/clazz/Ts2JavaAstFunction.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/clazz/Ts2JavaAstFunction.java index 0fc37a8..3d790be 100644 --- a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/clazz/Ts2JavaAstFunction.java +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/clazz/Ts2JavaAstFunction.java @@ -17,18 +17,26 @@ package com.caoccao.javet.buddy.ts2java.ast.clazz; import com.caoccao.javet.buddy.ts2java.ast.BaseTs2JavaAst; +import com.caoccao.javet.buddy.ts2java.ast.Ts2JavaAstAccessibility; +import com.caoccao.javet.buddy.ts2java.ast.Ts2JavaAstParam; +import com.caoccao.javet.buddy.ts2java.ast.Ts2JavaAstTsTypeAnn; import com.caoccao.javet.buddy.ts2java.ast.interfaces.ITs2JavaAst; import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemoDynamicType; import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemoFunction; import com.caoccao.javet.buddy.ts2java.ast.stmt.Ts2JavaAstBlockStmt; import com.caoccao.javet.swc4j.ast.clazz.Swc4jAstFunction; import com.caoccao.javet.swc4j.ast.enums.Swc4jAstAccessibility; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeList; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.jar.asm.MethodVisitor; import java.util.Optional; public class Ts2JavaAstFunction extends BaseTs2JavaAst { - protected final boolean _static; protected final Swc4jAstAccessibility accessibility; protected final Optional body; protected final Ts2JavaMemoFunction memoFunction; @@ -42,16 +50,40 @@ public Ts2JavaAstFunction( boolean _static, Swc4jAstAccessibility accessibility) { super(parent, ast, memo); - this._static = _static; this.accessibility = accessibility; - memoFunction = new Ts2JavaMemoFunction(); + memoFunction = new Ts2JavaMemoFunction() + .setStatic(_static); this.name = name; body = ast.getBody().map(stmt -> new Ts2JavaAstBlockStmt(this, stmt, memoFunction)); } @Override - public void compile() { + public Size apply(MethodVisitor methodVisitor, Implementation.Context context) { + if (body.isPresent()) { + return body.get().apply(methodVisitor, context); + } + return Size.ZERO; + } + @Override + public void compile() { + final Visibility visibility = Ts2JavaAstAccessibility.getVisibility(accessibility); + final TypeDescription returnType = ast.getReturnType() + .map(Ts2JavaAstTsTypeAnn::getTypeDescription) + .orElse(TypeDescription.ForLoadedType.of(void.class)); + memoFunction.setReturnType(returnType); + ast.getParams().stream() + .map(Ts2JavaAstParam::getLocalVariable) + .forEach(memoFunction::addLocalVariable); + final TypeList parameters = memoFunction.getParameters(); + final int initialOffset = memoFunction.getMaxOffset(); + memoFunction.pushLexicalScope(); + body.ifPresent(Ts2JavaAstBlockStmt::compile); + memo.setBuilder(memo.getBuilder().defineMethod(name, returnType, visibility) + .withParameters(parameters.toArray(new TypeDescription[0])) + .intercept(Implementation.Simple.of( + (implementationTarget, instrumentedMethod) -> new StackManipulation.Simple(this::apply), + memoFunction.getMaxOffset() - initialOffset))); } public Swc4jAstAccessibility getAccessibility() { @@ -69,8 +101,4 @@ public Ts2JavaMemoFunction getMemoFunction() { public String getName() { return name; } - - public boolean isStatic() { - return _static; - } } diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/expr/lit/Ts2JavaAstNumber.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/expr/lit/Ts2JavaAstNumber.java new file mode 100644 index 0000000..44bf2c8 --- /dev/null +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/expr/lit/Ts2JavaAstNumber.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024. caoccao.com Sam Cao + * + * 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.caoccao.javet.buddy.ts2java.ast.expr.lit; + +import com.caoccao.javet.buddy.ts2java.ast.BaseTs2JavaAst; +import com.caoccao.javet.buddy.ts2java.ast.interfaces.ITs2JavaAst; +import com.caoccao.javet.buddy.ts2java.ast.interfaces.ITs2JavaAstLit; +import com.caoccao.javet.buddy.ts2java.ast.interfaces.ITs2JavaAstPropName; +import com.caoccao.javet.buddy.ts2java.ast.interfaces.ITs2JavaAstTsLit; +import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemoFunction; +import com.caoccao.javet.buddy.ts2java.exceptions.Ts2JavaAstException; +import com.caoccao.javet.swc4j.ast.expr.lit.Swc4jAstNumber; +import com.caoccao.javet.utils.SimpleFreeMarkerFormat; +import com.caoccao.javet.utils.SimpleMap; +import com.caoccao.javet.utils.StringUtils; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.implementation.bytecode.constant.DoubleConstant; +import net.bytebuddy.implementation.bytecode.constant.FloatConstant; +import net.bytebuddy.implementation.bytecode.constant.IntegerConstant; +import net.bytebuddy.implementation.bytecode.constant.LongConstant; +import net.bytebuddy.jar.asm.MethodVisitor; + +public class Ts2JavaAstNumber + extends BaseTs2JavaAst + implements ITs2JavaAstLit, + ITs2JavaAstPropName, + ITs2JavaAstTsLit { + public Ts2JavaAstNumber( + ITs2JavaAst parent, + Swc4jAstNumber ast, + TypeDescription type, + Ts2JavaMemoFunction memo) { + super(parent, ast, memo); + this.type = type; + } + + @Override + public Size apply(MethodVisitor methodVisitor, Implementation.Context context) { + visitLineNumber(methodVisitor); + StackManipulation stackManipulation; + final boolean isNegative = ast.getMinusCount() % 2 == 1; + if (type.represents(int.class) || type.represents(short.class) || type.represents(byte.class)) { + stackManipulation = IntegerConstant.forValue(isNegative ? -ast.asInt() : ast.asInt()); + } else if (type.represents(long.class)) { + stackManipulation = LongConstant.forValue(isNegative ? -ast.asLong() : ast.asLong()); + } else if (type.represents(float.class)) { + stackManipulation = FloatConstant.forValue(isNegative ? -ast.asFloat() : ast.asFloat()); + } else if (type.represents(double.class)) { + stackManipulation = DoubleConstant.forValue(isNegative ? -ast.asDouble() : ast.asDouble()); + } else { + throw new Ts2JavaAstException( + ast, + SimpleFreeMarkerFormat.format("Number type ${type} is not supported.", + SimpleMap.of("type", type.getName()))); + } + return stackManipulation.apply(methodVisitor, context); + } + + @Override + public void compile() { + if (type == null) { + type = ast.getRaw() + .map(raw -> { + if (StringUtils.isNotEmpty(raw)) { + if (raw.contains(".")) { + return TypeDescription.ForLoadedType.of(double.class); + } else { + long value = Long.parseLong(raw); + if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) { + return TypeDescription.ForLoadedType.of(int.class); + } else { + return TypeDescription.ForLoadedType.of(long.class); + } + } + } else { + return null; + } + }) + .orElse(TypeDescription.ForLoadedType.of(int.class)); + } + } +} diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAst.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAst.java index 6a69a69..37d1aeb 100644 --- a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAst.java +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAst.java @@ -21,6 +21,7 @@ import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAst; import com.caoccao.javet.utils.SimpleFreeMarkerFormat; import com.caoccao.javet.utils.SimpleMap; +import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.bytecode.StackManipulation; import net.bytebuddy.jar.asm.MethodVisitor; @@ -40,6 +41,8 @@ default Size apply(MethodVisitor methodVisitor, Implementation.Context context) Memo getMemo(); + TypeDescription getType(); + @Override default boolean isValid() { return true; diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstJsxAttrValue.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstJsxAttrValue.java new file mode 100644 index 0000000..a6cb84d --- /dev/null +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstJsxAttrValue.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024. caoccao.com Sam Cao + * + * 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.caoccao.javet.buddy.ts2java.ast.interfaces; + +import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemo; +import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAstJsxAttrValue; + +public interface ITs2JavaAstJsxAttrValue + extends ITs2JavaAst { +} diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstKey.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstKey.java new file mode 100644 index 0000000..099c3ed --- /dev/null +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstKey.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024. caoccao.com Sam Cao + * + * 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.caoccao.javet.buddy.ts2java.ast.interfaces; + +import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemo; +import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAstKey; + +public interface ITs2JavaAstKey + extends ITs2JavaAst { +} diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstLit.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstLit.java new file mode 100644 index 0000000..29e1f7b --- /dev/null +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstLit.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024. caoccao.com Sam Cao + * + * 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.caoccao.javet.buddy.ts2java.ast.interfaces; + +import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemo; +import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAstLit; +import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAstPat; + +public interface ITs2JavaAstLit + extends ITs2JavaAstExpr, ITs2JavaAstJsxAttrValue { +} diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstPropName.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstPropName.java new file mode 100644 index 0000000..b849854 --- /dev/null +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstPropName.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024. caoccao.com Sam Cao + * + * 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.caoccao.javet.buddy.ts2java.ast.interfaces; + +import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemo; +import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAstPat; +import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAstPropName; + +public interface ITs2JavaAstPropName + extends ITs2JavaAstKey { +} diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstTsLit.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstTsLit.java new file mode 100644 index 0000000..5d201e2 --- /dev/null +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/interfaces/ITs2JavaAstTsLit.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024. caoccao.com Sam Cao + * + * 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.caoccao.javet.buddy.ts2java.ast.interfaces; + +import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemo; +import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAstTsLit; + +public interface ITs2JavaAstTsLit + extends ITs2JavaAst { +} diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/memo/Ts2JavaMemoFunction.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/memo/Ts2JavaMemoFunction.java index 1f99ee1..68355c1 100644 --- a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/memo/Ts2JavaMemoFunction.java +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/memo/Ts2JavaMemoFunction.java @@ -16,5 +16,109 @@ package com.caoccao.javet.buddy.ts2java.ast.memo; +import com.caoccao.javet.buddy.ts2java.compiler.JavaLexicalScope; +import com.caoccao.javet.buddy.ts2java.compiler.JavaLocalVariable; +import com.caoccao.javet.buddy.ts2java.exceptions.Ts2JavaException; +import com.caoccao.javet.utils.SimpleFreeMarkerFormat; +import com.caoccao.javet.utils.SimpleList; +import com.caoccao.javet.utils.SimpleMap; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.description.type.TypeList; + +import java.util.List; +import java.util.stream.Collectors; + public class Ts2JavaMemoFunction extends Ts2JavaMemo { + private final List lexicalScopes; + protected boolean _static; + protected int lineNumber; + protected int maxOffset; + protected int nextOffset; + protected TypeDescription returnType; + + public Ts2JavaMemoFunction() { + _static = false; + lexicalScopes = SimpleList.of(new JavaLexicalScope(0)); + lineNumber = -1; + maxOffset = nextOffset = _static ? 0 : 1; + returnType = TypeDescription.ForLoadedType.of(void.class); + } + + public void addLocalVariable(JavaLocalVariable localVariable) { + JavaLexicalScope lexicalScope = lexicalScopes.get(lexicalScopes.size() - 1); + lexicalScope.putLocalVariable(localVariable); + localVariable.setOffset(nextOffset); + nextOffset += localVariable.getType().getStackSize().getSize(); + if (nextOffset > maxOffset) { + maxOffset = nextOffset; + } + } + + public int getLineNumber() { + return lineNumber; + } + + public JavaLocalVariable getLocalVariable(String name) { + for (int lexicalScopeIndex = lexicalScopes.size() - 1; lexicalScopeIndex >= 0; lexicalScopeIndex--) { + JavaLexicalScope lexicalScope = lexicalScopes.get(lexicalScopeIndex); + JavaLocalVariable localVariable = lexicalScope.getLocalVariable(name); + if (localVariable != null) { + return localVariable; + } + } + throw new Ts2JavaException( + SimpleFreeMarkerFormat.format("The variable ${name} is not defined.", + SimpleMap.of("name", name))); + } + + public int getMaxOffset() { + return maxOffset; + } + + public int getNextOffset() { + return nextOffset; + } + + public TypeList getParameters() { + return new TypeList.Explicit( + lexicalScopes.get(0).getLocalVariables().stream() + .map(JavaLocalVariable::getType) + .collect(Collectors.toList())); + } + + public TypeDescription getReturnType() { + return returnType; + } + + public boolean isStatic() { + return _static; + } + + public Ts2JavaMemoFunction popLexicalScope() { + JavaLexicalScope lexicalScope = lexicalScopes.remove(lexicalScopes.size() - 1); + nextOffset -= lexicalScope.getLocalVariables().stream() + .mapToInt(v -> v.getType().getStackSize().getSize()) + .sum(); + return this; + } + + public Ts2JavaMemoFunction pushLexicalScope() { + lexicalScopes.add(new JavaLexicalScope(lexicalScopes.size())); + return this; + } + + public Ts2JavaMemoFunction setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + return this; + } + + public Ts2JavaMemoFunction setReturnType(TypeDescription returnType) { + this.returnType = returnType; + return this; + } + + public Ts2JavaMemoFunction setStatic(boolean _static) { + this._static = _static; + return this; + } } diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/stmt/Ts2JavaAstBlockStmt.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/stmt/Ts2JavaAstBlockStmt.java index cc04750..e27ba36 100644 --- a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/stmt/Ts2JavaAstBlockStmt.java +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/stmt/Ts2JavaAstBlockStmt.java @@ -27,6 +27,8 @@ import com.caoccao.javet.swc4j.ast.stmt.Swc4jAstReturnStmt; import com.caoccao.javet.utils.SimpleFreeMarkerFormat; import com.caoccao.javet.utils.SimpleMap; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.jar.asm.MethodVisitor; import java.util.ArrayList; import java.util.List; @@ -60,7 +62,16 @@ public Ts2JavaAstBlockStmt( } } + @Override + public Size apply(MethodVisitor methodVisitor, Implementation.Context context) { + return stmts.stream() + .map((stmt) -> stmt.apply(methodVisitor, context)) + .reduce(BaseTs2JavaAst::mergeSize) + .orElse(Size.ZERO); + } + @Override public void compile() { + stmts.forEach(ITs2JavaAstStmt::compile); } } diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/stmt/Ts2JavaAstReturnStmt.java b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/stmt/Ts2JavaAstReturnStmt.java index c15cc51..95c8cb2 100644 --- a/src/main/java/com/caoccao/javet/buddy/ts2java/ast/stmt/Ts2JavaAstReturnStmt.java +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/ast/stmt/Ts2JavaAstReturnStmt.java @@ -17,15 +17,22 @@ package com.caoccao.javet.buddy.ts2java.ast.stmt; import com.caoccao.javet.buddy.ts2java.ast.BaseTs2JavaAst; +import com.caoccao.javet.buddy.ts2java.ast.expr.lit.Ts2JavaAstNumber; import com.caoccao.javet.buddy.ts2java.ast.interfaces.ITs2JavaAst; import com.caoccao.javet.buddy.ts2java.ast.interfaces.ITs2JavaAstExpr; import com.caoccao.javet.buddy.ts2java.ast.interfaces.ITs2JavaAstStmt; import com.caoccao.javet.buddy.ts2java.ast.memo.Ts2JavaMemoFunction; +import com.caoccao.javet.buddy.ts2java.compiler.JavaClassCast; import com.caoccao.javet.buddy.ts2java.exceptions.Ts2JavaAstException; +import com.caoccao.javet.swc4j.ast.expr.lit.Swc4jAstNumber; import com.caoccao.javet.swc4j.ast.interfaces.ISwc4jAstExpr; import com.caoccao.javet.swc4j.ast.stmt.Swc4jAstReturnStmt; import com.caoccao.javet.utils.SimpleFreeMarkerFormat; import com.caoccao.javet.utils.SimpleMap; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.member.MethodReturn; +import net.bytebuddy.jar.asm.MethodVisitor; import java.util.Optional; @@ -47,7 +54,7 @@ public Ts2JavaAstReturnStmt( // TODO break; case Number: - // TODO + arg = new Ts2JavaAstNumber(this, expr.as(Swc4jAstNumber.class), null, memo); break; case Ident: // TODO @@ -67,8 +74,24 @@ public Ts2JavaAstReturnStmt( } } + @Override + public Size apply(MethodVisitor methodVisitor, Implementation.Context context) { + visitLineNumber(methodVisitor); + if (arg.isPresent()) { + Size sizeArg = arg.get().apply(methodVisitor, context); + TypeDescription argType = arg.get().getType(); + Size sizeCast = JavaClassCast.getUpCastStackManipulation(argType, memo.getReturnType()) + .map(stackManipulation -> stackManipulation.apply(methodVisitor, context)) + .orElse(Size.ZERO); + Size sizeReturn = MethodReturn.of(memo.getReturnType()).apply(methodVisitor, context); + return mergeSize(sizeArg, sizeCast, sizeReturn); + } + return Size.ZERO; + } + @Override public void compile() { + arg.ifPresent(ITs2JavaAstExpr::compile); } public Optional> getArg() { diff --git a/src/main/java/com/caoccao/javet/buddy/ts2java/compiler/JavaLocalVariable.java b/src/main/java/com/caoccao/javet/buddy/ts2java/compiler/JavaLocalVariable.java index 206505f..baeaa28 100644 --- a/src/main/java/com/caoccao/javet/buddy/ts2java/compiler/JavaLocalVariable.java +++ b/src/main/java/com/caoccao/javet/buddy/ts2java/compiler/JavaLocalVariable.java @@ -43,7 +43,7 @@ public TypeDescription getType() { return type; } - void setOffset(int offset) { + public void setOffset(int offset) { this.offset = offset; } } diff --git a/src/test/java/com/caoccao/javet/buddy/ts2java/TsClassX.java b/src/test/java/com/caoccao/javet/buddy/ts2java/TsClassX.java new file mode 100644 index 0000000..8050904 --- /dev/null +++ b/src/test/java/com/caoccao/javet/buddy/ts2java/TsClassX.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024. caoccao.com Sam Cao + * + * 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.caoccao.javet.buddy.ts2java; + +import com.caoccao.javet.swc4j.exceptions.Swc4jCoreException; +import com.caoccao.javet.swc4j.utils.SimpleList; +import com.caoccao.javet.utils.SimpleFreeMarkerFormat; +import com.caoccao.javet.utils.SimpleMap; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.*; + +public final class TsClassX { + private final List arguments; + private final String body; + private final Class returnType; + private Object instance; + private Class testClass; + private Method testMethod; + + public TsClassX(String body, Class returnType, TsMethodArgument... arguments) { + assertNotNull(body); + assertNotNull(returnType); + assertNotNull(arguments); + this.arguments = SimpleList.immutableOf(arguments); + this.body = body; + this.returnType = returnType; + instance = null; + testClass = null; + testMethod = null; + init(); + } + + public List getArguments() { + return arguments; + } + + public String getBody() { + return body; + } + + public Object getInstance() throws Exception { + if (instance == null) { + instance = testClass.getConstructor().newInstance(); + } + return instance; + } + + public Class getReturnType() { + return returnType; + } + + public Class getTestClass() { + return testClass; + } + + public Method getTestMethod() { + return testMethod; + } + + private void init() { + String argumentsString = arguments.stream() + .map(TsMethodArgument::toString) + .collect(Collectors.joining(", ")); + String codeString = SimpleFreeMarkerFormat.format( + "class Test {\n" + + " public test(${argumentsString}): ${returnType} {\n" + + " ${body}\n" + + " }\n" + + "}\n", + SimpleMap.of( + "argumentsString", argumentsString, + "returnType", returnType.getName(), + "body", body)); + Ts2JavaX ts2Java = new Ts2JavaX("com.test", codeString); + try { + ts2Java.transpile(); + } catch (Swc4jCoreException e) { + fail(e); + } + List> classes = ts2Java.getClasses(); + assertEquals(1, classes.size()); + testClass = classes.get(0); + assertEquals("Test", testClass.getSimpleName()); + assertEquals("com.test.Test", testClass.getName()); + try { + testMethod = testClass.getMethod( + "test", + arguments.stream().map(TsMethodArgument::getType).toArray(Class[]::new)); + assertEquals(returnType, testMethod.getReturnType()); + final int argumentsLength = arguments.size(); + final Parameter[] parameters = testMethod.getParameters(); + assertEquals(argumentsLength, testMethod.getParameterCount()); + assertEquals(argumentsLength, parameters.length); + IntStream.range(0, argumentsLength) + .forEach(i -> assertEquals( + arguments.get(i).getType(), + parameters[i].getType(), + "Argument[" + i + "] type mismatch")); + } catch (NoSuchMethodException e) { + fail(e); + } + } + + public Object invoke(Object... arguments) throws Exception { + return testMethod.invoke(getInstance(), arguments); + } +} diff --git a/src/test/java/com/caoccao/javet/buddy/ts2java/ast/TestBasicOperations.java b/src/test/java/com/caoccao/javet/buddy/ts2java/ast/TestBasicOperations.java index 182405c..8105d93 100644 --- a/src/test/java/com/caoccao/javet/buddy/ts2java/ast/TestBasicOperations.java +++ b/src/test/java/com/caoccao/javet/buddy/ts2java/ast/TestBasicOperations.java @@ -18,6 +18,7 @@ import com.caoccao.javet.buddy.ts2java.BaseTestTs2Java; import com.caoccao.javet.buddy.ts2java.TsClass; +import com.caoccao.javet.buddy.ts2java.TsClassX; import com.caoccao.javet.buddy.ts2java.TsMethodArgument; import org.junit.jupiter.api.Test; @@ -229,6 +230,14 @@ public void testMultiply_II_I() throws Exception { assertEquals(3 * 2, tsClass.invoke(3, 2)); } + @Test + public void testNumber() throws Exception { + TsClassX tsClass = new TsClassX( + "return 1;", + int.class); + assertEquals(1, tsClass.invoke()); + } + @Test public void testPow_DD_D() throws Exception { assertEquals(8D, pow(2D, 3D), 0.001D);