diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a02142d6..48150795 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -72,13 +72,13 @@ jobs: distribution: 'temurin' cache: 'maven' # Need to install all Python versions in the same run for tox - - name: Python 3.9, Python 3.10, Python 3.11 Setup + - name: Python 3.10, Python 3.11, Python 3.12 Setup uses: actions/setup-python@v4 with: python-version: | - 3.9 3.10 3.11 + 3.12 cache: 'pip' cache-dependency-path: | **/setup.py diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 537d10d8..a71e9907 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -77,13 +77,13 @@ jobs: key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 # Need to install all Python versions in the same run for tox - - name: Python 3.9, Python 3.10, Python 3.11 Setup + - name: Python 3.10, Python 3.11, Python 3.12 Setup uses: actions/setup-python@v4 with: python-version: | - 3.9 3.10 3.11 + 3.12 cache: 'pip' cache-dependency-path: | **/setup.py diff --git a/jpyinterpreter/setup.py b/jpyinterpreter/setup.py index 1b608dca..0f1b6359 100644 --- a/jpyinterpreter/setup.py +++ b/jpyinterpreter/setup.py @@ -77,9 +77,9 @@ def run(self): 'org-stubs': 'src/main/resources', }, test_suite='tests', - python_requires='>=3.9', + python_requires='>=3.10', install_requires=[ - 'JPype1>=1.4.1', + 'JPype1>=1.5.0', ], cmdclass={'build_py': FetchDependencies}, package_data={ diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CompareOp.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CompareOp.java index 276b8ddd..8ef59bd1 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CompareOp.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/CompareOp.java @@ -1,17 +1,19 @@ package ai.timefold.jpyinterpreter; +import java.util.Objects; + public enum CompareOp { - LESS_THAN(0, "__lt__"), - LESS_THAN_OR_EQUALS(1, "__le__"), - EQUALS(2, "__eq__"), - NOT_EQUALS(3, "__ne__"), - GREATER_THAN(4, "__gt__"), - GREATER_THAN_OR_EQUALS(5, "__ge__"); + LESS_THAN("<", "__lt__"), + LESS_THAN_OR_EQUALS("<=", "__le__"), + EQUALS("==", "__eq__"), + NOT_EQUALS("!=", "__ne__"), + GREATER_THAN(">", "__gt__"), + GREATER_THAN_OR_EQUALS(">=", "__ge__"); - public final int id; + public final String id; public final String dunderMethod; - CompareOp(int id, String dunderMethod) { + CompareOp(String id, String dunderMethod) { this.id = id; this.dunderMethod = dunderMethod; } @@ -25,9 +27,9 @@ public static CompareOp getOpForDunderMethod(String dunderMethod) { throw new IllegalArgumentException("No Op corresponds to dunder method (" + dunderMethod + ")"); } - public static CompareOp getOp(int id) { + public static CompareOp getOp(String id) { for (CompareOp op : CompareOp.values()) { - if (op.id == id) { + if (Objects.equals(op.id, id)) { return op; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeInstruction.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeInstruction.java index d7dbe8ac..4999f56d 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeInstruction.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeInstruction.java @@ -4,10 +4,11 @@ import ai.timefold.jpyinterpreter.opcodes.descriptor.OpcodeDescriptor; -public record PythonBytecodeInstruction(String opname, int offset, int arg, OptionalInt startsLine, +public record PythonBytecodeInstruction(String opname, int offset, int arg, + String argRepr, OptionalInt startsLine, boolean isJumpTarget) { public static PythonBytecodeInstruction atOffset(String opname, int offset) { - return new PythonBytecodeInstruction(opname, offset, 0, OptionalInt.empty(), false); + return new PythonBytecodeInstruction(opname, offset, 0, "", OptionalInt.empty(), false); } public static PythonBytecodeInstruction atOffset(OpcodeDescriptor instruction, int offset) { @@ -15,19 +16,24 @@ public static PythonBytecodeInstruction atOffset(OpcodeDescriptor instruction, i } public PythonBytecodeInstruction withArg(int newArg) { - return new PythonBytecodeInstruction(opname, offset, newArg, startsLine, isJumpTarget); + return new PythonBytecodeInstruction(opname, offset, newArg, argRepr, startsLine, isJumpTarget); + } + + public PythonBytecodeInstruction withArgRepr(String newArgRepr) { + return new PythonBytecodeInstruction(opname, offset, arg, newArgRepr, startsLine, isJumpTarget); } public PythonBytecodeInstruction startsLine(int lineNumber) { - return new PythonBytecodeInstruction(opname, offset, arg, OptionalInt.of(lineNumber), isJumpTarget); + return new PythonBytecodeInstruction(opname, offset, arg, argRepr, OptionalInt.of(lineNumber), + isJumpTarget); } public PythonBytecodeInstruction withIsJumpTarget(boolean isJumpTarget) { - return new PythonBytecodeInstruction(opname, offset, arg, startsLine, isJumpTarget); + return new PythonBytecodeInstruction(opname, offset, arg, argRepr, startsLine, isJumpTarget); } public PythonBytecodeInstruction markAsJumpTarget() { - return new PythonBytecodeInstruction(opname, offset, arg, startsLine, true); + return new PythonBytecodeInstruction(opname, offset, arg, argRepr, startsLine, true); } @Override diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java index 480ab410..e07e60bb 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonBytecodeToJavaBytecodeTranslator.java @@ -8,11 +8,13 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -30,6 +32,7 @@ import ai.timefold.jpyinterpreter.opcodes.OpcodeWithoutSource; import ai.timefold.jpyinterpreter.opcodes.SelfOpcodeWithoutSource; import ai.timefold.jpyinterpreter.opcodes.descriptor.GeneratorOpDescriptor; +import ai.timefold.jpyinterpreter.opcodes.variable.LoadFastAndClearOpcode; import ai.timefold.jpyinterpreter.types.BuiltinTypes; import ai.timefold.jpyinterpreter.types.PythonLikeFunction; import ai.timefold.jpyinterpreter.types.PythonLikeType; @@ -175,7 +178,7 @@ public static T forceTranslatePythonBytecodeToGenerator(PythonCompiledFuncti Method methodWithoutGenerics = getFunctionalInterfaceMethod(javaFunctionalInterfaceType); MethodDescriptor methodDescriptor = new MethodDescriptor(javaFunctionalInterfaceType, methodWithoutGenerics, - List.of()); + Collections.emptyList()); Class compiledClass = forceTranslatePythonBytecodeToGeneratorClass(pythonCompiledFunction, methodDescriptor, methodWithoutGenerics, false); return FunctionImplementor.createInstance(new PythonLikeTuple(), new PythonLikeDict(), @@ -252,7 +255,7 @@ public static Class translatePythonBytecodeToClass(PythonCompiledFunction null); translatePythonBytecodeToMethod(methodDescriptor, internalClassName, methodVisitor, pythonCompiledFunction, - isPythonLikeFunction, Integer.MAX_VALUE, isVirtual); // TODO: Use actual python version + isPythonLikeFunction, isVirtual); classWriter.visitEnd(); @@ -296,7 +299,7 @@ public static Class translatePythonBytecodeToClass(PythonCompiledFunction null); translatePythonBytecodeToMethod(methodDescriptor, internalClassName, methodVisitor, pythonCompiledFunction, - isPythonLikeFunction, Integer.MAX_VALUE, isVirtual); // TODO: Use actual python version + isPythonLikeFunction, isVirtual); String withoutGenericsSignature = Type.getMethodDescriptor(methodWithoutGenerics); if (!withoutGenericsSignature.equals(methodDescriptor.getMethodDescriptor())) { @@ -991,7 +994,7 @@ public static PythonFunctionType getFunctionType(PythonCompiledFunction pythonCo } private static void translatePythonBytecodeToMethod(MethodDescriptor method, String className, MethodVisitor methodVisitor, - PythonCompiledFunction pythonCompiledFunction, boolean isPythonLikeFunction, int pythonVersion, boolean isVirtual) { + PythonCompiledFunction pythonCompiledFunction, boolean isPythonLikeFunction, boolean isVirtual) { // Apply Method Adapters, which reorder try blocks and check the bytecode to ensure it valid methodVisitor = MethodVisitorAdapters.adapt(methodVisitor, method); @@ -1057,6 +1060,7 @@ private static void translatePythonBytecodeToMethod(MethodDescriptor method, Str FlowGraph flowGraph = FlowGraph.createFlowGraph(functionMetadata, initialStackMetadata, opcodeList); List stackMetadataForOpcodeIndex = flowGraph.getStackMetadataForOperations(); + writeInstructionsForOpcodes(functionMetadata, stackMetadataForOpcodeIndex, opcodeList); methodVisitor.visitLabel(end); @@ -1140,6 +1144,19 @@ public static void writeInstructionsForOpcodes(FunctionMetadata functionMetadata }); } + var requiredNullVariableSet = new TreeSet(); + for (Opcode opcode : opcodeList) { + if (opcode instanceof LoadFastAndClearOpcode loadAndClearOpcode) { + requiredNullVariableSet.add(loadAndClearOpcode.getInstruction().arg()); + } + } + + for (var requiredNullVariable : requiredNullVariableSet) { + methodVisitor.visitInsn(Opcodes.ACONST_NULL); + methodVisitor.visitVarInsn(Opcodes.ASTORE, + stackMetadataForOpcodeIndex.get(0).localVariableHelper.getPythonLocalVariableSlot(requiredNullVariable)); + } + for (int i = 0; i < opcodeList.size(); i++) { StackMetadata stackMetadata = stackMetadataForOpcodeIndex.get(i); PythonBytecodeInstruction instruction = pythonCompiledFunction.instructionList.get(i); @@ -1148,7 +1165,7 @@ public static void writeInstructionsForOpcodes(FunctionMetadata functionMetadata Label label = exceptionTableTargetLabelMap.get(instruction.offset()); methodVisitor.visitLabel(label); } - exceptionTableTryBlockMap.getOrDefault(instruction.offset(), List.of()).forEach(Runnable::run); + exceptionTableTryBlockMap.getOrDefault(instruction.offset(), Collections.emptyList()).forEach(Runnable::run); if (instruction.isJumpTarget() || bytecodeCounterToLabelMap.containsKey(instruction.offset())) { Label label = bytecodeCounterToLabelMap.computeIfAbsent(instruction.offset(), offset -> new Label()); @@ -1157,7 +1174,7 @@ public static void writeInstructionsForOpcodes(FunctionMetadata functionMetadata runAfterLabelAndBeforeArgumentors.accept(instruction); - bytecodeIndexToArgumentorsMap.getOrDefault(instruction.offset(), List.of()).forEach(Runnable::run); + bytecodeIndexToArgumentorsMap.getOrDefault(instruction.offset(), Collections.emptyList()).forEach(Runnable::run); if (exceptionTableStartLabelMap.containsKey(instruction.offset())) { Label label = exceptionTableStartLabelMap.get(instruction.offset()); diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonClassTranslator.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonClassTranslator.java index e2064387..9daa15af 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonClassTranslator.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonClassTranslator.java @@ -26,6 +26,7 @@ import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.SelfOpcodeWithoutSource; +import ai.timefold.jpyinterpreter.opcodes.controlflow.ReturnConstantValueOpcode; import ai.timefold.jpyinterpreter.opcodes.controlflow.ReturnValueOpcode; import ai.timefold.jpyinterpreter.opcodes.object.DeleteAttrOpcode; import ai.timefold.jpyinterpreter.opcodes.object.LoadAttrOpcode; @@ -788,7 +789,7 @@ private static void createInstanceMethod(PythonLikeType pythonLikeType, ClassWri function.getReturnType().orElse(BuiltinTypes.BASE_TYPE), function.totalArgCount() > 0 ? function.getParameterTypes().subList(1, function.getParameterTypes().size()) - : List.of())); + : Collections.emptyList())); } private static void createStaticMethod(PythonLikeType pythonLikeType, ClassWriter classWriter, String internalClassName, @@ -1394,6 +1395,9 @@ public static PythonLikeType getPythonReturnTypeOfFunction(PythonCompiledFunctio flowGraph.visitOperations(ReturnValueOpcode.class, (opcode, stackMetadata) -> { possibleReturnTypeList.add(stackMetadata.getTOSType()); }); + flowGraph.visitOperations(ReturnConstantValueOpcode.class, (opcode, stackMetadata) -> { + possibleReturnTypeList.add(opcode.getConstant(pythonCompiledFunction).$getGenericType()); + }); return possibleReturnTypeList.stream() .reduce(PythonLikeType::unifyWith) diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionSignature.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionSignature.java index 8088cd4e..ef6a056e 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionSignature.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonFunctionSignature.java @@ -56,12 +56,13 @@ public static PythonFunctionSignature forMethod(Method method) { public PythonFunctionSignature(MethodDescriptor methodDescriptor, PythonLikeType returnType, PythonLikeType... parameterTypes) { - this(methodDescriptor, List.of(), extractKeywordArgument(methodDescriptor), returnType, parameterTypes); + this(methodDescriptor, Collections.emptyList(), extractKeywordArgument(methodDescriptor), returnType, parameterTypes); } public PythonFunctionSignature(MethodDescriptor methodDescriptor, PythonLikeType returnType, List parameterTypeList) { - this(methodDescriptor, List.of(), extractKeywordArgument(methodDescriptor), returnType, parameterTypeList); + this(methodDescriptor, Collections.emptyList(), extractKeywordArgument(methodDescriptor), returnType, + parameterTypeList); } public PythonFunctionSignature(MethodDescriptor methodDescriptor, diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonVersion.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonVersion.java index c7dce3af..794fff78 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonVersion.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/PythonVersion.java @@ -5,11 +5,11 @@ public final class PythonVersion implements Comparable { private final int hexversion; - public static final PythonVersion PYTHON_3_9 = new PythonVersion(3, 9); public static final PythonVersion PYTHON_3_10 = new PythonVersion(3, 10); public static final PythonVersion PYTHON_3_11 = new PythonVersion(3, 11); + public static final PythonVersion PYTHON_3_12 = new PythonVersion(3, 12); - public static final PythonVersion MINIMUM_PYTHON_VERSION = PYTHON_3_9; + public static final PythonVersion MINIMUM_PYTHON_VERSION = PYTHON_3_10; public PythonVersion(int hexversion) { this.hexversion = hexversion; diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/StackMetadata.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/StackMetadata.java index 90c792df..9b314ca0 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/StackMetadata.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/StackMetadata.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -41,7 +42,7 @@ public StackMetadata(LocalVariableHelper localVariableHelper) { cellVariableValueSources.add(ValueSourceInfo.of(new OpcodeWithoutSource(), BuiltinTypes.BASE_TYPE)); } - this.callKeywordNameList = List.of(); + this.callKeywordNameList = Collections.emptyList(); } private StackMetadata(LocalVariableHelper localVariableHelper, List stackValueSources, @@ -62,11 +63,6 @@ public int getStackSize() { return stackValueSources.size(); } - public List getStackTypeList() { - return stackValueSources.stream().map(ValueSourceInfo::getValueType) - .collect(Collectors.toList()); - } - /** * Returns the list index for the given stack index (stack index is how many * elements below TOS (i.e. 0 is TOS, 1 is TOS1)). @@ -113,7 +109,8 @@ public PythonLikeType getTypeAtStackIndex(int index) { if (valueSourceInfo != null) { return valueSourceInfo.valueType; } - return null; + // Unknown type + return BuiltinTypes.BASE_TYPE; } /** @@ -126,20 +123,6 @@ public ValueSourceInfo getLocalVariableValueSource(int index) { return localVariableValueSources.get(index); } - /** - * Returns the type for the local variable in slot {@code index} - * - * @param index The slot - * @return The type for the local variable in the given slot - */ - public PythonLikeType getLocalVariableType(int index) { - ValueSourceInfo valueSourceInfo = localVariableValueSources.get(index); - if (valueSourceInfo != null) { - return valueSourceInfo.valueType; - } - return null; - } - /** * Returns the value source for the cell variable in slot {@code index} * @@ -150,20 +133,6 @@ public ValueSourceInfo getCellVariableValueSource(int index) { return cellVariableValueSources.get(index); } - /** - * Returns the type for the cell variable in slot {@code index} - * - * @param index The slot - * @return The type for the cell variable in the given slot - */ - public PythonLikeType getCellVariableType(int index) { - ValueSourceInfo valueSourceInfo = cellVariableValueSources.get(index); - if (valueSourceInfo != null) { - return valueSourceInfo.valueType; - } - return null; - } - public PythonLikeType getTOSType() { return getTypeAtStackIndex(0); } @@ -277,13 +246,6 @@ public StackMetadata pushTemps(PythonLikeType... types) { return out; } - public StackMetadata insertTemp(int tosIndex, PythonLikeType type) { - StackMetadata out = copy(); - out.stackValueSources.add(stackValueSources.size() - tosIndex, - ValueSourceInfo.of(new OpcodeWithoutSource(), type)); - return out; - } - /** * Return a new StackMetadata with {@code types} as the stack; * The original stack is cleared. diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementor.java index 093f8c1b..c1f46083 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/DunderOperatorImplementor.java @@ -20,6 +20,7 @@ import ai.timefold.jpyinterpreter.types.PythonKnownFunctionType; import ai.timefold.jpyinterpreter.types.PythonLikeFunction; import ai.timefold.jpyinterpreter.types.PythonLikeType; +import ai.timefold.jpyinterpreter.types.PythonSlice; import ai.timefold.jpyinterpreter.types.collections.PythonLikeList; import ai.timefold.jpyinterpreter.types.errors.TypeError; @@ -685,4 +686,52 @@ private static void binaryOpOverridingLeftIfSpecific(MethodVisitor methodVisitor methodVisitor.visitLabel(ifDefined); binaryOperator(methodVisitor, stackMetadata.localVariableHelper, operator); } + + public static void getSlice(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + // stack: ..., collection, start, end + var methodVisitor = functionMetadata.methodVisitor; + methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonSlice.class)); + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.POP); + methodVisitor.visitInsn(Opcodes.ACONST_NULL); + // stack: ..., collection, , , start, end, null + methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonSlice.class), + "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeObject.class), + Type.getType(PythonLikeObject.class), + Type.getType(PythonLikeObject.class)), + false); + // stack: ..., collection, slice + DunderOperatorImplementor.binaryOperator(methodVisitor, stackMetadata + .pop(3).pushTemps(stackMetadata.getTypeAtStackIndex(2), BuiltinTypes.SLICE_TYPE), + PythonBinaryOperator.GET_ITEM); + } + + public static void storeSlice(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + // stack: ..., values, collection, start, end + var methodVisitor = functionMetadata.methodVisitor; + methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonSlice.class)); + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.POP); + methodVisitor.visitInsn(Opcodes.ACONST_NULL); + // stack: ..., values, collection, , , start, end, null + methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonSlice.class), + "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeObject.class), + Type.getType(PythonLikeObject.class), + Type.getType(PythonLikeObject.class)), + false); + // stack: ..., values, collection, slice + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.POP); + // stack: ..., slice, values, collection + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.POP); + // stack: ..., collection, slice, values + DunderOperatorImplementor.ternaryOperator(functionMetadata, stackMetadata + .pop(4).pushTemps(stackMetadata.getTypeAtStackIndex(2), BuiltinTypes.SLICE_TYPE, + stackMetadata.getTypeAtStackIndex(3)), + PythonTernaryOperator.SET_ITEM); + methodVisitor.visitInsn(Opcodes.POP); + } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementor.java index 8813ab24..04325f51 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ExceptionImplementor.java @@ -27,6 +27,7 @@ import ai.timefold.jpyinterpreter.types.errors.PythonAssertionError; import ai.timefold.jpyinterpreter.types.errors.PythonBaseException; import ai.timefold.jpyinterpreter.types.errors.PythonTraceback; +import ai.timefold.jpyinterpreter.types.errors.StopIteration; import ai.timefold.jpyinterpreter.types.numeric.PythonBoolean; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; @@ -447,4 +448,19 @@ public static void handleExceptionInWith(FunctionMetadata functionMetadata, Stac localVariableHelper.freeLocal(); } + + public static void getValueFromStopIterationOrReraise(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + var methodVisitor = functionMetadata.methodVisitor; + Label isStopIteration = new Label(); + methodVisitor.visitInsn(Opcodes.DUP); + methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(StopIteration.class)); + methodVisitor.visitJumpInsn(Opcodes.IFNE, isStopIteration); + + ExceptionImplementor.reraise(methodVisitor); + + methodVisitor.visitLabel(isStopIteration); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(StopIteration.class)); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(StopIteration.class), + "getValue", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class)), false); + } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/FunctionImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/FunctionImplementor.java index 51960134..60b3ebed 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/FunctionImplementor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/FunctionImplementor.java @@ -101,9 +101,11 @@ public static void callBinaryMethod(MethodVisitor methodVisitor, String methodNa * TOS will be used as the first argument (self) by CALL_METHOD when calling the unbound method. * Otherwise, NULL and the object return by the attribute lookup are pushed. */ - public static void loadMethod(FunctionMetadata functionMetadata, MethodVisitor methodVisitor, String className, - PythonCompiledFunction function, - StackMetadata stackMetadata, PythonBytecodeInstruction instruction) { + public static void loadMethod(FunctionMetadata functionMetadata, StackMetadata stackMetadata, + int nameIndex) { + var methodVisitor = functionMetadata.methodVisitor; + var function = functionMetadata.pythonCompiledFunction; + var className = functionMetadata.className; PythonLikeType stackTosType = stackMetadata.getTOSType(); PythonLikeType tosType; boolean isTosType; @@ -114,10 +116,10 @@ public static void loadMethod(FunctionMetadata functionMetadata, MethodVisitor m tosType = stackTosType; isTosType = false; } - tosType.getMethodType(functionMetadata.pythonCompiledFunction.co_names.get(instruction.arg())).ifPresentOrElse( + tosType.getMethodType(functionMetadata.pythonCompiledFunction.co_names.get(nameIndex)).ifPresentOrElse( knownFunctionType -> { if (isTosType && knownFunctionType.isStaticMethod()) { - methodVisitor.visitLdcInsn(function.co_names.get(instruction.arg())); + methodVisitor.visitLdcInsn(function.co_names.get(nameIndex)); methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), @@ -133,7 +135,7 @@ public static void loadMethod(FunctionMetadata functionMetadata, MethodVisitor m methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), true); - methodVisitor.visitLdcInsn(function.co_names.get(instruction.arg())); + methodVisitor.visitLdcInsn(function.co_names.get(nameIndex)); methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), @@ -146,7 +148,7 @@ public static void loadMethod(FunctionMetadata functionMetadata, MethodVisitor m } } else if (isTosType && knownFunctionType.isClassMethod()) { methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(function.co_names.get(instruction.arg())); + methodVisitor.visitLdcInsn(function.co_names.get(nameIndex)); methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), @@ -157,7 +159,7 @@ public static void loadMethod(FunctionMetadata functionMetadata, MethodVisitor m methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), true); - methodVisitor.visitLdcInsn(function.co_names.get(instruction.arg())); + methodVisitor.visitLdcInsn(function.co_names.get(nameIndex)); methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), @@ -165,7 +167,7 @@ public static void loadMethod(FunctionMetadata functionMetadata, MethodVisitor m methodVisitor.visitInsn(Opcodes.SWAP); } else if (isTosType) { methodVisitor.visitInsn(Opcodes.DUP); - methodVisitor.visitLdcInsn(function.co_names.get(instruction.arg())); + methodVisitor.visitLdcInsn(function.co_names.get(nameIndex)); methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), @@ -176,7 +178,7 @@ public static void loadMethod(FunctionMetadata functionMetadata, MethodVisitor m methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), true); - methodVisitor.visitLdcInsn(function.co_names.get(instruction.arg())); + methodVisitor.visitLdcInsn(function.co_names.get(nameIndex)); methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getAttributeOrNull", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), @@ -184,7 +186,8 @@ public static void loadMethod(FunctionMetadata functionMetadata, MethodVisitor m methodVisitor.visitInsn(Opcodes.SWAP); } }, - () -> loadGenericMethod(functionMetadata, methodVisitor, className, function, stackMetadata, instruction)); + () -> loadGenericMethod(functionMetadata, methodVisitor, className, function, stackMetadata, + nameIndex)); } /** @@ -195,13 +198,13 @@ public static void loadMethod(FunctionMetadata functionMetadata, MethodVisitor m */ private static void loadGenericMethod(FunctionMetadata functionMetadata, MethodVisitor methodVisitor, String className, PythonCompiledFunction function, - StackMetadata stackMetadata, PythonBytecodeInstruction instruction) { + StackMetadata stackMetadata, int nameIndex) { methodVisitor.visitInsn(Opcodes.DUP); methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$getType", Type.getMethodDescriptor(Type.getType(PythonLikeType.class)), true); - methodVisitor.visitLdcInsn(function.co_names.get(instruction.arg())); + methodVisitor.visitLdcInsn(function.co_names.get(nameIndex)); methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PythonLikeType.class), "loadMethod", Type.getMethodDescriptor(Type.getType(PythonLikeObject.class), Type.getType(String.class)), @@ -216,7 +219,7 @@ private static void loadGenericMethod(FunctionMetadata functionMetadata, MethodV // TOS is null; type does not have attribute; do normal attribute lookup // Stack is object, null methodVisitor.visitInsn(Opcodes.POP); - ObjectImplementor.getAttribute(functionMetadata, methodVisitor, className, stackMetadata, instruction); + ObjectImplementor.getAttribute(functionMetadata, stackMetadata, nameIndex); // Stack is method methodVisitor.visitInsn(Opcodes.ACONST_NULL); diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementor.java index a2b115c0..9041c3bf 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/implementors/ObjectImplementor.java @@ -14,6 +14,7 @@ import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.PythonNone; import ai.timefold.jpyinterpreter.types.PythonString; +import ai.timefold.jpyinterpreter.types.PythonSuperObject; import ai.timefold.jpyinterpreter.types.errors.AttributeError; import ai.timefold.jpyinterpreter.types.wrappers.JavaObjectWrapper; @@ -30,11 +31,11 @@ public class ObjectImplementor { /** * Replaces TOS with getattr(TOS, co_names[instruction.arg]) */ - public static void getAttribute(FunctionMetadata functionMetadata, MethodVisitor methodVisitor, String className, - StackMetadata stackMetadata, - PythonBytecodeInstruction instruction) { + public static void getAttribute(FunctionMetadata functionMetadata, StackMetadata stackMetadata, int nameIndex) { + var methodVisitor = functionMetadata.methodVisitor; + var className = functionMetadata.className; PythonLikeType tosType = stackMetadata.getTOSType(); - String name = functionMetadata.pythonCompiledFunction.co_names.get(instruction.arg()); + String name = functionMetadata.pythonCompiledFunction.co_names.get(nameIndex); Optional maybeFieldDescriptor = tosType.getInstanceFieldDescriptor(name); if (maybeFieldDescriptor.isPresent()) { @@ -86,7 +87,7 @@ public static void getAttribute(FunctionMetadata functionMetadata, MethodVisitor // It a false field descriptor, which means TOS is a type and this is a field for a method // We can call $method$__getattribute__ directly (since type do not override it), // which is more efficient then going through the full logic of __getattribute__ dunder method impl. - PythonConstantsImplementor.loadName(methodVisitor, className, instruction.arg()); + PythonConstantsImplementor.loadName(methodVisitor, className, nameIndex); methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonString.class)); methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(PythonLikeObject.class), "$method$__getattribute__", Type.getMethodDescriptor( @@ -95,7 +96,7 @@ public static void getAttribute(FunctionMetadata functionMetadata, MethodVisitor true); } } else { - PythonConstantsImplementor.loadName(methodVisitor, className, instruction.arg()); + PythonConstantsImplementor.loadName(methodVisitor, className, nameIndex); DunderOperatorImplementor.binaryOperator(methodVisitor, stackMetadata.pushTemp(BuiltinTypes.STRING_TYPE), PythonBinaryOperator.GET_ATTRIBUTE); @@ -162,4 +163,34 @@ public static void setAttribute(FunctionMetadata functionMetadata, MethodVisitor PythonTernaryOperator.SET_ATTRIBUTE); } } + + /** + * Implement (super = TOS2)(TOS1, TOS).attr + */ + public static void getSuperAttribute(FunctionMetadata functionMetadata, + StackMetadata stackMetadata, + int nameIndex, + boolean isLoadMethod) { + var methodVisitor = functionMetadata.methodVisitor; + // Stack: super, type, instance + methodVisitor.visitTypeInsn(Opcodes.NEW, Type.getInternalName(PythonSuperObject.class)); + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.POP); + // Stack: super, , , type, instance + methodVisitor.visitInsn(Opcodes.SWAP); + methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(PythonLikeType.class)); + methodVisitor.visitInsn(Opcodes.SWAP); + methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(PythonSuperObject.class), + "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PythonLikeType.class), + Type.getType(PythonLikeObject.class))); + // Stack: super, superobject + ObjectImplementor.getAttribute(functionMetadata, stackMetadata.pop(2).pushTemp(BuiltinTypes.SUPER_TYPE), nameIndex); + methodVisitor.visitInsn(Opcodes.SWAP); + methodVisitor.visitInsn(Opcodes.POP); + if (isLoadMethod) { + methodVisitor.visitInsn(Opcodes.ACONST_NULL); + methodVisitor.visitInsn(Opcodes.SWAP); + } + } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/Opcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/Opcode.java index 194d3cb6..3d09b337 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/Opcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/Opcode.java @@ -65,9 +65,7 @@ default boolean isForcedJump() { static Opcode lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { return AbstractOpcode.lookupInstruction(instruction.opname()) - .lookupOpcodeForInstruction(instruction, pythonVersion) - .orElseThrow(() -> new UnsupportedOperationException( - "Could not find implementation for Opcode %s for Python version %s (instruction %s)" - .formatted(instruction.opname(), pythonVersion, instruction))); + .getVersionMapping() + .getOpcodeForVersion(instruction, pythonVersion); } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ReturnConstantValueOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ReturnConstantValueOpcode.java new file mode 100644 index 00000000..568b0874 --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ReturnConstantValueOpcode.java @@ -0,0 +1,58 @@ +package ai.timefold.jpyinterpreter.opcodes.controlflow; + +import java.util.Collections; +import java.util.List; + +import ai.timefold.jpyinterpreter.FunctionMetadata; +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.PythonCompiledFunction; +import ai.timefold.jpyinterpreter.PythonFunctionType; +import ai.timefold.jpyinterpreter.PythonLikeObject; +import ai.timefold.jpyinterpreter.StackMetadata; +import ai.timefold.jpyinterpreter.implementors.GeneratorImplementor; +import ai.timefold.jpyinterpreter.implementors.JavaPythonTypeConversionImplementor; +import ai.timefold.jpyinterpreter.implementors.PythonConstantsImplementor; +import ai.timefold.jpyinterpreter.types.PythonLikeType; + +public class ReturnConstantValueOpcode extends AbstractControlFlowOpcode { + + public ReturnConstantValueOpcode(PythonBytecodeInstruction instruction) { + super(instruction); + } + + @Override + public List getPossibleNextBytecodeIndexList() { + return Collections.emptyList(); + } + + @Override + public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, + StackMetadata stackMetadata) { + return Collections.emptyList(); + } + + @Override + public boolean isForcedJump() { + return true; + } + + public PythonLikeObject getConstant(PythonCompiledFunction function) { + return function.co_constants.get(instruction.arg()); + } + + @Override + public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + PythonLikeObject constant = getConstant(functionMetadata.pythonCompiledFunction); + PythonLikeType constantType = constant.$getGenericType(); + if (functionMetadata.functionType == PythonFunctionType.GENERATOR) { + PythonConstantsImplementor.loadConstant(functionMetadata.methodVisitor, functionMetadata.className, + instruction.arg()); + GeneratorImplementor.endGenerator(functionMetadata, stackMetadata.pushTemp(constantType)); + } else { + PythonConstantsImplementor.loadConstant(functionMetadata.methodVisitor, functionMetadata.className, + instruction.arg()); + JavaPythonTypeConversionImplementor.returnValue(functionMetadata.methodVisitor, functionMetadata.method, + stackMetadata.pushTemp(constantType)); + } + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ReturnValueOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ReturnValueOpcode.java index cd36d185..0c223bd4 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ReturnValueOpcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/controlflow/ReturnValueOpcode.java @@ -1,5 +1,6 @@ package ai.timefold.jpyinterpreter.opcodes.controlflow; +import java.util.Collections; import java.util.List; import ai.timefold.jpyinterpreter.FunctionMetadata; @@ -17,13 +18,13 @@ public ReturnValueOpcode(PythonBytecodeInstruction instruction) { @Override public List getPossibleNextBytecodeIndexList() { - return List.of(); + return Collections.emptyList(); } @Override public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return List.of(); + return Collections.emptyList(); } @Override diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/AsyncOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/AsyncOpDescriptor.java index dfa03fec..e2e60ba8 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/AsyncOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/AsyncOpDescriptor.java @@ -1,11 +1,5 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.opcodes.Opcode; - public enum AsyncOpDescriptor implements OpcodeDescriptor { /** * Implements TOS = get_awaitable(TOS), where get_awaitable(o) returns o if o is a coroutine object or a generator @@ -42,9 +36,8 @@ public enum AsyncOpDescriptor implements OpcodeDescriptor { SETUP_ASYNC_WITH; @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, - PythonVersion pythonVersion) { + public VersionMapping getVersionMapping() { // TODO - return Optional.empty(); + return VersionMapping.unimplemented(); } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/CollectionOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/CollectionOpDescriptor.java index 38844212..5f225175 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/CollectionOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/CollectionOpDescriptor.java @@ -1,10 +1,8 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.Function; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.collection.BuildConstantKeyMapOpcode; import ai.timefold.jpyinterpreter.opcodes.collection.BuildListOpcode; @@ -87,14 +85,14 @@ public enum CollectionOpDescriptor implements OpcodeDescriptor { DICT_UPDATE(MapPutAllOpcode::new), DICT_MERGE(MapMergeOpcode::new); - final Function instructionToOpcode; + final VersionMapping versionLookup; CollectionOpDescriptor(Function instructionToOpcode) { - this.instructionToOpcode = instructionToOpcode; + this.versionLookup = VersionMapping.constantMapping(instructionToOpcode); } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(instructionToOpcode.apply(instruction)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ControlOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ControlOpDescriptor.java index 60f4dafe..cb5951a5 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ControlOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ControlOpDescriptor.java @@ -1,9 +1,5 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Map; -import java.util.NavigableMap; -import java.util.Optional; -import java.util.TreeMap; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.ToIntBiFunction; @@ -20,7 +16,9 @@ import ai.timefold.jpyinterpreter.opcodes.controlflow.PopJumpIfIsNoneOpcode; import ai.timefold.jpyinterpreter.opcodes.controlflow.PopJumpIfIsNotNoneOpcode; import ai.timefold.jpyinterpreter.opcodes.controlflow.PopJumpIfTrueOpcode; +import ai.timefold.jpyinterpreter.opcodes.controlflow.ReturnConstantValueOpcode; import ai.timefold.jpyinterpreter.opcodes.controlflow.ReturnValueOpcode; +import ai.timefold.jpyinterpreter.opcodes.meta.NopOpcode; import ai.timefold.jpyinterpreter.util.JumpUtils; public enum ControlOpDescriptor implements OpcodeDescriptor { @@ -28,6 +26,7 @@ public enum ControlOpDescriptor implements OpcodeDescriptor { * Returns with TOS to the caller of the function. */ RETURN_VALUE(ReturnValueOpcode::new), + RETURN_CONST(ReturnConstantValueOpcode::new), JUMP_FORWARD(JumpAbsoluteOpcode::new, JumpUtils::getRelativeTarget), JUMP_BACKWARD(JumpAbsoluteOpcode::new, JumpUtils::getBackwardRelativeTarget), JUMP_BACKWARD_NO_INTERRUPT(JumpAbsoluteOpcode::new, JumpUtils::getBackwardRelativeTarget), @@ -37,43 +36,42 @@ public enum ControlOpDescriptor implements OpcodeDescriptor { POP_JUMP_IF_FALSE(PopJumpIfFalseOpcode::new, JumpUtils::getAbsoluteTarget), POP_JUMP_FORWARD_IF_FALSE(PopJumpIfFalseOpcode::new, JumpUtils::getRelativeTarget), POP_JUMP_BACKWARD_IF_FALSE(PopJumpIfFalseOpcode::new, JumpUtils::getBackwardRelativeTarget), + POP_JUMP_IF_NONE(PopJumpIfIsNoneOpcode::new, JumpUtils::getRelativeTarget), + POP_JUMP_IF_NOT_NONE(PopJumpIfIsNotNoneOpcode::new, JumpUtils::getRelativeTarget), POP_JUMP_FORWARD_IF_NONE(PopJumpIfIsNoneOpcode::new, JumpUtils::getRelativeTarget), POP_JUMP_BACKWARD_IF_NONE(PopJumpIfIsNoneOpcode::new, JumpUtils::getBackwardRelativeTarget), POP_JUMP_FORWARD_IF_NOT_NONE(PopJumpIfIsNotNoneOpcode::new, JumpUtils::getRelativeTarget), POP_JUMP_BACKWARD_IF_NOT_NONE(PopJumpIfIsNotNoneOpcode::new, JumpUtils::getBackwardRelativeTarget), JUMP_IF_NOT_EXC_MATCH(JumpIfNotExcMatchOpcode::new, JumpUtils::getAbsoluteTarget), - JUMP_IF_TRUE_OR_POP(Map.of(PythonVersion.MINIMUM_PYTHON_VERSION, JumpIfTrueOrPopOpcode::new), - Map.of(PythonVersion.MINIMUM_PYTHON_VERSION, JumpUtils::getAbsoluteTarget, - PythonVersion.PYTHON_3_11, JumpUtils::getRelativeTarget)), - JUMP_IF_FALSE_OR_POP(Map.of(PythonVersion.MINIMUM_PYTHON_VERSION, JumpIfFalseOrPopOpcode::new), - Map.of(PythonVersion.MINIMUM_PYTHON_VERSION, JumpUtils::getAbsoluteTarget, - PythonVersion.PYTHON_3_11, JumpUtils::getRelativeTarget)), + JUMP_IF_TRUE_OR_POP(new VersionMapping() + .mapWithLabels(PythonVersion.MINIMUM_PYTHON_VERSION, JumpIfTrueOrPopOpcode::new, JumpUtils::getAbsoluteTarget) + .mapWithLabels(PythonVersion.PYTHON_3_11, JumpIfTrueOrPopOpcode::new, JumpUtils::getRelativeTarget)), + JUMP_IF_FALSE_OR_POP(new VersionMapping() + .mapWithLabels(PythonVersion.MINIMUM_PYTHON_VERSION, JumpIfFalseOrPopOpcode::new, JumpUtils::getAbsoluteTarget) + .mapWithLabels(PythonVersion.PYTHON_3_11, JumpIfFalseOrPopOpcode::new, JumpUtils::getRelativeTarget)), JUMP_ABSOLUTE(JumpAbsoluteOpcode::new, JumpUtils::getAbsoluteTarget), - FOR_ITER(ForIterOpcode::new, JumpUtils::getRelativeTarget); + FOR_ITER(ForIterOpcode::new, + JumpUtils::getRelativeTarget), + END_FOR(NopOpcode::new); - final NavigableMap> versionToOpcodeFunction; - final NavigableMap> versionToLabelFunction; + final VersionMapping versionLookup; ControlOpDescriptor(Function opcodeFunction) { - this((instruction, ignoredLabel) -> opcodeFunction.apply(instruction), (ignored1, ignored2) -> 0); + this.versionLookup = VersionMapping.constantMapping(opcodeFunction); } ControlOpDescriptor(BiFunction opcodeFunction, ToIntBiFunction labelFunction) { - this(Map.of(PythonVersion.MINIMUM_PYTHON_VERSION, opcodeFunction), - Map.of(PythonVersion.MINIMUM_PYTHON_VERSION, labelFunction)); + this.versionLookup = VersionMapping.constantMapping((instruction, version) -> opcodeFunction.apply(instruction, + labelFunction.applyAsInt(instruction, version))); } - ControlOpDescriptor(Map> versionToOpcodeFunction, - Map> versionToLabelFunction) { - this.versionToOpcodeFunction = new TreeMap<>(versionToOpcodeFunction); - this.versionToLabelFunction = new TreeMap<>(versionToLabelFunction); + ControlOpDescriptor(VersionMapping versionLookup) { + this.versionLookup = versionLookup; } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(versionToOpcodeFunction.floorEntry(pythonVersion) - .getValue().apply(instruction, versionToLabelFunction.floorEntry(pythonVersion) - .getValue().applyAsInt(instruction, pythonVersion))); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/DunderOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/DunderOpDescriptor.java index 484ae572..f0b5150c 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/DunderOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/DunderOpDescriptor.java @@ -1,16 +1,16 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.Function; import ai.timefold.jpyinterpreter.PythonBinaryOperator; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; import ai.timefold.jpyinterpreter.PythonUnaryOperator; -import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.dunder.BinaryDunderOpcode; import ai.timefold.jpyinterpreter.opcodes.dunder.CompareOpcode; +import ai.timefold.jpyinterpreter.opcodes.dunder.GetSliceOpcode; import ai.timefold.jpyinterpreter.opcodes.dunder.NotOpcode; +import ai.timefold.jpyinterpreter.opcodes.dunder.StoreSliceOpcode; import ai.timefold.jpyinterpreter.opcodes.dunder.UniDunerOpcode; public enum DunderOpDescriptor implements OpcodeDescriptor { @@ -86,6 +86,16 @@ public enum DunderOpDescriptor implements OpcodeDescriptor { */ BINARY_SUBSCR(PythonBinaryOperator.GET_ITEM), + /** + * Implements TOS = TOS2[TOS1:TOS] + */ + BINARY_SLICE(GetSliceOpcode::new), + + /** + * Implements TOS2[TOS1:TOS] = TOS3 + */ + STORE_SLICE(StoreSliceOpcode::new), + /** * Implements TOS = TOS1 << TOS. */ @@ -180,10 +190,10 @@ public enum DunderOpDescriptor implements OpcodeDescriptor { */ INPLACE_OR(PythonBinaryOperator.INPLACE_OR); - final Function opcodeFunction; + final VersionMapping versionLookup; DunderOpDescriptor(Function opcodeFunction) { - this.opcodeFunction = opcodeFunction; + this.versionLookup = VersionMapping.constantMapping(opcodeFunction); } DunderOpDescriptor(PythonUnaryOperator binaryOperator) { @@ -195,7 +205,7 @@ public enum DunderOpDescriptor implements OpcodeDescriptor { } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(opcodeFunction.apply(instruction)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ExceptionOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ExceptionOpDescriptor.java index af9b9a5c..1621ac02 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ExceptionOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ExceptionOpDescriptor.java @@ -1,6 +1,5 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.ToIntBiFunction; @@ -9,6 +8,7 @@ import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.exceptions.CheckExcMatchOpcode; +import ai.timefold.jpyinterpreter.opcodes.exceptions.CleanupThrowOpcode; import ai.timefold.jpyinterpreter.opcodes.exceptions.LoadAssertionErrorOpcode; import ai.timefold.jpyinterpreter.opcodes.exceptions.PopBlockOpcode; import ai.timefold.jpyinterpreter.opcodes.exceptions.PopExceptOpcode; @@ -64,22 +64,23 @@ public enum ExceptionOpDescriptor implements OpcodeDescriptor { WITH_EXCEPT_START(WithExceptStartOpcode::new), SETUP_FINALLY(SetupFinallyOpcode::new, JumpUtils::getRelativeTarget), - SETUP_WITH(SetupWithOpcode::new, JumpUtils::getRelativeTarget); + SETUP_WITH(SetupWithOpcode::new, JumpUtils::getRelativeTarget), + CLEANUP_THROW(CleanupThrowOpcode::new); - final BiFunction opcodeConstructor; + final VersionMapping versionLookup; ExceptionOpDescriptor(Function opcodeFunction) { - opcodeConstructor = (instruction, pythonVersion) -> opcodeFunction.apply(instruction); + this.versionLookup = VersionMapping.constantMapping(opcodeFunction); } ExceptionOpDescriptor(BiFunction opcodeFunction, ToIntBiFunction jumpFunction) { - opcodeConstructor = (instruction, pythonVersion) -> opcodeFunction.apply(instruction, - jumpFunction.applyAsInt(instruction, pythonVersion)); + this.versionLookup = VersionMapping.constantMapping((instruction, pythonVersion) -> opcodeFunction.apply(instruction, + jumpFunction.applyAsInt(instruction, pythonVersion))); } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(opcodeConstructor.apply(instruction, pythonVersion)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/FunctionOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/FunctionOpDescriptor.java index f41ba189..9a4949a9 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/FunctionOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/FunctionOpDescriptor.java @@ -1,10 +1,8 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.Function; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.function.CallFunctionKeywordOpcode; import ai.timefold.jpyinterpreter.opcodes.function.CallFunctionOpcode; @@ -27,14 +25,14 @@ public enum FunctionOpDescriptor implements OpcodeDescriptor { CALL_METHOD(CallMethodOpcode::new), MAKE_FUNCTION(MakeFunctionOpcode::new); - final Function opcodeFunction; + final VersionMapping versionLookup; FunctionOpDescriptor(Function opcodeFunction) { - this.opcodeFunction = opcodeFunction; + this.versionLookup = VersionMapping.constantMapping(opcodeFunction); } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(opcodeFunction.apply(instruction)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/GeneratorOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/GeneratorOpDescriptor.java index 7d1cb851..051383e3 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/GeneratorOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/GeneratorOpDescriptor.java @@ -1,6 +1,5 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.ToIntBiFunction; @@ -14,6 +13,7 @@ import ai.timefold.jpyinterpreter.opcodes.generator.SendOpcode; import ai.timefold.jpyinterpreter.opcodes.generator.YieldFromOpcode; import ai.timefold.jpyinterpreter.opcodes.generator.YieldValueOpcode; +import ai.timefold.jpyinterpreter.opcodes.meta.NopOpcode; import ai.timefold.jpyinterpreter.opcodes.meta.ReturnGeneratorOpcode; import ai.timefold.jpyinterpreter.util.JumpUtils; @@ -51,6 +51,9 @@ public enum GeneratorOpDescriptor implements OpcodeDescriptor { * yielded value; TOS1 remains. When the subgenerator is exhausted, jump forward by its argument. */ SEND(SendOpcode::new, JumpUtils::getRelativeTarget), + + END_SEND(NopOpcode::new), + /** * Create a generator, coroutine, or async generator from the current frame. * Clear the current frame and return the newly created generator. A no-op for us, since we detect if @@ -59,20 +62,22 @@ public enum GeneratorOpDescriptor implements OpcodeDescriptor { */ RETURN_GENERATOR(ReturnGeneratorOpcode::new); - final BiFunction opcodeConstructor; + final VersionMapping versionLookup; GeneratorOpDescriptor(Function opcodeFunction) { - this.opcodeConstructor = (instruction, pythonVersion) -> opcodeFunction.apply(instruction); + this.versionLookup = VersionMapping.constantMapping(opcodeFunction); } GeneratorOpDescriptor(BiFunction opcodeConstructor, ToIntBiFunction labelFunction) { - this.opcodeConstructor = (instruction, pythonVersion) -> opcodeConstructor.apply(instruction, - labelFunction.applyAsInt(instruction, pythonVersion)); + this.versionLookup = VersionMapping.constantMapping( + (instruction, pythonVersion) -> opcodeConstructor.apply( + instruction, + labelFunction.applyAsInt(instruction, pythonVersion))); } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(opcodeConstructor.apply(instruction, pythonVersion)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/MetaOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/MetaOpDescriptor.java index 4c11648c..96239f4a 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/MetaOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/MetaOpDescriptor.java @@ -1,12 +1,11 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.Function; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.meta.NopOpcode; +import ai.timefold.jpyinterpreter.opcodes.meta.UnaryIntrinsicFunction; public enum MetaOpDescriptor implements OpcodeDescriptor { /** @@ -27,6 +26,7 @@ public enum MetaOpDescriptor implements OpcodeDescriptor { PRECALL(NopOpcode::new), MAKE_CELL(NopOpcode::new), COPY_FREE_VARS(NopOpcode::new), + CALL_INTRINSIC_1(UnaryIntrinsicFunction::lookup), // TODO EXTENDED_ARG(null), @@ -43,17 +43,14 @@ public enum MetaOpDescriptor implements OpcodeDescriptor { */ SETUP_ANNOTATIONS(null); - private final Function opcodeFunction; + private final VersionMapping versionLookup; MetaOpDescriptor(Function opcodeFunction) { - this.opcodeFunction = opcodeFunction; + this.versionLookup = VersionMapping.constantMapping(opcodeFunction); } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - if (opcodeFunction == null) { - return Optional.empty(); - } - return Optional.of(opcodeFunction.apply(instruction)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ModuleOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ModuleOpDescriptor.java index a9e56f9c..89cfe2dd 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ModuleOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ModuleOpDescriptor.java @@ -1,10 +1,8 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.Function; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.module.ImportFromOpcode; import ai.timefold.jpyinterpreter.opcodes.module.ImportNameOpcode; @@ -26,14 +24,14 @@ public enum ModuleOpDescriptor implements OpcodeDescriptor { "Impossible state/invalid bytecode: import * only allowed at module level"); }); - final Function opcodeFunction; + final VersionMapping versionLookup; ModuleOpDescriptor(Function opcodeFunction) { - this.opcodeFunction = opcodeFunction; + this.versionLookup = VersionMapping.constantMapping(opcodeFunction); } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(opcodeFunction.apply(instruction)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ObjectOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ObjectOpDescriptor.java index 5d9772a8..5aa0286f 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ObjectOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/ObjectOpDescriptor.java @@ -1,30 +1,41 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.Function; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; +import ai.timefold.jpyinterpreter.opcodes.function.LoadMethodOpcode; import ai.timefold.jpyinterpreter.opcodes.object.DeleteAttrOpcode; import ai.timefold.jpyinterpreter.opcodes.object.IsOpcode; import ai.timefold.jpyinterpreter.opcodes.object.LoadAttrOpcode; +import ai.timefold.jpyinterpreter.opcodes.object.LoadSuperAttrOpcode; import ai.timefold.jpyinterpreter.opcodes.object.StoreAttrOpcode; public enum ObjectOpDescriptor implements OpcodeDescriptor { IS_OP(IsOpcode::new), - LOAD_ATTR(LoadAttrOpcode::new), + LOAD_ATTR(new VersionMapping() + .map(PythonVersion.MINIMUM_PYTHON_VERSION, LoadAttrOpcode::new) + .map(PythonVersion.PYTHON_3_12, + instruction -> ((instruction.arg() & 1) == 1) + ? new LoadMethodOpcode(instruction.withArg(instruction.arg() >> 1)) + : new LoadAttrOpcode(instruction.withArg(instruction.arg() >> 1)))), + LOAD_SUPER_ATTR(LoadSuperAttrOpcode::new), STORE_ATTR(StoreAttrOpcode::new), DELETE_ATTR(DeleteAttrOpcode::new); - final Function opcodeFunction; + final VersionMapping versionLookup; ObjectOpDescriptor(Function opcodeFunction) { - this.opcodeFunction = opcodeFunction; + this(VersionMapping.constantMapping(opcodeFunction)); + } + + ObjectOpDescriptor(VersionMapping versionLookup) { + this.versionLookup = versionLookup; } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(opcodeFunction.apply(instruction)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/OpcodeDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/OpcodeDescriptor.java index 0b99a258..d179bc1e 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/OpcodeDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/OpcodeDescriptor.java @@ -1,11 +1,5 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; - -import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; -import ai.timefold.jpyinterpreter.opcodes.Opcode; - public sealed interface OpcodeDescriptor permits AsyncOpDescriptor, CollectionOpDescriptor, ControlOpDescriptor, @@ -21,6 +15,5 @@ public sealed interface OpcodeDescriptor permits AsyncOpDescriptor, VariableOpDescriptor { String name(); - Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, - PythonVersion pythonVersion); + VersionMapping getVersionMapping(); } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StackOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StackOpDescriptor.java index 30977c0f..9ec5fc2c 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StackOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StackOpDescriptor.java @@ -1,10 +1,8 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.Function; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.stack.CopyOpcode; import ai.timefold.jpyinterpreter.opcodes.stack.DupOpcode; @@ -57,14 +55,14 @@ public enum StackOpDescriptor implements OpcodeDescriptor { */ DUP_TOP_TWO(DupTwoOpcode::new); - final Function opcodeFunction; + final VersionMapping versionLookup; StackOpDescriptor(Function opcodeFunction) { - this.opcodeFunction = opcodeFunction; + this.versionLookup = VersionMapping.constantMapping(opcodeFunction); } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(opcodeFunction.apply(instruction)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StringOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StringOpDescriptor.java index 174c542e..0d571740 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StringOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/StringOpDescriptor.java @@ -1,10 +1,8 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.Function; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.string.BuildStringOpcode; import ai.timefold.jpyinterpreter.opcodes.string.FormatValueOpcode; @@ -19,14 +17,14 @@ public enum StringOpDescriptor implements OpcodeDescriptor { FORMAT_VALUE(FormatValueOpcode::new), BUILD_STRING(BuildStringOpcode::new); - final Function opcodeFunction; + final VersionMapping versionLookup; StringOpDescriptor(Function opcodeFunction) { - this.opcodeFunction = opcodeFunction; + this.versionLookup = VersionMapping.constantMapping(opcodeFunction); } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - return Optional.of(opcodeFunction.apply(instruction)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/VariableOpDescriptor.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/VariableOpDescriptor.java index fc46cac8..0316f3c1 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/VariableOpDescriptor.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/VariableOpDescriptor.java @@ -1,10 +1,8 @@ package ai.timefold.jpyinterpreter.opcodes.descriptor; -import java.util.Optional; import java.util.function.Function; import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; -import ai.timefold.jpyinterpreter.PythonVersion; import ai.timefold.jpyinterpreter.opcodes.Opcode; import ai.timefold.jpyinterpreter.opcodes.variable.DeleteDerefOpcode; import ai.timefold.jpyinterpreter.opcodes.variable.DeleteFastOpcode; @@ -12,6 +10,7 @@ import ai.timefold.jpyinterpreter.opcodes.variable.LoadClosureOpcode; import ai.timefold.jpyinterpreter.opcodes.variable.LoadConstantOpcode; import ai.timefold.jpyinterpreter.opcodes.variable.LoadDerefOpcode; +import ai.timefold.jpyinterpreter.opcodes.variable.LoadFastAndClearOpcode; import ai.timefold.jpyinterpreter.opcodes.variable.LoadFastOpcode; import ai.timefold.jpyinterpreter.opcodes.variable.LoadGlobalOpcode; import ai.timefold.jpyinterpreter.opcodes.variable.StoreDerefOpcode; @@ -21,32 +20,39 @@ public enum VariableOpDescriptor implements OpcodeDescriptor { LOAD_CONST(LoadConstantOpcode::new), - LOAD_NAME(null), //TODO - STORE_NAME(null), //TODO - DELETE_NAME(null), //TODO + LOAD_NAME(VersionMapping.unimplemented()), //TODO + STORE_NAME(VersionMapping.unimplemented()), //TODO + DELETE_NAME(VersionMapping.unimplemented()), //TODO LOAD_GLOBAL(LoadGlobalOpcode::new), STORE_GLOBAL(StoreGlobalOpcode::new), DELETE_GLOBAL(DeleteGlobalOpcode::new), + // TODO: Implement unbound local variable checks LOAD_FAST(LoadFastOpcode::new), + + // This is LOAD_FAST but do an unbound variable check + LOAD_FAST_CHECK(LoadFastOpcode::new), + + LOAD_FAST_AND_CLEAR(LoadFastAndClearOpcode::new), STORE_FAST(StoreFastOpcode::new), DELETE_FAST(DeleteFastOpcode::new), LOAD_CLOSURE(LoadClosureOpcode::new), LOAD_DEREF(LoadDerefOpcode::new), STORE_DEREF(StoreDerefOpcode::new), DELETE_DEREF(DeleteDerefOpcode::new), - LOAD_CLASSDEREF(null); + LOAD_CLASSDEREF(VersionMapping.unimplemented()); - final Function opcodeFunction; + final VersionMapping versionLookup; VariableOpDescriptor(Function opcodeFunction) { - this.opcodeFunction = opcodeFunction; + this(VersionMapping.constantMapping(opcodeFunction)); + } + + VariableOpDescriptor(VersionMapping lookup) { + this.versionLookup = lookup; } @Override - public Optional lookupOpcodeForInstruction(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - if (opcodeFunction == null) { - return Optional.empty(); - } - return Optional.of(opcodeFunction.apply(instruction)); + public VersionMapping getVersionMapping() { + return versionLookup; } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/VersionMapping.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/VersionMapping.java new file mode 100644 index 00000000..294c0b5c --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/descriptor/VersionMapping.java @@ -0,0 +1,70 @@ +package ai.timefold.jpyinterpreter.opcodes.descriptor; + +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.ToIntBiFunction; + +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.PythonVersion; +import ai.timefold.jpyinterpreter.opcodes.Opcode; + +public final class VersionMapping { + private final NavigableMap> versionToMappingMap; + + public VersionMapping() { + this.versionToMappingMap = new TreeMap<>(); + } + + private VersionMapping( + NavigableMap> versionToMappingMap) { + this.versionToMappingMap = versionToMappingMap; + } + + public static VersionMapping unimplemented() { + return new VersionMapping(); + } + + public static VersionMapping constantMapping(Function mapper) { + return new VersionMapping() + .map(PythonVersion.MINIMUM_PYTHON_VERSION, mapper); + } + + public static VersionMapping constantMapping(BiFunction mapper) { + return new VersionMapping() + .map(PythonVersion.MINIMUM_PYTHON_VERSION, mapper); + } + + public VersionMapping map(PythonVersion version, Function mapper) { + var mapCopy = new TreeMap<>(versionToMappingMap); + mapCopy.put(version, (instruction, ignored) -> mapper.apply(instruction)); + return new VersionMapping(mapCopy); + } + + public VersionMapping map(PythonVersion version, BiFunction mapper) { + var mapCopy = new TreeMap<>(versionToMappingMap); + mapCopy.put(version, mapper); + return new VersionMapping(mapCopy); + } + + public VersionMapping mapWithLabels(PythonVersion version, + BiFunction mapper, + ToIntBiFunction labelMapper) { + var mapCopy = new TreeMap<>(versionToMappingMap); + mapCopy.put(version, + (instruction, actualVersion) -> mapper.apply(instruction, labelMapper.applyAsInt(instruction, actualVersion))); + return new VersionMapping(mapCopy); + } + + public Opcode getOpcodeForVersion(PythonBytecodeInstruction instruction, + PythonVersion pythonVersion) { + var mappingForVersion = versionToMappingMap.floorEntry(pythonVersion); + if (mappingForVersion == null) { + throw new UnsupportedOperationException( + "Could not find implementation for Opcode %s for Python version %s (instruction %s)" + .formatted(instruction.opname(), pythonVersion, instruction)); + } + return mappingForVersion.getValue().apply(instruction, pythonVersion); + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/CompareOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/CompareOpcode.java index 12312add..48f5fa42 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/CompareOpcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/CompareOpcode.java @@ -25,6 +25,6 @@ public StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionM @Override public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { DunderOperatorImplementor.compareValues(functionMetadata.methodVisitor, stackMetadata, - CompareOp.getOp(instruction.arg())); + CompareOp.getOp(instruction.argRepr())); } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/GetSliceOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/GetSliceOpcode.java new file mode 100644 index 00000000..3b510c19 --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/GetSliceOpcode.java @@ -0,0 +1,28 @@ +package ai.timefold.jpyinterpreter.opcodes.dunder; + +import ai.timefold.jpyinterpreter.FunctionMetadata; +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.StackMetadata; +import ai.timefold.jpyinterpreter.ValueSourceInfo; +import ai.timefold.jpyinterpreter.implementors.DunderOperatorImplementor; +import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; +import ai.timefold.jpyinterpreter.types.BuiltinTypes; + +public class GetSliceOpcode extends AbstractOpcode { + public GetSliceOpcode(PythonBytecodeInstruction instruction) { + super(instruction); + } + + @Override + protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + // TODO: Type the result + return stackMetadata.pop(3) + .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, + stackMetadata.getValueSourcesUpToStackIndex(3))); + } + + @Override + public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + DunderOperatorImplementor.getSlice(functionMetadata, stackMetadata); + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/StoreSliceOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/StoreSliceOpcode.java new file mode 100644 index 00000000..d697fed8 --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/dunder/StoreSliceOpcode.java @@ -0,0 +1,23 @@ +package ai.timefold.jpyinterpreter.opcodes.dunder; + +import ai.timefold.jpyinterpreter.FunctionMetadata; +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.StackMetadata; +import ai.timefold.jpyinterpreter.implementors.DunderOperatorImplementor; +import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; + +public class StoreSliceOpcode extends AbstractOpcode { + public StoreSliceOpcode(PythonBytecodeInstruction instruction) { + super(instruction); + } + + @Override + protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + return stackMetadata.pop(4); + } + + @Override + public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + DunderOperatorImplementor.storeSlice(functionMetadata, stackMetadata); + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/CleanupThrowOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/CleanupThrowOpcode.java new file mode 100644 index 00000000..ed8ed639 --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/CleanupThrowOpcode.java @@ -0,0 +1,30 @@ +package ai.timefold.jpyinterpreter.opcodes.exceptions; + +import ai.timefold.jpyinterpreter.FunctionMetadata; +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.StackMetadata; +import ai.timefold.jpyinterpreter.ValueSourceInfo; +import ai.timefold.jpyinterpreter.implementors.ExceptionImplementor; +import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; +import ai.timefold.jpyinterpreter.types.BuiltinTypes; + +import org.objectweb.asm.Opcodes; + +public class CleanupThrowOpcode extends AbstractOpcode { + public CleanupThrowOpcode(PythonBytecodeInstruction instruction) { + super(instruction); + } + + @Override + protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + return stackMetadata.pop(3).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getTOSValueSource())); + } + + @Override + public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + ExceptionImplementor.getValueFromStopIterationOrReraise(functionMetadata, stackMetadata); + var methodVisitor = functionMetadata.methodVisitor; + methodVisitor.visitInsn(Opcodes.DUP_X2); + methodVisitor.visitInsn(Opcodes.POP2); + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/RaiseVarargsOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/RaiseVarargsOpcode.java index 85cf0373..71687493 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/RaiseVarargsOpcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/RaiseVarargsOpcode.java @@ -1,5 +1,6 @@ package ai.timefold.jpyinterpreter.opcodes.exceptions; +import java.util.Collections; import java.util.List; import ai.timefold.jpyinterpreter.FunctionMetadata; @@ -16,13 +17,13 @@ public RaiseVarargsOpcode(PythonBytecodeInstruction instruction) { @Override public List getPossibleNextBytecodeIndexList() { - return List.of(); + return Collections.emptyList(); } @Override public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return List.of(); + return Collections.emptyList(); } @Override diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/ReraiseOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/ReraiseOpcode.java index d5522c5c..329a20ba 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/ReraiseOpcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/exceptions/ReraiseOpcode.java @@ -1,5 +1,6 @@ package ai.timefold.jpyinterpreter.opcodes.exceptions; +import java.util.Collections; import java.util.List; import ai.timefold.jpyinterpreter.FunctionMetadata; @@ -16,13 +17,13 @@ public ReraiseOpcode(PythonBytecodeInstruction instruction) { @Override public List getPossibleNextBytecodeIndexList() { - return List.of(); + return Collections.emptyList(); } @Override public List getStackMetadataAfterInstructionForBranches(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - return List.of(); + return Collections.emptyList(); } @Override diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallOpcode.java index 07f02cfc..02c97fb4 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallOpcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/CallOpcode.java @@ -1,5 +1,6 @@ package ai.timefold.jpyinterpreter.opcodes.function; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -41,7 +42,7 @@ protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functi .orElseGet(() -> stackMetadata.pop(instruction.arg() + 2) .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))) - .setCallKeywordNameList(List.of()); + .setCallKeywordNameList(Collections.emptyList()); } functionType = stackMetadata.getTypeAtStackIndex(instruction.arg()); @@ -63,11 +64,12 @@ protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functi .orElseGet(() -> stackMetadata.pop(instruction.arg() + 2) .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2)))) - .setCallKeywordNameList(List.of()); + .setCallKeywordNameList(Collections.emptyList()); } return stackMetadata.pop(instruction.arg() + 2).push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, - stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2))).setCallKeywordNameList(List.of()); + stackMetadata.getValueSourcesUpToStackIndex(instruction.arg() + 2))) + .setCallKeywordNameList(Collections.emptyList()); } @Override diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/LoadMethodOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/LoadMethodOpcode.java index 404ec472..ad4c0ae2 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/LoadMethodOpcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/function/LoadMethodOpcode.java @@ -41,7 +41,6 @@ protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functi @Override public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - FunctionImplementor.loadMethod(functionMetadata, functionMetadata.methodVisitor, functionMetadata.className, - functionMetadata.pythonCompiledFunction, stackMetadata, instruction); + FunctionImplementor.loadMethod(functionMetadata, stackMetadata, instruction.arg()); } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/StopIteratorErrorOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/StopIteratorErrorOpcode.java new file mode 100644 index 00000000..75d44512 --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/generator/StopIteratorErrorOpcode.java @@ -0,0 +1,25 @@ +package ai.timefold.jpyinterpreter.opcodes.generator; + +import ai.timefold.jpyinterpreter.FunctionMetadata; +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.StackMetadata; +import ai.timefold.jpyinterpreter.ValueSourceInfo; +import ai.timefold.jpyinterpreter.implementors.ExceptionImplementor; +import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; +import ai.timefold.jpyinterpreter.types.BuiltinTypes; + +public class StopIteratorErrorOpcode extends AbstractOpcode { + public StopIteratorErrorOpcode(PythonBytecodeInstruction instruction) { + super(instruction); + } + + @Override + protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + return stackMetadata.pop().push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getTOSValueSource())); + } + + @Override + public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + ExceptionImplementor.getValueFromStopIterationOrReraise(functionMetadata, stackMetadata); + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/UnaryIntrinsicFunction.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/UnaryIntrinsicFunction.java new file mode 100644 index 00000000..860f0f3c --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/meta/UnaryIntrinsicFunction.java @@ -0,0 +1,57 @@ +package ai.timefold.jpyinterpreter.opcodes.meta; + +import java.util.function.Function; + +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.PythonUnaryOperator; +import ai.timefold.jpyinterpreter.opcodes.Opcode; +import ai.timefold.jpyinterpreter.opcodes.collection.ListToTupleOpcode; +import ai.timefold.jpyinterpreter.opcodes.dunder.UniDunerOpcode; +import ai.timefold.jpyinterpreter.opcodes.generator.StopIteratorErrorOpcode; + +public enum UnaryIntrinsicFunction { + INTRINSIC_1_INVALID(ignored -> { + throw new UnsupportedOperationException("INTRINSIC_1_INVALID"); + }), + INTRINSIC_PRINT(ignored -> { + throw new UnsupportedOperationException("INTRINSIC_PRINT"); + }), + INTRINSIC_IMPORT_STAR(ignored -> { + throw new UnsupportedOperationException("INTRINSIC_IMPORT_STAR"); + }), + INTRINSIC_STOPITERATION_ERROR(StopIteratorErrorOpcode::new), + INTRINSIC_ASYNC_GEN_WRAP(ignored -> { + throw new UnsupportedOperationException("INTRINSIC_ASYNC_GEN_WRAP"); + }), + INTRINSIC_UNARY_POSITIVE(instruction -> new UniDunerOpcode(instruction, PythonUnaryOperator.POSITIVE)), + INTRINSIC_LIST_TO_TUPLE(ListToTupleOpcode::new), + INTRINSIC_TYPEVAR(ignored -> { + throw new UnsupportedOperationException("INTRINSIC_TYPEVAR"); + }), + INTRINSIC_PARAMSPEC(ignored -> { + throw new UnsupportedOperationException("INTRINSIC_PARAMSPEC"); + }), + INTRINSIC_TYPEVARTUPLE(ignored -> { + throw new UnsupportedOperationException("INTRINSIC_TYPEVARTUPLE"); + }), + INTRINSIC_SUBSCRIPT_GENERIC(ignored -> { + throw new UnsupportedOperationException("INTRINSIC_SUBSCRIPT_GENERIC"); + }), + INTRINSIC_TYPEALIAS(ignored -> { + throw new UnsupportedOperationException("INTRINSIC_TYPEALIAS"); + }); + + final Function opcodeFunction; + + UnaryIntrinsicFunction(Function opcodeFunction) { + this.opcodeFunction = opcodeFunction; + } + + public Opcode getOpcode(PythonBytecodeInstruction instruction) { + return opcodeFunction.apply(instruction); + } + + public static Opcode lookup(PythonBytecodeInstruction instruction) { + return UnaryIntrinsicFunction.valueOf(instruction.argRepr()).getOpcode(instruction); + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/LoadAttrOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/LoadAttrOpcode.java index c7d69e2a..09ce0ee4 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/LoadAttrOpcode.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/LoadAttrOpcode.java @@ -10,7 +10,6 @@ import ai.timefold.jpyinterpreter.types.PythonLikeType; public class LoadAttrOpcode extends AbstractOpcode { - public LoadAttrOpcode(PythonBytecodeInstruction instruction) { super(instruction); } @@ -18,7 +17,8 @@ public LoadAttrOpcode(PythonBytecodeInstruction instruction) { @Override protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { PythonLikeType tosType = stackMetadata.getTOSType(); - return tosType.getInstanceFieldDescriptor(functionMetadata.pythonCompiledFunction.co_names.get(instruction.arg())) + int arg = instruction.arg(); + return tosType.getInstanceFieldDescriptor(functionMetadata.pythonCompiledFunction.co_names.get(arg)) .map(fieldDescriptor -> stackMetadata.pop() .push(ValueSourceInfo.of(this, fieldDescriptor.fieldPythonLikeType(), stackMetadata.getValueSourcesUpToStackIndex(1)))) @@ -28,8 +28,8 @@ protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functi @Override public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { - ObjectImplementor.getAttribute(functionMetadata, functionMetadata.methodVisitor, functionMetadata.className, + ObjectImplementor.getAttribute(functionMetadata, stackMetadata, - instruction); + instruction.arg()); } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/LoadSuperAttrOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/LoadSuperAttrOpcode.java new file mode 100644 index 00000000..f327440c --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/object/LoadSuperAttrOpcode.java @@ -0,0 +1,41 @@ +package ai.timefold.jpyinterpreter.opcodes.object; + +import ai.timefold.jpyinterpreter.FunctionMetadata; +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.StackMetadata; +import ai.timefold.jpyinterpreter.ValueSourceInfo; +import ai.timefold.jpyinterpreter.implementors.ObjectImplementor; +import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; +import ai.timefold.jpyinterpreter.types.BuiltinTypes; + +public class LoadSuperAttrOpcode extends AbstractOpcode { + final int nameIndex; + final boolean isLoadMethod; + final boolean isTwoArgSuper; + + public LoadSuperAttrOpcode(PythonBytecodeInstruction instruction) { + super(instruction); + nameIndex = instruction.arg() >> 2; + isLoadMethod = (instruction.arg() & 1) == 1; + isTwoArgSuper = (instruction.arg() & 2) == 2; + } + + @Override + protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + if (isLoadMethod) { + // Pop 3, Push None and Method + return stackMetadata.pop(3) + .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourcesUpToStackIndex(3))) + .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourcesUpToStackIndex(3))); + } else { + // Pop 3, Push Attribute + return stackMetadata.pop(3) + .push(ValueSourceInfo.of(this, BuiltinTypes.BASE_TYPE, stackMetadata.getValueSourcesUpToStackIndex(3))); + } + } + + @Override + public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + ObjectImplementor.getSuperAttribute(functionMetadata, stackMetadata, nameIndex, isLoadMethod); + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadFastAndClearOpcode.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadFastAndClearOpcode.java new file mode 100644 index 00000000..d8d5afa5 --- /dev/null +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/opcodes/variable/LoadFastAndClearOpcode.java @@ -0,0 +1,27 @@ +package ai.timefold.jpyinterpreter.opcodes.variable; + +import ai.timefold.jpyinterpreter.FunctionMetadata; +import ai.timefold.jpyinterpreter.PythonBytecodeInstruction; +import ai.timefold.jpyinterpreter.StackMetadata; +import ai.timefold.jpyinterpreter.implementors.VariableImplementor; +import ai.timefold.jpyinterpreter.opcodes.AbstractOpcode; + +public class LoadFastAndClearOpcode extends AbstractOpcode { + + public LoadFastAndClearOpcode(PythonBytecodeInstruction instruction) { + super(instruction); + } + + @Override + protected StackMetadata getStackMetadataAfterInstruction(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + return stackMetadata.push(stackMetadata.getLocalVariableValueSource(instruction.arg())); + } + + @Override + public void implement(FunctionMetadata functionMetadata, StackMetadata stackMetadata) { + VariableImplementor.loadLocalVariable(functionMetadata.methodVisitor, instruction, + stackMetadata.localVariableHelper); + VariableImplementor.deleteLocalVariable(functionMetadata.methodVisitor, instruction, + stackMetadata.localVariableHelper); + } +} diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSlice.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSlice.java index 3fab656c..23d2e8e6 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSlice.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonSlice.java @@ -1,6 +1,5 @@ package ai.timefold.jpyinterpreter.types; -import java.math.BigInteger; import java.util.Map; import java.util.Objects; @@ -143,62 +142,23 @@ public static int asValidEndIntIndexForLength(PythonInteger index, int length) { } } - public PythonLikeTuple indices(PythonInteger sequenceLength) { - PythonInteger startIndex, stopIndex, strideLength; - boolean isReversed = false; - - if (this.step == PythonNone.INSTANCE) { - strideLength = PythonInteger.ONE; - } else if (this.step instanceof PythonInteger) { - strideLength = (PythonInteger) this.step; - isReversed = strideLength.compareTo(PythonInteger.ZERO) < 0; - } else { - strideLength = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(start); - isReversed = strideLength.compareTo(PythonInteger.ZERO) < 0; - } - - if (strideLength.value.intValueExact() == 0) { - throw new ValueError("stride length cannot be zero"); - } - - if (start instanceof PythonInteger) { - startIndex = (PythonInteger) start; - } else if (start == PythonNone.INSTANCE) { - startIndex = isReversed ? PythonInteger.valueOf(sequenceLength.value.subtract(BigInteger.ONE)) : PythonInteger.ZERO; - } else { - startIndex = ((PythonInteger) UnaryDunderBuiltin.INDEX.invoke(start)); - } - - if (startIndex.compareTo(PythonInteger.ZERO) < 0) { - startIndex = sequenceLength.add(startIndex); - } - - if (!isReversed && startIndex.value.compareTo(sequenceLength.value) > 0) { - startIndex = sequenceLength; - } else if (isReversed && startIndex.value.compareTo(sequenceLength.value.subtract(BigInteger.ONE)) > 0) { - startIndex = PythonInteger.valueOf(sequenceLength.value.subtract(BigInteger.ONE)); - } + private record SliceIndices(int start, int stop, int strideLength) { + } - if (stop instanceof PythonInteger) { - stopIndex = (PythonInteger) stop; - } else if (stop == PythonNone.INSTANCE) { - stopIndex = - isReversed ? PythonInteger.valueOf(sequenceLength.value.negate().subtract(BigInteger.ONE)) : sequenceLength; - } else { - stopIndex = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(stop); - } + private SliceIndices getSliceIndices(int length) { + return new SliceIndices(getStartIndex(length), getStopIndex(length), getStrideLength()); + } - if (stopIndex.compareTo(PythonInteger.ZERO) < 0) { - stopIndex = sequenceLength.add(stopIndex); - } + private SliceIndices getSliceIndices(PythonInteger length) { + return getSliceIndices(length.getValue().intValue()); + } - if (!isReversed && stopIndex.value.compareTo(sequenceLength.value) > 0) { - stopIndex = sequenceLength; - } else if (isReversed && stopIndex.value.compareTo(sequenceLength.value.subtract(BigInteger.ONE)) > 0) { - stopIndex = PythonInteger.valueOf(sequenceLength.value.subtract(BigInteger.ONE)); - } + public PythonLikeTuple indices(PythonInteger sequenceLength) { + var sliceIndices = getSliceIndices(sequenceLength); - return PythonLikeTuple.fromItems(startIndex, stopIndex, strideLength); + return PythonLikeTuple.fromItems(PythonInteger.valueOf(sliceIndices.start), + PythonInteger.valueOf(sliceIndices.start), + PythonInteger.valueOf(sliceIndices.strideLength)); } public int getStartIndex(int length) { @@ -256,10 +216,10 @@ public int getStrideLength() { if (step instanceof PythonInteger) { strideLength = (PythonInteger) step; - } else if (step == PythonNone.INSTANCE) { - strideLength = PythonInteger.ONE; - } else { + } else if (step != null && step != PythonNone.INSTANCE) { strideLength = (PythonInteger) UnaryDunderBuiltin.INDEX.invoke(step); + } else { + strideLength = PythonInteger.ONE; } int out = strideLength.value.intValueExact(); @@ -268,71 +228,20 @@ public int getStrideLength() { throw new ValueError("stride length cannot be zero"); } - return strideLength.value.intValueExact(); + return out; } public void iterate(int length, SliceConsumer consumer) { - int startIndex, stopIndex, strideLength; - boolean isReversed = false; - - if (this.step == PythonNone.INSTANCE) { - strideLength = 1; - } else if (this.step instanceof PythonInteger) { - strideLength = ((PythonInteger) this.step).value.intValueExact(); - isReversed = strideLength < 0; - } else { - strideLength = ((PythonInteger) UnaryDunderBuiltin.INDEX.invoke(start)).value.intValueExact(); - isReversed = strideLength < 0; - } - - if (strideLength == 0) { - throw new ValueError("stride length cannot be zero"); - } - - if (start instanceof PythonInteger) { - startIndex = ((PythonInteger) start).value.intValueExact(); - } else if (start == PythonNone.INSTANCE) { - startIndex = isReversed ? length - 1 : 0; - } else { - startIndex = ((PythonInteger) UnaryDunderBuiltin.INDEX.invoke(start)).value.intValueExact(); - } - - if (startIndex < 0) { - startIndex = length + startIndex; - } - - if (!isReversed && startIndex > length) { - startIndex = length; - } else if (isReversed && startIndex > length - 1) { - startIndex = length - 1; - } - - if (stop instanceof PythonInteger) { - stopIndex = ((PythonInteger) stop).value.intValueExact(); - } else if (stop == PythonNone.INSTANCE) { - stopIndex = isReversed ? -length - 1 : length; // use -length - 1 so length - stopIndex = -1 - } else { - stopIndex = ((PythonInteger) UnaryDunderBuiltin.INDEX.invoke(stop)).value.intValueExact(); - } - - if (stopIndex < 0) { - stopIndex = length + stopIndex; - } - - if (!isReversed && stopIndex > length) { - stopIndex = length; - } else if (isReversed && stopIndex > length - 1) { - stopIndex = length - 1; - } + var sliceIndices = getSliceIndices(length); int step = 0; - if (isReversed) { - for (int i = startIndex; i > stopIndex; i += strideLength) { + if (sliceIndices.strideLength < 0) { + for (int i = sliceIndices.start; i > sliceIndices.stop; i += sliceIndices.strideLength) { consumer.accept(i, step); step++; } } else { - for (int i = startIndex; i < stopIndex; i += strideLength) { + for (int i = sliceIndices.start; i < sliceIndices.stop; i += sliceIndices.strideLength) { consumer.accept(i, step); step++; } @@ -344,10 +253,9 @@ private boolean isReversed() { } public int getSliceSize(int length) { - int start = getStartIndex(length); - int stop = getStopIndex(length); - int span = stop - start; - int strideLength = getStrideLength(); + var sliceIndices = getSliceIndices(length); + int span = sliceIndices.stop - sliceIndices.start; + int strideLength = sliceIndices.strideLength; // ceil division return span / strideLength + (span % strideLength == 0 ? 0 : 1); diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java index ee04bc3e..d9f4dd82 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java @@ -746,7 +746,7 @@ public PythonString format(List positionalArguments, Map extends AbstractPythonL PlanningCloneable>, PythonLikeComparable, RandomAccess { - public static PythonLikeTuple EMPTY = PythonLikeTuple.fromList(List.of()); + public static PythonLikeTuple EMPTY = PythonLikeTuple.fromList(Collections.emptyList()); final List delegate; private int remainderToAdd; diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictKeyView.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictKeyView.java index 11340a97..f2ee0f9c 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictKeyView.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictKeyView.java @@ -132,10 +132,10 @@ public PythonLikeSet difference(DictKeyView other) { } public PythonLikeSet symmetricDifference(DictKeyView other) { - PythonLikeSet out = new PythonLikeSet(); + var out = new PythonLikeSet<>(); out.delegate.addAll(keySet); other.keySet.stream() // for each item in other - .filter(Predicate.not(out.delegate::add)) // add each item + .filter(Predicate.not(e -> out.delegate.add(e))) // add each item .forEach(out.delegate::remove); // add return false iff item already in set, so this remove // all items in both this and other return out; @@ -143,8 +143,7 @@ public PythonLikeSet symmetricDifference(DictKeyView other) { @Override public boolean equals(Object o) { - if (o instanceof DictKeyView) { - DictKeyView other = (DictKeyView) o; + if (o instanceof DictKeyView other) { return keySet.equals(other.keySet); } return false; diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonBaseException.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonBaseException.java index bfd3cd7f..87e77790 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonBaseException.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/errors/PythonBaseException.java @@ -1,5 +1,6 @@ package ai.timefold.jpyinterpreter.types.errors; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,7 +43,7 @@ private static String getMessageFromArgs(List args) { } public PythonBaseException(PythonLikeType type) { - this(type, List.of()); + this(type, Collections.emptyList()); } public PythonBaseException(PythonLikeType type, List args) { diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JumpUtils.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JumpUtils.java index 5dc1981e..554157bc 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JumpUtils.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/JumpUtils.java @@ -12,27 +12,31 @@ public static int getInstructionIndexForByteOffset(int byteOffset, PythonVersion return byteOffset >> 1; } + private static int parseArgRepr(PythonBytecodeInstruction instruction) { + return Integer.parseInt(instruction.argRepr().substring(3)) / 2; + } + public static int getAbsoluteTarget(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - if (pythonVersion.isBefore(PythonVersion.PYTHON_3_10)) { - return instruction.arg() >> 1; - } else { + if (pythonVersion.isBefore(PythonVersion.PYTHON_3_12)) { return instruction.arg(); + } else { + return parseArgRepr(instruction); } } public static int getRelativeTarget(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - if (pythonVersion.isBefore(PythonVersion.PYTHON_3_10)) { - return instruction.offset() + (instruction.arg() >> 1) + 1; - } else { + if (pythonVersion.isBefore(PythonVersion.PYTHON_3_12)) { return instruction.offset() + instruction.arg() + 1; + } else { + return parseArgRepr(instruction); } } public static int getBackwardRelativeTarget(PythonBytecodeInstruction instruction, PythonVersion pythonVersion) { - if (pythonVersion.isBefore(PythonVersion.PYTHON_3_10)) { - return instruction.offset() - (instruction.arg() >> 1) + 1; - } else { + if (pythonVersion.isBefore(PythonVersion.PYTHON_3_12)) { return instruction.offset() - instruction.arg() + 1; + } else { + return parseArgRepr(instruction); } } } diff --git a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentSpec.java b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentSpec.java index aa81af1c..6695670c 100644 --- a/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentSpec.java +++ b/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/arguments/ArgumentSpec.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -43,10 +44,10 @@ private ArgumentSpec(String functionName, Class functionReturnType) { this.functionName = functionName + "()"; requiredPositionalArguments = 0; numberOfPositionalArguments = 0; - argumentNameList = List.of(); - argumentTypeList = List.of(); - argumentKindList = List.of(); - argumentDefaultList = List.of(); + argumentNameList = Collections.emptyList(); + argumentTypeList = Collections.emptyList(); + argumentKindList = Collections.emptyList(); + argumentDefaultList = Collections.emptyList(); extraPositionalsArgumentIndex = Optional.empty(); extraKeywordsArgumentIndex = Optional.empty(); nullableArgumentSet = new BitSet(); @@ -536,7 +537,7 @@ private PythonFunctionSignature getPythonFunctionSignatureForMethodDescriptor(Me defaultParameterValueList = (List) (List) argumentDefaultList.subList(firstDefault, argumentDefaultList.size()); } else { - defaultParameterValueList = List.of(); + defaultParameterValueList = Collections.emptyList(); } List parameterTypeList = argumentTypeList.stream() diff --git a/jpyinterpreter/src/main/python/conversions.py b/jpyinterpreter/src/main/python/conversions.py index 847857f5..1ba0e7f0 100644 --- a/jpyinterpreter/src/main/python/conversions.py +++ b/jpyinterpreter/src/main/python/conversions.py @@ -2,12 +2,40 @@ import inspect from dataclasses import dataclass from typing import TYPE_CHECKING -from jpype import JLong, JDouble, JBoolean, JProxy + +from jpype import JLong, JDouble, JBoolean, JProxy, JImplementationFor if TYPE_CHECKING: from java.util import IdentityHashMap + +# Workaround for https://github.com/jpype-project/jpype/issues/1178 +@JImplementationFor('java.lang.Throwable') +class _JavaException: + @staticmethod + def _get_exception_with_cause(exception): + if exception is None: + return None + try: + raise Exception(f'{exception.getClass().getSimpleName()}: {exception.getMessage()}') + except Exception as e: + cause = _JavaException._get_exception_with_cause(exception.getCause()) + if cause is not None: + try: + raise e from cause + except Exception as return_value: + return return_value + else: + return e + @property + def __cause__(self): + if self.getCause() is not None: + return _JavaException._get_exception_with_cause(self.getCause()) + else: + return None + + def get_translated_java_system_error_message(error): from ai.timefold.jpyinterpreter.util import TracebackUtils top_line = f'{error.getClass().getSimpleName()}: {error.getMessage()}' diff --git a/jpyinterpreter/src/main/python/translator.py b/jpyinterpreter/src/main/python/translator.py index 8f4bf99f..5a599966 100644 --- a/jpyinterpreter/src/main/python/translator.py +++ b/jpyinterpreter/src/main/python/translator.py @@ -6,8 +6,8 @@ from jpype import JInt, JBoolean, JProxy, JClass, JArray -MINIMUM_SUPPORTED_PYTHON_VERSION = (3, 9) -MAXIMUM_SUPPORTED_PYTHON_VERSION = (3, 11) +MINIMUM_SUPPORTED_PYTHON_VERSION = (3, 10) +MAXIMUM_SUPPORTED_PYTHON_VERSION = (3, 12) global_dict_to_instance = dict() global_dict_to_key_set = dict() @@ -206,7 +206,7 @@ def get_function_bytecode_object(python_function): .atOffset(instruction.opname, JInt(instruction.offset // 2)) .withIsJumpTarget(JBoolean(instruction.is_jump_target))) if instruction.arg is not None: - java_instruction = java_instruction.withArg(instruction.arg) + java_instruction = java_instruction.withArg(instruction.arg).withArgRepr(instruction.argrepr) if instruction.starts_line: java_instruction = java_instruction.startsLine(instruction.starts_line) @@ -270,9 +270,10 @@ def get_code_bytecode_object(python_code): .atOffset(instruction.opname, JInt(instruction.offset // 2)) .withIsJumpTarget(JBoolean(instruction.is_jump_target))) if instruction.arg is not None: - java_instruction = java_instruction.withArg(instruction.arg) + java_instruction = java_instruction.withArg(instruction.arg).withArgRepr(instruction.argrepr) if instruction.starts_line: java_instruction = java_instruction.startsLine(instruction.starts_line) + instruction_list.add(java_instruction) python_compiled_function.module = '__code__' @@ -409,13 +410,21 @@ def wrapped_function(*args): return wrapped_function +def try_or_reraise(function): + from java.lang import Exception as JException + try: + return function() + except JException as e: + raise RuntimeError(f'{e.getClass().getSimpleName()}: {e.getMessage()}\n{e.stacktrace()}') + + def as_java(python_function): return as_typed_java(python_function) def as_untyped_java(python_function): from ai.timefold.jpyinterpreter.types import PythonLikeFunction - java_function = translate_python_bytecode_to_java_bytecode(python_function, PythonLikeFunction) + java_function = try_or_reraise(lambda: translate_python_bytecode_to_java_bytecode(python_function, PythonLikeFunction)) return wrap_untyped_java_function(java_function) @@ -424,14 +433,14 @@ def as_typed_java(python_function): function_bytecode = get_function_bytecode_object(python_function) function_interface_declaration = PythonClassTranslator.getInterfaceForPythonFunction(function_bytecode) function_interface_class = PythonClassTranslator.getInterfaceClassForDeclaration(function_interface_declaration) - java_function = translate_python_bytecode_to_java_bytecode(python_function, function_interface_class) + java_function = try_or_reraise(lambda: translate_python_bytecode_to_java_bytecode(python_function, function_interface_class)) return wrap_typed_java_function(java_function) def _force_as_java_generator(python_function): from ai.timefold.jpyinterpreter.types import PythonLikeFunction - java_function = _force_translate_python_bytecode_to_generator_java_bytecode(python_function, - PythonLikeFunction) + java_function = try_or_reraise(lambda: _force_translate_python_bytecode_to_generator_java_bytecode(python_function, + PythonLikeFunction)) return wrap_untyped_java_function(java_function) diff --git a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/PythonFunctionBuilder.java b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/PythonFunctionBuilder.java index c78d1322..765fd7cf 100644 --- a/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/PythonFunctionBuilder.java +++ b/jpyinterpreter/src/test/java/ai/timefold/jpyinterpreter/util/PythonFunctionBuilder.java @@ -691,6 +691,10 @@ public PythonFunctionBuilder getFromModule(String attributeName) { * @param compareOp The comparison to perform */ public PythonFunctionBuilder compare(CompareOp compareOp) { - return op(DunderOpDescriptor.COMPARE_OP, compareOp.id); + PythonBytecodeInstruction instruction = instruction(DunderOpDescriptor.COMPARE_OP) + .withArg(0) + .withArgRepr(compareOp.id); + instructionList.add(instruction); + return this; } } diff --git a/jpyinterpreter/tests/test_version_check.py b/jpyinterpreter/tests/test_version_check.py index 882f47dd..d06ac459 100644 --- a/jpyinterpreter/tests/test_version_check.py +++ b/jpyinterpreter/tests/test_version_check.py @@ -4,11 +4,13 @@ def test_version_check(): assert is_python_version_supported((2, 7, 0, 'Final', 0)) is False assert is_python_version_supported((3, 8, 0, 'Final', 0)) is False - assert is_python_version_supported((3, 9, 0, 'Final', 0)) is True - assert is_python_version_supported((3, 9, 1, 'Final', 0)) is True + assert is_python_version_supported((3, 9, 0, 'Final', 0)) is False + assert is_python_version_supported((3, 9, 1, 'Final', 0)) is False assert is_python_version_supported((3, 10, 0, 'Final', 0)) is True assert is_python_version_supported((3, 10, 5, 'Final', 0)) is True assert is_python_version_supported((3, 11, 0, 'Final', 0)) is True assert is_python_version_supported((3, 11, 3, 'Final', 0)) is True - assert is_python_version_supported((3, 12, 0, 'Final', 0)) is False - assert is_python_version_supported((3, 12, 3, 'Final', 0)) is False + assert is_python_version_supported((3, 12, 0, 'Final', 0)) is True + assert is_python_version_supported((3, 12, 3, 'Final', 0)) is True + assert is_python_version_supported((3, 12, 0, 'Final', 0)) is True + assert is_python_version_supported((3, 13, 0, 'Final', 0)) is False diff --git a/jpyinterpreter/tox.ini b/jpyinterpreter/tox.ini index 41652c67..8085643a 100644 --- a/jpyinterpreter/tox.ini +++ b/jpyinterpreter/tox.ini @@ -4,14 +4,14 @@ # and then run "tox" from this directory. [tox] -env_list = py39,py310,py311 +env_list = py310,py311,p312 [testenv] pass_env = * # needed by tox4, to pass JAVA_HOME deps = - pytest - pytest-cov - coverage - JPype1>=1.4.0 + pytest>=8.1.1 + pytest-cov>=4.1.0 + coverage>=7.4.3 + JPype1>=1.5.0 commands = pytest --import-mode=importlib {posargs} diff --git a/setup.py b/setup.py index 566e08ab..5e249bb6 100644 --- a/setup.py +++ b/setup.py @@ -114,7 +114,7 @@ def find_stub_files(stub_root: str): 'ai-stubs': 'timefold-solver-python-core/src/main/resources', }, test_suite='tests', - python_requires='>=3.9', + python_requires='>=3.10', install_requires=[ 'JPype1>=1.5.0', ], diff --git a/tox.ini b/tox.ini index 81bae38f..6810ef26 100644 --- a/tox.ini +++ b/tox.ini @@ -4,16 +4,12 @@ # and then run "tox" from this directory. [tox] -# Dropped support for Python 3.9 since other scientific python dependencies now dropped support -# https://scientific-python.org/specs/spec-0000/ -# numpy dropping support for Python 3.9 on April 5th -# https://numpy.org/neps/nep-0029-deprecation_policy.html -env_list = py310,py311 +env_list = py310,py311,p312 [testenv] pass_env = * # needed by tox4, to pass JAVA_HOME deps = - pytest>=8.0.2 + pytest>=8.1.1 pytest-cov>=4.1.0 coverage>=7.4.3 JPype1>=1.5.0